-
Notifications
You must be signed in to change notification settings - Fork 494
/
chain.ts
114 lines (100 loc) · 3.12 KB
/
chain.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import { firstValueFrom, Observable } from 'rxjs'
import { filter, map } from 'rxjs/operators'
import { Chain, ProviderRpcErrorCode } from '@web3-onboard/common'
import { addNewChain, switchChain } from './provider.js'
import { state } from './store/index.js'
import { switchChainModal$ } from './streams.js'
import { validateSetChainOptions } from './validation.js'
import type { WalletState } from './types.js'
import { toHexString } from './utils.js'
async function setChain(options: {
chainId: string | number
chainNamespace?: string
wallet?: WalletState['label']
}): Promise<boolean> {
const error = validateSetChainOptions(options)
if (error) {
throw error
}
const { wallets, chains } = state.get()
const { chainId, chainNamespace = 'evm', wallet: walletToSet } = options
const chainIdHex = toHexString(chainId)
// validate that chainId has been added to chains
const chain = chains.find(
({ namespace, id }) => namespace === chainNamespace && id === chainIdHex
)
if (!chain) {
throw new Error(
`Chain with chainId: ${chainId} and chainNamespace: ${chainNamespace} has not been set and must be added when Onboard is initialized.`
)
}
const wallet = walletToSet
? wallets.find(({ label }) => label === walletToSet)
: wallets[0]
// validate a wallet is connected
if (!wallet) {
throw new Error(
walletToSet
? `Wallet with label ${walletToSet} is not connected`
: 'A wallet must be connected before a chain can be set'
)
}
const [walletConnectedChain] = wallet.chains
// check if wallet is already connected to chainId
if (
walletConnectedChain.namespace === chainNamespace &&
walletConnectedChain.id === chainIdHex
) {
return true
}
try {
await switchChain(wallet.provider, chainIdHex)
return true
} catch (error) {
const { code } = error as { code: number }
const switchChainModalClosed$ = switchChainModal$.pipe(
filter(x => x === null),
map(() => false)
)
if (
code === ProviderRpcErrorCode.CHAIN_NOT_ADDED ||
code === ProviderRpcErrorCode.UNRECOGNIZED_CHAIN_ID
) {
// chain has not been added to wallet
return chainNotInWallet(
wallet,
chain,
switchChainModalClosed$,
chainIdHex
)
}
if (code === ProviderRpcErrorCode.UNSUPPORTED_METHOD) {
// method not supported
switchChainModal$.next({ chain })
return firstValueFrom(switchChainModalClosed$)
}
}
return false
}
const chainNotInWallet = async (
wallet: WalletState,
chain: Chain,
switchChainModalClosed$: Observable<boolean>,
chainIdHex: string
): Promise<boolean> => {
try {
await addNewChain(wallet.provider, chain)
await switchChain(wallet.provider, chainIdHex)
return true
} catch (error) {
const { code } = error as { code: number }
if (code === ProviderRpcErrorCode.ACCOUNT_ACCESS_REJECTED) {
// add new chain rejected by user
return false
}
// display notification to user to switch chain
switchChainModal$.next({ chain })
return firstValueFrom(switchChainModalClosed$)
}
}
export default setChain