Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore: absorb coinbase connector changes
- Loading branch information
1 parent
ec1a899
commit e6f9712
Showing
4 changed files
with
249 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,234 @@ | ||
import { | ||
CoinbaseWalletSDK, | ||
type ProviderInterface, | ||
} from '@coinbase/wallet-sdk'; | ||
import { | ||
ChainNotConfiguredError, | ||
type Connector, | ||
createConnector, | ||
} from '@wagmi/core'; | ||
import type { Evaluate, Mutable } from '@wagmi/core/internal'; | ||
import { | ||
type AddEthereumChainParameter, | ||
type ProviderRpcError, | ||
SwitchChainError, | ||
UserRejectedRequestError, | ||
getAddress, | ||
numberToHex, | ||
} from 'viem'; | ||
|
||
export type CoinbaseWalletParameters = Evaluate< | ||
Mutable<ConstructorParameters<typeof CoinbaseWalletSDK>[0]> & { | ||
preference?: { | ||
options: 'all' | 'smartWalletOnly' | 'eoaOnly'; | ||
}; | ||
} | ||
>; | ||
|
||
coinbaseWallet.type = 'coinbaseWallet' as const; | ||
export function coinbaseWallet(parameters: CoinbaseWalletParameters) { | ||
type Properties = {}; | ||
|
||
let sdk: CoinbaseWalletSDK | undefined; | ||
let walletProvider: ProviderInterface | undefined; | ||
|
||
let accountsChanged: Connector['onAccountsChanged'] | undefined; | ||
let chainChanged: Connector['onChainChanged'] | undefined; | ||
let disconnect: Connector['onDisconnect'] | undefined; | ||
|
||
return createConnector<ProviderInterface, Properties>((config) => ({ | ||
id: 'coinbaseWalletSDK', | ||
name: 'Coinbase Wallet', | ||
supportsSimulation: true, | ||
type: coinbaseWallet.type, | ||
async connect({ chainId } = {}) { | ||
try { | ||
const provider = await this.getProvider(); | ||
const accounts = ( | ||
(await provider.request({ | ||
method: 'eth_requestAccounts', | ||
})) as string[] | ||
).map((x) => getAddress(x)); | ||
|
||
if (!accountsChanged) { | ||
accountsChanged = this.onAccountsChanged.bind(this); | ||
provider.on('accountsChanged', accountsChanged); | ||
} | ||
if (!chainChanged) { | ||
chainChanged = this.onChainChanged.bind(this); | ||
provider.on('chainChanged', chainChanged); | ||
} | ||
if (!disconnect) { | ||
disconnect = this.onDisconnect.bind(this); | ||
provider.on('disconnect', disconnect); | ||
} | ||
|
||
// Switch to chain if provided | ||
let currentChainId: number = await this.getChainId(); | ||
if (chainId && currentChainId !== chainId) { | ||
const chain = await this.switchChain!({ chainId }).catch((error) => { | ||
if (error.code === UserRejectedRequestError.code) throw error; | ||
return { id: currentChainId }; | ||
}); | ||
currentChainId = chain?.id ?? currentChainId; | ||
} | ||
|
||
return { accounts, chainId: currentChainId }; | ||
} catch (error) { | ||
if ( | ||
/(user closed modal|accounts received is empty|user denied account)/i.test( | ||
(error as Error).message, | ||
) | ||
) | ||
throw new UserRejectedRequestError(error as Error); | ||
throw error; | ||
} | ||
}, | ||
async disconnect() { | ||
const provider = await this.getProvider(); | ||
|
||
if (accountsChanged) { | ||
provider.removeListener('accountsChanged', accountsChanged); | ||
accountsChanged = undefined; | ||
} | ||
if (chainChanged) { | ||
provider.removeListener('chainChanged', chainChanged); | ||
chainChanged = undefined; | ||
} | ||
if (disconnect) { | ||
provider.removeListener('disconnect', disconnect); | ||
disconnect = undefined; | ||
} | ||
|
||
provider.disconnect(); | ||
}, | ||
async getAccounts() { | ||
const provider = await this.getProvider(); | ||
return ( | ||
await provider.request<string[]>({ | ||
method: 'eth_accounts', | ||
}) | ||
).map((x) => getAddress(x)); | ||
}, | ||
async getChainId() { | ||
const provider = await this.getProvider(); | ||
const chainId = await provider.request({ method: 'eth_chainId' }); | ||
return Number(chainId); | ||
}, | ||
async getProvider() { | ||
if (!walletProvider) { | ||
const { default: CoinbaseWalletSDK } = await import( | ||
'@coinbase/wallet-sdk' | ||
); | ||
let SDK: typeof CoinbaseWalletSDK; | ||
if ( | ||
typeof CoinbaseWalletSDK !== 'function' && | ||
typeof (CoinbaseWalletSDK as { default: typeof CoinbaseWalletSDK }) | ||
.default === 'function' | ||
) { | ||
SDK = (CoinbaseWalletSDK as { default: typeof CoinbaseWalletSDK }) | ||
.default; | ||
} else { | ||
SDK = CoinbaseWalletSDK as unknown as typeof CoinbaseWalletSDK; | ||
} | ||
sdk = new SDK(parameters); | ||
|
||
walletProvider = sdk.makeWeb3Provider( | ||
parameters.preference, | ||
) as ProviderInterface; | ||
} | ||
|
||
return walletProvider as ProviderInterface; | ||
}, | ||
async isAuthorized() { | ||
try { | ||
const accounts = await this.getAccounts(); | ||
return !!accounts.length; | ||
} catch { | ||
return false; | ||
} | ||
}, | ||
async switchChain({ addEthereumChainParameter, chainId }) { | ||
const chain = config.chains.find((chain) => chain.id === chainId); | ||
if (!chain) throw new SwitchChainError(new ChainNotConfiguredError()); | ||
|
||
const provider = await this.getProvider(); | ||
|
||
try { | ||
await provider.request({ | ||
method: 'wallet_switchEthereumChain', | ||
params: [{ chainId: numberToHex(chain.id) }], | ||
}); | ||
return chain; | ||
} catch (error) { | ||
// Indicates chain is not added to provider | ||
if ((error as ProviderRpcError).code === 4902) { | ||
try { | ||
let blockExplorerUrls; | ||
if (addEthereumChainParameter?.blockExplorerUrls) | ||
blockExplorerUrls = addEthereumChainParameter.blockExplorerUrls; | ||
else | ||
blockExplorerUrls = chain.blockExplorers?.default.url | ||
? [chain.blockExplorers?.default.url] | ||
: []; | ||
|
||
let rpcUrls; | ||
if (addEthereumChainParameter?.rpcUrls?.length) | ||
rpcUrls = addEthereumChainParameter.rpcUrls; | ||
else rpcUrls = [chain.rpcUrls.default?.http[0] ?? '']; | ||
|
||
const addEthereumChain = { | ||
blockExplorerUrls, | ||
chainId: numberToHex(chainId), | ||
chainName: addEthereumChainParameter?.chainName ?? chain.name, | ||
iconUrls: addEthereumChainParameter?.iconUrls, | ||
nativeCurrency: | ||
addEthereumChainParameter?.nativeCurrency ?? | ||
chain.nativeCurrency, | ||
rpcUrls, | ||
} satisfies AddEthereumChainParameter; | ||
|
||
await provider.request({ | ||
method: 'wallet_addEthereumChain', | ||
params: [addEthereumChain], | ||
}); | ||
|
||
return chain; | ||
} catch (error) { | ||
throw new UserRejectedRequestError(error as Error); | ||
} | ||
} | ||
|
||
throw new SwitchChainError(error as Error); | ||
} | ||
}, | ||
onAccountsChanged(accounts) { | ||
if (accounts.length === 0) this.onDisconnect(); | ||
else | ||
config.emitter.emit('change', { | ||
accounts: accounts.map((x) => getAddress(x)), | ||
}); | ||
}, | ||
onChainChanged(chain) { | ||
const chainId = Number(chain); | ||
config.emitter.emit('change', { chainId }); | ||
}, | ||
async onDisconnect(_error) { | ||
config.emitter.emit('disconnect'); | ||
|
||
const provider = await this.getProvider(); | ||
if (accountsChanged) { | ||
provider.removeListener('accountsChanged', accountsChanged); | ||
accountsChanged = undefined; | ||
} | ||
if (chainChanged) { | ||
provider.removeListener('chainChanged', chainChanged); | ||
chainChanged = undefined; | ||
} | ||
if (disconnect) { | ||
provider.removeListener('disconnect', disconnect); | ||
disconnect = undefined; | ||
} | ||
}, | ||
})); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.