@wagmi/connectors 3.1.1 → 4.0.0

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