@wagmi/connectors 5.0.0 → 5.0.2

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/metaMask.ts CHANGED
@@ -1,16 +1,12 @@
1
- import {
2
- EventType,
1
+ import type {
3
2
  MetaMaskSDK,
4
- type MetaMaskSDKOptions,
3
+ MetaMaskSDKOptions,
5
4
  SDKProvider,
6
5
  } from '@metamask/sdk'
6
+ import { ChainNotConfiguredError, createConnector } from '@wagmi/core'
7
+ import type { Evaluate, ExactPartial } from '@wagmi/core/internal'
7
8
  import {
8
- ChainNotConfiguredError,
9
- createConnector,
10
- normalizeChainId,
11
- } from '@wagmi/core'
12
- import type { Evaluate, ExactPartial, Omit } from '@wagmi/core/internal'
13
- import {
9
+ type AddEthereumChainParameter,
14
10
  type Address,
15
11
  type ProviderConnectInfo,
16
12
  type ProviderRpcError,
@@ -18,31 +14,15 @@ import {
18
14
  RpcError,
19
15
  SwitchChainError,
20
16
  UserRejectedRequestError,
21
- type WalletPermission,
22
17
  getAddress,
23
18
  numberToHex,
24
19
  } from 'viem'
25
20
 
26
21
  export type MetaMaskParameters = Evaluate<
27
- ExactPartial<
28
- Omit<
29
- MetaMaskSDKOptions,
30
- | 'checkInstallationImmediately'
31
- | 'checkInstallationOnAllCalls'
32
- | 'defaultReadOnlyChainId'
33
- | 'readonlyRPCMap'
34
- >
35
- >
22
+ ExactPartial<Omit<MetaMaskSDKOptions, '_source' | 'readonlyRPCMap'>>
36
23
  >
37
24
 
38
25
  metaMask.type = 'metaMask' as const
39
- /**
40
- * @deprecated
41
- *
42
- * __Warning__ This connector has a large file size due to the underlying `@metamask/sdk`. For mobile
43
- * support, it is recommended to use {@link walletConnect}. For desktop support, you should rely on Multi Injected
44
- * Provider Discovery (EIP-6963) via the Wagmi {@link Config}.
45
- */
46
26
  export function metaMask(parameters: MetaMaskParameters = {}) {
47
27
  type Provider = SDKProvider
48
28
  type Properties = {
@@ -52,7 +32,8 @@ export function metaMask(parameters: MetaMaskParameters = {}) {
52
32
  type Listener = Parameters<Provider['on']>[1]
53
33
 
54
34
  let sdk: MetaMaskSDK
55
- let walletProvider: Provider | undefined
35
+ let provider: Provider | undefined
36
+ let providerPromise: Promise<typeof provider>
56
37
 
57
38
  return createConnector<Provider, Properties, StorageItem>((config) => ({
58
39
  id: 'metaMaskSDK',
@@ -66,33 +47,13 @@ export function metaMask(parameters: MetaMaskParameters = {}) {
66
47
  async connect({ chainId, isReconnecting } = {}) {
67
48
  const provider = await this.getProvider()
68
49
 
69
- let accounts: readonly Address[] | null = null
70
- if (!isReconnecting) {
71
- accounts = await this.getAccounts().catch(() => null)
72
- const isAuthorized = !!accounts?.length
73
- if (isAuthorized)
74
- // Attempt to show another prompt for selecting account if already connected
75
- try {
76
- const permissions = (await provider.request({
77
- method: 'wallet_requestPermissions',
78
- params: [{ eth_accounts: {} }],
79
- })) as WalletPermission[]
80
- accounts = permissions[0]?.caveats?.[0]?.value?.map(getAddress)
81
- } catch (err) {
82
- const error = err as RpcError
83
- // Not all injected providers support `wallet_requestPermissions` (e.g. MetaMask iOS).
84
- // Only bubble up error if user rejects request
85
- if (error.code === UserRejectedRequestError.code)
86
- throw new UserRejectedRequestError(error)
87
- // Or prompt is already open
88
- if (error.code === ResourceUnavailableRpcError.code) throw error
89
- }
90
- }
50
+ let accounts: readonly Address[] = []
51
+ if (isReconnecting) accounts = await this.getAccounts().catch(() => [])
91
52
 
92
53
  try {
93
54
  if (!accounts?.length) {
94
55
  const requestedAccounts = (await sdk.connect()) as string[]
95
- accounts = requestedAccounts.map(getAddress)
56
+ accounts = requestedAccounts.map((x) => getAddress(x))
96
57
  }
97
58
 
98
59
  provider.removeListener(
@@ -106,20 +67,8 @@ export function metaMask(parameters: MetaMaskParameters = {}) {
106
67
  provider.on('chainChanged', this.onChainChanged as Listener)
107
68
  provider.on('disconnect', this.onDisconnect.bind(this) as Listener)
108
69
 
109
- // Backward compatibility with older wallet (<7.3) version that return accounts before authorization
110
- if (!sdk.isExtensionActive() && !sdk._getConnection()?.isAuthorized()) {
111
- function waitForAuthorized() {
112
- return new Promise((resolve) => {
113
- const connection = sdk._getConnection()
114
- const connector = connection?.getConnector()
115
- connector?.once(EventType.AUTHORIZED, () => resolve(true))
116
- })
117
- }
118
- await waitForAuthorized()
119
- }
120
-
121
70
  // Switch to chain if provided
122
- let currentChainId = await this.getChainId()
71
+ let currentChainId = (await this.getChainId()) as number
123
72
  if (chainId && currentChainId !== chainId) {
124
73
  const chain = await this.switchChain!({ chainId }).catch((error) => {
125
74
  if (error.code === UserRejectedRequestError.code) throw error
@@ -161,48 +110,62 @@ export function metaMask(parameters: MetaMaskParameters = {}) {
161
110
  const accounts = (await provider.request({
162
111
  method: 'eth_accounts',
163
112
  })) as string[]
164
- return accounts.map(getAddress)
113
+ return accounts.map((x) => getAddress(x))
165
114
  },
166
115
  async getChainId() {
167
116
  const provider = await this.getProvider()
168
117
  const chainId =
169
- provider.chainId ?? (await provider?.request({ method: 'eth_chainId' }))
170
- return normalizeChainId(chainId)
118
+ provider.getChainId() ||
119
+ (await provider?.request({ method: 'eth_chainId' }))
120
+ return Number(chainId)
171
121
  },
172
122
  async getProvider() {
173
- if (!walletProvider) {
174
- if (!sdk || !sdk?.isInitialized()) {
175
- sdk = new MetaMaskSDK({
176
- enableDebug: false,
177
- dappMetadata: { name: 'wagmi' },
178
- extensionOnly: true,
179
- modals: {
180
- // Disable by default since it pops up when mobile tries to reconnect
181
- otp() {
182
- const noop = () => {}
183
- return { mount: noop, unmount: noop }
184
- },
185
- },
186
- useDeeplink: true,
187
- _source: 'wagmi',
188
- ...parameters,
189
- checkInstallationImmediately: false,
190
- checkInstallationOnAllCalls: false,
191
- })
192
- await sdk.init()
193
- }
194
- try {
195
- walletProvider = sdk.getProvider()
196
- } catch (error) {
197
- // TODO: SDK sometimes throws errors when MM extension or mobile provider is not detected (don't throw for those errors)
198
- const regex = /^SDK state invalid -- undefined( mobile)? provider$/
199
- if (!regex.test((error as Error).message)) throw error
200
- }
123
+ async function initProvider() {
124
+ // Unwrapping import for Vite compatibility.
125
+ // See: https://github.com/vitejs/vite/issues/9703
126
+ const { default: MetaMaskSDK_ } = await import('@metamask/sdk')
127
+ const MetaMaskSDK = (() => {
128
+ if (
129
+ typeof MetaMaskSDK_ !== 'function' &&
130
+ typeof MetaMaskSDK_.default === 'function'
131
+ )
132
+ return MetaMaskSDK_.default
133
+ return MetaMaskSDK_ as unknown as typeof MetaMaskSDK_.default
134
+ })()
135
+
136
+ sdk = new MetaMaskSDK({
137
+ dappMetadata: {},
138
+ ...parameters,
139
+ _source: 'wagmi',
140
+ readonlyRPCMap: Object.fromEntries(
141
+ config.chains.map((chain) => [
142
+ chain.id,
143
+ chain.rpcUrls.default.http[0]!,
144
+ ]),
145
+ ),
146
+ })
147
+ await sdk.init()
148
+ return sdk.getProvider()!
149
+ }
150
+
151
+ if (!provider) {
152
+ if (!providerPromise) providerPromise = initProvider()
153
+ provider = await providerPromise
201
154
  }
202
- return walletProvider!
155
+ return provider!
203
156
  },
204
157
  async isAuthorized() {
205
158
  try {
159
+ const isMobileBrowser =
160
+ typeof navigator !== 'undefined'
161
+ ? /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
162
+ navigator.userAgent,
163
+ )
164
+ : false
165
+
166
+ // MetaMask Mobile doesn't support persisted sessions.
167
+ if (isMobileBrowser) return false
168
+
206
169
  const isDisconnected =
207
170
  // If shim exists in storage, connector is disconnected
208
171
  await config.storage?.getItem('metaMaskSDK.disconnected')
@@ -214,7 +177,7 @@ export function metaMask(parameters: MetaMaskParameters = {}) {
214
177
  return false
215
178
  }
216
179
  },
217
- async switchChain({ chainId }) {
180
+ async switchChain({ addEthereumChainParameter, chainId }) {
218
181
  const provider = await this.getProvider()
219
182
 
220
183
  const chain = config.chains.find((x) => x.id === chainId)
@@ -247,24 +210,34 @@ export function metaMask(parameters: MetaMaskParameters = {}) {
247
210
  try {
248
211
  const { default: blockExplorer, ...blockExplorers } =
249
212
  chain.blockExplorers ?? {}
250
- let blockExplorerUrls: string[] = []
251
- if (blockExplorer)
213
+ let blockExplorerUrls
214
+ if (addEthereumChainParameter?.blockExplorerUrls)
215
+ blockExplorerUrls = addEthereumChainParameter.blockExplorerUrls
216
+ else if (blockExplorer)
252
217
  blockExplorerUrls = [
253
218
  blockExplorer.url,
254
219
  ...Object.values(blockExplorers).map((x) => x.url),
255
220
  ]
256
221
 
222
+ let rpcUrls
223
+ if (addEthereumChainParameter?.rpcUrls?.length)
224
+ rpcUrls = addEthereumChainParameter.rpcUrls
225
+ else rpcUrls = [chain.rpcUrls.default?.http[0] ?? '']
226
+
227
+ const addEthereumChain = {
228
+ blockExplorerUrls,
229
+ chainId: numberToHex(chainId),
230
+ chainName: addEthereumChainParameter?.chainName ?? chain.name,
231
+ iconUrls: addEthereumChainParameter?.iconUrls,
232
+ nativeCurrency:
233
+ addEthereumChainParameter?.nativeCurrency ??
234
+ chain.nativeCurrency,
235
+ rpcUrls,
236
+ } satisfies AddEthereumChainParameter
237
+
257
238
  await provider.request({
258
239
  method: 'wallet_addEthereumChain',
259
- params: [
260
- {
261
- chainId: numberToHex(chainId),
262
- chainName: chain.name,
263
- nativeCurrency: chain.nativeCurrency,
264
- rpcUrls: [chain.rpcUrls.default?.http[0] ?? ''],
265
- blockExplorerUrls,
266
- },
267
- ],
240
+ params: [addEthereumChain],
268
241
  })
269
242
 
270
243
  const currentChainId = await this.getChainId()
@@ -294,17 +267,20 @@ export function metaMask(parameters: MetaMaskParameters = {}) {
294
267
  await config.storage?.removeItem('metaMaskSDK.disconnected')
295
268
  }
296
269
  // Regular change event
297
- else config.emitter.emit('change', { accounts: accounts.map(getAddress) })
270
+ else
271
+ config.emitter.emit('change', {
272
+ accounts: accounts.map((x) => getAddress(x)),
273
+ })
298
274
  },
299
275
  onChainChanged(chain) {
300
- const chainId = normalizeChainId(chain)
276
+ const chainId = Number(chain)
301
277
  config.emitter.emit('change', { chainId })
302
278
  },
303
279
  async onConnect(connectInfo) {
304
280
  const accounts = await this.getAccounts()
305
281
  if (accounts.length === 0) return
306
282
 
307
- const chainId = normalizeChainId(connectInfo.chainId)
283
+ const chainId = Number(connectInfo.chainId)
308
284
  config.emitter.emit('connect', { accounts, chainId })
309
285
 
310
286
  const provider = await this.getProvider()
@@ -324,6 +300,12 @@ export function metaMask(parameters: MetaMaskParameters = {}) {
324
300
  if (provider && !!(await this.getAccounts()).length) return
325
301
  }
326
302
 
303
+ // Remove cached SDK properties.
304
+ if (typeof localStorage !== 'undefined') {
305
+ localStorage.removeItem('MMSDK_cached_address')
306
+ localStorage.removeItem('MMSDK_cached_chainId')
307
+ }
308
+
327
309
  // No need to remove 'metaMaskSDK.disconnected' from storage because `onDisconnect` is typically
328
310
  // only called when the wallet is disconnected through the wallet's interface, meaning the wallet
329
311
  // actually disconnected and we don't need to simulate it.
package/src/safe.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { SafeAppProvider } from '@safe-global/safe-apps-provider'
2
- import { type Opts, default as SafeAppsSDK } from '@safe-global/safe-apps-sdk'
1
+ import type { SafeAppProvider } from '@safe-global/safe-apps-provider'
2
+ import type { Opts } from '@safe-global/safe-apps-sdk'
3
3
  import {
4
+ type Connector,
4
5
  ProviderNotFoundError,
5
6
  createConnector,
6
- normalizeChainId,
7
7
  } from '@wagmi/core'
8
8
  import type { Evaluate } from '@wagmi/core/internal'
9
9
  import { getAddress } from 'viem'
@@ -31,14 +31,8 @@ export function safe(parameters: SafeParameters = {}) {
31
31
  type StorageItem = { 'safe.disconnected': true }
32
32
 
33
33
  let provider_: Provider | undefined
34
- let SDK: typeof SafeAppsSDK.default
35
- if (
36
- typeof SafeAppsSDK !== 'function' &&
37
- typeof SafeAppsSDK.default === 'function'
38
- )
39
- SDK = SafeAppsSDK.default
40
- else SDK = SafeAppsSDK as unknown as typeof SafeAppsSDK.default
41
- const sdk = new SDK(parameters)
34
+
35
+ let disconnect: Connector['onDisconnect'] | undefined
42
36
 
43
37
  return createConnector<Provider, Properties, StorageItem>((config) => ({
44
38
  id: 'safe',
@@ -51,7 +45,10 @@ export function safe(parameters: SafeParameters = {}) {
51
45
  const accounts = await this.getAccounts()
52
46
  const chainId = await this.getChainId()
53
47
 
54
- provider.on('disconnect', this.onDisconnect.bind(this))
48
+ if (!disconnect) {
49
+ disconnect = this.onDisconnect.bind(this)
50
+ provider.on('disconnect', disconnect)
51
+ }
55
52
 
56
53
  // Remove disconnected shim if it exists
57
54
  if (shimDisconnect) await config.storage?.removeItem('safe.disconnected')
@@ -62,7 +59,10 @@ export function safe(parameters: SafeParameters = {}) {
62
59
  const provider = await this.getProvider()
63
60
  if (!provider) throw new ProviderNotFoundError()
64
61
 
65
- provider.removeListener('disconnect', this.onDisconnect.bind(this))
62
+ if (disconnect) {
63
+ provider.removeListener('disconnect', disconnect)
64
+ disconnect = undefined
65
+ }
66
66
 
67
67
  // Add shim signalling connector is disconnected
68
68
  if (shimDisconnect)
@@ -82,8 +82,23 @@ export function safe(parameters: SafeParameters = {}) {
82
82
  if (!isIframe) return
83
83
 
84
84
  if (!provider_) {
85
+ const { default: SafeAppsSDK } = await import(
86
+ '@safe-global/safe-apps-sdk'
87
+ )
88
+ let SDK: typeof SafeAppsSDK.default
89
+ if (
90
+ typeof SafeAppsSDK !== 'function' &&
91
+ typeof SafeAppsSDK.default === 'function'
92
+ )
93
+ SDK = SafeAppsSDK.default
94
+ else SDK = SafeAppsSDK as unknown as typeof SafeAppsSDK.default
95
+ const sdk = new SDK(parameters)
96
+
85
97
  const safe = await sdk.safe.getInfo()
86
98
  if (!safe) throw new Error('Could not load Safe information')
99
+ const { SafeAppProvider } = await import(
100
+ '@safe-global/safe-apps-provider'
101
+ )
87
102
  provider_ = new SafeAppProvider(safe, sdk)
88
103
  }
89
104
  return provider_
@@ -91,7 +106,7 @@ export function safe(parameters: SafeParameters = {}) {
91
106
  async getChainId() {
92
107
  const provider = await this.getProvider()
93
108
  if (!provider) throw new ProviderNotFoundError()
94
- return normalizeChainId(provider.chainId)
109
+ return Number(provider.chainId)
95
110
  },
96
111
  async isAuthorized() {
97
112
  try {
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const version = '5.0.0'
1
+ export const version = '5.0.2'