@wagmi/core 3.4.2 → 3.4.3
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/dist/esm/exports/tempo.js +1 -2
- package/dist/esm/exports/tempo.js.map +1 -1
- package/dist/esm/tempo/Connectors.js +278 -570
- package/dist/esm/tempo/Connectors.js.map +1 -1
- package/dist/esm/tsconfig.build.tsbuildinfo +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/types/exports/tempo.d.ts +1 -2
- package/dist/types/exports/tempo.d.ts.map +1 -1
- package/dist/types/tempo/Connectors.d.ts +58 -86
- package/dist/types/tempo/Connectors.d.ts.map +1 -1
- package/dist/types/version.d.ts +1 -1
- package/package.json +5 -1
- package/src/exports/tempo.ts +2 -1
- package/src/tempo/Connectors.ts +406 -798
- package/src/version.ts +1 -1
- package/dist/esm/tempo/KeyManager.js +0 -154
- package/dist/esm/tempo/KeyManager.js.map +0 -1
- package/dist/types/tempo/KeyManager.d.ts +0 -71
- package/dist/types/tempo/KeyManager.d.ts.map +0 -1
- package/src/tempo/KeyManager.ts +0 -241
package/src/tempo/Connectors.ts
CHANGED
|
@@ -1,680 +1,137 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import type {
|
|
2
|
+
Provider as AccountsProvider,
|
|
3
|
+
Rpc as AccountsRpc,
|
|
4
|
+
dangerous_secp256k1 as accountsDangerousSecp256k1,
|
|
5
|
+
dialog as accountsDialog,
|
|
6
|
+
webAuthn as accountsWebAuthn,
|
|
7
|
+
} from 'accounts'
|
|
5
8
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
type
|
|
9
|
-
|
|
9
|
+
type Address,
|
|
10
|
+
numberToHex,
|
|
11
|
+
type ProviderConnectInfo,
|
|
12
|
+
type RpcError,
|
|
10
13
|
SwitchChainError,
|
|
14
|
+
UserRejectedRequestError,
|
|
15
|
+
withRetry,
|
|
11
16
|
} from 'viem'
|
|
12
|
-
|
|
13
|
-
generatePrivateKey,
|
|
14
|
-
type LocalAccount,
|
|
15
|
-
privateKeyToAccount,
|
|
16
|
-
} from 'viem/accounts'
|
|
17
|
-
import {
|
|
18
|
-
Account,
|
|
19
|
-
WebAuthnP256,
|
|
20
|
-
WebCryptoP256,
|
|
21
|
-
walletNamespaceCompat,
|
|
22
|
-
} from 'viem/tempo'
|
|
17
|
+
|
|
23
18
|
import { createConnector } from '../connectors/createConnector.js'
|
|
19
|
+
import type { Connector } from '../createConfig.js'
|
|
24
20
|
import { ChainNotConfiguredError } from '../errors/config.js'
|
|
25
|
-
import type { OneOf } from '../types/utils.js'
|
|
26
|
-
import type * as KeyManager from './KeyManager.js'
|
|
27
|
-
|
|
28
|
-
/** @deprecated use `webAuthn.Parameters` instead */
|
|
29
|
-
export type WebAuthnParameters = webAuthn.Parameters
|
|
30
21
|
|
|
31
|
-
|
|
22
|
+
type AccountsModule = {
|
|
23
|
+
dialog: typeof accountsDialog
|
|
24
|
+
Provider: typeof AccountsProvider
|
|
25
|
+
dangerous_secp256k1: typeof accountsDangerousSecp256k1
|
|
26
|
+
webAuthn: typeof accountsWebAuthn
|
|
27
|
+
}
|
|
28
|
+
type AccountsDialogParameters = NonNullable<
|
|
29
|
+
Parameters<typeof accountsDialog>[0]
|
|
30
|
+
>
|
|
31
|
+
type AccountsProviderParameters = NonNullable<
|
|
32
|
+
Parameters<typeof AccountsProvider.create>[0]
|
|
33
|
+
>
|
|
34
|
+
type AccountsAdapter = NonNullable<AccountsProviderParameters['adapter']>
|
|
35
|
+
type AccountsDangerousSecp256k1Parameters = NonNullable<
|
|
36
|
+
Parameters<typeof accountsDangerousSecp256k1>[0]
|
|
37
|
+
>
|
|
38
|
+
type AccountsStorage = NonNullable<AccountsProviderParameters['storage']>
|
|
39
|
+
type AccountsWebAuthnParameters = NonNullable<
|
|
40
|
+
Parameters<typeof accountsWebAuthn>[0]
|
|
41
|
+
>
|
|
42
|
+
type Provider = Pick<
|
|
43
|
+
ReturnType<typeof AccountsProvider.create>,
|
|
44
|
+
'getAccount' | 'getClient' | 'on' | 'removeListener' | 'request'
|
|
45
|
+
>
|
|
46
|
+
type AccountsConnectParameters = NonNullable<
|
|
47
|
+
AccountsRpc.wallet_connect.Decoded['params']
|
|
48
|
+
>[0]
|
|
49
|
+
type CapabilitiesRequest = AccountsConnectParameters['capabilities']
|
|
50
|
+
type InternalAccount =
|
|
51
|
+
AccountsRpc.wallet_connect.Encoded['returns']['accounts'][number]
|
|
52
|
+
|
|
53
|
+
const tempoWalletIcon =
|
|
54
|
+
'data:image/svg+xml,<svg width="269" height="269" viewBox="0 0 269 269" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="269" height="269" fill="black"/><path d="M123.273 190.794H93.445L121.09 105.318H85.7334L93.445 80.2642H191.95L184.238 105.318H150.773L123.273 190.794Z" fill="white"/></svg>'
|
|
55
|
+
|
|
56
|
+
/** @deprecated use `tempoWallet.Parameters` instead */
|
|
57
|
+
export type TempoWalletParameters = tempoWallet.Parameters
|
|
32
58
|
|
|
33
59
|
/**
|
|
34
|
-
* Connector for
|
|
60
|
+
* Connector for the Tempo Wallet dialog.
|
|
35
61
|
*/
|
|
36
|
-
export function
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
return {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
// TODO(v3): Make `withCapabilities: true` default behavior
|
|
55
|
-
connect<withCapabilities extends boolean = false>(parameters: {
|
|
56
|
-
chainId?: number | undefined
|
|
57
|
-
capabilities?:
|
|
58
|
-
| (OneOf<
|
|
59
|
-
| {
|
|
60
|
-
label?: string | undefined
|
|
61
|
-
type: 'sign-up'
|
|
62
|
-
}
|
|
63
|
-
| {
|
|
64
|
-
selectAccount?: boolean | undefined
|
|
65
|
-
type: 'sign-in'
|
|
66
|
-
}
|
|
67
|
-
| {
|
|
68
|
-
type?: undefined
|
|
69
|
-
}
|
|
70
|
-
> & {
|
|
71
|
-
credential?: { id: string; publicKey: Hex.Hex } | undefined
|
|
72
|
-
sign?:
|
|
73
|
-
| {
|
|
74
|
-
hash: Hex.Hex
|
|
75
|
-
}
|
|
76
|
-
| undefined
|
|
77
|
-
})
|
|
78
|
-
| undefined
|
|
79
|
-
isReconnecting?: boolean | undefined
|
|
80
|
-
withCapabilities?: withCapabilities | boolean | undefined
|
|
81
|
-
}): Promise<{
|
|
82
|
-
accounts: withCapabilities extends true
|
|
83
|
-
? readonly {
|
|
84
|
-
address: Address.Address
|
|
85
|
-
capabilities: { signature?: Hex.Hex | undefined }
|
|
86
|
-
}[]
|
|
87
|
-
: readonly Address.Address[]
|
|
88
|
-
chainId: number
|
|
89
|
-
}>
|
|
90
|
-
}
|
|
91
|
-
type Provider = Pick<EIP1193Provider, 'request'>
|
|
92
|
-
type StorageItem = {
|
|
93
|
-
[
|
|
94
|
-
key: `pendingKeyAuthorization:${string}`
|
|
95
|
-
]: KeyAuthorization.KeyAuthorization
|
|
96
|
-
'webAuthn.activeCredential': WebAuthnP256.P256Credential
|
|
97
|
-
'webAuthn.lastActiveCredential': WebAuthnP256.P256Credential
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return createConnector<Provider, Properties, StorageItem>((config) => ({
|
|
101
|
-
id: 'webAuthn',
|
|
102
|
-
name: 'EOA (WebAuthn)',
|
|
103
|
-
type: 'webAuthn',
|
|
104
|
-
async setup() {
|
|
105
|
-
const credential = await config.storage?.getItem(
|
|
106
|
-
'webAuthn.activeCredential',
|
|
107
|
-
)
|
|
108
|
-
if (!credential) return
|
|
109
|
-
account = Account.fromWebAuthnP256(credential, {
|
|
110
|
-
rpId: options.getOptions?.rpId ?? options.rpId,
|
|
62
|
+
export function tempoWallet(parameters: tempoWallet.Parameters = {}) {
|
|
63
|
+
const {
|
|
64
|
+
dialog: dialogOption,
|
|
65
|
+
host,
|
|
66
|
+
icon = tempoWalletIcon,
|
|
67
|
+
name,
|
|
68
|
+
rdns,
|
|
69
|
+
...providerParameters
|
|
70
|
+
} = parameters
|
|
71
|
+
|
|
72
|
+
return _setup({
|
|
73
|
+
createAdapter(accounts) {
|
|
74
|
+
return accounts.dialog({
|
|
75
|
+
dialog: dialogOption,
|
|
76
|
+
host,
|
|
77
|
+
icon,
|
|
78
|
+
name,
|
|
79
|
+
rdns,
|
|
111
80
|
})
|
|
112
81
|
},
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const credential =
|
|
122
|
-
capabilities.credential as WebAuthnP256.P256Credential
|
|
123
|
-
config.storage?.setItem(
|
|
124
|
-
'webAuthn.activeCredential',
|
|
125
|
-
normalizeValue(credential),
|
|
126
|
-
)
|
|
127
|
-
config.storage?.setItem(
|
|
128
|
-
'webAuthn.lastActiveCredential',
|
|
129
|
-
normalizeValue(credential),
|
|
130
|
-
)
|
|
131
|
-
account = Account.fromWebAuthnP256(credential, {
|
|
132
|
-
rpId: options.getOptions?.rpId ?? options.rpId,
|
|
133
|
-
})
|
|
134
|
-
const address = getAddress(account.address)
|
|
135
|
-
const chainId = parameters.chainId ?? config.chains[0]?.id
|
|
136
|
-
if (!chainId) throw new ChainNotConfiguredError()
|
|
137
|
-
return {
|
|
138
|
-
accounts: (parameters.withCapabilities
|
|
139
|
-
? [{ address }]
|
|
140
|
-
: [address]) as never,
|
|
141
|
-
chainId,
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (
|
|
146
|
-
accessKeyOptions?.strict &&
|
|
147
|
-
accessKeyOptions.expiry &&
|
|
148
|
-
accessKeyOptions.expiry < Date.now() / 1000
|
|
149
|
-
)
|
|
150
|
-
throw new Error(
|
|
151
|
-
`\`grantAccessKey.expiry = ${accessKeyOptions.expiry}\` is in the past (${new Date(accessKeyOptions.expiry * 1000).toLocaleString()}). Please provide a valid expiry.`,
|
|
152
|
-
)
|
|
153
|
-
|
|
154
|
-
// We are going to need to find:
|
|
155
|
-
// - a WebAuthn `credential` to instantiate an account
|
|
156
|
-
// - optionally, a `keyPair` to use as the access key for the account
|
|
157
|
-
// - optionally, a signed `keyAuthorization` to provision the access key
|
|
158
|
-
const {
|
|
159
|
-
credential,
|
|
160
|
-
keyAuthorization,
|
|
161
|
-
keyPair,
|
|
162
|
-
signature: signedHash,
|
|
163
|
-
} = await (async () => {
|
|
164
|
-
// If the connection type is of "sign-up", we are going to create a new credential
|
|
165
|
-
// and provision an access key (if needed).
|
|
166
|
-
if (capabilities.type === 'sign-up') {
|
|
167
|
-
// Create credential (sign up)
|
|
168
|
-
const createOptions_remote = await options.keyManager.getChallenge?.()
|
|
169
|
-
const label =
|
|
170
|
-
capabilities.label ??
|
|
171
|
-
options.createOptions?.label ??
|
|
172
|
-
new Date().toISOString()
|
|
173
|
-
const rpId =
|
|
174
|
-
createOptions_remote?.rp?.id ??
|
|
175
|
-
options.createOptions?.rpId ??
|
|
176
|
-
options.rpId
|
|
177
|
-
const credential = await WebAuthnP256.createCredential({
|
|
178
|
-
...(options.createOptions ?? {}),
|
|
179
|
-
label,
|
|
180
|
-
rpId,
|
|
181
|
-
...(createOptions_remote ?? {}),
|
|
182
|
-
})
|
|
183
|
-
await options.keyManager.setPublicKey({
|
|
184
|
-
credential: credential.raw,
|
|
185
|
-
publicKey: credential.publicKey,
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
// Get key pair (access key) to use for the account.
|
|
189
|
-
// Skip if signing a hash — access key provisioning is deferred.
|
|
190
|
-
const keyPair = await (async () => {
|
|
191
|
-
if (signHash) return undefined
|
|
192
|
-
if (!accessKeyOptions) return undefined
|
|
193
|
-
return await WebCryptoP256.createKeyPair()
|
|
194
|
-
})()
|
|
195
|
-
|
|
196
|
-
return { credential, keyPair, signature: undefined }
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// If we are not selecting an account, we will check if an active credential is present in
|
|
200
|
-
// storage and if so, we will use it to instantiate an account.
|
|
201
|
-
if (!capabilities.selectAccount) {
|
|
202
|
-
const credential = (await config.storage?.getItem(
|
|
203
|
-
'webAuthn.activeCredential',
|
|
204
|
-
)) as WebAuthnP256.getCredential.ReturnValue | undefined
|
|
205
|
-
|
|
206
|
-
if (credential) {
|
|
207
|
-
// If signing a hash, skip local keypair checks and return
|
|
208
|
-
// the stored credential — the hash will be signed via
|
|
209
|
-
// `account.sign` since `createCredential` cannot sign.
|
|
210
|
-
if (signHash)
|
|
211
|
-
return { credential, keyPair: undefined, signature: undefined }
|
|
212
|
-
|
|
213
|
-
// Get key pair (access key) to use for the account.
|
|
214
|
-
const keyPair = await (async () => {
|
|
215
|
-
if (!accessKeyOptions) return undefined
|
|
216
|
-
const address = Address.fromPublicKey(
|
|
217
|
-
PublicKey.fromHex(credential.publicKey),
|
|
218
|
-
)
|
|
219
|
-
return await idb.get(`accessKey:${address}`)
|
|
220
|
-
})()
|
|
221
|
-
|
|
222
|
-
// If the access key provisioning is not in strict mode, return the credential and key pair (if exists).
|
|
223
|
-
if (!accessKeyOptions?.strict)
|
|
224
|
-
return { credential, keyPair, signature: undefined }
|
|
225
|
-
|
|
226
|
-
// If a key pair is found, return the credential and key pair.
|
|
227
|
-
if (keyPair) return { credential, keyPair, signature: undefined }
|
|
228
|
-
|
|
229
|
-
// If we are reconnecting, throw an error if not found.
|
|
230
|
-
if (parameters.isReconnecting)
|
|
231
|
-
throw new Error('credential not found.')
|
|
232
|
-
|
|
233
|
-
// Otherwise, we want to continue to sign up or register against new key pair.
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Discover credential
|
|
238
|
-
{
|
|
239
|
-
// Get key pair (access key) to use for the account.
|
|
240
|
-
// Skip if signing a hash — access key provisioning is deferred.
|
|
241
|
-
const keyPair = await (async () => {
|
|
242
|
-
if (signHash) return undefined
|
|
243
|
-
if (!accessKeyOptions) return undefined
|
|
244
|
-
return await WebCryptoP256.createKeyPair()
|
|
245
|
-
})()
|
|
246
|
-
|
|
247
|
-
// If we are provisioning an access key, we will need to sign a key authorization.
|
|
248
|
-
// We will need the hash (digest) to sign, and the address of the access key to construct the key authorization.
|
|
249
|
-
const { hash, keyAuthorization_unsigned } = await (async () => {
|
|
250
|
-
const accessKeyAddress = keyPair
|
|
251
|
-
? Address.fromPublicKey(keyPair.publicKey)
|
|
252
|
-
: undefined
|
|
253
|
-
|
|
254
|
-
if (!accessKeyAddress)
|
|
255
|
-
return { keyAuthorization_unsigned: undefined, hash: undefined }
|
|
256
|
-
|
|
257
|
-
const chainId = parameters.chainId ?? config.chains[0]?.id
|
|
258
|
-
const keyAuthorization_unsigned = KeyAuthorization.from({
|
|
259
|
-
address: accessKeyAddress,
|
|
260
|
-
chainId: chainId ? BigInt(chainId) : undefined,
|
|
261
|
-
expiry: accessKeyOptions?.expiry,
|
|
262
|
-
strict: accessKeyOptions?.strict ?? false,
|
|
263
|
-
type: 'p256',
|
|
264
|
-
})
|
|
265
|
-
const hash = KeyAuthorization.getSignPayload(
|
|
266
|
-
keyAuthorization_unsigned,
|
|
267
|
-
)
|
|
268
|
-
return { keyAuthorization_unsigned, hash }
|
|
269
|
-
})()
|
|
270
|
-
|
|
271
|
-
// If no active credential, we will attempt to load the last active credential from storage.
|
|
272
|
-
const lastActiveCredential = !capabilities.selectAccount
|
|
273
|
-
? await config.storage?.getItem('webAuthn.lastActiveCredential')
|
|
274
|
-
: undefined
|
|
275
|
-
const credential = await WebAuthnP256.getCredential({
|
|
276
|
-
...(options.getOptions ?? {}),
|
|
277
|
-
credentialId: lastActiveCredential?.id,
|
|
278
|
-
async getPublicKey(credential) {
|
|
279
|
-
const publicKey = await options.keyManager.getPublicKey({
|
|
280
|
-
credential,
|
|
281
|
-
})
|
|
282
|
-
if (!publicKey) throw new Error('publicKey not found.')
|
|
283
|
-
return publicKey
|
|
284
|
-
},
|
|
285
|
-
hash: signHash ?? hash,
|
|
286
|
-
rpId: options.getOptions?.rpId ?? options.rpId,
|
|
287
|
-
})
|
|
288
|
-
|
|
289
|
-
const envelope = SignatureEnvelope.from({
|
|
290
|
-
metadata: credential.metadata,
|
|
291
|
-
signature: credential.signature,
|
|
292
|
-
publicKey: PublicKey.fromHex(credential.publicKey),
|
|
293
|
-
type: 'webAuthn',
|
|
294
|
-
})
|
|
295
|
-
|
|
296
|
-
const keyAuthorization = keyAuthorization_unsigned
|
|
297
|
-
? KeyAuthorization.from({
|
|
298
|
-
...keyAuthorization_unsigned,
|
|
299
|
-
signature: envelope,
|
|
300
|
-
})
|
|
301
|
-
: undefined
|
|
302
|
-
|
|
303
|
-
const signature =
|
|
304
|
-
signHash && !keyAuthorization_unsigned
|
|
305
|
-
? SignatureEnvelope.serialize(envelope)
|
|
306
|
-
: undefined
|
|
307
|
-
|
|
308
|
-
return { credential, keyAuthorization, keyPair, signature }
|
|
309
|
-
}
|
|
310
|
-
})()
|
|
311
|
-
|
|
312
|
-
config.storage?.setItem(
|
|
313
|
-
'webAuthn.lastActiveCredential',
|
|
314
|
-
normalizeValue(credential),
|
|
315
|
-
)
|
|
316
|
-
config.storage?.setItem(
|
|
317
|
-
'webAuthn.activeCredential',
|
|
318
|
-
normalizeValue(credential),
|
|
319
|
-
)
|
|
320
|
-
|
|
321
|
-
account = Account.fromWebAuthnP256(credential, {
|
|
322
|
-
rpId: options.getOptions?.rpId ?? options.rpId,
|
|
323
|
-
})
|
|
324
|
-
|
|
325
|
-
let signature: Hex.Hex | undefined
|
|
326
|
-
if (signHash && !signedHash) {
|
|
327
|
-
signature = await account.sign({ hash: signHash })
|
|
328
|
-
} else if (signedHash) {
|
|
329
|
-
signature = signedHash
|
|
330
|
-
} else if (keyPair) {
|
|
331
|
-
accessKey = Account.fromWebCryptoP256(keyPair, {
|
|
332
|
-
access: account,
|
|
333
|
-
})
|
|
334
|
-
|
|
335
|
-
// If we are reconnecting, check if the access key is expired.
|
|
336
|
-
if (parameters.isReconnecting) {
|
|
337
|
-
if (
|
|
338
|
-
'keyAuthorization' in keyPair &&
|
|
339
|
-
keyPair.keyAuthorization.expiry &&
|
|
340
|
-
keyPair.keyAuthorization.expiry < Date.now() / 1000
|
|
341
|
-
) {
|
|
342
|
-
// remove any pending key authorizations from storage.
|
|
343
|
-
await config?.storage?.removeItem(
|
|
344
|
-
`pendingKeyAuthorization:${account.address.toLowerCase()}`,
|
|
345
|
-
)
|
|
82
|
+
icon,
|
|
83
|
+
id: rdns ?? 'xyz.tempo',
|
|
84
|
+
name: name ?? 'Tempo Wallet',
|
|
85
|
+
providerParameters,
|
|
86
|
+
rdns: rdns ?? 'xyz.tempo',
|
|
87
|
+
type: 'injected',
|
|
88
|
+
})
|
|
89
|
+
}
|
|
346
90
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
throw new Error(message)
|
|
354
|
-
}
|
|
355
|
-
// otherwise, fall back to the root account.
|
|
356
|
-
// biome-ignore lint/suspicious/noConsole: notify
|
|
357
|
-
console.warn(`${message} Falling back to passkey.`)
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
// If we are not reconnecting, orchestrate the provisioning of the access key.
|
|
361
|
-
else {
|
|
362
|
-
const keyAuth =
|
|
363
|
-
keyAuthorization ??
|
|
364
|
-
(await account.signKeyAuthorization(accessKey, {
|
|
365
|
-
...accessKeyOptions,
|
|
366
|
-
chainId: BigInt(parameters.chainId ?? config.chains[0]?.id ?? 0),
|
|
367
|
-
} as never))
|
|
368
|
-
|
|
369
|
-
await config?.storage?.setItem(
|
|
370
|
-
`pendingKeyAuthorization:${account.address.toLowerCase()}`,
|
|
371
|
-
keyAuth as never,
|
|
372
|
-
)
|
|
373
|
-
await idb.set(`accessKey:${account.address.toLowerCase()}`, {
|
|
374
|
-
...keyPair,
|
|
375
|
-
keyAuthorization: keyAuth,
|
|
376
|
-
})
|
|
377
|
-
}
|
|
378
|
-
// If we are granting an access key and it is in strict mode, throw an error if the access key is not provisioned.
|
|
379
|
-
} else if (accessKeyOptions?.strict) {
|
|
380
|
-
await config.storage?.removeItem('webAuthn.activeCredential')
|
|
381
|
-
throw new Error('access key not found')
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
const address = getAddress(account.address)
|
|
385
|
-
|
|
386
|
-
const chainId = parameters.chainId ?? config.chains[0]?.id
|
|
387
|
-
if (!chainId) throw new ChainNotConfiguredError()
|
|
388
|
-
|
|
389
|
-
return {
|
|
390
|
-
accounts: (parameters.withCapabilities
|
|
391
|
-
? [{ address, capabilities: { signature } }]
|
|
392
|
-
: [address]) as never,
|
|
393
|
-
chainId,
|
|
394
|
-
}
|
|
395
|
-
},
|
|
396
|
-
async disconnect() {
|
|
397
|
-
await config.storage?.removeItem('webAuthn.activeCredential')
|
|
398
|
-
config.emitter.emit('disconnect')
|
|
399
|
-
account = undefined
|
|
400
|
-
},
|
|
401
|
-
async getAccounts() {
|
|
402
|
-
if (!account) return []
|
|
403
|
-
return [getAddress(account.address)]
|
|
404
|
-
},
|
|
405
|
-
async getChainId() {
|
|
406
|
-
return config.chains[0]?.id!
|
|
407
|
-
},
|
|
408
|
-
async isAuthorized() {
|
|
409
|
-
try {
|
|
410
|
-
const accounts = await this.getAccounts()
|
|
411
|
-
return !!accounts.length
|
|
412
|
-
} catch (error) {
|
|
413
|
-
// biome-ignore lint/suspicious/noConsole: notify
|
|
414
|
-
console.error(
|
|
415
|
-
'Connector.webAuthn: Failed to check authorization',
|
|
416
|
-
error,
|
|
417
|
-
)
|
|
418
|
-
return false
|
|
419
|
-
}
|
|
420
|
-
},
|
|
421
|
-
async switchChain({ chainId }) {
|
|
422
|
-
const chain = config.chains.find((chain) => chain.id === chainId)
|
|
423
|
-
if (!chain) throw new SwitchChainError(new ChainNotConfiguredError())
|
|
424
|
-
return chain
|
|
425
|
-
},
|
|
426
|
-
onAccountsChanged() {},
|
|
427
|
-
onChainChanged(chain) {
|
|
428
|
-
const chainId = Number(chain)
|
|
429
|
-
config.emitter.emit('change', { chainId })
|
|
430
|
-
},
|
|
431
|
-
async onDisconnect() {
|
|
432
|
-
config.emitter.emit('disconnect')
|
|
433
|
-
account = undefined
|
|
434
|
-
},
|
|
435
|
-
async getClient({ chainId } = {}) {
|
|
436
|
-
const chain =
|
|
437
|
-
config.chains.find((x) => x.id === chainId) ?? config.chains[0]
|
|
438
|
-
if (!chain) throw new ChainNotConfiguredError()
|
|
439
|
-
|
|
440
|
-
const transports = config.transports
|
|
441
|
-
if (!transports) throw new ChainNotConfiguredError()
|
|
442
|
-
|
|
443
|
-
const transport = transports[chain.id]
|
|
444
|
-
if (!transport) throw new ChainNotConfiguredError()
|
|
445
|
-
|
|
446
|
-
const targetAccount = await (async () => {
|
|
447
|
-
if (!accessKey) return account
|
|
448
|
-
if (!account) throw new Error('account not found.')
|
|
449
|
-
|
|
450
|
-
const item = await idb.get(`accessKey:${account.address.toLowerCase()}`)
|
|
451
|
-
if (
|
|
452
|
-
item?.keyAuthorization.expiry &&
|
|
453
|
-
item.keyAuthorization.expiry < Date.now() / 1000
|
|
454
|
-
) {
|
|
455
|
-
// remove any pending key authorizations from storage.
|
|
456
|
-
await config?.storage?.removeItem(
|
|
457
|
-
`pendingKeyAuthorization:${account.address.toLowerCase()}`,
|
|
458
|
-
)
|
|
459
|
-
|
|
460
|
-
const message = `Access key expired (on ${new Date(item.keyAuthorization.expiry * 1000).toLocaleString()}).`
|
|
461
|
-
|
|
462
|
-
// if in strict mode, disconnect and throw an error.
|
|
463
|
-
if (accessKeyOptions?.strict) {
|
|
464
|
-
await this.disconnect()
|
|
465
|
-
throw new Error(message)
|
|
466
|
-
}
|
|
91
|
+
export declare namespace tempoWallet {
|
|
92
|
+
export type Parameters = Omit<
|
|
93
|
+
AccountsProviderParameters,
|
|
94
|
+
'adapter' | 'chains'
|
|
95
|
+
> &
|
|
96
|
+
AccountsDialogParameters
|
|
467
97
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
console.warn(`${message} Falling back to passkey.`)
|
|
471
|
-
return account
|
|
472
|
-
}
|
|
473
|
-
return accessKey
|
|
474
|
-
})()
|
|
475
|
-
if (!targetAccount) throw new Error('account not found.')
|
|
476
|
-
|
|
477
|
-
const targetChain = defineChain({
|
|
478
|
-
...chain,
|
|
479
|
-
prepareTransactionRequest: [
|
|
480
|
-
async (args, { phase }) => {
|
|
481
|
-
const keyAuthorization = await (async () => {
|
|
482
|
-
{
|
|
483
|
-
const keyAuthorization = (
|
|
484
|
-
args as {
|
|
485
|
-
keyAuthorization?:
|
|
486
|
-
| KeyAuthorization.KeyAuthorization
|
|
487
|
-
| undefined
|
|
488
|
-
}
|
|
489
|
-
).keyAuthorization
|
|
490
|
-
if (keyAuthorization) return keyAuthorization
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
const keyAuthorization = await config.storage?.getItem(
|
|
494
|
-
`pendingKeyAuthorization:${targetAccount?.address.toLowerCase()}`,
|
|
495
|
-
)
|
|
496
|
-
await config.storage?.removeItem(
|
|
497
|
-
`pendingKeyAuthorization:${targetAccount?.address.toLowerCase()}`,
|
|
498
|
-
)
|
|
499
|
-
return keyAuthorization
|
|
500
|
-
})()
|
|
501
|
-
|
|
502
|
-
const [prepareTransactionRequestFn, options] = (() => {
|
|
503
|
-
if (!chain.prepareTransactionRequest)
|
|
504
|
-
return [undefined, undefined]
|
|
505
|
-
if (typeof chain.prepareTransactionRequest === 'function')
|
|
506
|
-
return [chain.prepareTransactionRequest, undefined]
|
|
507
|
-
return chain.prepareTransactionRequest
|
|
508
|
-
})()
|
|
509
|
-
|
|
510
|
-
const request = await (async () => {
|
|
511
|
-
if (!prepareTransactionRequestFn) return {}
|
|
512
|
-
if (!options || options?.runAt?.includes(phase))
|
|
513
|
-
return await prepareTransactionRequestFn(args, { phase })
|
|
514
|
-
return {}
|
|
515
|
-
})()
|
|
516
|
-
|
|
517
|
-
return {
|
|
518
|
-
...args,
|
|
519
|
-
...request,
|
|
520
|
-
keyAuthorization,
|
|
521
|
-
}
|
|
522
|
-
},
|
|
523
|
-
{
|
|
524
|
-
runAt: [
|
|
525
|
-
'afterFillParameters',
|
|
526
|
-
'beforeFillParameters',
|
|
527
|
-
'beforeFillTransaction',
|
|
528
|
-
],
|
|
529
|
-
},
|
|
530
|
-
],
|
|
531
|
-
})
|
|
98
|
+
export type ConnectParameters<withCapabilities extends boolean = false> =
|
|
99
|
+
setup.ConnectParameters<withCapabilities>
|
|
532
100
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
chain: targetChain,
|
|
536
|
-
transport: walletNamespaceCompat(transport, {
|
|
537
|
-
account: targetAccount,
|
|
538
|
-
}),
|
|
539
|
-
})
|
|
540
|
-
},
|
|
541
|
-
async getProvider({ chainId } = {}) {
|
|
542
|
-
const { request } = await this.getClient!({ chainId })
|
|
543
|
-
return { request }
|
|
544
|
-
},
|
|
545
|
-
}))
|
|
101
|
+
export type ConnectReturnType<withCapabilities extends boolean = false> =
|
|
102
|
+
setup.ConnectReturnType<withCapabilities>
|
|
546
103
|
}
|
|
547
104
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
| Pick<
|
|
553
|
-
WebAuthnP256.createCredential.Parameters,
|
|
554
|
-
'createFn' | 'label' | 'rpId' | 'userId' | 'timeout'
|
|
555
|
-
>
|
|
556
|
-
| undefined
|
|
557
|
-
/** Options for WebAuthn authentication. */
|
|
558
|
-
getOptions?:
|
|
559
|
-
| Pick<WebAuthnP256.getCredential.Parameters, 'getFn' | 'rpId'>
|
|
560
|
-
| undefined
|
|
561
|
-
/**
|
|
562
|
-
* Whether or not to grant an access key upon connection, and optionally, expiry + limits to assign to the key.
|
|
563
|
-
*/
|
|
564
|
-
grantAccessKey?:
|
|
565
|
-
| boolean
|
|
566
|
-
| (Pick<KeyAuthorization.KeyAuthorization, 'expiry' | 'limits'> & {
|
|
567
|
-
/** Whether or not to throw an error and disconnect if the access key is not provisioned or is expired. */
|
|
568
|
-
strict?: boolean | undefined
|
|
569
|
-
})
|
|
570
|
-
/** Public key manager. */
|
|
571
|
-
keyManager: KeyManager.KeyManager
|
|
572
|
-
/** The RP ID to use for WebAuthn. */
|
|
573
|
-
rpId?: string | undefined
|
|
574
|
-
}
|
|
575
|
-
}
|
|
105
|
+
/** @deprecated use `webAuthn.Parameters` instead */
|
|
106
|
+
export type WebAuthnParameters = webAuthn.Parameters
|
|
107
|
+
|
|
108
|
+
webAuthn.type = 'webAuthn' as const
|
|
576
109
|
|
|
577
110
|
/**
|
|
578
|
-
*
|
|
579
|
-
*
|
|
580
|
-
* @see https://developer.mozilla.org/en-US/docs/Web/API/Window/structuredClone
|
|
111
|
+
* Connector for a WebAuthn EOA.
|
|
581
112
|
*/
|
|
582
|
-
function
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
return
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
113
|
+
export function webAuthn(parameters: webAuthn.Parameters = {}) {
|
|
114
|
+
const { authUrl, ceremony, icon, name, rdns, ...providerParameters } =
|
|
115
|
+
parameters
|
|
116
|
+
|
|
117
|
+
return _setup({
|
|
118
|
+
createAdapter(accounts) {
|
|
119
|
+
return ceremony
|
|
120
|
+
? accounts.webAuthn({ ceremony, icon, name, rdns })
|
|
121
|
+
: accounts.webAuthn({ authUrl, icon, name, rdns })
|
|
122
|
+
},
|
|
123
|
+
icon,
|
|
124
|
+
id: 'webAuthn',
|
|
125
|
+
name: name ?? 'EOA (WebAuthn)',
|
|
126
|
+
providerParameters,
|
|
127
|
+
rdns,
|
|
128
|
+
type: 'webAuthn',
|
|
129
|
+
})
|
|
596
130
|
}
|
|
597
131
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
| (<type>(
|
|
602
|
-
txMode: IDBTransactionMode,
|
|
603
|
-
callback: (store: IDBObjectStore) => type | PromiseLike<type>,
|
|
604
|
-
) => Promise<type>)
|
|
605
|
-
| undefined
|
|
606
|
-
|
|
607
|
-
const idb = {
|
|
608
|
-
/**
|
|
609
|
-
* Get a value by its key.
|
|
610
|
-
*
|
|
611
|
-
* @param key
|
|
612
|
-
* @param customStore Method to get a custom store. Use with caution (see the docs).
|
|
613
|
-
*/
|
|
614
|
-
get<type = any>(key: IDBValidKey): Promise<type | undefined> {
|
|
615
|
-
return this.defaultGetStore()('readonly', (store) =>
|
|
616
|
-
this.promisifyRequest(store.get(key)),
|
|
617
|
-
)
|
|
618
|
-
},
|
|
619
|
-
/**
|
|
620
|
-
* Set a value with a key.
|
|
621
|
-
*
|
|
622
|
-
* @param key
|
|
623
|
-
* @param value
|
|
624
|
-
* @param customStore Method to get a custom store. Use with caution (see the docs).
|
|
625
|
-
*/
|
|
626
|
-
set(key: IDBValidKey, value: any): Promise<void> {
|
|
627
|
-
return this.defaultGetStore()('readwrite', (store) => {
|
|
628
|
-
store.put(value, key)
|
|
629
|
-
return this.promisifyRequest(store.transaction)
|
|
630
|
-
})
|
|
631
|
-
},
|
|
632
|
-
defaultGetStore() {
|
|
633
|
-
if (!defaultGetStoreFunc)
|
|
634
|
-
defaultGetStoreFunc = this.createStore('keyval-store', 'keyval')
|
|
635
|
-
return defaultGetStoreFunc
|
|
636
|
-
},
|
|
637
|
-
createStore(
|
|
638
|
-
dbName: string,
|
|
639
|
-
storeName: string,
|
|
640
|
-
): NonNullable<typeof defaultGetStoreFunc> {
|
|
641
|
-
let dbp: Promise<IDBDatabase> | undefined
|
|
642
|
-
|
|
643
|
-
const getDB = () => {
|
|
644
|
-
if (dbp) return dbp
|
|
645
|
-
const request = indexedDB.open(dbName)
|
|
646
|
-
request.onupgradeneeded = () =>
|
|
647
|
-
request.result.createObjectStore(storeName)
|
|
648
|
-
dbp = this.promisifyRequest(request)
|
|
649
|
-
|
|
650
|
-
dbp.then(
|
|
651
|
-
(db) => {
|
|
652
|
-
// It seems like Safari sometimes likes to just close the connection.
|
|
653
|
-
// It's supposed to fire this event when that happens. Let's hope it does!
|
|
654
|
-
db.onclose = () => {
|
|
655
|
-
dbp = undefined
|
|
656
|
-
}
|
|
657
|
-
},
|
|
658
|
-
() => {},
|
|
659
|
-
)
|
|
660
|
-
return dbp
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
return (txMode, callback) =>
|
|
664
|
-
getDB().then((db) =>
|
|
665
|
-
callback(db.transaction(storeName, txMode).objectStore(storeName)),
|
|
666
|
-
)
|
|
667
|
-
},
|
|
668
|
-
promisifyRequest<type = undefined>(
|
|
669
|
-
request: IDBRequest<type> | IDBTransaction,
|
|
670
|
-
): Promise<type> {
|
|
671
|
-
return new Promise<type>((resolve, reject) => {
|
|
672
|
-
// @ts-ignore - file size hacks
|
|
673
|
-
request.oncomplete = request.onsuccess = () => resolve(request.result)
|
|
674
|
-
// @ts-ignore - file size hacks
|
|
675
|
-
request.onabort = request.onerror = () => reject(request.error)
|
|
676
|
-
})
|
|
677
|
-
},
|
|
132
|
+
export declare namespace webAuthn {
|
|
133
|
+
export type Parameters = AccountsWebAuthnParameters &
|
|
134
|
+
Omit<AccountsProviderParameters, 'adapter' | 'chains'>
|
|
678
135
|
}
|
|
679
136
|
|
|
680
137
|
/** @deprecated use `dangerous_secp256k1.Parameters` instead */
|
|
@@ -690,167 +147,318 @@ dangerous_secp256k1.type = 'dangerous_secp256k1' as const
|
|
|
690
147
|
* length of the storage used.
|
|
691
148
|
*/
|
|
692
149
|
export function dangerous_secp256k1(
|
|
693
|
-
|
|
150
|
+
parameters: dangerous_secp256k1.Parameters = {},
|
|
694
151
|
) {
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
type Properties = {
|
|
698
|
-
// TODO(v3): Make `withCapabilities: true` default behavior
|
|
699
|
-
connect<withCapabilities extends boolean = false>(parameters: {
|
|
700
|
-
capabilities?: { type?: 'sign-up' | undefined } | undefined
|
|
701
|
-
chainId?: number | undefined
|
|
702
|
-
isReconnecting?: boolean | undefined
|
|
703
|
-
withCapabilities?: withCapabilities | boolean | undefined
|
|
704
|
-
}): Promise<{
|
|
705
|
-
accounts: readonly Address.Address[]
|
|
706
|
-
chainId: number
|
|
707
|
-
}>
|
|
708
|
-
}
|
|
709
|
-
type Provider = Pick<EIP1193Provider, 'request'>
|
|
710
|
-
type StorageItem = {
|
|
711
|
-
'secp256k1.activeAddress': Address.Address
|
|
712
|
-
'secp256k1.lastActiveAddress': Address.Address
|
|
713
|
-
[key: `secp256k1.${string}.privateKey`]: Hex.Hex
|
|
714
|
-
}
|
|
152
|
+
const { icon, name, privateKey, rdns, ...providerParameters } = parameters
|
|
715
153
|
|
|
716
|
-
return
|
|
154
|
+
return _setup({
|
|
155
|
+
createAdapter(accounts) {
|
|
156
|
+
return accounts.dangerous_secp256k1({ icon, name, privateKey, rdns })
|
|
157
|
+
},
|
|
158
|
+
icon,
|
|
717
159
|
id: 'secp256k1',
|
|
718
|
-
name: 'EOA (Secp256k1)',
|
|
160
|
+
name: name ?? 'EOA (Secp256k1)',
|
|
161
|
+
providerParameters,
|
|
162
|
+
rdns,
|
|
719
163
|
type: 'secp256k1',
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
const privateKey = await config.storage?.getItem(
|
|
723
|
-
`secp256k1.${address}.privateKey`,
|
|
724
|
-
)
|
|
725
|
-
if (privateKey) account = privateKeyToAccount(privateKey)
|
|
726
|
-
else if (
|
|
727
|
-
address &&
|
|
728
|
-
options.account &&
|
|
729
|
-
Address.isEqual(address, options.account.address)
|
|
730
|
-
)
|
|
731
|
-
account = options.account
|
|
732
|
-
},
|
|
733
|
-
async connect(parameters = {}) {
|
|
734
|
-
const address = await (async () => {
|
|
735
|
-
if (
|
|
736
|
-
'capabilities' in parameters &&
|
|
737
|
-
parameters.capabilities?.type === 'sign-up'
|
|
738
|
-
) {
|
|
739
|
-
const privateKey = generatePrivateKey()
|
|
740
|
-
const account = privateKeyToAccount(privateKey)
|
|
741
|
-
const address = account.address
|
|
742
|
-
await config.storage?.setItem(
|
|
743
|
-
`secp256k1.${address}.privateKey`,
|
|
744
|
-
privateKey,
|
|
745
|
-
)
|
|
746
|
-
await config.storage?.setItem('secp256k1.activeAddress', address)
|
|
747
|
-
await config.storage?.setItem('secp256k1.lastActiveAddress', address)
|
|
748
|
-
return address
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
const address = await config.storage?.getItem(
|
|
752
|
-
'secp256k1.lastActiveAddress',
|
|
753
|
-
)
|
|
754
|
-
const privateKey = await config.storage?.getItem(
|
|
755
|
-
`secp256k1.${address}.privateKey`,
|
|
756
|
-
)
|
|
757
|
-
|
|
758
|
-
if (privateKey) account = privateKeyToAccount(privateKey)
|
|
759
|
-
else if (options.account) {
|
|
760
|
-
account = options.account
|
|
761
|
-
await config.storage?.setItem(
|
|
762
|
-
'secp256k1.lastActiveAddress',
|
|
763
|
-
account.address,
|
|
764
|
-
)
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
if (!account) throw new Error('account not found.')
|
|
768
|
-
|
|
769
|
-
await config.storage?.setItem(
|
|
770
|
-
'secp256k1.activeAddress',
|
|
771
|
-
account.address,
|
|
772
|
-
)
|
|
773
|
-
return account.address
|
|
774
|
-
})()
|
|
164
|
+
})
|
|
165
|
+
}
|
|
775
166
|
|
|
776
|
-
|
|
777
|
-
|
|
167
|
+
export declare namespace dangerous_secp256k1 {
|
|
168
|
+
export type Parameters = AccountsDangerousSecp256k1Parameters &
|
|
169
|
+
Omit<AccountsProviderParameters, 'adapter' | 'chains'>
|
|
170
|
+
}
|
|
778
171
|
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
return [getAddress(account.address)]
|
|
172
|
+
function createAccountsStorage(
|
|
173
|
+
storage: {
|
|
174
|
+
getItem(key: string, defaultValue?: null | undefined): unknown
|
|
175
|
+
setItem(key: string, value: unknown): void | Promise<void>
|
|
176
|
+
removeItem(key: string): void | Promise<void>
|
|
177
|
+
},
|
|
178
|
+
namespace: string,
|
|
179
|
+
): AccountsStorage {
|
|
180
|
+
const prefix = `accounts.${namespace}`
|
|
181
|
+
return {
|
|
182
|
+
async getItem<value>(key: string) {
|
|
183
|
+
return ((await storage.getItem(`${prefix}.${key}`, null)) ??
|
|
184
|
+
null) as value | null
|
|
793
185
|
},
|
|
794
|
-
async
|
|
795
|
-
|
|
186
|
+
async removeItem(key) {
|
|
187
|
+
await storage.removeItem(`${prefix}.${key}`)
|
|
796
188
|
},
|
|
797
|
-
async
|
|
798
|
-
|
|
799
|
-
const accounts = await this.getAccounts()
|
|
800
|
-
return !!accounts.length
|
|
801
|
-
} catch (error) {
|
|
802
|
-
// biome-ignore lint/suspicious/noConsole: notify
|
|
803
|
-
console.error(
|
|
804
|
-
'Connector.secp256k1: Failed to check authorization',
|
|
805
|
-
error,
|
|
806
|
-
)
|
|
807
|
-
return false
|
|
808
|
-
}
|
|
189
|
+
async setItem(key, value) {
|
|
190
|
+
await storage.setItem(`${prefix}.${key}`, value)
|
|
809
191
|
},
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function createMemoryAccountsStorage(): AccountsStorage {
|
|
196
|
+
const map = new Map<string, unknown>()
|
|
197
|
+
return {
|
|
198
|
+
async getItem<value>(key: string) {
|
|
199
|
+
return (map.get(key) ?? null) as value | null
|
|
814
200
|
},
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
const chainId = Number(chain)
|
|
818
|
-
config.emitter.emit('change', { chainId })
|
|
201
|
+
async removeItem(key) {
|
|
202
|
+
map.delete(key)
|
|
819
203
|
},
|
|
820
|
-
async
|
|
821
|
-
|
|
822
|
-
account = undefined
|
|
204
|
+
async setItem(key, value) {
|
|
205
|
+
map.set(key, value)
|
|
823
206
|
},
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
config.chains.find((x) => x.id === chainId) ?? config.chains[0]
|
|
827
|
-
if (!chain) throw new ChainNotConfiguredError()
|
|
207
|
+
}
|
|
208
|
+
}
|
|
828
209
|
|
|
829
|
-
|
|
830
|
-
|
|
210
|
+
function _setup(parameters: setup.Parameters) {
|
|
211
|
+
type Properties = {
|
|
212
|
+
connect<withCapabilities extends boolean = false>(
|
|
213
|
+
parameters?: setup.ConnectParameters<withCapabilities>,
|
|
214
|
+
): Promise<setup.ConnectReturnType<withCapabilities>>
|
|
215
|
+
}
|
|
831
216
|
|
|
832
|
-
|
|
833
|
-
|
|
217
|
+
return createConnector<Provider, Properties>((config) => {
|
|
218
|
+
const chains = config.chains
|
|
219
|
+
let providerPromise: Promise<Provider> | undefined
|
|
834
220
|
|
|
835
|
-
|
|
221
|
+
let accountsChanged: Connector['onAccountsChanged'] | undefined
|
|
222
|
+
let chainChanged: ((chain: string) => void) | undefined
|
|
223
|
+
let connect: ((connectInfo: ProviderConnectInfo) => void) | undefined
|
|
224
|
+
let disconnect: ((error?: Error | undefined) => void) | undefined
|
|
836
225
|
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
transport: walletNamespaceCompat(transport, {
|
|
841
|
-
account,
|
|
842
|
-
}),
|
|
226
|
+
async function getAccountsModule() {
|
|
227
|
+
return await import('accounts').catch(() => {
|
|
228
|
+
throw new Error('dependency "accounts" not found')
|
|
843
229
|
})
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async function getProvider() {
|
|
233
|
+
providerPromise ??= (async () => {
|
|
234
|
+
const accounts = await getAccountsModule()
|
|
235
|
+
return accounts.Provider.create({
|
|
236
|
+
...parameters.providerParameters,
|
|
237
|
+
adapter: parameters.createAdapter(accounts),
|
|
238
|
+
chains: config.chains as never,
|
|
239
|
+
storage:
|
|
240
|
+
parameters.providerParameters.storage ??
|
|
241
|
+
(config.storage
|
|
242
|
+
? createAccountsStorage(config.storage, parameters.id)
|
|
243
|
+
: createMemoryAccountsStorage()),
|
|
244
|
+
}) as unknown as Provider
|
|
245
|
+
})()
|
|
246
|
+
|
|
247
|
+
return await providerPromise
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
icon: parameters.icon,
|
|
252
|
+
id: parameters.id,
|
|
253
|
+
name: parameters.name,
|
|
254
|
+
rdns: parameters.rdns,
|
|
255
|
+
type: parameters.type,
|
|
256
|
+
async connect(connectParameters = {}) {
|
|
257
|
+
const { chainId, isReconnecting, withCapabilities } = connectParameters
|
|
258
|
+
const capabilities =
|
|
259
|
+
'capabilities' in connectParameters
|
|
260
|
+
? connectParameters.capabilities
|
|
261
|
+
: undefined
|
|
262
|
+
|
|
263
|
+
let accounts: readonly InternalAccount[] = []
|
|
264
|
+
let currentChainId: number | undefined
|
|
265
|
+
|
|
266
|
+
if (isReconnecting) {
|
|
267
|
+
accounts = await this.getAccounts()
|
|
268
|
+
.then((accounts) =>
|
|
269
|
+
accounts.map((address) => ({ address, capabilities: {} })),
|
|
270
|
+
)
|
|
271
|
+
.catch(() => [])
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
if (!accounts.length && !isReconnecting) {
|
|
276
|
+
const provider = await getProvider()
|
|
277
|
+
const response = (await provider.request({
|
|
278
|
+
method: 'wallet_connect',
|
|
279
|
+
params: [
|
|
280
|
+
{
|
|
281
|
+
...(chainId ? { chainId } : {}),
|
|
282
|
+
...(capabilities ? { capabilities } : {}),
|
|
283
|
+
},
|
|
284
|
+
] as never,
|
|
285
|
+
})) as AccountsRpc.wallet_connect.Encoded['returns']
|
|
286
|
+
accounts = response.accounts
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
currentChainId ??= await this.getChainId()
|
|
290
|
+
if (!currentChainId) throw new ChainNotConfiguredError()
|
|
291
|
+
|
|
292
|
+
const provider = await getProvider()
|
|
293
|
+
if (connect) {
|
|
294
|
+
provider.removeListener('connect', connect)
|
|
295
|
+
connect = undefined
|
|
296
|
+
}
|
|
297
|
+
if (!accountsChanged) {
|
|
298
|
+
accountsChanged = this.onAccountsChanged.bind(this)
|
|
299
|
+
provider.on('accountsChanged', accountsChanged as never)
|
|
300
|
+
}
|
|
301
|
+
if (!chainChanged) {
|
|
302
|
+
chainChanged = this.onChainChanged.bind(this)
|
|
303
|
+
provider.on('chainChanged', chainChanged)
|
|
304
|
+
}
|
|
305
|
+
if (!disconnect) {
|
|
306
|
+
disconnect = this.onDisconnect.bind(this)
|
|
307
|
+
provider.on('disconnect', disconnect)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
accounts: (withCapabilities
|
|
312
|
+
? accounts
|
|
313
|
+
: accounts.map((account) => account.address)) as never,
|
|
314
|
+
chainId: currentChainId,
|
|
315
|
+
}
|
|
316
|
+
} catch (error) {
|
|
317
|
+
const rpcError = error as RpcError
|
|
318
|
+
if (rpcError.code === UserRejectedRequestError.code)
|
|
319
|
+
throw new UserRejectedRequestError(rpcError)
|
|
320
|
+
throw rpcError
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
async disconnect() {
|
|
324
|
+
const provider = await getProvider()
|
|
325
|
+
|
|
326
|
+
if (chainChanged) {
|
|
327
|
+
provider.removeListener('chainChanged', chainChanged)
|
|
328
|
+
chainChanged = undefined
|
|
329
|
+
}
|
|
330
|
+
if (disconnect) {
|
|
331
|
+
provider.removeListener('disconnect', disconnect)
|
|
332
|
+
disconnect = undefined
|
|
333
|
+
}
|
|
334
|
+
if (!connect) {
|
|
335
|
+
connect = this.onConnect?.bind(this)
|
|
336
|
+
if (connect) provider.on('connect', connect)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
await provider.request({ method: 'wallet_disconnect' })
|
|
340
|
+
},
|
|
341
|
+
async getAccounts() {
|
|
342
|
+
const provider = await getProvider()
|
|
343
|
+
return await provider.request({ method: 'eth_accounts' })
|
|
344
|
+
},
|
|
345
|
+
async getChainId() {
|
|
346
|
+
const provider = await getProvider()
|
|
347
|
+
return Number(await provider.request({ method: 'eth_chainId' }))
|
|
348
|
+
},
|
|
349
|
+
async getClient({ chainId } = {}) {
|
|
350
|
+
const provider = await getProvider()
|
|
351
|
+
return Object.assign(provider.getClient({ chainId }), {
|
|
352
|
+
account: provider.getAccount(),
|
|
353
|
+
}) as never
|
|
354
|
+
},
|
|
355
|
+
async getProvider() {
|
|
356
|
+
return await getProvider()
|
|
357
|
+
},
|
|
358
|
+
async isAuthorized() {
|
|
359
|
+
try {
|
|
360
|
+
const accounts = await withRetry(() => this.getAccounts())
|
|
361
|
+
return !!accounts.length
|
|
362
|
+
} catch {
|
|
363
|
+
return false
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
onAccountsChanged(accounts) {
|
|
367
|
+
config.emitter.emit('change', {
|
|
368
|
+
accounts: accounts as readonly Address[],
|
|
369
|
+
})
|
|
370
|
+
},
|
|
371
|
+
onChainChanged(chain) {
|
|
372
|
+
config.emitter.emit('change', { chainId: Number(chain) })
|
|
373
|
+
},
|
|
374
|
+
async onConnect(connectInfo) {
|
|
375
|
+
const accounts = await this.getAccounts()
|
|
376
|
+
if (accounts.length === 0) return
|
|
377
|
+
|
|
378
|
+
const chainId = Number(connectInfo.chainId)
|
|
379
|
+
config.emitter.emit('connect', { accounts, chainId })
|
|
380
|
+
|
|
381
|
+
const provider = await getProvider()
|
|
382
|
+
if (connect) {
|
|
383
|
+
provider.removeListener('connect', connect)
|
|
384
|
+
connect = undefined
|
|
385
|
+
}
|
|
386
|
+
if (!accountsChanged) {
|
|
387
|
+
accountsChanged = this.onAccountsChanged.bind(this)
|
|
388
|
+
provider.on('accountsChanged', accountsChanged as never)
|
|
389
|
+
}
|
|
390
|
+
if (!chainChanged) {
|
|
391
|
+
chainChanged = this.onChainChanged.bind(this)
|
|
392
|
+
provider.on('chainChanged', chainChanged)
|
|
393
|
+
}
|
|
394
|
+
if (!disconnect) {
|
|
395
|
+
disconnect = this.onDisconnect.bind(this)
|
|
396
|
+
provider.on('disconnect', disconnect)
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
async onDisconnect(_error) {
|
|
400
|
+
const provider = await getProvider()
|
|
401
|
+
|
|
402
|
+
config.emitter.emit('disconnect')
|
|
403
|
+
|
|
404
|
+
if (chainChanged) {
|
|
405
|
+
provider.removeListener('chainChanged', chainChanged)
|
|
406
|
+
chainChanged = undefined
|
|
407
|
+
}
|
|
408
|
+
if (disconnect) {
|
|
409
|
+
provider.removeListener('disconnect', disconnect)
|
|
410
|
+
disconnect = undefined
|
|
411
|
+
}
|
|
412
|
+
if (!connect) {
|
|
413
|
+
connect = this.onConnect?.bind(this)
|
|
414
|
+
if (connect) provider.on('connect', connect)
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
async setup() {
|
|
418
|
+
if (!connect) {
|
|
419
|
+
const provider = await getProvider()
|
|
420
|
+
connect = this.onConnect?.bind(this)
|
|
421
|
+
if (connect) provider.on('connect', connect)
|
|
422
|
+
}
|
|
423
|
+
},
|
|
424
|
+
async switchChain({ chainId }) {
|
|
425
|
+
const chain = chains.find((chain) => chain.id === chainId)
|
|
426
|
+
if (!chain) throw new SwitchChainError(new ChainNotConfiguredError())
|
|
427
|
+
|
|
428
|
+
const provider = await getProvider()
|
|
429
|
+
await provider.request({
|
|
430
|
+
method: 'wallet_switchEthereumChain',
|
|
431
|
+
params: [{ chainId: numberToHex(chainId) }],
|
|
432
|
+
})
|
|
433
|
+
|
|
434
|
+
return chain
|
|
435
|
+
},
|
|
436
|
+
}
|
|
437
|
+
})
|
|
850
438
|
}
|
|
851
439
|
|
|
852
|
-
export declare namespace
|
|
440
|
+
export declare namespace setup {
|
|
853
441
|
export type Parameters = {
|
|
854
|
-
|
|
442
|
+
createAdapter: (accounts: AccountsModule) => AccountsAdapter
|
|
443
|
+
icon?: string | undefined
|
|
444
|
+
id: string
|
|
445
|
+
name: string
|
|
446
|
+
providerParameters: Omit<AccountsProviderParameters, 'adapter' | 'chains'>
|
|
447
|
+
rdns?: string | readonly string[] | undefined
|
|
448
|
+
type: string
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
export type ConnectParameters<withCapabilities extends boolean = false> = {
|
|
452
|
+
capabilities?: CapabilitiesRequest | undefined
|
|
453
|
+
chainId?: number | undefined
|
|
454
|
+
isReconnecting?: boolean | undefined
|
|
455
|
+
withCapabilities?: withCapabilities | boolean | undefined
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
export type ConnectReturnType<withCapabilities extends boolean = false> = {
|
|
459
|
+
accounts: withCapabilities extends true
|
|
460
|
+
? readonly InternalAccount[]
|
|
461
|
+
: readonly Address[]
|
|
462
|
+
chainId: number
|
|
855
463
|
}
|
|
856
464
|
}
|