forked from wevm/references
-
Notifications
You must be signed in to change notification settings - Fork 0
/
metaMask.ts
141 lines (125 loc) · 4.81 KB
/
metaMask.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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
import {
ConnectorNotFoundError,
ResourceUnavailableError,
UserRejectedRequestError,
getClient,
} from '@wagmi/core'
import type { Address, RpcError } from '@wagmi/core'
import type { Chain } from '@wagmi/core/chains'
import { getAddress } from 'ethers/lib/utils.js'
import type { InjectedConnectorOptions } from './injected'
import { InjectedConnector } from './injected'
import { Ethereum } from './types'
export type MetaMaskConnectorOptions = Pick<
InjectedConnectorOptions,
'shimChainChangedDisconnect' | 'shimDisconnect'
> & {
/**
* While "disconnected" with `shimDisconnect`, allows user to select a different MetaMask account (than the currently connected account) when trying to connect.
*/
UNSTABLE_shimOnConnectSelectAccount?: boolean
}
export class MetaMaskConnector extends InjectedConnector {
readonly id = 'metaMask'
protected shimDisconnectKey = `${this.id}.shimDisconnect`
#UNSTABLE_shimOnConnectSelectAccount: MetaMaskConnectorOptions['UNSTABLE_shimOnConnectSelectAccount']
constructor({
chains,
options: options_,
}: {
chains?: Chain[]
options?: MetaMaskConnectorOptions
} = {}) {
const options = {
name: 'MetaMask',
shimDisconnect: true,
shimChainChangedDisconnect: true,
getProvider() {
function getReady(ethereum?: Ethereum) {
const isMetaMask = !!ethereum?.isMetaMask
if (!isMetaMask) return
// Brave tries to make itself look like MetaMask
// Could also try RPC `web3_clientVersion` if following is unreliable
if (ethereum.isBraveWallet && !ethereum._events && !ethereum._state)
return
if (ethereum.isApexWallet) return
if (ethereum.isAvalanche) return
if (ethereum.isKuCoinWallet) return
if (ethereum.isPortal) return
if (ethereum.isTokenPocket) return
if (ethereum.isTokenary) return
return ethereum
}
if (typeof window === 'undefined') return
const ethereum = window.ethereum as Ethereum | undefined
if (ethereum?.providers) return ethereum.providers.find(getReady)
return getReady(ethereum)
},
...options_,
}
super({ chains, options })
this.#UNSTABLE_shimOnConnectSelectAccount =
options.UNSTABLE_shimOnConnectSelectAccount
}
async connect({ chainId }: { chainId?: number } = {}) {
try {
const provider = await this.getProvider()
if (!provider) throw new ConnectorNotFoundError()
if (provider.on) {
provider.on('accountsChanged', this.onAccountsChanged)
provider.on('chainChanged', this.onChainChanged)
provider.on('disconnect', this.onDisconnect)
}
this.emit('message', { type: 'connecting' })
// Attempt to show wallet select prompt with `wallet_requestPermissions` when
// `shimDisconnect` is active and account is in disconnected state (flag in storage)
let account: Address | null = null
if (
this.#UNSTABLE_shimOnConnectSelectAccount &&
this.options?.shimDisconnect &&
!getClient().storage?.getItem(this.shimDisconnectKey)
) {
account = await this.getAccount().catch(() => null)
const isConnected = !!account
if (isConnected)
// Attempt to show another prompt for selecting wallet if already connected
try {
await provider.request({
method: 'wallet_requestPermissions',
params: [{ eth_accounts: {} }],
})
// User may have selected a different account so we will need to revalidate here.
account = await this.getAccount()
} catch (error) {
// Not all MetaMask injected providers support `wallet_requestPermissions` (e.g. MetaMask iOS).
// Only bubble up error if user rejects request
if (this.isUserRejectedRequestError(error))
throw new UserRejectedRequestError(error)
}
}
if (!account) {
const accounts = await provider.request({
method: 'eth_requestAccounts',
})
account = getAddress(accounts[0] as string)
}
// Switch to chain if provided
let id = await this.getChainId()
let unsupported = this.isChainUnsupported(id)
if (chainId && id !== chainId) {
const chain = await this.switchChain(chainId)
id = chain.id
unsupported = this.isChainUnsupported(id)
}
if (this.options?.shimDisconnect)
getClient().storage?.setItem(this.shimDisconnectKey, true)
return { account, chain: { id, unsupported }, provider }
} catch (error) {
if (this.isUserRejectedRequestError(error))
throw new UserRejectedRequestError(error)
if ((error as RpcError).code === -32002)
throw new ResourceUnavailableError(error)
throw error
}
}
}