// Copyright 2023 @soul-wallet/extension-koni authors & contributors // SPDX-License-Identifier: Apache-2.0 import type { EvmProvider } from '@soul-wallet/extension-inject/types'; import SafeEventEmitter from '@metamask/safe-event-emitter'; import { EvmProviderError } from '@soul-wallet/extension-base/background/errors/EvmProviderError'; import { SendRequest } from '@soul-wallet/extension-base/page/types'; import { JsonRpcRequest, JsonRpcResponse, JsonRpcSuccess } from 'json-rpc-engine'; import { RequestArguments } from 'web3-core'; export interface SendSyncJsonRpcRequest extends JsonRpcRequest { method: 'net_version'; } let subscribeFlag = false; export class SoulWalletEvmProvider extends SafeEventEmitter implements EvmProvider { public readonly isSoulWallet = true; public readonly isMetaMask = false; public readonly version; protected sendMessage: SendRequest; protected _connected = false; constructor (sendMessage: SendRequest, version: string) { super(); this.version = version; this.sendMessage = sendMessage; this._connected = true; } get connected () { return this._connected; } public isConnected () { return this._connected; } protected subscribeExtensionEvents () { if (subscribeFlag) { return; } this.sendMessage('evm(events.subscribe)', null, ({ payload, type }) => { if (['connect', 'disconnect', 'accountsChanged', 'chainChanged', 'message', 'data', 'reconnect', 'error'].includes(type)) { if (type === 'connect') { this._connected = true; } else if (type === 'disconnect') { this._connected = false; } const finalType = type === 'data' ? 'message' : type; // eslint-disable-next-line @typescript-eslint/no-unsafe-call this.emit(finalType, payload); } else { console.warn('Can not handle event', type, payload); } }) .then((done) => { subscribeFlag = true; }) .catch(() => { subscribeFlag = false; }); subscribeFlag = true; } public async enable () { return this.request({ method: 'eth_requestAccounts' }); } public override on (eventName: string | symbol, listener: (...args: any[]) => void): this { this.subscribeExtensionEvents(); super.on(eventName, listener); return this; } public override once (eventName: string | symbol, listener: (...args: any[]) => void): this { this.subscribeExtensionEvents(); super.once(eventName, listener); return this; } request ({ method, params }: RequestArguments): Promise { // Subscribe events switch (method) { case 'eth_requestAccounts': return new Promise((resolve, reject) => { const origin = document.title !== '' ? document.title : window.location.hostname; this.sendMessage('pub(authorize.tabV2)', { origin, accountAuthType: 'evm' }) .then(() => { // Return account list this.request({ method: 'eth_accounts' }) .then((accounts) => { // @ts-ignore resolve(accounts); }).catch((e: EvmProviderError) => { reject(e); }); }).catch((e: EvmProviderError) => { reject(e); }); }); default: return new Promise((resolve, reject) => { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment this.sendMessage('evm(request)', { params, method }) .then((result) => { resolve(result as T); }) .catch((e: EvmProviderError) => { reject(e); }); }); } } private _sendSync (payload: JsonRpcRequest): JsonRpcResponse { let result: JsonRpcSuccess['result']; switch (payload.method) { case 'net_version': result = this.version ? `SoulWallet v${this.version}` : null; break; default: throw new Error(`Not support ${payload.method}`); } return { id: payload.id, jsonrpc: payload.jsonrpc, result }; } send (method: string, params?: T[]): Promise>; send (payload: JsonRpcRequest, callback: (error: Error | null, result?: JsonRpcResponse) => void): void; send (payload: SendSyncJsonRpcRequest): JsonRpcResponse; send (methodOrPayload: unknown, callbackOrArgs?: unknown): unknown { if ( typeof methodOrPayload === 'string' && (!callbackOrArgs || Array.isArray(callbackOrArgs)) ) { return this.request({ method: methodOrPayload, params: callbackOrArgs }); } else if ( methodOrPayload && typeof methodOrPayload === 'object' && typeof callbackOrArgs === 'function' ) { return this.request(methodOrPayload as JsonRpcRequest).then((rs) => { (callbackOrArgs as (...args: unknown[]) => void)(rs); }); } return this._sendSync(methodOrPayload as SendSyncJsonRpcRequest); } sendAsync (payload: JsonRpcRequest, callback: (error: (Error | null), result?: JsonRpcResponse) => void): void { this.request(payload) .then((result) => { // @ts-ignore callback(null, { result }); }) .catch((e: EvmProviderError) => { callback(e); }); } }