@0xsequence/wallet-wdk 3.0.0-beta.9 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/.turbo/turbo-lint.log +4 -0
  3. package/.turbo/turbo-typecheck.log +4 -0
  4. package/CHANGELOG.md +170 -0
  5. package/dist/dbs/auth-commitments.d.ts.map +1 -1
  6. package/dist/dbs/auth-keys.d.ts +3 -1
  7. package/dist/dbs/auth-keys.d.ts.map +1 -1
  8. package/dist/dbs/auth-keys.js +16 -4
  9. package/dist/dbs/messages.d.ts.map +1 -1
  10. package/dist/dbs/passkey-credentials.d.ts.map +1 -1
  11. package/dist/dbs/recovery.d.ts.map +1 -1
  12. package/dist/dbs/signatures.d.ts.map +1 -1
  13. package/dist/dbs/transactions.d.ts.map +1 -1
  14. package/dist/dbs/wallets.d.ts.map +1 -1
  15. package/dist/env.d.ts +22 -0
  16. package/dist/env.d.ts.map +1 -0
  17. package/dist/env.js +30 -0
  18. package/dist/identity/signer.d.ts +5 -4
  19. package/dist/identity/signer.d.ts.map +1 -1
  20. package/dist/identity/signer.js +11 -4
  21. package/dist/index.d.ts +1 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +1 -0
  24. package/dist/sequence/cron.d.ts +3 -0
  25. package/dist/sequence/cron.d.ts.map +1 -1
  26. package/dist/sequence/cron.js +72 -39
  27. package/dist/sequence/handlers/authcode-pkce.d.ts +2 -1
  28. package/dist/sequence/handlers/authcode-pkce.d.ts.map +1 -1
  29. package/dist/sequence/handlers/authcode-pkce.js +4 -4
  30. package/dist/sequence/handlers/authcode.d.ts +4 -1
  31. package/dist/sequence/handlers/authcode.d.ts.map +1 -1
  32. package/dist/sequence/handlers/authcode.js +23 -6
  33. package/dist/sequence/handlers/devices.d.ts +1 -1
  34. package/dist/sequence/handlers/devices.d.ts.map +1 -1
  35. package/dist/sequence/handlers/devices.js +1 -1
  36. package/dist/sequence/handlers/guard.d.ts +1 -1
  37. package/dist/sequence/handlers/guard.d.ts.map +1 -1
  38. package/dist/sequence/handlers/guard.js +22 -19
  39. package/dist/sequence/handlers/identity.d.ts +3 -1
  40. package/dist/sequence/handlers/identity.d.ts.map +1 -1
  41. package/dist/sequence/handlers/identity.js +14 -7
  42. package/dist/sequence/handlers/mnemonic.d.ts.map +1 -1
  43. package/dist/sequence/handlers/mnemonic.js +21 -18
  44. package/dist/sequence/handlers/otp.d.ts +2 -1
  45. package/dist/sequence/handlers/otp.d.ts.map +1 -1
  46. package/dist/sequence/handlers/otp.js +4 -3
  47. package/dist/sequence/handlers/passkeys.d.ts +6 -4
  48. package/dist/sequence/handlers/passkeys.d.ts.map +1 -1
  49. package/dist/sequence/handlers/passkeys.js +8 -5
  50. package/dist/sequence/handlers/recovery.js +1 -1
  51. package/dist/sequence/index.d.ts +2 -0
  52. package/dist/sequence/index.d.ts.map +1 -1
  53. package/dist/sequence/index.js +1 -0
  54. package/dist/sequence/manager.d.ts +67 -55
  55. package/dist/sequence/manager.d.ts.map +1 -1
  56. package/dist/sequence/manager.js +77 -17
  57. package/dist/sequence/messages.js +1 -1
  58. package/dist/sequence/passkeys-provider.d.ts +24 -0
  59. package/dist/sequence/passkeys-provider.d.ts.map +1 -0
  60. package/dist/sequence/passkeys-provider.js +15 -0
  61. package/dist/sequence/recovery.d.ts +2 -0
  62. package/dist/sequence/recovery.d.ts.map +1 -1
  63. package/dist/sequence/recovery.js +100 -34
  64. package/dist/sequence/signers.d.ts.map +1 -1
  65. package/dist/sequence/signers.js +3 -1
  66. package/dist/sequence/transactions.d.ts.map +1 -1
  67. package/dist/sequence/transactions.js +5 -2
  68. package/dist/sequence/wallets.d.ts +2 -1
  69. package/dist/sequence/wallets.d.ts.map +1 -1
  70. package/dist/sequence/wallets.js +32 -22
  71. package/eslint.config.js +12 -0
  72. package/package.json +16 -14
  73. package/src/dbs/auth-commitments.ts +1 -1
  74. package/src/dbs/auth-keys.ts +20 -6
  75. package/src/dbs/messages.ts +1 -1
  76. package/src/dbs/passkey-credentials.ts +1 -1
  77. package/src/dbs/recovery.ts +1 -1
  78. package/src/dbs/signatures.ts +1 -1
  79. package/src/dbs/transactions.ts +1 -1
  80. package/src/dbs/wallets.ts +1 -1
  81. package/src/env.ts +58 -0
  82. package/src/identity/signer.ts +13 -7
  83. package/src/index.ts +1 -0
  84. package/src/sequence/cron.ts +75 -42
  85. package/src/sequence/handlers/authcode-pkce.ts +6 -4
  86. package/src/sequence/handlers/authcode.ts +26 -5
  87. package/src/sequence/handlers/devices.ts +1 -1
  88. package/src/sequence/handlers/guard.ts +6 -4
  89. package/src/sequence/handlers/identity.ts +18 -8
  90. package/src/sequence/handlers/mnemonic.ts +5 -3
  91. package/src/sequence/handlers/otp.ts +5 -3
  92. package/src/sequence/handlers/passkeys.ts +13 -13
  93. package/src/sequence/handlers/recovery.ts +1 -1
  94. package/src/sequence/index.ts +2 -0
  95. package/src/sequence/manager.ts +168 -14
  96. package/src/sequence/messages.ts +1 -1
  97. package/src/sequence/passkeys-provider.ts +55 -0
  98. package/src/sequence/recovery.ts +165 -56
  99. package/src/sequence/signers.ts +3 -1
  100. package/src/sequence/transactions.ts +6 -2
  101. package/src/sequence/wallets.ts +39 -25
  102. package/test/authcode-pkce.test.ts +2 -3
  103. package/test/authcode.test.ts +6 -8
  104. package/test/constants.ts +4 -2
  105. package/test/guard.test.ts +5 -5
  106. package/test/identity-signer.test.ts +1 -1
  107. package/test/otp.test.ts +1 -1
  108. package/test/passkeys.test.ts +1 -1
  109. package/test/recovery.test.ts +3 -3
  110. package/test/sessions.test.ts +1 -1
  111. package/test/{test-ssr-safety.mjs → test-ssr-safety.js} +143 -137
  112. package/test/transactions.test.ts +3 -3
  113. package/test/wallets.test.ts +5 -5
@@ -5,6 +5,7 @@ import { createAttestationVerifyingFetch } from '@0xsequence/tee-verifier'
5
5
  import { Config, Constants, Context, Extensions, Network } from '@0xsequence/wallet-primitives'
6
6
  import { Address } from 'ox'
7
7
  import * as Db from '../dbs/index.js'
8
+ import { resolveWdkEnv, type WdkEnv } from '../env.js'
8
9
  import { Cron } from './cron.js'
9
10
  import { Devices } from './devices.js'
10
11
  import { Guards, GuardRole } from './guards.js'
@@ -31,6 +32,7 @@ import { GuardHandler, PromptCodeHandler } from './handlers/guard.js'
31
32
  import { PasskeyCredential } from '../dbs/index.js'
32
33
  import { PromptMnemonicHandler } from './handlers/mnemonic.js'
33
34
  import { PromptOtpHandler } from './handlers/otp.js'
35
+ import { defaultPasskeyProvider, type PasskeyProvider } from './passkeys-provider.js'
34
36
 
35
37
  export type ManagerOptions = {
36
38
  verbose?: boolean
@@ -52,6 +54,9 @@ export type ManagerOptions = {
52
54
 
53
55
  dbPruningInterval?: number
54
56
 
57
+ env?: WdkEnv
58
+ passkeyProvider?: PasskeyProvider
59
+
55
60
  stateProvider?: State.Provider
56
61
  networks?: Network.Network[]
57
62
  relayers?: Relayer.Relayer[] | (() => Relayer.Relayer[])
@@ -70,7 +75,7 @@ export type ManagerOptions = {
70
75
 
71
76
  identity?: {
72
77
  url?: string
73
- fetch?: typeof window.fetch
78
+ fetch?: typeof fetch
74
79
  verifyAttestation?: boolean
75
80
  expectedPcr0?: string[]
76
81
  scope?: string
@@ -95,6 +100,72 @@ export type ManagerOptions = {
95
100
  }
96
101
  }
97
102
 
103
+ export type ResolvedIdentityOptions = {
104
+ url: string
105
+ fetch?: typeof fetch
106
+ verifyAttestation: boolean
107
+ expectedPcr0?: string[]
108
+ scope?: string
109
+ email: {
110
+ enabled: boolean
111
+ }
112
+ google: {
113
+ enabled: boolean
114
+ clientId: string
115
+ }
116
+ apple: {
117
+ enabled: boolean
118
+ clientId: string
119
+ }
120
+ customProviders?: {
121
+ kind: `custom-${string}`
122
+ authMethod: 'id-token' | 'authcode' | 'authcode-pkce'
123
+ issuer: string
124
+ oauthUrl: string
125
+ clientId: string
126
+ }[]
127
+ }
128
+
129
+ export type ResolvedManagerOptions = {
130
+ verbose: boolean
131
+
132
+ extensions: Extensions.Extensions
133
+ context: Context.Context
134
+ context4337: Context.Context
135
+ guest: Address.Address
136
+
137
+ encryptedPksDb: CoreSigners.Pk.Encrypted.EncryptedPksDb
138
+ managerDb: Db.Wallets
139
+ transactionsDb: Db.Transactions
140
+ signaturesDb: Db.Signatures
141
+ messagesDb: Db.Messages
142
+ authCommitmentsDb: Db.AuthCommitments
143
+ authKeysDb: Db.AuthKeys
144
+ recoveryDb: Db.Recovery
145
+ passkeyCredentialsDb: Db.PasskeyCredentials
146
+
147
+ dbPruningInterval: number
148
+
149
+ env: WdkEnv
150
+ passkeyProvider: PasskeyProvider
151
+
152
+ stateProvider: State.Provider
153
+ networks: Network.Network[]
154
+ relayers: Relayer.Relayer[] | (() => Relayer.Relayer[])
155
+ bundlers: Bundler.Bundler[]
156
+ guardUrl: string
157
+ guardAddresses: Record<GuardRole, Address.Address>
158
+
159
+ nonWitnessableSigners: Address.Address[]
160
+
161
+ defaultGuardTopology: Config.Topology
162
+ defaultRecoverySettings: RecoverySettings
163
+
164
+ multiInjectedProviderDiscovery: boolean
165
+
166
+ identity: ResolvedIdentityOptions
167
+ }
168
+
98
169
  export const ManagerOptionsDefaults = {
99
170
  verbose: false,
100
171
 
@@ -115,7 +186,9 @@ export const ManagerOptionsDefaults = {
115
186
 
116
187
  dbPruningInterval: 1000 * 60 * 60 * 24, // 24 hours
117
188
 
118
- stateProvider: new State.Sequence.Provider(),
189
+ passkeyProvider: defaultPasskeyProvider,
190
+
191
+ stateProvider: typeof fetch !== 'undefined' ? new State.Sequence.Provider(undefined, fetch) : undefined,
119
192
  networks: Network.ALL,
120
193
  relayers: () => {
121
194
  if (typeof window !== 'undefined') {
@@ -160,6 +233,7 @@ export const ManagerOptionsDefaults = {
160
233
  defaultRecoverySettings: {
161
234
  requiredDeltaTime: 2592000n, // 30 days (in seconds)
162
235
  minTimestamp: 0n,
236
+ includeTestnets: false,
163
237
  },
164
238
 
165
239
  multiInjectedProviderDiscovery: true,
@@ -186,13 +260,45 @@ export const CreateWalletOptionsDefaults = {
186
260
  useGuard: false,
187
261
  }
188
262
 
189
- export function applyManagerOptionsDefaults(options?: ManagerOptions) {
190
- const merged = {
191
- ...ManagerOptionsDefaults,
192
- ...options,
193
- identity: { ...ManagerOptionsDefaults.identity, ...options?.identity },
263
+ export function applyManagerOptionsDefaults(options?: ManagerOptions): ResolvedManagerOptions {
264
+ const env = resolveWdkEnv(options?.env)
265
+
266
+ const identity: ResolvedIdentityOptions = {
267
+ ...ManagerOptionsDefaults.identity,
268
+ ...options?.identity,
269
+ email: { ...ManagerOptionsDefaults.identity.email, ...options?.identity?.email },
270
+ google: { ...ManagerOptionsDefaults.identity.google, ...options?.identity?.google },
271
+ apple: { ...ManagerOptionsDefaults.identity.apple, ...options?.identity?.apple },
272
+ }
273
+
274
+ if (!identity.fetch && env.fetch) {
275
+ identity.fetch = env.fetch
276
+ }
277
+
278
+ let encryptedPksDb = options?.encryptedPksDb ?? ManagerOptionsDefaults.encryptedPksDb
279
+ if (!options?.encryptedPksDb && options?.env) {
280
+ encryptedPksDb = new CoreSigners.Pk.Encrypted.EncryptedPksDb(undefined, undefined, env)
281
+ }
282
+
283
+ let authKeysDb = options?.authKeysDb ?? ManagerOptionsDefaults.authKeysDb
284
+ if (!options?.authKeysDb && options?.env) {
285
+ authKeysDb = new Db.AuthKeys(undefined, env)
194
286
  }
195
287
 
288
+ let stateProvider = options?.stateProvider ?? ManagerOptionsDefaults.stateProvider
289
+ if (!options?.stateProvider && options?.env?.fetch) {
290
+ stateProvider = new State.Sequence.Provider(undefined, options.env.fetch)
291
+ } else if (!stateProvider && env.fetch) {
292
+ stateProvider = new State.Sequence.Provider(undefined, env.fetch)
293
+ }
294
+
295
+ if (!stateProvider) {
296
+ throw new Error('stateProvider is required. Provide ManagerOptions.stateProvider or env.fetch')
297
+ }
298
+
299
+ const extensions = options?.extensions ?? ManagerOptionsDefaults.extensions
300
+ const defaultGuardTopology = options?.defaultGuardTopology ?? ManagerOptionsDefaults.defaultGuardTopology
301
+
196
302
  // Merge and normalize non-witnessable signers.
197
303
  // We always include the sessions extension address for the active extensions set.
198
304
  const nonWitnessable = new Set<string>()
@@ -202,12 +308,12 @@ export function applyManagerOptionsDefaults(options?: ManagerOptions) {
202
308
  for (const address of options?.nonWitnessableSigners ?? []) {
203
309
  nonWitnessable.add(address.toLowerCase())
204
310
  }
205
- nonWitnessable.add(merged.extensions.sessions.toLowerCase())
311
+ nonWitnessable.add(extensions.sessions.toLowerCase())
206
312
 
207
313
  // Include static signer leaves from the guard topology (e.g. recovery guard signer),
208
314
  // but ignore the placeholder address that is later replaced per-role.
209
- if (merged.defaultGuardTopology) {
210
- const guardTopologySigners = Config.getSigners(merged.defaultGuardTopology)
315
+ if (defaultGuardTopology) {
316
+ const guardTopologySigners = Config.getSigners(defaultGuardTopology)
211
317
  for (const signer of guardTopologySigners.signers) {
212
318
  if (Address.isEqual(signer, Constants.PlaceholderAddress)) {
213
319
  continue
@@ -219,14 +325,52 @@ export function applyManagerOptionsDefaults(options?: ManagerOptions) {
219
325
  }
220
326
  }
221
327
 
222
- merged.nonWitnessableSigners = Array.from(nonWitnessable) as Address.Address[]
328
+ return {
329
+ verbose: options?.verbose ?? ManagerOptionsDefaults.verbose,
223
330
 
224
- return merged
331
+ extensions,
332
+ context: options?.context ?? ManagerOptionsDefaults.context,
333
+ context4337: options?.context4337 ?? ManagerOptionsDefaults.context4337,
334
+ guest: options?.guest ?? ManagerOptionsDefaults.guest,
335
+
336
+ encryptedPksDb,
337
+ managerDb: options?.managerDb ?? ManagerOptionsDefaults.managerDb,
338
+ transactionsDb: options?.transactionsDb ?? ManagerOptionsDefaults.transactionsDb,
339
+ signaturesDb: options?.signaturesDb ?? ManagerOptionsDefaults.signaturesDb,
340
+ messagesDb: options?.messagesDb ?? ManagerOptionsDefaults.messagesDb,
341
+ authCommitmentsDb: options?.authCommitmentsDb ?? ManagerOptionsDefaults.authCommitmentsDb,
342
+ recoveryDb: options?.recoveryDb ?? ManagerOptionsDefaults.recoveryDb,
343
+ authKeysDb,
344
+ passkeyCredentialsDb: options?.passkeyCredentialsDb ?? ManagerOptionsDefaults.passkeyCredentialsDb,
345
+
346
+ dbPruningInterval: options?.dbPruningInterval ?? ManagerOptionsDefaults.dbPruningInterval,
347
+
348
+ env,
349
+ passkeyProvider: options?.passkeyProvider ?? ManagerOptionsDefaults.passkeyProvider,
350
+
351
+ stateProvider,
352
+ networks: options?.networks ?? ManagerOptionsDefaults.networks,
353
+ relayers: options?.relayers ?? ManagerOptionsDefaults.relayers,
354
+ bundlers: options?.bundlers ?? ManagerOptionsDefaults.bundlers,
355
+ guardUrl: options?.guardUrl ?? ManagerOptionsDefaults.guardUrl,
356
+ guardAddresses: options?.guardAddresses ?? ManagerOptionsDefaults.guardAddresses,
357
+
358
+ nonWitnessableSigners: Array.from(nonWitnessable) as Address.Address[],
359
+
360
+ defaultGuardTopology,
361
+ defaultRecoverySettings: options?.defaultRecoverySettings ?? ManagerOptionsDefaults.defaultRecoverySettings,
362
+
363
+ multiInjectedProviderDiscovery:
364
+ options?.multiInjectedProviderDiscovery ?? ManagerOptionsDefaults.multiInjectedProviderDiscovery,
365
+
366
+ identity,
367
+ }
225
368
  }
226
369
 
227
370
  export type RecoverySettings = {
228
371
  requiredDeltaTime: bigint
229
372
  minTimestamp: bigint
373
+ includeTestnets?: boolean
230
374
  }
231
375
 
232
376
  export type Databases = {
@@ -283,6 +427,8 @@ export type Shared = {
283
427
 
284
428
  readonly sequence: Sequence
285
429
  readonly databases: Databases
430
+ readonly env: WdkEnv
431
+ readonly passkeyProvider: PasskeyProvider
286
432
 
287
433
  readonly handlers: Map<string, Handler>
288
434
 
@@ -415,7 +561,7 @@ export class Manager {
415
561
  const ops = applyManagerOptionsDefaults(options)
416
562
 
417
563
  // Build relayers list
418
- let relayers: Relayer.Relayer[] = []
564
+ const relayers: Relayer.Relayer[] = []
419
565
 
420
566
  // Add EIP-6963 relayers if enabled
421
567
  if (ops.multiInjectedProviderDiscovery) {
@@ -469,6 +615,9 @@ export class Manager {
469
615
  pruningInterval: ops.dbPruningInterval,
470
616
  },
471
617
 
618
+ env: ops.env,
619
+ passkeyProvider: ops.passkeyProvider,
620
+
472
621
  modules: {} as any,
473
622
  handlers: new Map(),
474
623
  }
@@ -501,6 +650,7 @@ export class Manager {
501
650
  modules.signatures,
502
651
  shared.sequence.extensions,
503
652
  shared.sequence.stateProvider,
653
+ shared.passkeyProvider,
504
654
  )
505
655
  shared.handlers.set(Kinds.LoginPasskey, this.passkeysHandler)
506
656
 
@@ -523,7 +673,7 @@ export class Manager {
523
673
  const identityInstrument = new IdentityInstrument(ops.identity.url, ops.identity.scope, verifyingFetch)
524
674
 
525
675
  if (ops.identity.email?.enabled) {
526
- this.otpHandler = new OtpHandler(identityInstrument, modules.signatures, shared.databases.authKeys)
676
+ this.otpHandler = new OtpHandler(identityInstrument, modules.signatures, shared.databases.authKeys, shared.env)
527
677
  shared.handlers.set(Kinds.LoginEmailOtp, this.otpHandler)
528
678
  }
529
679
  if (ops.identity.google?.enabled) {
@@ -538,6 +688,7 @@ export class Manager {
538
688
  modules.signatures,
539
689
  shared.databases.authCommitments,
540
690
  shared.databases.authKeys,
691
+ shared.env,
541
692
  ),
542
693
  )
543
694
  }
@@ -553,6 +704,7 @@ export class Manager {
553
704
  modules.signatures,
554
705
  shared.databases.authCommitments,
555
706
  shared.databases.authKeys,
707
+ shared.env,
556
708
  ),
557
709
  )
558
710
  }
@@ -573,6 +725,7 @@ export class Manager {
573
725
  modules.signatures,
574
726
  shared.databases.authCommitments,
575
727
  shared.databases.authKeys,
728
+ shared.env,
576
729
  ),
577
730
  )
578
731
  break
@@ -588,6 +741,7 @@ export class Manager {
588
741
  modules.signatures,
589
742
  shared.databases.authCommitments,
590
743
  shared.databases.authKeys,
744
+ shared.env,
591
745
  ),
592
746
  )
593
747
  break
@@ -242,7 +242,7 @@ export class Messages implements MessagesInterface {
242
242
  const message = await this.getByMessageOrSignatureId(messageOrSignatureId)
243
243
  await this.shared.databases.signatures.del(message.signatureId)
244
244
  await this.shared.databases.messages.del(message.id)
245
- } catch (error) {
245
+ } catch {
246
246
  // Ignore
247
247
  }
248
248
  }
@@ -0,0 +1,55 @@
1
+ import { Signers, State } from '@0xsequence/wallet-core'
2
+ import type { Extensions } from '@0xsequence/wallet-primitives'
3
+ import type { Address, Hex } from 'ox'
4
+
5
+ export type PasskeySigner = Signers.SapientSigner &
6
+ Signers.Witnessable & {
7
+ credentialId: string
8
+ publicKey: Extensions.Passkeys.PublicKey
9
+ imageHash: Hex.Hex
10
+ }
11
+
12
+ export type PasskeyProvider = {
13
+ create: (
14
+ extensions: Pick<Extensions.Extensions, 'passkeys'>,
15
+ options?: Signers.Passkey.CreatePasskeyOptions,
16
+ ) => Promise<PasskeySigner>
17
+ find: (
18
+ stateReader: State.Reader,
19
+ extensions: Pick<Extensions.Extensions, 'passkeys'>,
20
+ options?: Signers.Passkey.FindPasskeyOptions,
21
+ ) => Promise<PasskeySigner | undefined>
22
+ loadFromWitness: (
23
+ stateReader: State.Reader,
24
+ extensions: Pick<Extensions.Extensions, 'passkeys'>,
25
+ wallet: Address.Address,
26
+ imageHash: Hex.Hex,
27
+ options?: Signers.Passkey.FindPasskeyOptions,
28
+ ) => Promise<PasskeySigner | undefined>
29
+ fromCredential: (args: {
30
+ credentialId: string
31
+ publicKey: Extensions.Passkeys.PublicKey
32
+ extensions: Pick<Extensions.Extensions, 'passkeys'>
33
+ embedMetadata?: boolean
34
+ metadata?: Extensions.Passkeys.PasskeyMetadata
35
+ webauthn?: Signers.Passkey.WebAuthnLike
36
+ }) => PasskeySigner
37
+ isSigner?: (signer: unknown) => signer is PasskeySigner
38
+ }
39
+
40
+ export const defaultPasskeyProvider: PasskeyProvider = {
41
+ create: (extensions, options) => Signers.Passkey.Passkey.create(extensions, options),
42
+ find: (stateReader, extensions, options) => Signers.Passkey.Passkey.find(stateReader, extensions, options),
43
+ loadFromWitness: (stateReader, extensions, wallet, imageHash, options) =>
44
+ Signers.Passkey.Passkey.loadFromWitness(stateReader, extensions, wallet, imageHash, options),
45
+ fromCredential: ({ credentialId, publicKey, extensions, embedMetadata, metadata, webauthn }) =>
46
+ new Signers.Passkey.Passkey({
47
+ credentialId,
48
+ publicKey,
49
+ extensions,
50
+ embedMetadata,
51
+ metadata,
52
+ webauthn,
53
+ }),
54
+ isSigner: (signer: unknown): signer is PasskeySigner => signer instanceof Signers.Passkey.Passkey,
55
+ }
@@ -1,12 +1,16 @@
1
1
  import { Envelope } from '@0xsequence/wallet-core'
2
2
  import { Config, Constants, Extensions, GenericTree, Payload } from '@0xsequence/wallet-primitives'
3
- import { Address, Hex, Provider, RpcTransport } from 'ox'
3
+ import { Abi, AbiFunction, Address, Hex, Provider, RpcTransport } from 'ox'
4
4
  import { MnemonicHandler } from './handlers/mnemonic.js'
5
5
  import { Shared } from './manager.js'
6
6
  import { Actions, Module } from './types/index.js'
7
7
  import { QueuedRecoveryPayload } from './types/recovery.js'
8
8
  import { Kinds, RecoverySigner } from './types/signer.js'
9
9
 
10
+ const AGGREGATE3 = Abi.from([
11
+ 'function aggregate3((address target, bool allowFailure, bytes callData)[] calls) external payable returns ((bool success, bytes returnData)[])',
12
+ ])[0]!
13
+
10
14
  export interface RecoveryInterface {
11
15
  /**
12
16
  * Retrieves the list of configured recovery signers for a given wallet.
@@ -284,7 +288,7 @@ export class Recovery implements RecoveryInterface {
284
288
  }
285
289
 
286
290
  await this.updateRecoveryModule(modules, (leaves) => {
287
- const next = leaves.filter((l) => l.signer !== address)
291
+ const next = leaves.filter((l) => !Address.isEqual(l.signer, address))
288
292
  if (next.length === 0) {
289
293
  return [
290
294
  {
@@ -514,9 +518,16 @@ export class Recovery implements RecoveryInterface {
514
518
  async fetchQueuedPayloads(wallet: Address.Address, chainId?: number): Promise<QueuedRecoveryPayload[]> {
515
519
  // Create providers for each network
516
520
  const providers = this.shared.sequence.networks
517
- .filter((network) => (chainId ? network.chainId === chainId : true))
521
+ .filter((network) =>
522
+ chainId
523
+ ? network.chainId === chainId
524
+ : !this.shared.sequence.defaultRecoverySettings.includeTestnets
525
+ ? network.type !== 'testnet'
526
+ : true,
527
+ )
518
528
  .map((network) => ({
519
529
  chainId: network.chainId,
530
+ multicall3Address: network.contracts?.multicall3,
520
531
  provider: Provider.from(RpcTransport.fromHttp(network.rpcUrl)),
521
532
  }))
522
533
 
@@ -526,69 +537,167 @@ export class Recovery implements RecoveryInterface {
526
537
  return []
527
538
  }
528
539
 
540
+ const recoveryExtension = this.shared.sequence.extensions.recovery
529
541
  const payloads: QueuedRecoveryPayload[] = []
530
542
 
531
- for (const signer of signers) {
532
- for (const { chainId, provider } of providers) {
533
- const totalPayloads = await Extensions.Recovery.totalQueuedPayloads(
534
- provider,
535
- this.shared.sequence.extensions.recovery,
536
- wallet,
537
- signer.address,
538
- )
539
-
540
- for (let i = 0n; i < totalPayloads; i++) {
541
- const payloadHash = await Extensions.Recovery.queuedPayloadHashOf(
542
- provider,
543
- this.shared.sequence.extensions.recovery,
544
- wallet,
545
- signer.address,
546
- i,
547
- )
548
-
549
- const timestamp = await Extensions.Recovery.timestampForQueuedPayload(
550
- provider,
551
- this.shared.sequence.extensions.recovery,
552
- wallet,
553
- signer.address,
554
- payloadHash,
555
- )
556
-
557
- const payload = await this.shared.sequence.stateProvider.getPayload(payloadHash)
558
-
559
- // If ready, we need to check if it was executed already
560
- // for this, we check if the wallet nonce for the given space
561
- // is greater than the nonce in the payload
562
- if (timestamp < Date.now() / 1000 && payload && Payload.isCalls(payload.payload)) {
563
- const nonce = await this.shared.modules.wallets.getNonce(chainId, wallet, payload.payload.space)
564
- if (nonce > i) {
565
- continue
543
+ await Promise.all(
544
+ providers.map(async ({ chainId, provider, multicall3Address }) => {
545
+ try {
546
+ let totalPayloadsBySigner: bigint[]
547
+
548
+ if (multicall3Address) {
549
+ try {
550
+ // Batch all totalQueuedPayloads calls for every signer into a single Multicall3 request.
551
+ // This reduces N signer calls per network down to 1 call per network.
552
+ totalPayloadsBySigner = await this.fetchTotalQueuedPayloadsBatched(
553
+ provider,
554
+ recoveryExtension,
555
+ wallet,
556
+ signers,
557
+ multicall3Address,
558
+ )
559
+ } catch (err) {
560
+ console.error(
561
+ `Recovery.fetchQueuedPayloads multicall3 failed for chainId ${chainId}, retrying with individual calls:`,
562
+ err,
563
+ )
564
+ totalPayloadsBySigner = await this.fetchTotalQueuedPayloadsFallback(
565
+ provider,
566
+ recoveryExtension,
567
+ wallet,
568
+ signers,
569
+ )
566
570
  }
571
+ } else {
572
+ totalPayloadsBySigner = await this.fetchTotalQueuedPayloadsFallback(
573
+ provider,
574
+ recoveryExtension,
575
+ wallet,
576
+ signers,
577
+ )
567
578
  }
568
579
 
569
- // The id is the index + signer address + chainId + wallet address
570
- const id = `${i}-${signer.address}-${chainId}-${wallet}`
571
-
572
- // Create a new payload
573
- const payloadEntry: QueuedRecoveryPayload = {
574
- id,
575
- index: i,
576
- recoveryModule: this.shared.sequence.extensions.recovery,
577
- wallet: wallet,
578
- signer: signer.address,
579
- chainId,
580
- startTimestamp: timestamp,
581
- endTimestamp: timestamp + signer.requiredDeltaTime,
582
- payloadHash,
583
- payload: payload?.payload,
580
+ for (let s = 0; s < signers.length; s++) {
581
+ const signer = signers[s]!
582
+ const totalPayloads = totalPayloadsBySigner[s]!
583
+ if (totalPayloads === 0n) continue
584
+
585
+ // Only make individual calls for the rare case where payloads actually exist
586
+ for (let i = 0n; i < totalPayloads; i++) {
587
+ const payloadHash = await Extensions.Recovery.queuedPayloadHashOf(
588
+ provider,
589
+ recoveryExtension,
590
+ wallet,
591
+ signer.address,
592
+ i,
593
+ )
594
+
595
+ const timestamp = await Extensions.Recovery.timestampForQueuedPayload(
596
+ provider,
597
+ recoveryExtension,
598
+ wallet,
599
+ signer.address,
600
+ payloadHash,
601
+ )
602
+
603
+ const payload = await this.shared.sequence.stateProvider.getPayload(payloadHash)
604
+
605
+ // If ready, we need to check if it was executed already
606
+ // for this, we check if the wallet nonce for the given space
607
+ // is greater than the nonce in the payload
608
+ if (timestamp < Date.now() / 1000 && payload && Payload.isCalls(payload.payload)) {
609
+ const nonce = await this.shared.modules.wallets.getNonce(chainId, wallet, payload.payload.space)
610
+ if (nonce > i) {
611
+ continue
612
+ }
613
+ }
614
+
615
+ // The id is the index + signer address + chainId + wallet address
616
+ const id = `${i}-${signer.address}-${chainId}-${wallet}`
617
+
618
+ const payloadEntry: QueuedRecoveryPayload = {
619
+ id,
620
+ index: i,
621
+ recoveryModule: recoveryExtension,
622
+ wallet: wallet,
623
+ signer: signer.address,
624
+ chainId,
625
+ startTimestamp: timestamp,
626
+ endTimestamp: timestamp + signer.requiredDeltaTime,
627
+ payloadHash,
628
+ payload: payload?.payload,
629
+ }
630
+
631
+ payloads.push(payloadEntry)
632
+ }
584
633
  }
585
-
586
- payloads.push(payloadEntry)
634
+ } catch (err) {
635
+ console.error(`Recovery.fetchQueuedPayloads error for chainId ${chainId}:`, err)
587
636
  }
637
+ }),
638
+ )
639
+
640
+ return payloads
641
+ }
642
+
643
+ private async fetchTotalQueuedPayloadsBatched(
644
+ provider: Provider.Provider,
645
+ recoveryExtension: Address.Address,
646
+ wallet: Address.Address,
647
+ signers: RecoverySigner[],
648
+ multicall3Address: Address.Address,
649
+ ): Promise<bigint[]> {
650
+ const calls = signers.map((signer) => ({
651
+ target: recoveryExtension,
652
+ allowFailure: true,
653
+ callData: AbiFunction.encodeData(Extensions.Recovery.TOTAL_QUEUED_PAYLOADS, [wallet, signer.address]),
654
+ }))
655
+
656
+ const response = await provider.request({
657
+ method: 'eth_call',
658
+ params: [
659
+ {
660
+ to: multicall3Address,
661
+ data: AbiFunction.encodeData(AGGREGATE3, [calls]),
662
+ },
663
+ 'latest',
664
+ ],
665
+ })
666
+
667
+ const results = AbiFunction.decodeResult(AGGREGATE3, response) as readonly {
668
+ success: boolean
669
+ returnData: Hex.Hex
670
+ }[]
671
+
672
+ return results.map((result) => {
673
+ if (!result.success || result.returnData === '0x') {
674
+ return 0n
588
675
  }
676
+ return Hex.toBigInt(result.returnData)
677
+ })
678
+ }
679
+
680
+ private fetchTotalQueuedPayloadsFallback = async (
681
+ provider: Provider.Provider,
682
+ recoveryExtension: Address.Address,
683
+ wallet: Address.Address,
684
+ signers: RecoverySigner[],
685
+ ): Promise<bigint[]> => {
686
+ const result: bigint[] = signers.map(() => 0n)
687
+
688
+ // Fallback to individual calls if the multicall3 call fails
689
+ for (let s = 0; s < signers.length; s++) {
690
+ const signer = signers[s]!
691
+ const totalPayloads = await Extensions.Recovery.totalQueuedPayloads(
692
+ provider,
693
+ recoveryExtension,
694
+ wallet,
695
+ signer.address,
696
+ )
697
+ result[s] = totalPayloads
589
698
  }
590
699
 
591
- return payloads
700
+ return result
592
701
  }
593
702
 
594
703
  async encodeRecoverySignature(imageHash: Hex.Hex, signer: Address.Address) {
@@ -81,7 +81,9 @@ export class Signers {
81
81
  if (isWitnessExtraSignerKind(message)) {
82
82
  return toKnownKind(message.signerKind)
83
83
  }
84
- } catch {}
84
+ } catch {
85
+ // ignore
86
+ }
85
87
 
86
88
  return undefined
87
89
  }
@@ -347,7 +347,11 @@ export class Transactions implements TransactionsInterface {
347
347
  return []
348
348
  }
349
349
 
350
- const feeOptions = await relayer.feeOptions(tx.wallet, tx.envelope.chainId, tx.envelope.payload.calls)
350
+ // Determine the to address for the built transaction
351
+ const walletStatus = await wallet.getStatus(provider)
352
+ const to = walletStatus.isDeployed ? wallet.address : wallet.guest
353
+
354
+ const feeOptions = await relayer.feeOptions(tx.wallet, tx.envelope.chainId, to, tx.envelope.payload.calls)
351
355
 
352
356
  if (feeOptions.options.length === 0) {
353
357
  const { name, icon } = relayer instanceof Relayer.EIP6963.EIP6963Relayer ? relayer.info : {}
@@ -492,7 +496,7 @@ export class Transactions implements TransactionsInterface {
492
496
  let tx: Transaction | undefined
493
497
  try {
494
498
  tx = await this.get(transactionOrSignatureId)
495
- } catch (e) {
499
+ } catch {
496
500
  // If not found, it might be a signature ID
497
501
  const signature = await this.shared.modules.signatures.get(transactionOrSignatureId)
498
502
  if (!signature) {