@wagmi/connectors 4.0.0-alpha.3 → 4.0.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/injected.ts DELETED
@@ -1,531 +0,0 @@
1
- import {
2
- ChainNotConfiguredError,
3
- ProviderNotFoundError,
4
- createConnector,
5
- normalizeChainId,
6
- } from '@wagmi/core'
7
- import type { Evaluate } from '@wagmi/core/internal'
8
- import {
9
- type Address,
10
- type EIP1193Provider,
11
- type ProviderConnectInfo,
12
- ProviderRpcError,
13
- ResourceUnavailableRpcError,
14
- RpcError,
15
- SwitchChainError,
16
- UserRejectedRequestError,
17
- getAddress,
18
- numberToHex,
19
- } from 'viem'
20
-
21
- export type InjectedParameters = {
22
- /**
23
- * MetaMask and other injected providers do not support programmatic disconnect.
24
- * This flag simulates the disconnect behavior by keeping track of connection status in storage. See [GitHub issue](https://github.com/MetaMask/metamask-extension/issues/10353) for more info.
25
- * @default true
26
- */
27
- shimDisconnect?: boolean | undefined
28
- unstable_shimAsyncInject?: boolean | number | undefined
29
- /**
30
- * [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) Ethereum Provider to target
31
- */
32
- target?:
33
- | TargetId
34
- | (TargetMap[TargetId] & { id: string })
35
- | (() => (TargetMap[TargetId] & { id: string }) | undefined)
36
- | undefined
37
- }
38
-
39
- const targetMap = {
40
- coinbaseWallet: {
41
- name: 'Coinbase Wallet',
42
- provider(window) {
43
- if (window?.coinbaseWalletExtension) return window.coinbaseWalletExtension
44
- return findProvider(window, 'isCoinbaseWallet')
45
- },
46
- },
47
- metaMask: {
48
- name: 'MetaMask',
49
- provider(window) {
50
- return findProvider(window, (provider) => {
51
- if (!provider.isMetaMask) return false
52
- // Brave tries to make itself look like MetaMask
53
- // Could also try RPC `web3_clientVersion` if following is unreliable
54
- if (provider.isBraveWallet && !provider._events && !provider._state)
55
- return false
56
- // Other wallets that try to look like MetaMask
57
- const flags: WalletProviderFlags[] = [
58
- 'isApexWallet',
59
- 'isAvalanche',
60
- 'isBitKeep',
61
- 'isBlockWallet',
62
- 'isKuCoinWallet',
63
- 'isMathWallet',
64
- 'isOkxWallet',
65
- 'isOKExWallet',
66
- 'isOneInchIOSWallet',
67
- 'isOneInchAndroidWallet',
68
- 'isOpera',
69
- 'isPortal',
70
- 'isRabby',
71
- 'isTokenPocket',
72
- 'isTokenary',
73
- 'isZerion',
74
- ]
75
- for (const flag of flags) if (provider[flag]) return false
76
- return true
77
- })
78
- },
79
- },
80
- phantom: {
81
- name: 'Phantom',
82
- provider(window) {
83
- if (window?.phantom?.ethereum) return window.phantom?.ethereum
84
- return findProvider(window, 'isPhantom')
85
- },
86
- },
87
- } as const satisfies TargetMap
88
-
89
- export function injected(parameters: InjectedParameters = {}) {
90
- const { shimDisconnect = true, unstable_shimAsyncInject } = parameters
91
-
92
- function getTarget(): Evaluate<TargetMap[TargetId] & { id: string }> {
93
- const target = parameters.target
94
- if (typeof target === 'function') {
95
- const result = target()
96
- if (result) return result
97
- }
98
-
99
- if (typeof target === 'object') return target
100
-
101
- if (typeof target === 'string')
102
- return {
103
- ...(targetMap[target as keyof typeof targetMap] ?? {
104
- name: `${target[0]!.toUpperCase()}${target.slice(1)}`,
105
- provider: `is${target[0]!.toUpperCase()}${target.slice(1)}`,
106
- }),
107
- id: target,
108
- }
109
-
110
- return {
111
- id: 'injected',
112
- name: 'Injected',
113
- provider(window) {
114
- return window?.ethereum
115
- },
116
- }
117
- }
118
-
119
- type Provider = WalletProvider | undefined
120
- type Properties = {
121
- onConnect(connectInfo: ProviderConnectInfo): void
122
- }
123
- type StorageItem = { [_ in `${string}.connected`]: true }
124
-
125
- return createConnector<Provider, Properties, StorageItem>((config) => ({
126
- get id() {
127
- return getTarget().id
128
- },
129
- get name() {
130
- return getTarget().name
131
- },
132
- async setup() {
133
- const provider = await this.getProvider()
134
- // Only start listening for events if `target` is set, otherwise `injected()` will also receive events
135
- if (provider && parameters.target) {
136
- provider.on('accountsChanged', this.onAccountsChanged.bind(this))
137
- provider.on('connect', this.onConnect.bind(this))
138
- }
139
- },
140
- async connect({ chainId } = {}) {
141
- const provider = await this.getProvider()
142
- if (!provider) throw new ProviderNotFoundError()
143
-
144
- // Attempt to show select prompt with `wallet_requestPermissions` when
145
- // `shimDisconnect` is active and account is in disconnected state (flag in storage)
146
- const isDisconnected =
147
- shimDisconnect &&
148
- !(await config.storage?.getItem(`${this.id}.connected`))
149
-
150
- let accounts: readonly Address[] | null = null
151
- if (isDisconnected) {
152
- accounts = await this.getAccounts().catch(() => null)
153
- const isAuthorized = !!accounts?.length
154
- if (isAuthorized)
155
- // Attempt to show another prompt for selecting connector if already connected
156
- try {
157
- const permissions = await provider.request({
158
- method: 'wallet_requestPermissions',
159
- params: [{ eth_accounts: {} }],
160
- })
161
- accounts = permissions[0]?.caveats?.[0]?.value?.map(getAddress)
162
- } catch (err) {
163
- const error = err as RpcError
164
- // Not all injected providers support `wallet_requestPermissions` (e.g. MetaMask iOS).
165
- // Only bubble up error if user rejects request
166
- if (error.code === UserRejectedRequestError.code)
167
- throw new UserRejectedRequestError(error)
168
- // Or prompt is already open
169
- if (error.code === ResourceUnavailableRpcError.code) throw error
170
- }
171
- }
172
-
173
- try {
174
- if (!accounts?.length) {
175
- const requestedAccounts = await provider.request({
176
- method: 'eth_requestAccounts',
177
- })
178
- accounts = requestedAccounts.map(getAddress)
179
- }
180
-
181
- provider.removeListener('connect', this.onConnect.bind(this))
182
- provider.on('accountsChanged', this.onAccountsChanged.bind(this))
183
- provider.on('chainChanged', this.onChainChanged)
184
- provider.on('disconnect', this.onDisconnect.bind(this))
185
-
186
- // Switch to chain if provided
187
- let currentChainId = await this.getChainId()
188
- if (chainId && currentChainId !== chainId) {
189
- const chain = await this.switchChain?.({ chainId }).catch(() => ({
190
- id: currentChainId,
191
- }))
192
- currentChainId = chain?.id ?? currentChainId
193
- }
194
-
195
- // Add shim to storage signalling connector is connected
196
- if (shimDisconnect)
197
- await config.storage?.setItem(`${this.id}.connected`, true)
198
-
199
- return { accounts, chainId: currentChainId }
200
- } catch (err) {
201
- const error = err as RpcError
202
- if (error.code === UserRejectedRequestError.code)
203
- throw new UserRejectedRequestError(error)
204
- if (error.code === ResourceUnavailableRpcError.code)
205
- throw new ResourceUnavailableRpcError(error)
206
- throw error
207
- }
208
- },
209
- async disconnect() {
210
- const provider = await this.getProvider()
211
- if (!provider) throw new ProviderNotFoundError()
212
-
213
- provider.removeListener(
214
- 'accountsChanged',
215
- this.onAccountsChanged.bind(this),
216
- )
217
- provider.removeListener('chainChanged', this.onChainChanged)
218
- provider.removeListener('disconnect', this.onDisconnect.bind(this))
219
- provider.on('connect', this.onConnect.bind(this))
220
-
221
- // Remove shim signalling connector is disconnected
222
- if (shimDisconnect)
223
- await config.storage?.removeItem(`${this.id}.connected`)
224
- },
225
- async getAccounts() {
226
- const provider = await this.getProvider()
227
- if (!provider) throw new ProviderNotFoundError()
228
- const accounts = await provider.request({ method: 'eth_accounts' })
229
- return accounts.map(getAddress)
230
- },
231
- async getChainId() {
232
- const provider = await this.getProvider()
233
- if (!provider) throw new ProviderNotFoundError()
234
- const hexChainId = await provider.request({ method: 'eth_chainId' })
235
- return normalizeChainId(hexChainId)
236
- },
237
- async getProvider() {
238
- if (typeof window === 'undefined') return undefined
239
- const target = getTarget()
240
- if (typeof target.provider === 'function')
241
- return target.provider(window as Window | undefined)
242
- return findProvider(window, target.provider)
243
- },
244
- async isAuthorized() {
245
- try {
246
- const isDisconnected =
247
- shimDisconnect &&
248
- // If shim does not exist in storage, connector is disconnected
249
- !(await config.storage?.getItem(`${this.id}.connected`))
250
- if (isDisconnected) return false
251
-
252
- const provider = await this.getProvider()
253
- if (!provider) {
254
- if (
255
- unstable_shimAsyncInject !== undefined &&
256
- unstable_shimAsyncInject !== false
257
- ) {
258
- // If no provider is found, check for async injection
259
- // https://github.com/wagmi-dev/references/issues/167
260
- // https://github.com/MetaMask/detect-provider
261
- const handleEthereum = async () => {
262
- if (typeof window !== 'undefined')
263
- window.removeEventListener(
264
- 'ethereum#initialized',
265
- handleEthereum,
266
- )
267
- const provider = await this.getProvider()
268
- return !!provider
269
- }
270
- const timeout =
271
- typeof unstable_shimAsyncInject === 'number'
272
- ? unstable_shimAsyncInject
273
- : 1_000
274
- const res = await Promise.race([
275
- ...(typeof window !== 'undefined'
276
- ? [
277
- new Promise<boolean>((resolve) =>
278
- window.addEventListener(
279
- 'ethereum#initialized',
280
- () => resolve(handleEthereum()),
281
- { once: true },
282
- ),
283
- ),
284
- ]
285
- : []),
286
- new Promise<boolean>((resolve) =>
287
- setTimeout(() => resolve(handleEthereum()), timeout),
288
- ),
289
- ])
290
- if (res) return true
291
- }
292
-
293
- throw new ProviderNotFoundError()
294
- }
295
-
296
- const accounts = await this.getAccounts()
297
- return !!accounts.length
298
- } catch {
299
- return false
300
- }
301
- },
302
- async switchChain({ chainId }) {
303
- const provider = await this.getProvider()
304
- if (!provider) throw new ProviderNotFoundError()
305
-
306
- const chain = config.chains.find((x) => x.id === chainId)
307
- if (!chain) throw new SwitchChainError(new ChainNotConfiguredError())
308
-
309
- const id = numberToHex(chainId)
310
-
311
- try {
312
- await Promise.all([
313
- provider.request({
314
- method: 'wallet_switchEthereumChain',
315
- params: [{ chainId: id }],
316
- }),
317
- new Promise<void>((resolve) =>
318
- config.emitter.once('change', ({ chainId: currentChainId }) => {
319
- if (currentChainId === chainId) resolve()
320
- }),
321
- ),
322
- ])
323
- return chain
324
- } catch (err) {
325
- const error = err as RpcError
326
-
327
- // Indicates chain is not added to provider
328
- if (
329
- error.code === 4902 ||
330
- // Unwrapping for MetaMask Mobile
331
- // https://github.com/MetaMask/metamask-mobile/issues/2944#issuecomment-976988719
332
- (error as ProviderRpcError<{ originalError?: { code: number } }>)
333
- ?.data?.originalError?.code === 4902
334
- ) {
335
- try {
336
- const { default: blockExplorer, ...blockExplorers } =
337
- chain.blockExplorers ?? {}
338
- let blockExplorerUrls: string[] = []
339
- if (blockExplorer)
340
- blockExplorerUrls = [
341
- blockExplorer.url,
342
- ...Object.values(blockExplorers).map((x) => x.url),
343
- ]
344
-
345
- await provider.request({
346
- method: 'wallet_addEthereumChain',
347
- params: [
348
- {
349
- chainId: id,
350
- chainName: chain.name,
351
- nativeCurrency: chain.nativeCurrency,
352
- rpcUrls: [chain.rpcUrls.public?.http[0] ?? ''],
353
- blockExplorerUrls,
354
- },
355
- ],
356
- })
357
-
358
- const currentChainId = await this.getChainId()
359
- if (currentChainId !== chainId)
360
- throw new UserRejectedRequestError(
361
- new Error('User rejected switch after adding network.'),
362
- )
363
-
364
- return chain
365
- } catch (error) {
366
- throw new UserRejectedRequestError(error as Error)
367
- }
368
- }
369
-
370
- if (error.code === UserRejectedRequestError.code)
371
- throw new UserRejectedRequestError(error)
372
- throw new SwitchChainError(error)
373
- }
374
- },
375
- async onAccountsChanged(accounts) {
376
- // Disconnect if there are no accounts
377
- if (accounts.length === 0) this.onDisconnect()
378
- // Connect if emitter is listening for connect event (e.g. is disconnected)
379
- else if (config.emitter.listenerCount('connect')) {
380
- const chainId = (await this.getChainId()).toString()
381
- this.onConnect({ chainId })
382
- }
383
- // Regular change event
384
- else config.emitter.emit('change', { accounts: accounts.map(getAddress) })
385
- },
386
- onChainChanged(chain) {
387
- const chainId = normalizeChainId(chain)
388
- config.emitter.emit('change', { chainId })
389
- },
390
- async onConnect(connectInfo) {
391
- const accounts = await this.getAccounts()
392
- if (accounts.length === 0) return
393
-
394
- const chainId = normalizeChainId(connectInfo.chainId)
395
- config.emitter.emit('connect', { accounts, chainId })
396
-
397
- const provider = await this.getProvider()
398
- if (provider) {
399
- provider.removeListener('connect', this.onConnect.bind(this))
400
- provider.on('accountsChanged', this.onAccountsChanged.bind(this))
401
- provider.on('chainChanged', this.onChainChanged)
402
- provider.on('disconnect', this.onDisconnect.bind(this))
403
- }
404
-
405
- // Add shim to storage signalling connector is connected
406
- if (shimDisconnect)
407
- await config.storage?.setItem(`${this.id}.connected`, true)
408
- },
409
- async onDisconnect(error) {
410
- const provider = await this.getProvider()
411
-
412
- // If MetaMask emits a `code: 1013` error, wait for reconnection before disconnecting
413
- // https://github.com/MetaMask/providers/pull/120
414
- if (error && (error as RpcError<1013>).code === 1013) {
415
- if (provider && !!(await this.getAccounts()).length) return
416
- }
417
-
418
- // No need to remove `shimDisconnectStorageKey` from storage because `onDisconnect` is typically
419
- // only called when the wallet is disconnected through the wallet's interface, meaning the wallet
420
- // actually disconnected and we don't need to simulate it.
421
- config.emitter.emit('disconnect')
422
-
423
- if (provider) {
424
- provider.removeListener(
425
- 'accountsChanged',
426
- this.onAccountsChanged.bind(this),
427
- )
428
- provider.removeListener('chainChanged', this.onChainChanged)
429
- provider.removeListener('disconnect', this.onDisconnect.bind(this))
430
- provider.on('connect', this.onConnect.bind(this))
431
- }
432
- },
433
- }))
434
- }
435
-
436
- type Target = {
437
- name: string
438
- provider:
439
- | WalletProviderFlags
440
- | ((window?: Window | undefined) => WalletProvider | undefined)
441
- }
442
-
443
- export type TargetId = Evaluate<WalletProviderFlags> extends `is${infer name}`
444
- ? name extends `${infer char}${infer rest}`
445
- ? `${Lowercase<char>}${rest}`
446
- : never
447
- : never
448
-
449
- type TargetMap = { [_ in TargetId]?: Target | undefined }
450
-
451
- type WalletProviderFlags =
452
- | 'isApexWallet'
453
- | 'isAvalanche'
454
- | 'isBackpack'
455
- | 'isBifrost'
456
- | 'isBitKeep'
457
- | 'isBitski'
458
- | 'isBlockWallet'
459
- | 'isBraveWallet'
460
- | 'isCoinbaseWallet'
461
- | 'isDawn'
462
- | 'isEnkrypt'
463
- | 'isExodus'
464
- | 'isFrame'
465
- | 'isFrontier'
466
- | 'isGamestop'
467
- | 'isHyperPay'
468
- | 'isImToken'
469
- | 'isKuCoinWallet'
470
- | 'isMathWallet'
471
- | 'isMetaMask'
472
- | 'isOkxWallet'
473
- | 'isOKExWallet'
474
- | 'isOneInchAndroidWallet'
475
- | 'isOneInchIOSWallet'
476
- | 'isOpera'
477
- | 'isPhantom'
478
- | 'isPortal'
479
- | 'isRabby'
480
- | 'isRainbow'
481
- | 'isStatus'
482
- | 'isTally'
483
- | 'isTokenPocket'
484
- | 'isTokenary'
485
- | 'isTrust'
486
- | 'isTrustWallet'
487
- | 'isXDEFI'
488
- | 'isZerion'
489
-
490
- type WalletProvider = Evaluate<
491
- EIP1193Provider & {
492
- [key in WalletProviderFlags]?: true | undefined
493
- } & {
494
- providers?: WalletProvider[] | undefined
495
- /** Only exists in MetaMask as of 2022/04/03 */
496
- _events?: { connect?: (() => void) | undefined } | undefined
497
- /** Only exists in MetaMask as of 2022/04/03 */
498
- _state?:
499
- | {
500
- accounts?: string[]
501
- initialized?: boolean
502
- isConnected?: boolean
503
- isPermanentlyDisconnected?: boolean
504
- isUnlocked?: boolean
505
- }
506
- | undefined
507
- }
508
- >
509
-
510
- type Window = {
511
- coinbaseWalletExtension?: WalletProvider | undefined
512
- ethereum?: WalletProvider | undefined
513
- phantom?: { ethereum: WalletProvider } | undefined
514
- }
515
-
516
- function findProvider(
517
- window: globalThis.Window | Window | undefined,
518
- select?: WalletProviderFlags | ((provider: WalletProvider) => boolean),
519
- ) {
520
- function isProvider(provider: WalletProvider) {
521
- if (typeof select === 'function') return select(provider)
522
- if (typeof select === 'string') return provider[select]
523
- return true
524
- }
525
-
526
- const ethereum = (window as Window).ethereum
527
- if (ethereum?.providers)
528
- return ethereum.providers.find((provider) => isProvider(provider))
529
- if (ethereum && isProvider(ethereum)) return ethereum
530
- return undefined
531
- }