@wagmi/core 3.0.2 → 3.2.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.
- package/dist/esm/exports/internal.js.map +1 -1
- package/dist/esm/exports/query.js +1 -1
- package/dist/esm/exports/query.js.map +1 -1
- package/dist/esm/exports/tempo.js +9 -0
- package/dist/esm/exports/tempo.js.map +1 -0
- package/dist/esm/tempo/Connectors.js +534 -0
- package/dist/esm/tempo/Connectors.js.map +1 -0
- package/dist/esm/tempo/KeyManager.js +106 -0
- package/dist/esm/tempo/KeyManager.js.map +1 -0
- package/dist/esm/tempo/actions/amm.js +437 -0
- package/dist/esm/tempo/actions/amm.js.map +1 -0
- package/dist/esm/tempo/actions/dex.js +1059 -0
- package/dist/esm/tempo/actions/dex.js.map +1 -0
- package/dist/esm/tempo/actions/faucet.js +64 -0
- package/dist/esm/tempo/actions/faucet.js.map +1 -0
- package/dist/esm/tempo/actions/fee.js +160 -0
- package/dist/esm/tempo/actions/fee.js.map +1 -0
- package/dist/esm/tempo/actions/index.js +11 -0
- package/dist/esm/tempo/actions/index.js.map +1 -0
- package/dist/esm/tempo/actions/nonce.js +91 -0
- package/dist/esm/tempo/actions/nonce.js.map +1 -0
- package/dist/esm/tempo/actions/policy.js +532 -0
- package/dist/esm/tempo/actions/policy.js.map +1 -0
- package/dist/esm/tempo/actions/reward.js +384 -0
- package/dist/esm/tempo/actions/reward.js.map +1 -0
- package/dist/esm/tempo/actions/token.js +1717 -0
- package/dist/esm/tempo/actions/token.js.map +1 -0
- package/dist/esm/tempo/actions/utils.js +2 -0
- package/dist/esm/tempo/actions/utils.js.map +1 -0
- package/dist/esm/tsconfig.build.tsbuildinfo +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/types/exports/internal.d.ts +1 -1
- package/dist/types/exports/internal.d.ts.map +1 -1
- package/dist/types/exports/query.d.ts +1 -1
- package/dist/types/exports/query.d.ts.map +1 -1
- package/dist/types/exports/tempo.d.ts +5 -0
- package/dist/types/exports/tempo.d.ts.map +1 -0
- package/dist/types/tempo/Connectors.d.ts +99 -0
- package/dist/types/tempo/Connectors.d.ts.map +1 -0
- package/dist/types/tempo/KeyManager.d.ts +71 -0
- package/dist/types/tempo/KeyManager.d.ts.map +1 -0
- package/dist/types/tempo/actions/amm.d.ts +387 -0
- package/dist/types/tempo/actions/amm.d.ts.map +1 -0
- package/dist/types/tempo/actions/dex.d.ts +926 -0
- package/dist/types/tempo/actions/dex.d.ts.map +1 -0
- package/dist/types/tempo/actions/faucet.d.ts +70 -0
- package/dist/types/tempo/actions/faucet.d.ts.map +1 -0
- package/dist/types/tempo/actions/fee.d.ts +141 -0
- package/dist/types/tempo/actions/fee.d.ts.map +1 -0
- package/dist/types/tempo/actions/index.d.ts +10 -0
- package/dist/types/tempo/actions/index.d.ts.map +1 -0
- package/dist/types/tempo/actions/nonce.d.ts +79 -0
- package/dist/types/tempo/actions/nonce.d.ts.map +1 -0
- package/dist/types/tempo/actions/policy.d.ts +478 -0
- package/dist/types/tempo/actions/policy.d.ts.map +1 -0
- package/dist/types/tempo/actions/reward.d.ts +340 -0
- package/dist/types/tempo/actions/reward.d.ts.map +1 -0
- package/dist/types/tempo/actions/token.d.ts +1531 -0
- package/dist/types/tempo/actions/token.d.ts.map +1 -0
- package/dist/types/tempo/actions/utils.d.ts +9 -0
- package/dist/types/tempo/actions/utils.d.ts.map +1 -0
- package/dist/types/types/utils.d.ts +16 -0
- package/dist/types/types/utils.d.ts.map +1 -1
- package/dist/types/version.d.ts +1 -1
- package/package.json +14 -2
- package/src/exports/internal.ts +3 -0
- package/src/exports/query.ts +4 -1
- package/src/exports/tempo.ts +14 -0
- package/src/tempo/Connectors.ts +762 -0
- package/src/tempo/KeyManager.ts +176 -0
- package/src/tempo/actions/amm.ts +678 -0
- package/src/tempo/actions/dex.ts +1685 -0
- package/src/tempo/actions/faucet.ts +95 -0
- package/src/tempo/actions/fee.ts +259 -0
- package/src/tempo/actions/index.ts +10 -0
- package/src/tempo/actions/nonce.ts +147 -0
- package/src/tempo/actions/policy.ts +827 -0
- package/src/tempo/actions/reward.ts +624 -0
- package/src/tempo/actions/token.ts +2598 -0
- package/src/tempo/actions/utils.ts +26 -0
- package/src/types/utils.ts +19 -0
- package/src/version.ts +1 -1
- package/tempo/package.json +5 -0
|
@@ -0,0 +1,762 @@
|
|
|
1
|
+
import * as Address from 'ox/Address'
|
|
2
|
+
import type * as Hex from 'ox/Hex'
|
|
3
|
+
import * as PublicKey from 'ox/PublicKey'
|
|
4
|
+
import { KeyAuthorization, SignatureEnvelope } from 'ox/tempo'
|
|
5
|
+
import {
|
|
6
|
+
createClient,
|
|
7
|
+
defineChain,
|
|
8
|
+
type EIP1193Provider,
|
|
9
|
+
getAddress,
|
|
10
|
+
SwitchChainError,
|
|
11
|
+
} from 'viem'
|
|
12
|
+
import {
|
|
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'
|
|
23
|
+
import { createConnector } from '../connectors/createConnector.js'
|
|
24
|
+
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
|
+
|
|
31
|
+
webAuthn.type = 'webAuthn' as const
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Connector for a WebAuthn EOA.
|
|
35
|
+
*/
|
|
36
|
+
export function webAuthn(options: webAuthn.Parameters) {
|
|
37
|
+
let account: Account.RootAccount | undefined
|
|
38
|
+
let accessKey: Account.AccessKeyAccount | undefined
|
|
39
|
+
|
|
40
|
+
const defaultAccessKeyOptions = {
|
|
41
|
+
expiry: Math.floor(
|
|
42
|
+
(Date.now() + 24 * 60 * 60 * 1000) / 1000, // one day
|
|
43
|
+
),
|
|
44
|
+
strict: false,
|
|
45
|
+
}
|
|
46
|
+
const accessKeyOptions = (() => {
|
|
47
|
+
if (typeof options.grantAccessKey === 'object')
|
|
48
|
+
return { ...defaultAccessKeyOptions, ...options.grantAccessKey }
|
|
49
|
+
if (options.grantAccessKey === true) return defaultAccessKeyOptions
|
|
50
|
+
return undefined
|
|
51
|
+
})()
|
|
52
|
+
|
|
53
|
+
type Properties = {
|
|
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
|
+
| undefined
|
|
72
|
+
isReconnecting?: boolean | undefined
|
|
73
|
+
withCapabilities?: withCapabilities | boolean | undefined
|
|
74
|
+
}): Promise<{ accounts: readonly Address.Address[]; chainId: number }>
|
|
75
|
+
}
|
|
76
|
+
type Provider = Pick<EIP1193Provider, 'request'>
|
|
77
|
+
type StorageItem = {
|
|
78
|
+
[
|
|
79
|
+
key: `pendingKeyAuthorization:${string}`
|
|
80
|
+
]: KeyAuthorization.KeyAuthorization
|
|
81
|
+
'webAuthn.activeCredential': WebAuthnP256.P256Credential
|
|
82
|
+
'webAuthn.lastActiveCredential': WebAuthnP256.P256Credential
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return createConnector<Provider, Properties, StorageItem>((config) => ({
|
|
86
|
+
id: 'webAuthn',
|
|
87
|
+
name: 'EOA (WebAuthn)',
|
|
88
|
+
type: 'webAuthn',
|
|
89
|
+
async setup() {
|
|
90
|
+
const credential = await config.storage?.getItem(
|
|
91
|
+
'webAuthn.activeCredential',
|
|
92
|
+
)
|
|
93
|
+
if (!credential) return
|
|
94
|
+
account = Account.fromWebAuthnP256(credential)
|
|
95
|
+
},
|
|
96
|
+
async connect(parameters = {}) {
|
|
97
|
+
const capabilities =
|
|
98
|
+
'capabilities' in parameters ? (parameters.capabilities ?? {}) : {}
|
|
99
|
+
|
|
100
|
+
if (
|
|
101
|
+
accessKeyOptions?.strict &&
|
|
102
|
+
accessKeyOptions.expiry &&
|
|
103
|
+
accessKeyOptions.expiry < Date.now() / 1000
|
|
104
|
+
)
|
|
105
|
+
throw new Error(
|
|
106
|
+
`\`grantAccessKey.expiry = ${accessKeyOptions.expiry}\` is in the past (${new Date(accessKeyOptions.expiry * 1000).toLocaleString()}). Please provide a valid expiry.`,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
// We are going to need to find:
|
|
110
|
+
// - a WebAuthn `credential` to instantiate an account
|
|
111
|
+
// - optionally, a `keyPair` to use as the access key for the account
|
|
112
|
+
// - optionally, a signed `keyAuthorization` to provision the access key
|
|
113
|
+
const { credential, keyAuthorization, keyPair } = await (async () => {
|
|
114
|
+
// If the connection type is of "sign-up", we are going to create a new credential
|
|
115
|
+
// and provision an access key (if needed).
|
|
116
|
+
if (capabilities.type === 'sign-up') {
|
|
117
|
+
// Create credential (sign up)
|
|
118
|
+
const createOptions_remote = await options.keyManager.getChallenge?.()
|
|
119
|
+
const label =
|
|
120
|
+
capabilities.label ??
|
|
121
|
+
options.createOptions?.label ??
|
|
122
|
+
new Date().toISOString()
|
|
123
|
+
const rpId =
|
|
124
|
+
createOptions_remote?.rp?.id ??
|
|
125
|
+
options.createOptions?.rpId ??
|
|
126
|
+
options.rpId
|
|
127
|
+
const credential = await WebAuthnP256.createCredential({
|
|
128
|
+
...(options.createOptions ?? {}),
|
|
129
|
+
label,
|
|
130
|
+
rpId,
|
|
131
|
+
...(createOptions_remote ?? {}),
|
|
132
|
+
})
|
|
133
|
+
await options.keyManager.setPublicKey({
|
|
134
|
+
credential: credential.raw,
|
|
135
|
+
publicKey: credential.publicKey,
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
// Get key pair (access key) to use for the account.
|
|
139
|
+
const keyPair = await (async () => {
|
|
140
|
+
if (!accessKeyOptions) return undefined
|
|
141
|
+
return await WebCryptoP256.createKeyPair()
|
|
142
|
+
})()
|
|
143
|
+
|
|
144
|
+
return { credential, keyPair }
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// If we are not selecting an account, we will check if an active credential is present in
|
|
148
|
+
// storage and if so, we will use it to instantiate an account.
|
|
149
|
+
if (!capabilities.selectAccount) {
|
|
150
|
+
const credential = (await config.storage?.getItem(
|
|
151
|
+
'webAuthn.activeCredential',
|
|
152
|
+
)) as WebAuthnP256.getCredential.ReturnValue | undefined
|
|
153
|
+
|
|
154
|
+
if (credential) {
|
|
155
|
+
// Get key pair (access key) to use for the account.
|
|
156
|
+
const keyPair = await (async () => {
|
|
157
|
+
if (!accessKeyOptions) return undefined
|
|
158
|
+
const address = Address.fromPublicKey(
|
|
159
|
+
PublicKey.fromHex(credential.publicKey),
|
|
160
|
+
)
|
|
161
|
+
return await idb.get(`accessKey:${address}`)
|
|
162
|
+
})()
|
|
163
|
+
|
|
164
|
+
// If the access key provisioning is not in strict mode, return the credential and key pair (if exists).
|
|
165
|
+
if (!accessKeyOptions?.strict) return { credential, keyPair }
|
|
166
|
+
|
|
167
|
+
// If a key pair is found, return the credential and key pair.
|
|
168
|
+
if (keyPair) return { credential, keyPair }
|
|
169
|
+
|
|
170
|
+
// If we are reconnecting, throw an error if not found.
|
|
171
|
+
if (parameters.isReconnecting)
|
|
172
|
+
throw new Error('credential not found.')
|
|
173
|
+
|
|
174
|
+
// Otherwise, we want to continue to sign up or register against new key pair.
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Discover credential
|
|
179
|
+
{
|
|
180
|
+
// Get key pair (access key) to use for the account.
|
|
181
|
+
const keyPair = await (async () => {
|
|
182
|
+
if (!accessKeyOptions) return undefined
|
|
183
|
+
return await WebCryptoP256.createKeyPair()
|
|
184
|
+
})()
|
|
185
|
+
|
|
186
|
+
// If we are provisioning an access key, we will need to sign a key authorization.
|
|
187
|
+
// We will need the hash (digest) to sign, and the address of the access key to construct the key authorization.
|
|
188
|
+
const { hash, keyAuthorization_unsigned } = await (async () => {
|
|
189
|
+
if (!keyPair)
|
|
190
|
+
return { accessKeyAddress: undefined, hash: undefined }
|
|
191
|
+
const accessKeyAddress = Address.fromPublicKey(keyPair.publicKey)
|
|
192
|
+
const keyAuthorization_unsigned = KeyAuthorization.from({
|
|
193
|
+
...accessKeyOptions,
|
|
194
|
+
address: accessKeyAddress,
|
|
195
|
+
type: 'p256',
|
|
196
|
+
})
|
|
197
|
+
const hash = KeyAuthorization.getSignPayload(
|
|
198
|
+
keyAuthorization_unsigned,
|
|
199
|
+
)
|
|
200
|
+
return { keyAuthorization_unsigned, hash }
|
|
201
|
+
})()
|
|
202
|
+
|
|
203
|
+
// If no active credential, we will attempt to load the last active credential from storage.
|
|
204
|
+
const lastActiveCredential = !capabilities.selectAccount
|
|
205
|
+
? await config.storage?.getItem('webAuthn.lastActiveCredential')
|
|
206
|
+
: undefined
|
|
207
|
+
const credential = await WebAuthnP256.getCredential({
|
|
208
|
+
...(options.getOptions ?? {}),
|
|
209
|
+
credentialId: lastActiveCredential?.id,
|
|
210
|
+
async getPublicKey(credential) {
|
|
211
|
+
const publicKey = await options.keyManager.getPublicKey({
|
|
212
|
+
credential,
|
|
213
|
+
})
|
|
214
|
+
if (!publicKey) throw new Error('publicKey not found.')
|
|
215
|
+
return publicKey
|
|
216
|
+
},
|
|
217
|
+
hash,
|
|
218
|
+
rpId: options.getOptions?.rpId ?? options.rpId,
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
const keyAuthorization = keyAuthorization_unsigned
|
|
222
|
+
? KeyAuthorization.from({
|
|
223
|
+
...keyAuthorization_unsigned,
|
|
224
|
+
signature: SignatureEnvelope.from({
|
|
225
|
+
metadata: credential.metadata,
|
|
226
|
+
signature: credential.signature,
|
|
227
|
+
publicKey: PublicKey.fromHex(credential.publicKey),
|
|
228
|
+
type: 'webAuthn',
|
|
229
|
+
}),
|
|
230
|
+
})
|
|
231
|
+
: undefined
|
|
232
|
+
|
|
233
|
+
return { credential, keyAuthorization, keyPair }
|
|
234
|
+
}
|
|
235
|
+
})()
|
|
236
|
+
|
|
237
|
+
config.storage?.setItem(
|
|
238
|
+
'webAuthn.lastActiveCredential',
|
|
239
|
+
normalizeValue(credential),
|
|
240
|
+
)
|
|
241
|
+
config.storage?.setItem(
|
|
242
|
+
'webAuthn.activeCredential',
|
|
243
|
+
normalizeValue(credential),
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
account = Account.fromWebAuthnP256(credential)
|
|
247
|
+
|
|
248
|
+
if (keyPair) {
|
|
249
|
+
accessKey = Account.fromWebCryptoP256(keyPair, {
|
|
250
|
+
access: account,
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
// If we are reconnecting, check if the access key is expired.
|
|
254
|
+
if (parameters.isReconnecting) {
|
|
255
|
+
if (
|
|
256
|
+
'keyAuthorization' in keyPair &&
|
|
257
|
+
keyPair.keyAuthorization.expiry &&
|
|
258
|
+
keyPair.keyAuthorization.expiry < Date.now() / 1000
|
|
259
|
+
) {
|
|
260
|
+
// remove any pending key authorizations from storage.
|
|
261
|
+
await config?.storage?.removeItem(
|
|
262
|
+
`pendingKeyAuthorization:${account.address.toLowerCase()}`,
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
const message = `Access key expired (on ${new Date(keyPair.keyAuthorization.expiry * 1000).toLocaleString()}).`
|
|
266
|
+
accessKey = undefined
|
|
267
|
+
|
|
268
|
+
// if in strict mode, disconnect and throw an error.
|
|
269
|
+
if (accessKeyOptions?.strict) {
|
|
270
|
+
await this.disconnect()
|
|
271
|
+
throw new Error(message)
|
|
272
|
+
}
|
|
273
|
+
// otherwise, fall back to the root account.
|
|
274
|
+
// biome-ignore lint/suspicious/noConsole: notify
|
|
275
|
+
console.warn(`${message} Falling back to passkey.`)
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
// If we are not reconnecting, orchestrate the provisioning of the access key.
|
|
279
|
+
else {
|
|
280
|
+
const keyAuth =
|
|
281
|
+
keyAuthorization ??
|
|
282
|
+
(await account.signKeyAuthorization(accessKey, accessKeyOptions))
|
|
283
|
+
|
|
284
|
+
await config?.storage?.setItem(
|
|
285
|
+
`pendingKeyAuthorization:${account.address.toLowerCase()}`,
|
|
286
|
+
keyAuth,
|
|
287
|
+
)
|
|
288
|
+
await idb.set(`accessKey:${account.address.toLowerCase()}`, {
|
|
289
|
+
...keyPair,
|
|
290
|
+
keyAuthorization: keyAuth,
|
|
291
|
+
})
|
|
292
|
+
}
|
|
293
|
+
// If we are granting an access key and it is in strict mode, throw an error if the access key is not provisioned.
|
|
294
|
+
} else if (accessKeyOptions?.strict) {
|
|
295
|
+
await config.storage?.removeItem('webAuthn.activeCredential')
|
|
296
|
+
throw new Error('access key not found')
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const address = getAddress(account.address)
|
|
300
|
+
|
|
301
|
+
const chainId = parameters.chainId ?? config.chains[0]?.id
|
|
302
|
+
if (!chainId) throw new ChainNotConfiguredError()
|
|
303
|
+
|
|
304
|
+
return {
|
|
305
|
+
accounts: (parameters.withCapabilities
|
|
306
|
+
? [{ address }]
|
|
307
|
+
: [address]) as never,
|
|
308
|
+
chainId,
|
|
309
|
+
}
|
|
310
|
+
},
|
|
311
|
+
async disconnect() {
|
|
312
|
+
await config.storage?.removeItem('webAuthn.activeCredential')
|
|
313
|
+
config.emitter.emit('disconnect')
|
|
314
|
+
account = undefined
|
|
315
|
+
},
|
|
316
|
+
async getAccounts() {
|
|
317
|
+
if (!account) return []
|
|
318
|
+
return [getAddress(account.address)]
|
|
319
|
+
},
|
|
320
|
+
async getChainId() {
|
|
321
|
+
return config.chains[0]?.id!
|
|
322
|
+
},
|
|
323
|
+
async isAuthorized() {
|
|
324
|
+
try {
|
|
325
|
+
const accounts = await this.getAccounts()
|
|
326
|
+
return !!accounts.length
|
|
327
|
+
} catch (error) {
|
|
328
|
+
// biome-ignore lint/suspicious/noConsole: notify
|
|
329
|
+
console.error(
|
|
330
|
+
'Connector.webAuthn: Failed to check authorization',
|
|
331
|
+
error,
|
|
332
|
+
)
|
|
333
|
+
return false
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
async switchChain({ chainId }) {
|
|
337
|
+
const chain = config.chains.find((chain) => chain.id === chainId)
|
|
338
|
+
if (!chain) throw new SwitchChainError(new ChainNotConfiguredError())
|
|
339
|
+
return chain
|
|
340
|
+
},
|
|
341
|
+
onAccountsChanged() {},
|
|
342
|
+
onChainChanged(chain) {
|
|
343
|
+
const chainId = Number(chain)
|
|
344
|
+
config.emitter.emit('change', { chainId })
|
|
345
|
+
},
|
|
346
|
+
async onDisconnect() {
|
|
347
|
+
config.emitter.emit('disconnect')
|
|
348
|
+
account = undefined
|
|
349
|
+
},
|
|
350
|
+
async getClient({ chainId } = {}) {
|
|
351
|
+
const chain =
|
|
352
|
+
config.chains.find((x) => x.id === chainId) ?? config.chains[0]
|
|
353
|
+
if (!chain) throw new ChainNotConfiguredError()
|
|
354
|
+
|
|
355
|
+
const transports = config.transports
|
|
356
|
+
if (!transports) throw new ChainNotConfiguredError()
|
|
357
|
+
|
|
358
|
+
const transport = transports[chain.id]
|
|
359
|
+
if (!transport) throw new ChainNotConfiguredError()
|
|
360
|
+
|
|
361
|
+
const targetAccount = await (async () => {
|
|
362
|
+
if (!accessKey) return account
|
|
363
|
+
|
|
364
|
+
const item = await idb.get(
|
|
365
|
+
`accessKey:${accessKey.address.toLowerCase()}`,
|
|
366
|
+
)
|
|
367
|
+
if (
|
|
368
|
+
item?.keyAuthorization.expiry &&
|
|
369
|
+
item.keyAuthorization.expiry < Date.now() / 1000
|
|
370
|
+
) {
|
|
371
|
+
// remove any pending key authorizations from storage.
|
|
372
|
+
await config?.storage?.removeItem(
|
|
373
|
+
`pendingKeyAuthorization:${accessKey.address.toLowerCase()}`,
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
const message = `Access key expired (on ${new Date(item.keyAuthorization.expiry * 1000).toLocaleString()}).`
|
|
377
|
+
|
|
378
|
+
// if in strict mode, disconnect and throw an error.
|
|
379
|
+
if (accessKeyOptions?.strict) {
|
|
380
|
+
await this.disconnect()
|
|
381
|
+
throw new Error(message)
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// otherwise, fall back to the root account.
|
|
385
|
+
// biome-ignore lint/suspicious/noConsole: notify
|
|
386
|
+
console.warn(`${message} Falling back to passkey.`)
|
|
387
|
+
return account
|
|
388
|
+
}
|
|
389
|
+
return accessKey
|
|
390
|
+
})()
|
|
391
|
+
if (!targetAccount) throw new Error('account not found.')
|
|
392
|
+
|
|
393
|
+
const targetChain = defineChain({
|
|
394
|
+
...chain,
|
|
395
|
+
async prepareTransactionRequest(args, { phase }) {
|
|
396
|
+
const keyAuthorization = await (async () => {
|
|
397
|
+
{
|
|
398
|
+
const keyAuthorization = (
|
|
399
|
+
args as {
|
|
400
|
+
keyAuthorization?:
|
|
401
|
+
| KeyAuthorization.KeyAuthorization
|
|
402
|
+
| undefined
|
|
403
|
+
}
|
|
404
|
+
).keyAuthorization
|
|
405
|
+
if (keyAuthorization) return keyAuthorization
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const keyAuthorization = await config.storage?.getItem(
|
|
409
|
+
`pendingKeyAuthorization:${targetAccount?.address.toLowerCase()}`,
|
|
410
|
+
)
|
|
411
|
+
await config.storage?.removeItem(
|
|
412
|
+
`pendingKeyAuthorization:${targetAccount?.address.toLowerCase()}`,
|
|
413
|
+
)
|
|
414
|
+
return keyAuthorization
|
|
415
|
+
})()
|
|
416
|
+
|
|
417
|
+
const [prepareTransactionRequestFn, options] = (() => {
|
|
418
|
+
if (!chain.prepareTransactionRequest) return [undefined, undefined]
|
|
419
|
+
if (typeof chain.prepareTransactionRequest === 'function')
|
|
420
|
+
return [chain.prepareTransactionRequest, undefined]
|
|
421
|
+
return chain.prepareTransactionRequest
|
|
422
|
+
})()
|
|
423
|
+
|
|
424
|
+
const request = await (async () => {
|
|
425
|
+
if (!prepareTransactionRequestFn) return {}
|
|
426
|
+
if (!options || options?.runAt?.includes(phase))
|
|
427
|
+
return await prepareTransactionRequestFn(args, { phase })
|
|
428
|
+
return {}
|
|
429
|
+
})()
|
|
430
|
+
|
|
431
|
+
return {
|
|
432
|
+
...args,
|
|
433
|
+
...request,
|
|
434
|
+
keyAuthorization,
|
|
435
|
+
}
|
|
436
|
+
},
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
return createClient({
|
|
440
|
+
account: targetAccount,
|
|
441
|
+
chain: targetChain,
|
|
442
|
+
transport: walletNamespaceCompat(transport, {
|
|
443
|
+
account: targetAccount,
|
|
444
|
+
}),
|
|
445
|
+
})
|
|
446
|
+
},
|
|
447
|
+
async getProvider({ chainId } = {}) {
|
|
448
|
+
const { request } = await this.getClient!({ chainId })
|
|
449
|
+
return { request }
|
|
450
|
+
},
|
|
451
|
+
}))
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
export namespace webAuthn {
|
|
455
|
+
export type Parameters = {
|
|
456
|
+
/** Options for WebAuthn registration. */
|
|
457
|
+
createOptions?:
|
|
458
|
+
| Pick<
|
|
459
|
+
WebAuthnP256.createCredential.Parameters,
|
|
460
|
+
'createFn' | 'label' | 'rpId' | 'userId' | 'timeout'
|
|
461
|
+
>
|
|
462
|
+
| undefined
|
|
463
|
+
/** Options for WebAuthn authentication. */
|
|
464
|
+
getOptions?:
|
|
465
|
+
| Pick<WebAuthnP256.getCredential.Parameters, 'getFn' | 'rpId'>
|
|
466
|
+
| undefined
|
|
467
|
+
/**
|
|
468
|
+
* Whether or not to grant an access key upon connection, and optionally, expiry + limits to assign to the key.
|
|
469
|
+
*/
|
|
470
|
+
grantAccessKey?:
|
|
471
|
+
| boolean
|
|
472
|
+
| (Pick<KeyAuthorization.KeyAuthorization, 'expiry' | 'limits'> & {
|
|
473
|
+
/** Whether or not to throw an error and disconnect if the access key is not provisioned or is expired. */
|
|
474
|
+
strict?: boolean | undefined
|
|
475
|
+
})
|
|
476
|
+
/** Public key manager. */
|
|
477
|
+
keyManager: KeyManager.KeyManager
|
|
478
|
+
/** The RP ID to use for WebAuthn. */
|
|
479
|
+
rpId?: string | undefined
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Normalizes a value into a structured-clone compatible format.
|
|
485
|
+
*
|
|
486
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Window/structuredClone
|
|
487
|
+
*/
|
|
488
|
+
function normalizeValue<type>(value: type): type {
|
|
489
|
+
if (Array.isArray(value)) return value.map(normalizeValue) as never
|
|
490
|
+
if (typeof value === 'function') return undefined as never
|
|
491
|
+
if (typeof value !== 'object' || value === null) return value
|
|
492
|
+
if (Object.getPrototypeOf(value) !== Object.prototype)
|
|
493
|
+
try {
|
|
494
|
+
return structuredClone(value)
|
|
495
|
+
} catch {
|
|
496
|
+
return undefined as never
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const normalized: Record<string, unknown> = {}
|
|
500
|
+
for (const [k, v] of Object.entries(value)) normalized[k] = normalizeValue(v)
|
|
501
|
+
return normalized as never
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Based on `idb-keyval`
|
|
505
|
+
// https://github.com/jakearchibald/idb-keyval
|
|
506
|
+
let defaultGetStoreFunc:
|
|
507
|
+
| (<type>(
|
|
508
|
+
txMode: IDBTransactionMode,
|
|
509
|
+
callback: (store: IDBObjectStore) => type | PromiseLike<type>,
|
|
510
|
+
) => Promise<type>)
|
|
511
|
+
| undefined
|
|
512
|
+
|
|
513
|
+
const idb = {
|
|
514
|
+
/**
|
|
515
|
+
* Get a value by its key.
|
|
516
|
+
*
|
|
517
|
+
* @param key
|
|
518
|
+
* @param customStore Method to get a custom store. Use with caution (see the docs).
|
|
519
|
+
*/
|
|
520
|
+
get<type = any>(key: IDBValidKey): Promise<type | undefined> {
|
|
521
|
+
return this.defaultGetStore()('readonly', (store) =>
|
|
522
|
+
this.promisifyRequest(store.get(key)),
|
|
523
|
+
)
|
|
524
|
+
},
|
|
525
|
+
/**
|
|
526
|
+
* Set a value with a key.
|
|
527
|
+
*
|
|
528
|
+
* @param key
|
|
529
|
+
* @param value
|
|
530
|
+
* @param customStore Method to get a custom store. Use with caution (see the docs).
|
|
531
|
+
*/
|
|
532
|
+
set(key: IDBValidKey, value: any): Promise<void> {
|
|
533
|
+
return this.defaultGetStore()('readwrite', (store) => {
|
|
534
|
+
store.put(value, key)
|
|
535
|
+
return this.promisifyRequest(store.transaction)
|
|
536
|
+
})
|
|
537
|
+
},
|
|
538
|
+
defaultGetStore() {
|
|
539
|
+
if (!defaultGetStoreFunc)
|
|
540
|
+
defaultGetStoreFunc = this.createStore('keyval-store', 'keyval')
|
|
541
|
+
return defaultGetStoreFunc
|
|
542
|
+
},
|
|
543
|
+
createStore(
|
|
544
|
+
dbName: string,
|
|
545
|
+
storeName: string,
|
|
546
|
+
): NonNullable<typeof defaultGetStoreFunc> {
|
|
547
|
+
let dbp: Promise<IDBDatabase> | undefined
|
|
548
|
+
|
|
549
|
+
const getDB = () => {
|
|
550
|
+
if (dbp) return dbp
|
|
551
|
+
const request = indexedDB.open(dbName)
|
|
552
|
+
request.onupgradeneeded = () =>
|
|
553
|
+
request.result.createObjectStore(storeName)
|
|
554
|
+
dbp = this.promisifyRequest(request)
|
|
555
|
+
|
|
556
|
+
dbp.then(
|
|
557
|
+
(db) => {
|
|
558
|
+
// It seems like Safari sometimes likes to just close the connection.
|
|
559
|
+
// It's supposed to fire this event when that happens. Let's hope it does!
|
|
560
|
+
db.onclose = () => {
|
|
561
|
+
dbp = undefined
|
|
562
|
+
}
|
|
563
|
+
},
|
|
564
|
+
() => {},
|
|
565
|
+
)
|
|
566
|
+
return dbp
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
return (txMode, callback) =>
|
|
570
|
+
getDB().then((db) =>
|
|
571
|
+
callback(db.transaction(storeName, txMode).objectStore(storeName)),
|
|
572
|
+
)
|
|
573
|
+
},
|
|
574
|
+
promisifyRequest<type = undefined>(
|
|
575
|
+
request: IDBRequest<type> | IDBTransaction,
|
|
576
|
+
): Promise<type> {
|
|
577
|
+
return new Promise<type>((resolve, reject) => {
|
|
578
|
+
// @ts-ignore - file size hacks
|
|
579
|
+
request.oncomplete = request.onsuccess = () => resolve(request.result)
|
|
580
|
+
// @ts-ignore - file size hacks
|
|
581
|
+
request.onabort = request.onerror = () => reject(request.error)
|
|
582
|
+
})
|
|
583
|
+
},
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/** @deprecated use `dangerous_secp256k1.Parameters` instead */
|
|
587
|
+
export type Dangerous_Secp256k1Parameters = dangerous_secp256k1.Parameters
|
|
588
|
+
|
|
589
|
+
dangerous_secp256k1.type = 'dangerous_secp256k1' as const
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* Connector for a Secp256k1 EOA.
|
|
593
|
+
*
|
|
594
|
+
* WARNING: NOT RECOMMENDED FOR PRODUCTION USAGE.
|
|
595
|
+
* This connector stores private keys in clear text, and are bound to the session
|
|
596
|
+
* length of the storage used.
|
|
597
|
+
*/
|
|
598
|
+
export function dangerous_secp256k1(
|
|
599
|
+
options: dangerous_secp256k1.Parameters = {},
|
|
600
|
+
) {
|
|
601
|
+
let account: LocalAccount | undefined
|
|
602
|
+
|
|
603
|
+
type Properties = {
|
|
604
|
+
// TODO(v3): Make `withCapabilities: true` default behavior
|
|
605
|
+
connect<withCapabilities extends boolean = false>(parameters: {
|
|
606
|
+
capabilities?: { type?: 'sign-up' | undefined } | undefined
|
|
607
|
+
chainId?: number | undefined
|
|
608
|
+
isReconnecting?: boolean | undefined
|
|
609
|
+
withCapabilities?: withCapabilities | boolean | undefined
|
|
610
|
+
}): Promise<{
|
|
611
|
+
accounts: readonly Address.Address[]
|
|
612
|
+
chainId: number
|
|
613
|
+
}>
|
|
614
|
+
}
|
|
615
|
+
type Provider = Pick<EIP1193Provider, 'request'>
|
|
616
|
+
type StorageItem = {
|
|
617
|
+
'secp256k1.activeAddress': Address.Address
|
|
618
|
+
'secp256k1.lastActiveAddress': Address.Address
|
|
619
|
+
[key: `secp256k1.${string}.privateKey`]: Hex.Hex
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
return createConnector<Provider, Properties, StorageItem>((config) => ({
|
|
623
|
+
id: 'secp256k1',
|
|
624
|
+
name: 'EOA (Secp256k1)',
|
|
625
|
+
type: 'secp256k1',
|
|
626
|
+
async setup() {
|
|
627
|
+
const address = await config.storage?.getItem('secp256k1.activeAddress')
|
|
628
|
+
const privateKey = await config.storage?.getItem(
|
|
629
|
+
`secp256k1.${address}.privateKey`,
|
|
630
|
+
)
|
|
631
|
+
if (privateKey) account = privateKeyToAccount(privateKey)
|
|
632
|
+
else if (
|
|
633
|
+
address &&
|
|
634
|
+
options.account &&
|
|
635
|
+
Address.isEqual(address, options.account.address)
|
|
636
|
+
)
|
|
637
|
+
account = options.account
|
|
638
|
+
},
|
|
639
|
+
async connect(parameters = {}) {
|
|
640
|
+
const address = await (async () => {
|
|
641
|
+
if (
|
|
642
|
+
'capabilities' in parameters &&
|
|
643
|
+
parameters.capabilities?.type === 'sign-up'
|
|
644
|
+
) {
|
|
645
|
+
const privateKey = generatePrivateKey()
|
|
646
|
+
const account = privateKeyToAccount(privateKey)
|
|
647
|
+
const address = account.address
|
|
648
|
+
await config.storage?.setItem(
|
|
649
|
+
`secp256k1.${address}.privateKey`,
|
|
650
|
+
privateKey,
|
|
651
|
+
)
|
|
652
|
+
await config.storage?.setItem('secp256k1.activeAddress', address)
|
|
653
|
+
await config.storage?.setItem('secp256k1.lastActiveAddress', address)
|
|
654
|
+
return address
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const address = await config.storage?.getItem(
|
|
658
|
+
'secp256k1.lastActiveAddress',
|
|
659
|
+
)
|
|
660
|
+
const privateKey = await config.storage?.getItem(
|
|
661
|
+
`secp256k1.${address}.privateKey`,
|
|
662
|
+
)
|
|
663
|
+
|
|
664
|
+
if (privateKey) account = privateKeyToAccount(privateKey)
|
|
665
|
+
else if (options.account) {
|
|
666
|
+
account = options.account
|
|
667
|
+
await config.storage?.setItem(
|
|
668
|
+
'secp256k1.lastActiveAddress',
|
|
669
|
+
account.address,
|
|
670
|
+
)
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
if (!account) throw new Error('account not found.')
|
|
674
|
+
|
|
675
|
+
await config.storage?.setItem(
|
|
676
|
+
'secp256k1.activeAddress',
|
|
677
|
+
account.address,
|
|
678
|
+
)
|
|
679
|
+
return account.address
|
|
680
|
+
})()
|
|
681
|
+
|
|
682
|
+
const chainId = parameters.chainId ?? config.chains[0]?.id
|
|
683
|
+
if (!chainId) throw new ChainNotConfiguredError()
|
|
684
|
+
|
|
685
|
+
return {
|
|
686
|
+
accounts: (parameters.withCapabilities
|
|
687
|
+
? [{ address }]
|
|
688
|
+
: [address]) as never,
|
|
689
|
+
chainId,
|
|
690
|
+
}
|
|
691
|
+
},
|
|
692
|
+
async disconnect() {
|
|
693
|
+
await config.storage?.removeItem('secp256k1.activeAddress')
|
|
694
|
+
account = undefined
|
|
695
|
+
},
|
|
696
|
+
async getAccounts() {
|
|
697
|
+
if (!account) return []
|
|
698
|
+
return [getAddress(account.address)]
|
|
699
|
+
},
|
|
700
|
+
async getChainId() {
|
|
701
|
+
return config.chains[0]?.id!
|
|
702
|
+
},
|
|
703
|
+
async isAuthorized() {
|
|
704
|
+
try {
|
|
705
|
+
const accounts = await this.getAccounts()
|
|
706
|
+
return !!accounts.length
|
|
707
|
+
} catch (error) {
|
|
708
|
+
// biome-ignore lint/suspicious/noConsole: notify
|
|
709
|
+
console.error(
|
|
710
|
+
'Connector.secp256k1: Failed to check authorization',
|
|
711
|
+
error,
|
|
712
|
+
)
|
|
713
|
+
return false
|
|
714
|
+
}
|
|
715
|
+
},
|
|
716
|
+
async switchChain({ chainId }) {
|
|
717
|
+
const chain = config.chains.find((chain) => chain.id === chainId)
|
|
718
|
+
if (!chain) throw new SwitchChainError(new ChainNotConfiguredError())
|
|
719
|
+
return chain
|
|
720
|
+
},
|
|
721
|
+
onAccountsChanged() {},
|
|
722
|
+
onChainChanged(chain) {
|
|
723
|
+
const chainId = Number(chain)
|
|
724
|
+
config.emitter.emit('change', { chainId })
|
|
725
|
+
},
|
|
726
|
+
async onDisconnect() {
|
|
727
|
+
config.emitter.emit('disconnect')
|
|
728
|
+
account = undefined
|
|
729
|
+
},
|
|
730
|
+
async getClient({ chainId } = {}) {
|
|
731
|
+
const chain =
|
|
732
|
+
config.chains.find((x) => x.id === chainId) ?? config.chains[0]
|
|
733
|
+
if (!chain) throw new ChainNotConfiguredError()
|
|
734
|
+
|
|
735
|
+
const transports = config.transports
|
|
736
|
+
if (!transports) throw new ChainNotConfiguredError()
|
|
737
|
+
|
|
738
|
+
const transport = transports[chain.id]
|
|
739
|
+
if (!transport) throw new ChainNotConfiguredError()
|
|
740
|
+
|
|
741
|
+
if (!account) throw new Error('account not found.')
|
|
742
|
+
|
|
743
|
+
return createClient({
|
|
744
|
+
account,
|
|
745
|
+
chain,
|
|
746
|
+
transport: walletNamespaceCompat(transport, {
|
|
747
|
+
account,
|
|
748
|
+
}),
|
|
749
|
+
})
|
|
750
|
+
},
|
|
751
|
+
async getProvider({ chainId } = {}) {
|
|
752
|
+
const { request } = await this.getClient!({ chainId })
|
|
753
|
+
return { request }
|
|
754
|
+
},
|
|
755
|
+
}))
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
export declare namespace dangerous_secp256k1 {
|
|
759
|
+
export type Parameters = {
|
|
760
|
+
account?: LocalAccount | undefined
|
|
761
|
+
}
|
|
762
|
+
}
|