Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V2 provider proxy #389

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 52 additions & 19 deletions packages/provider/src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
JsonRpcRequest,
JsonRpcResponseCallback,
JsonRpcSender,
maybeChainId
maybeChainId,
findNetworkConfig
} from '@0xsequence/network'

import { resolveArrayProperties, Signer } from '@0xsequence/wallet'
Expand Down Expand Up @@ -188,21 +189,13 @@ export class Web3Signer extends Signer implements TypedDataSigner {
async getWalletConfig(chainId?: ChainIdLike): Promise<commons.config.Config> {
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<AccountStatus> {
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<NetworkConfig[]> {
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -513,7 +508,9 @@ const hexlifyTransaction = (
}

class UncheckedJsonRpcSigner extends Web3Signer {
sendTransaction(transaction: Deferrable<ethers.providers.TransactionRequest>): Promise<commons.transaction.TransactionResponse> {
sendTransaction(
transaction: Deferrable<ethers.providers.TransactionRequest>
): Promise<commons.transaction.TransactionResponse> {
return this.sendUncheckedTransaction(transaction).then(hash => {
return <commons.transaction.TransactionResponse>{
chainId: 0,
Expand All @@ -530,3 +527,39 @@ class UncheckedJsonRpcSigner extends Web3Signer {
})
}
}

export class ProviderProxy extends Web3Provider {
chainId: number

private providers: Record<number, Web3Provider>

private get _provider(): Web3Provider {
return this.providers[this.chainId]
}

constructor(providers: Record<number, Web3Provider>, chainId: number) {
super(undefined as any, chainId)
this.providers = providers
this.chainId = chainId
}

async send(method: string, params: Array<any>): Promise<any> {
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<number> {
return this._provider.getChainId()
}
}
144 changes: 82 additions & 62 deletions packages/provider/src/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -99,17 +101,25 @@ export class Wallet implements WalletProvider {
private networks: NetworkConfig[]
private providers: { [chainId: number]: Web3Provider }

constructor(network?: string | number, config?: Partial<ProviderConfig>) {
private providerProxy: ProviderProxy

constructor(chainId?: ChainIdLike, config?: Partial<ProviderConfig>) {
// 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) {
Expand All @@ -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()
Expand Down Expand Up @@ -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<boolean> => {
Expand Down Expand Up @@ -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 } {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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?: {
Expand Down