diff --git a/packages/provider/src/provider.ts b/packages/provider/src/provider.ts index 46fee8bec..d767a6b35 100644 --- a/packages/provider/src/provider.ts +++ b/packages/provider/src/provider.ts @@ -8,7 +8,8 @@ import { JsonRpcRequest, JsonRpcResponseCallback, JsonRpcSender, - maybeChainId + maybeChainId, + findNetworkConfig } from '@0xsequence/network' import { resolveArrayProperties, Signer } from '@0xsequence/wallet' @@ -188,21 +189,13 @@ export class Web3Signer extends Signer implements TypedDataSigner { async getWalletConfig(chainId?: ChainIdLike): Promise { const reqChainId = maybeChainId(chainId) || this.defaultChainId if (!reqChainId) throw new Error('chainId is required') - return (await this.provider.send( - 'sequence_getWalletConfig', - [reqChainId], - reqChainId - ))[0] + return (await this.provider.send('sequence_getWalletConfig', [reqChainId], reqChainId))[0] } async getWalletState(chainId?: ChainIdLike): Promise { const reqChainId = maybeChainId(chainId) || this.defaultChainId - if (!reqChainId) throw new Error('chainId is required') - return (await this.provider.send( - 'sequence_getWalletState', - [reqChainId], - reqChainId - ))[0].status + if (!reqChainId) throw new Error('chainId is required') + return (await this.provider.send('sequence_getWalletState', [reqChainId], reqChainId))[0].status } async getNetworks(): Promise { @@ -219,7 +212,10 @@ export class Web3Signer extends Signer implements TypedDataSigner { throw new Error(`walletConfig returned zero results for authChainId {authChainId}`) } - return universal.genericCoderFor(config.version).config.signersOf(config).map((s) => s.address) + return universal + .genericCoderFor(config.version) + .config.signersOf(config) + .map(s => s.address) } // signMessage matches implementation from ethers JsonRpcSigner for compatibility, but with @@ -233,10 +229,7 @@ export class Web3Signer extends Signer implements TypedDataSigner { // NOTE: as of ethers v5.5, it switched to using personal_sign, see // https://github.com/ethers-io/ethers.js/pull/1542 and see // https://github.com/WalletConnect/walletconnect-docs/issues/32 for additional info. - return provider!.send( - sequenceVerified ? 'sequence_sign' : 'personal_sign', - [ethers.utils.hexlify(data), address] - ) + return provider!.send(sequenceVerified ? 'sequence_sign' : 'personal_sign', [ethers.utils.hexlify(data), address]) } // signTypedData matches implementation from ethers JsonRpcSigner for compatibility, but with @@ -331,7 +324,9 @@ export class Web3Signer extends Signer implements TypedDataSigner { // updateConfig.. // NOTE: this is not supported by the remote wallet by default. - async updateConfig(newConfig?: commons.config.Config): Promise<[commons.config.Config, ethers.providers.TransactionResponse | undefined]> { + async updateConfig( + newConfig?: commons.config.Config + ): Promise<[commons.config.Config, ethers.providers.TransactionResponse | undefined]> { // sequence_updateConfig const [config, tx] = await this.provider.send('sequence_updateConfig', [newConfig], this.defaultChainId) if (tx === null) { @@ -513,7 +508,9 @@ const hexlifyTransaction = ( } class UncheckedJsonRpcSigner extends Web3Signer { - sendTransaction(transaction: Deferrable): Promise { + sendTransaction( + transaction: Deferrable + ): Promise { return this.sendUncheckedTransaction(transaction).then(hash => { return { chainId: 0, @@ -530,3 +527,39 @@ class UncheckedJsonRpcSigner extends Web3Signer { }) } } + +export class ProviderProxy extends Web3Provider { + chainId: number + + private providers: Record + + private get _provider(): Web3Provider { + return this.providers[this.chainId] + } + + constructor(providers: Record, chainId: number) { + super(undefined as any, chainId) + this.providers = providers + this.chainId = chainId + } + + async send(method: string, params: Array): Promise { + if (method === 'wallet_switchEthereumChain') { + this.chainId = params[0].chainId + + this.emit('chainChanged', this.chainId) + + return null + } + + return this._provider.send(method, params) + } + + async getNetwork() { + return this._provider.getNetwork() + } + + async getChainId(): Promise { + return this._provider.getChainId() + } +} diff --git a/packages/provider/src/wallet.ts b/packages/provider/src/wallet.ts index adec7eaa4..0fb29ea40 100644 --- a/packages/provider/src/wallet.ts +++ b/packages/provider/src/wallet.ts @@ -16,10 +16,12 @@ import { updateNetworkConfig, ensureValidNetworks, sortNetworks, - findSupportedNetwork + findSupportedNetwork, + networks, + ChainId } from '@0xsequence/network' import { getDefaultConnectionInfo, logger } from '@0xsequence/utils' -import { Web3Provider, Web3Signer } from './provider' +import { ProviderProxy, Web3Provider, Web3Signer } from './provider' import { MuxMessageProvider, WindowMessageProvider, @@ -99,17 +101,25 @@ export class Wallet implements WalletProvider { private networks: NetworkConfig[] private providers: { [chainId: number]: Web3Provider } - constructor(network?: string | number, config?: Partial) { + private providerProxy: ProviderProxy + + constructor(chainId?: ChainIdLike, config?: Partial) { // config is a Partial, so that we may intersect it with the DefaultProviderConfig, // which allows easy overriding and control of the config. this.config = { ...DefaultProviderConfig } if (config) { this.config = { ...this.config, ...config } } - if (network) { - this.config.defaultNetworkId = network - } else if (!this.config.defaultNetworkId) { - this.config.defaultNetworkId = 'mainnet' + if (chainId) { + const network = findSupportedNetwork(chainId) + + if (network) { + this.config.defaultNetworkId = network.chainId + } + } + + if (!this.config.defaultNetworkId) { + this.config.defaultNetworkId = ChainId.MAINNET } if (config?.localStorage) { @@ -119,6 +129,7 @@ export class Wallet implements WalletProvider { this.transport = {} this.networks = [] this.providers = {} + this.providerProxy = new ProviderProxy(this.providers, this.config.defaultNetworkId) this.connectedSites = new LocalStore('@sequence.connectedSites', []) this.utils = new WalletUtils(this) this.init() @@ -445,13 +456,21 @@ export class Wallet implements WalletProvider { throw new Error('networks have not been set by session. connect first.') } - const network = findNetworkConfig(this.networks, this.config.defaultNetworkId!) + const provider = this.getProvider() - if (!network) { - throw new Error('networks must have a default chain specified') + if (!provider) { + throw new Error('provider not found') } - return network.chainId + return provider?.getChainId() + + // const network = findNetworkConfig(this.networks, this.config.defaultNetworkId!) + + // if (!network) { + // throw new Error('networks must have a default chain specified') + // } + + // return network.chainId } openWallet = async (path?: string, intent?: OpenWalletIntent, networkId?: string | number): Promise => { @@ -488,62 +507,23 @@ export class Wallet implements WalletProvider { } } - let network: NetworkConfig | undefined = findNetworkConfig(this.networks, this.config.defaultNetworkId!)! - if (chainId) { - network = findNetworkConfig(this.networks, chainId) - if (!network) { - throw new Error(`network ${chainId} is not in the network list`) - } - } + const network: NetworkConfig | undefined = findNetworkConfig(this.networks, chainId || this.providerProxy.chainId)! - // return memoized network provider - if (this.providers[network.chainId]) { - return this.providers[network.chainId] + if (!network) { + throw new Error(`network ${chainId} is not in the network list`) } - // builder web3 provider stack - let provider: Web3Provider - - // network.provider may be set by the ProviderConfig override - const rpcProvider = new providers.JsonRpcProvider(getDefaultConnectionInfo(network.rpcUrl), network.chainId) - - if (network.isDefaultChain) { - // communicating with defaultChain will prioritize the wallet message transport - const router = new JsonRpcRouter( - [ - loggingProviderMiddleware, - exceptionProviderMiddleware, - new EagerProvider({ accountAddress: this.session!.accountAddress, walletContext: this.session!.walletContext }), - new SigningProvider(this.transport!.provider!), - this.transport.cachedProvider! - ], - new JsonRpcSender(rpcProvider) - ) + // if the provider does not exist, create it + if (!this.providers[network.chainId]) { + this.providers[network.chainId] = this.createProvider(network) + } - provider = new Web3Provider(router, network.chainId) + if (chainId) { + // return memoized network provider + return this.providers[network.chainId] } else { - // communicating with another chain will bind to that network, but will forward - // any signing-related requests to the wallet message transport - const router = new JsonRpcRouter( - [ - loggingProviderMiddleware, - exceptionProviderMiddleware, - new EagerProvider({ - accountAddress: this.session!.accountAddress, - walletContext: this.session!.walletContext, - chainId: network.chainId - }), - new SigningProvider(this.transport.provider!), - new CachedProvider({ defaultChainId: network.chainId }) - ], - new JsonRpcSender(rpcProvider) - ) - - provider = new Web3Provider(router, network.chainId) + return this.providerProxy } - - this.providers[network.chainId] = provider - return provider } getAllProviders(): { [chainId: number]: Web3Provider } { @@ -583,6 +563,46 @@ export class Wallet implements WalletProvider { this.transport.messageProvider?.unregister() } + private createProvider(network: NetworkConfig) { + // network.provider may be set by the ProviderConfig override + const rpcProvider = new providers.JsonRpcProvider(getDefaultConnectionInfo(network.rpcUrl), network.chainId) + + if (network.isDefaultChain) { + // communicating with defaultChain will prioritize the wallet message transport + const router = new JsonRpcRouter( + [ + loggingProviderMiddleware, + exceptionProviderMiddleware, + new EagerProvider({ accountAddress: this.session!.accountAddress, walletContext: this.session!.walletContext }), + new SigningProvider(this.transport!.provider!), + this.transport.cachedProvider! + ], + new JsonRpcSender(rpcProvider) + ) + + return new Web3Provider(router, network.chainId) + } else { + // communicating with another chain will bind to that network, but will forward + // any signing-related requests to the wallet message transport + const router = new JsonRpcRouter( + [ + loggingProviderMiddleware, + exceptionProviderMiddleware, + new EagerProvider({ + accountAddress: this.session!.accountAddress, + walletContext: this.session!.walletContext, + chainId: network.chainId + }), + new SigningProvider(this.transport.provider!), + new CachedProvider({ defaultChainId: network.chainId }) + ], + new JsonRpcSender(rpcProvider) + ) + + return new Web3Provider(router, network.chainId) + } + } + private saveSession = async (session: WalletSession) => { logger.debug('wallet provider: saving session') const data = JSON.stringify(session) @@ -700,7 +720,7 @@ export interface ProviderConfig { // defaultNetworkId is the primary network of a dapp and the default network a // provider will communicate. Note: this setting is also configurable from the // Wallet constructor's first argument. - defaultNetworkId?: string | number + defaultNetworkId?: number // transports for dapp to wallet jron-rpc communication transports?: {