From e1818aed17b68e0e39cddc55dbb50eda90c6b5e2 Mon Sep 17 00:00:00 2001 From: "Chiaki.C" Date: Thu, 7 Dec 2023 12:12:42 +0800 Subject: [PATCH 1/6] refactor: rename sessionKey to sessionKeyEnv --- packages/blocto-sdk/src/providers/ethereum.ts | 47 ++++++++++--------- .../src/providers/types/ethereum.d.ts | 2 +- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/packages/blocto-sdk/src/providers/ethereum.ts b/packages/blocto-sdk/src/providers/ethereum.ts index 0b3ad9e3..0f478299 100644 --- a/packages/blocto-sdk/src/providers/ethereum.ts +++ b/packages/blocto-sdk/src/providers/ethereum.ts @@ -63,7 +63,7 @@ export default class EthereumProvider injectedWalletServer?: string; appId: string; _blocto: { - sessionKey: KEY_SESSION; + sessionKeyEnv: KEY_SESSION; walletServer: string; blockchainName: string; networkType: string; @@ -74,7 +74,7 @@ export default class EthereumProvider private get existedSDK() { return (window as any).ethereum; } - + constructor({ chainId, rpc, walletServer, appId }: EthereumProviderConfig) { super(); // setup chainId @@ -89,7 +89,7 @@ export default class EthereumProvider // NOTE: _blocto is not fully initialized yet at this point // Any function should call #getBloctoProperties() to get the full _blocto properties this._blocto = { - sessionKey: KEY_SESSION.prod, + sessionKeyEnv: KEY_SESSION.prod, walletServer: this.injectedWalletServer || '', blockchainName: '', networkType: '', @@ -123,7 +123,7 @@ export default class EthereumProvider ); this._blocto = { ...this._blocto, - sessionKey: ETH_SESSION_KEY_MAPPING[blocto_service_environment], + sessionKeyEnv: ETH_SESSION_KEY_MAPPING[blocto_service_environment], walletServer: this.injectedWalletServer || ETH_ENV_WALLET_SERVER_MAPPING[blocto_service_environment], @@ -312,7 +312,8 @@ export default class EthereumProvider async request(payload: EIP1193RequestPayload): Promise { if (!payload?.method) throw ethErrors.rpc.invalidRequest(); - const { blockchainName, switchableNetwork, sessionKey } = + + const { blockchainName, switchableNetwork, sessionKeyEnv } = await this.#getBloctoProperties(); if (this.existedSDK?.isBlocto) { @@ -363,11 +364,11 @@ export default class EthereumProvider return this.handleDisconnect(); } case 'eth_accounts': - return getEvmAddress(sessionKey, blockchainName) || []; + return getEvmAddress(sessionKeyEnv, blockchainName) || []; } // Method that requires user to be connected - if (!getEvmAddress(sessionKey, blockchainName)) { + if (!getEvmAddress(sessionKeyEnv, blockchainName)) { const email = payload?.params?.[0]; if (payload.method === 'eth_requestAccounts' && isEmail(email)) { await this.enable(email); @@ -386,7 +387,7 @@ export default class EthereumProvider } // eslint-disable-next-line case 'eth_coinbase': { - result = getEvmAddress(sessionKey, blockchainName)?.[0]; + result = getEvmAddress(sessionKeyEnv, blockchainName)?.[0]; break; } case 'eth_signTypedData_v3': @@ -440,9 +441,9 @@ export default class EthereumProvider } async bloctoApi(url: string, options?: RequestInit): Promise { - const { walletServer, blockchainName, sessionKey } = + const { walletServer, blockchainName, sessionKeyEnv } = await this.#getBloctoProperties(); - const sessionId = getAccountStorage(sessionKey)?.code || ''; + const sessionId = getAccountStorage(sessionKeyEnv)?.code || ''; if (!sessionId) { throw ethErrors.provider.unauthorized(); } @@ -457,7 +458,7 @@ export default class EthereumProvider ...options, }) .then((response) => - responseSessionGuard(response, sessionKey, () => { + responseSessionGuard(response, sessionKeyEnv, () => { this.eventListeners?.disconnect.forEach((listener) => listener(ethErrors.provider.disconnected()) ); @@ -543,7 +544,7 @@ export default class EthereumProvider // eip-1102 alias // DEPRECATED API: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1102.md async enable(email?: string): Promise { - const { walletServer, blockchainName, sessionKey } = + const { walletServer, blockchainName, sessionKeyEnv } = await this.#getBloctoProperties(); if (this.existedSDK?.isBlocto) { @@ -556,7 +557,7 @@ export default class EthereumProvider method: 'wallet_switchEthereumChain', params: [{ chainId: this.chainId }], }); - setEvmAddress(sessionKey, blockchainName, [this.existedSDK.address]); + setEvmAddress(sessionKeyEnv, blockchainName, [this.existedSDK.address]); } return new Promise((resolve, reject) => // add a small delay to make sure the network has been switched @@ -567,7 +568,7 @@ export default class EthereumProvider ); } - const address = getEvmAddress(sessionKey, blockchainName); + const address = getEvmAddress(sessionKeyEnv, blockchainName); if (address) { return new Promise((resolve) => { resolve(address); @@ -595,7 +596,7 @@ export default class EthereumProvider listener({ chainId: this.chainId }) ); setAccountStorage( - sessionKey, + sessionKeyEnv, { code: e.data.code, evm: { @@ -645,9 +646,9 @@ export default class EthereumProvider async fetchAccounts(): Promise { this.#checkNetworkMatched(); - const { blockchainName, sessionKey } = await this.#getBloctoProperties(); + const { blockchainName, sessionKeyEnv } = await this.#getBloctoProperties(); const { accounts } = await this.bloctoApi<{ accounts: [] }>(`/accounts`); - setEvmAddress(sessionKey, blockchainName, accounts); + setEvmAddress(sessionKeyEnv, blockchainName, accounts); return accounts; } @@ -712,9 +713,9 @@ export default class EthereumProvider if (!targetChainId) { throw ethErrors.rpc.invalidParams(); } - const { walletServer, blockchainName, sessionKey, switchableNetwork } = + const { walletServer, blockchainName, sessionKeyEnv, switchableNetwork } = await this.#getBloctoProperties(); - const oldAccount = getEvmAddress(sessionKey, blockchainName)?.[0]; + const oldAccount = getEvmAddress(sessionKeyEnv, blockchainName)?.[0]; const oldChainId = parseChainId(this.chainId); const newChainId = parseChainId(targetChainId); if (oldChainId === newChainId) { @@ -777,7 +778,7 @@ export default class EthereumProvider detatchFrame(switchChainFrame); if (e.data?.addr && oldAccount) { setAccountStorage( - sessionKey, + sessionKeyEnv, { code: e.data?.code, evm: { @@ -806,7 +807,7 @@ export default class EthereumProvider this.eventListeners?.chainChanged.forEach((listener) => listener(this.chainId) ); - removeAllEvmAddress(sessionKey); + removeAllEvmAddress(sessionKeyEnv); this.eventListeners?.disconnect.forEach((listener) => listener(ethErrors.provider.disconnected()) ); @@ -892,8 +893,8 @@ export default class EthereumProvider if (this.existedSDK?.isBlocto) { return this.existedSDK.request({ method: 'wallet_disconnect' }); } - const { sessionKey } = await this.#getBloctoProperties(); - removeAllEvmAddress(sessionKey); + const { sessionKeyEnv } = await this.#getBloctoProperties(); + removeAllEvmAddress(sessionKeyEnv); this.eventListeners?.disconnect.forEach((listener) => listener(ethErrors.provider.disconnected()) ); diff --git a/packages/blocto-sdk/src/providers/types/ethereum.d.ts b/packages/blocto-sdk/src/providers/types/ethereum.d.ts index d4935f4a..c6acb713 100644 --- a/packages/blocto-sdk/src/providers/types/ethereum.d.ts +++ b/packages/blocto-sdk/src/providers/types/ethereum.d.ts @@ -34,7 +34,7 @@ export interface EthereumProviderInterface networkVersion: string | number; rpc: string; _blocto: { - sessionKey: KEY_SESSION; + sessionKeyEnv: KEY_SESSION; walletServer: string; blockchainName: string; networkType: string; From 7cc3c8190434a8482f20572457bc63568f013612 Mon Sep 17 00:00:00 2001 From: "Chiaki.C" Date: Thu, 7 Dec 2023 12:12:42 +0800 Subject: [PATCH 2/6] refactor: getBloctoProperties --- packages/blocto-sdk/src/providers/ethereum.ts | 39 ++++++++++++------- .../src/providers/types/ethereum.d.ts | 2 - 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/packages/blocto-sdk/src/providers/ethereum.ts b/packages/blocto-sdk/src/providers/ethereum.ts index 0f478299..f6cbb2b1 100644 --- a/packages/blocto-sdk/src/providers/ethereum.ts +++ b/packages/blocto-sdk/src/providers/ethereum.ts @@ -37,7 +37,7 @@ import { isValidTransaction, isValidTransactions, } from '../lib/isValidTransaction'; -import { EvmSupportMapping, getEvmSupport } from '../lib/getEvmSupport'; +import { getEvmSupport } from '../lib/getEvmSupport'; import { ethErrors } from 'eth-rpc-errors'; import { isHexString, utf8ToHex } from '../lib/utf8toHex'; @@ -67,7 +67,6 @@ export default class EthereumProvider walletServer: string; blockchainName: string; networkType: string; - supportNetworkList: EvmSupportMapping; switchableNetwork: SwitchableNetwork; }; @@ -93,34 +92,39 @@ export default class EthereumProvider walletServer: this.injectedWalletServer || '', blockchainName: '', networkType: '', - supportNetworkList: {}, switchableNetwork: {}, }; this.appId = appId || DEFAULT_APP_ID; } async #getBloctoProperties(): Promise { - if (!Object.keys(this._blocto.supportNetworkList).length) { - await getEvmSupport() - .then((result) => (this._blocto.supportNetworkList = result)) - .catch((e) => { - throw ethErrors.provider.custom({ - code: 1001, - message: `Get blocto server failed: ${e.message}`, - }); - }); + if ( + this._blocto.sessionKeyEnv && + this._blocto.walletServer && + this._blocto.blockchainName && + this._blocto.networkType && + this._blocto.switchableNetwork + ) { + return this._blocto; } + const supportNetworkList = await getEvmSupport().catch((e) => { + throw ethErrors.provider.custom({ + code: 1001, + message: `Get blocto server failed: ${e.message}`, + }); + }); const { chain_id, name, network_type, blocto_service_environment, display_name, - } = this._blocto.supportNetworkList[this.networkVersion] ?? {}; - if (!chain_id) + } = supportNetworkList[this.networkVersion] ?? {}; + if (!chain_id) { throw ethErrors.provider.unsupportedMethod( `Get support chain failed: ${this.networkVersion} might not be supported yet.` ); + } this._blocto = { ...this._blocto, sessionKeyEnv: ETH_SESSION_KEY_MAPPING[blocto_service_environment], @@ -150,7 +154,12 @@ export default class EthereumProvider chainId: `${number}`; rpcUrls: string[]; }): Promise { - const { supportNetworkList } = await this.#getBloctoProperties(); + const supportNetworkList = await getEvmSupport().catch((e) => { + throw ethErrors.provider.custom({ + code: 1001, + message: `Get blocto server failed: ${e.message}`, + }); + }); const { chain_id, name, diff --git a/packages/blocto-sdk/src/providers/types/ethereum.d.ts b/packages/blocto-sdk/src/providers/types/ethereum.d.ts index c6acb713..cd55aa66 100644 --- a/packages/blocto-sdk/src/providers/types/ethereum.d.ts +++ b/packages/blocto-sdk/src/providers/types/ethereum.d.ts @@ -1,7 +1,6 @@ import { IEthereumProvider } from 'eip1193-provider'; import { BaseConfig, KEY_SESSION } from '../../constants'; import BloctoProviderInterface from './blocto.d'; -import { EvmSupportMapping } from '../../lib/getEvmSupport'; export interface EthereumProviderConfig extends BaseConfig { chainId: string | number | null; @@ -38,7 +37,6 @@ export interface EthereumProviderInterface walletServer: string; blockchainName: string; networkType: string; - supportNetworkList: EvmSupportMapping; switchableNetwork: SwitchableNetwork; }; sendUserOperation(userOp: IUserOperation): Promise; From 3017eb13fb535d313033ab663fa1882e03d954e0 Mon Sep 17 00:00:00 2001 From: "Chiaki.C" Date: Thu, 7 Dec 2023 12:13:34 +0800 Subject: [PATCH 3/6] refactor: support load multiple chains in evm provider config --- .changeset/fluffy-pandas-rule.md | 5 ++ packages/blocto-sdk/src/index.d.ts | 4 +- packages/blocto-sdk/src/providers/ethereum.ts | 57 +++++++++++++------ .../src/providers/types/ethereum.d.ts | 11 +++- 4 files changed, 56 insertions(+), 21 deletions(-) create mode 100644 .changeset/fluffy-pandas-rule.md diff --git a/.changeset/fluffy-pandas-rule.md b/.changeset/fluffy-pandas-rule.md new file mode 100644 index 00000000..496228c7 --- /dev/null +++ b/.changeset/fluffy-pandas-rule.md @@ -0,0 +1,5 @@ +--- +'@blocto/sdk': minor +--- + +Support load multiple chain configs in evm provider diff --git a/packages/blocto-sdk/src/index.d.ts b/packages/blocto-sdk/src/index.d.ts index d21ca3c2..264e90f0 100644 --- a/packages/blocto-sdk/src/index.d.ts +++ b/packages/blocto-sdk/src/index.d.ts @@ -25,8 +25,8 @@ export { EthereumTypes, }; export declare interface BloctoSDKConfig extends BaseConfig { - ethereum?: Omit; - aptos?: Omit; + ethereum?: EthereumProviderConfig; + aptos?: AptosProviderConfig; } declare class BloctoSDK { ethereum?: EthereumProviderInterface; diff --git a/packages/blocto-sdk/src/providers/ethereum.ts b/packages/blocto-sdk/src/providers/ethereum.ts index f6cbb2b1..b686dadb 100644 --- a/packages/blocto-sdk/src/providers/ethereum.ts +++ b/packages/blocto-sdk/src/providers/ethereum.ts @@ -5,6 +5,7 @@ import { EIP1193RequestPayload, EthereumProviderConfig, EthereumProviderInterface, + AddEthereumChainParameter, JsonRpcRequest, JsonRpcResponse, JsonRpcCallback, @@ -63,6 +64,7 @@ export default class EthereumProvider injectedWalletServer?: string; appId: string; _blocto: { + unloadedNetwork?: AddEthereumChainParameter[]; sessionKeyEnv: KEY_SESSION; walletServer: string; blockchainName: string; @@ -73,20 +75,10 @@ export default class EthereumProvider private get existedSDK() { return (window as any).ethereum; } - - constructor({ chainId, rpc, walletServer, appId }: EthereumProviderConfig) { + + constructor(config: EthereumProviderConfig) { super(); - // setup chainId - invariant(chainId, "'chainId' is required"); - this.networkVersion = `${parseChainId(chainId)}`; - this.chainId = `0x${parseChainId(chainId).toString(16)}`; - // setup rpc - this.rpc = rpc || ETH_RPC_LIST[this.networkVersion]; - invariant(this.rpc, "'rpc' is required"); - // setup injectedWalletServer - this.injectedWalletServer = walletServer; - // NOTE: _blocto is not fully initialized yet at this point - // Any function should call #getBloctoProperties() to get the full _blocto properties + this.injectedWalletServer = config.walletServer; this._blocto = { sessionKeyEnv: KEY_SESSION.prod, walletServer: this.injectedWalletServer || '', @@ -94,10 +86,41 @@ export default class EthereumProvider networkType: '', switchableNetwork: {}, }; - this.appId = appId || DEFAULT_APP_ID; + this.appId = config.appId || DEFAULT_APP_ID; + if ('chainId' in config) { + const { chainId, rpc } = config; + invariant(chainId, "'chainId' is required"); + this.networkVersion = `${parseChainId(chainId)}`; + this.chainId = `0x${parseChainId(chainId).toString(16)}`; + // setup rpc + this.rpc = rpc || ETH_RPC_LIST[this.networkVersion]; + invariant(this.rpc, "'rpc' is required"); + } else { + const { defaultChainId, switchableChains } = config; + invariant(defaultChainId, "'defaultChainId' is required"); + this.networkVersion = `${parseChainId(defaultChainId)}`; + this.chainId = `0x${parseChainId(defaultChainId).toString(16)}`; + // get config from switchableChains array + const chainConfig = switchableChains.find( + (chain) => parseChainId(chain.chainId) === parseChainId(defaultChainId) + ); + if (!chainConfig) { + throw ethErrors.provider.custom({ + code: 1001, + message: `Chain ${defaultChainId} is not in switchableChains list`, + }); + } + this.rpc = chainConfig.rpcUrls?.[0] || ETH_RPC_LIST[this.networkVersion]; + invariant(this.rpc, "'rpc' is required"); + this._blocto.unloadedNetwork = switchableChains; + } } async #getBloctoProperties(): Promise { + if (this._blocto?.unloadedNetwork) { + await this.loadSwitchableNetwork(this._blocto.unloadedNetwork); + delete this._blocto.unloadedNetwork; + } if ( this._blocto.sessionKeyEnv && this._blocto.walletServer && @@ -154,6 +177,7 @@ export default class EthereumProvider chainId: `${number}`; rpcUrls: string[]; }): Promise { + await this.#getBloctoProperties(); const supportNetworkList = await getEvmSupport().catch((e) => { throw ethErrors.provider.custom({ code: 1001, @@ -910,10 +934,7 @@ export default class EthereumProvider } async loadSwitchableNetwork( - networkList: { - chainId: string; - rpcUrls?: string[]; - }[] + networkList: AddEthereumChainParameter[] ): Promise { // setup switchable list if user set networkList if (networkList?.length) { diff --git a/packages/blocto-sdk/src/providers/types/ethereum.d.ts b/packages/blocto-sdk/src/providers/types/ethereum.d.ts index cd55aa66..cac6bc01 100644 --- a/packages/blocto-sdk/src/providers/types/ethereum.d.ts +++ b/packages/blocto-sdk/src/providers/types/ethereum.d.ts @@ -2,12 +2,20 @@ import { IEthereumProvider } from 'eip1193-provider'; import { BaseConfig, KEY_SESSION } from '../../constants'; import BloctoProviderInterface from './blocto.d'; -export interface EthereumProviderConfig extends BaseConfig { +interface SingleChainConfig extends BaseConfig { chainId: string | number | null; rpc?: string; walletServer?: string; } +interface MultiChainConfig extends BaseConfig { + defaultChainId: string | number | null; + walletServer?: string; + switchableChains: AddEthereumChainParameter[]; +} +// EthereumProviderConfig can be both single chain or multi chain config. +export type EthereumProviderConfig = SingleChainConfig | MultiChainConfig; + export interface EIP1193RequestPayload { id?: number; jsonrpc?: string; @@ -53,6 +61,7 @@ export interface EthereumProviderInterface export interface AddEthereumChainParameter { chainId: string; rpcUrls: string[]; + [key: string]: any; } export interface JsonRpcRequest { From 7103cf2029a0f35a638e7049f05e57de7eaa1ef9 Mon Sep 17 00:00:00 2001 From: "Chiaki.C" Date: Thu, 7 Dec 2023 12:13:34 +0800 Subject: [PATCH 4/6] fix: switch chain init _blocto --- packages/blocto-sdk/src/providers/ethereum.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/blocto-sdk/src/providers/ethereum.ts b/packages/blocto-sdk/src/providers/ethereum.ts index b686dadb..962af6d1 100644 --- a/packages/blocto-sdk/src/providers/ethereum.ts +++ b/packages/blocto-sdk/src/providers/ethereum.ts @@ -763,11 +763,16 @@ export default class EthereumProvider this.networkVersion = `${newChainId}`; this.chainId = `0x${newChainId.toString(16)}`; this.rpc = switchableNetwork[newChainId].rpc_url; + this._blocto = { + ...this._blocto, + blockchainName: '', + networkType: '', + }; if (!oldAccount) { this.eventListeners?.chainChanged.forEach((listener) => listener(this.chainId) ); - this.#getBloctoProperties(); + await this.#getBloctoProperties(); return null; } // Go login flow when switching to a different blocto server @@ -791,6 +796,11 @@ export default class EthereumProvider this.networkVersion = `${oldChainId}`; this.chainId = `0x${oldChainId.toString(16)}`; this.rpc = switchableNetwork[oldChainId].rpc_url; + this._blocto = { + ...this._blocto, + blockchainName: '', + networkType: '', + }; this.#getBloctoProperties(); throw error; }); @@ -850,6 +860,12 @@ export default class EthereumProvider this.networkVersion = `${oldChainId}`; this.chainId = `0x${oldChainId.toString(16)}`; this.rpc = switchableNetwork[oldChainId].rpc_url; + this._blocto = { + ...this._blocto, + blockchainName: '', + networkType: '', + }; + this.#getBloctoProperties(); reject(ethErrors.provider.userRejectedRequest()); } } From e2ab769d6f44f2cbb41033911c5b88f5ac6b7a4f Mon Sep 17 00:00:00 2001 From: "Chiaki.C" Date: Thu, 7 Dec 2023 12:13:34 +0800 Subject: [PATCH 5/6] feat: export support chain list in evm provider --- packages/blocto-sdk/src/providers/ethereum.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/blocto-sdk/src/providers/ethereum.ts b/packages/blocto-sdk/src/providers/ethereum.ts index 962af6d1..c355d6c8 100644 --- a/packages/blocto-sdk/src/providers/ethereum.ts +++ b/packages/blocto-sdk/src/providers/ethereum.ts @@ -972,6 +972,22 @@ export default class EthereumProvider } } + async supportChainList(): Promise<{ chainId: string; chainName: string }[]> { + const supportNetworkList = await getEvmSupport().catch((e) => { + throw ethErrors.provider.custom({ + code: 1001, + message: `Get blocto server failed: ${e.message}`, + }); + }); + return Object.keys(supportNetworkList).map((chainId) => { + const { display_name } = supportNetworkList[chainId]; + return { + chainId, + chainName: display_name, + }; + }); + } + override on(event: string, listener: (arg: any) => void): void { if (this.existedSDK?.isBlocto) this.existedSDK.on(event, listener); From 235d0aef30f2528ec810b451c2f42f4550c2fb8b Mon Sep 17 00:00:00 2001 From: "Chiaki.C" Date: Thu, 7 Dec 2023 12:13:34 +0800 Subject: [PATCH 6/6] fix: add supportChainList to interface --- packages/blocto-sdk/src/providers/types/ethereum.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/blocto-sdk/src/providers/types/ethereum.d.ts b/packages/blocto-sdk/src/providers/types/ethereum.d.ts index cac6bc01..9ce5bc07 100644 --- a/packages/blocto-sdk/src/providers/types/ethereum.d.ts +++ b/packages/blocto-sdk/src/providers/types/ethereum.d.ts @@ -55,6 +55,7 @@ export interface EthereumProviderInterface rpcUrls?: string[]; }[] ): Promise; + supportChainList(): Promise<{ chainId: string; chainName: string }[]>; injectedWalletServer?: string; }