@0xsequence/wallet-wdk 0.0.0-20250520201059

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 (173) hide show
  1. package/.env.test +3 -0
  2. package/.turbo/turbo-build.log +5 -0
  3. package/CHANGELOG.md +11 -0
  4. package/LICENSE +202 -0
  5. package/dist/dbs/auth-commitments.d.ts +17 -0
  6. package/dist/dbs/auth-commitments.d.ts.map +1 -0
  7. package/dist/dbs/auth-commitments.js +13 -0
  8. package/dist/dbs/auth-keys.d.ts +19 -0
  9. package/dist/dbs/auth-keys.d.ts.map +1 -0
  10. package/dist/dbs/auth-keys.js +67 -0
  11. package/dist/dbs/generic.d.ts +33 -0
  12. package/dist/dbs/generic.d.ts.map +1 -0
  13. package/dist/dbs/generic.js +170 -0
  14. package/dist/dbs/index.d.ts +12 -0
  15. package/dist/dbs/index.d.ts.map +1 -0
  16. package/dist/dbs/index.js +8 -0
  17. package/dist/dbs/messages.d.ts +6 -0
  18. package/dist/dbs/messages.d.ts.map +1 -0
  19. package/dist/dbs/messages.js +13 -0
  20. package/dist/dbs/recovery.d.ts +6 -0
  21. package/dist/dbs/recovery.d.ts.map +1 -0
  22. package/dist/dbs/recovery.js +13 -0
  23. package/dist/dbs/signatures.d.ts +6 -0
  24. package/dist/dbs/signatures.d.ts.map +1 -0
  25. package/dist/dbs/signatures.js +13 -0
  26. package/dist/dbs/transactions.d.ts +6 -0
  27. package/dist/dbs/transactions.d.ts.map +1 -0
  28. package/dist/dbs/transactions.js +13 -0
  29. package/dist/dbs/wallets.d.ts +6 -0
  30. package/dist/dbs/wallets.d.ts.map +1 -0
  31. package/dist/dbs/wallets.js +13 -0
  32. package/dist/identity/signer.d.ts +17 -0
  33. package/dist/identity/signer.d.ts.map +1 -0
  34. package/dist/identity/signer.js +58 -0
  35. package/dist/index.d.ts +3 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +2 -0
  38. package/dist/sequence/cron.d.ts +19 -0
  39. package/dist/sequence/cron.d.ts.map +1 -0
  40. package/dist/sequence/cron.js +118 -0
  41. package/dist/sequence/devices.d.ts +14 -0
  42. package/dist/sequence/devices.d.ts.map +1 -0
  43. package/dist/sequence/devices.js +43 -0
  44. package/dist/sequence/handlers/authcode-pkce.d.ts +14 -0
  45. package/dist/sequence/handlers/authcode-pkce.d.ts.map +1 -0
  46. package/dist/sequence/handlers/authcode-pkce.js +48 -0
  47. package/dist/sequence/handlers/authcode.d.ts +25 -0
  48. package/dist/sequence/handlers/authcode.d.ts.map +1 -0
  49. package/dist/sequence/handlers/authcode.js +91 -0
  50. package/dist/sequence/handlers/devices.d.ts +14 -0
  51. package/dist/sequence/handlers/devices.d.ts.map +1 -0
  52. package/dist/sequence/handlers/devices.js +39 -0
  53. package/dist/sequence/handlers/handler.d.ts +8 -0
  54. package/dist/sequence/handlers/handler.d.ts.map +1 -0
  55. package/dist/sequence/handlers/handler.js +1 -0
  56. package/dist/sequence/handlers/identity.d.ts +21 -0
  57. package/dist/sequence/handlers/identity.d.ts.map +1 -0
  58. package/dist/sequence/handlers/identity.js +86 -0
  59. package/dist/sequence/handlers/index.d.ts +7 -0
  60. package/dist/sequence/handlers/index.d.ts.map +1 -0
  61. package/dist/sequence/handlers/index.js +5 -0
  62. package/dist/sequence/handlers/mnemonic.d.ts +19 -0
  63. package/dist/sequence/handlers/mnemonic.d.ts.map +1 -0
  64. package/dist/sequence/handlers/mnemonic.js +67 -0
  65. package/dist/sequence/handlers/otp.d.ts +20 -0
  66. package/dist/sequence/handlers/otp.d.ts.map +1 -0
  67. package/dist/sequence/handlers/otp.js +83 -0
  68. package/dist/sequence/handlers/passkeys.d.ts +17 -0
  69. package/dist/sequence/handlers/passkeys.d.ts.map +1 -0
  70. package/dist/sequence/handlers/passkeys.js +63 -0
  71. package/dist/sequence/handlers/recovery.d.ts +15 -0
  72. package/dist/sequence/handlers/recovery.d.ts.map +1 -0
  73. package/dist/sequence/handlers/recovery.js +72 -0
  74. package/dist/sequence/index.d.ts +12 -0
  75. package/dist/sequence/index.d.ts.map +1 -0
  76. package/dist/sequence/index.js +9 -0
  77. package/dist/sequence/logger.d.ts +7 -0
  78. package/dist/sequence/logger.d.ts.map +1 -0
  79. package/dist/sequence/logger.js +11 -0
  80. package/dist/sequence/manager.d.ts +287 -0
  81. package/dist/sequence/manager.d.ts.map +1 -0
  82. package/dist/sequence/manager.js +356 -0
  83. package/dist/sequence/messages.d.ts +18 -0
  84. package/dist/sequence/messages.d.ts.map +1 -0
  85. package/dist/sequence/messages.js +115 -0
  86. package/dist/sequence/recovery.d.ts +30 -0
  87. package/dist/sequence/recovery.d.ts.map +1 -0
  88. package/dist/sequence/recovery.js +314 -0
  89. package/dist/sequence/sessions.d.ts +26 -0
  90. package/dist/sequence/sessions.d.ts.map +1 -0
  91. package/dist/sequence/sessions.js +169 -0
  92. package/dist/sequence/signatures.d.ts +21 -0
  93. package/dist/sequence/signatures.d.ts.map +1 -0
  94. package/dist/sequence/signatures.js +192 -0
  95. package/dist/sequence/signers.d.ts +14 -0
  96. package/dist/sequence/signers.d.ts.map +1 -0
  97. package/dist/sequence/signers.js +74 -0
  98. package/dist/sequence/transactions.d.ts +26 -0
  99. package/dist/sequence/transactions.d.ts.map +1 -0
  100. package/dist/sequence/transactions.js +201 -0
  101. package/dist/sequence/types/index.d.ts +9 -0
  102. package/dist/sequence/types/index.d.ts.map +1 -0
  103. package/dist/sequence/types/index.js +2 -0
  104. package/dist/sequence/types/message-request.d.ts +23 -0
  105. package/dist/sequence/types/message-request.d.ts.map +1 -0
  106. package/dist/sequence/types/message-request.js +1 -0
  107. package/dist/sequence/types/recovery.d.ts +15 -0
  108. package/dist/sequence/types/recovery.d.ts.map +1 -0
  109. package/dist/sequence/types/recovery.js +1 -0
  110. package/dist/sequence/types/signature-request.d.ts +76 -0
  111. package/dist/sequence/types/signature-request.d.ts.map +1 -0
  112. package/dist/sequence/types/signature-request.js +11 -0
  113. package/dist/sequence/types/signer.d.ts +28 -0
  114. package/dist/sequence/types/signer.d.ts.map +1 -0
  115. package/dist/sequence/types/signer.js +10 -0
  116. package/dist/sequence/types/transaction-request.d.ts +41 -0
  117. package/dist/sequence/types/transaction-request.d.ts.map +1 -0
  118. package/dist/sequence/types/transaction-request.js +1 -0
  119. package/dist/sequence/types/wallet.d.ts +21 -0
  120. package/dist/sequence/types/wallet.d.ts.map +1 -0
  121. package/dist/sequence/types/wallet.js +1 -0
  122. package/dist/sequence/wallets.d.ts +121 -0
  123. package/dist/sequence/wallets.d.ts.map +1 -0
  124. package/dist/sequence/wallets.js +632 -0
  125. package/package.json +40 -0
  126. package/src/dbs/auth-commitments.ts +26 -0
  127. package/src/dbs/auth-keys.ts +85 -0
  128. package/src/dbs/generic.ts +194 -0
  129. package/src/dbs/index.ts +13 -0
  130. package/src/dbs/messages.ts +16 -0
  131. package/src/dbs/recovery.ts +15 -0
  132. package/src/dbs/signatures.ts +15 -0
  133. package/src/dbs/transactions.ts +16 -0
  134. package/src/dbs/wallets.ts +16 -0
  135. package/src/identity/signer.ts +78 -0
  136. package/src/index.ts +2 -0
  137. package/src/sequence/cron.ts +134 -0
  138. package/src/sequence/devices.ts +53 -0
  139. package/src/sequence/handlers/authcode-pkce.ts +70 -0
  140. package/src/sequence/handlers/authcode.ts +116 -0
  141. package/src/sequence/handlers/devices.ts +53 -0
  142. package/src/sequence/handlers/handler.ts +14 -0
  143. package/src/sequence/handlers/identity.ts +101 -0
  144. package/src/sequence/handlers/index.ts +6 -0
  145. package/src/sequence/handlers/mnemonic.ts +88 -0
  146. package/src/sequence/handlers/otp.ts +107 -0
  147. package/src/sequence/handlers/passkeys.ts +84 -0
  148. package/src/sequence/handlers/recovery.ts +88 -0
  149. package/src/sequence/index.ts +25 -0
  150. package/src/sequence/logger.ts +11 -0
  151. package/src/sequence/manager.ts +634 -0
  152. package/src/sequence/messages.ts +146 -0
  153. package/src/sequence/recovery.ts +429 -0
  154. package/src/sequence/sessions.ts +238 -0
  155. package/src/sequence/signatures.ts +263 -0
  156. package/src/sequence/signers.ts +88 -0
  157. package/src/sequence/transactions.ts +281 -0
  158. package/src/sequence/types/index.ts +27 -0
  159. package/src/sequence/types/message-request.ts +26 -0
  160. package/src/sequence/types/recovery.ts +15 -0
  161. package/src/sequence/types/signature-request.ts +89 -0
  162. package/src/sequence/types/signer.ts +32 -0
  163. package/src/sequence/types/transaction-request.ts +47 -0
  164. package/src/sequence/types/wallet.ts +24 -0
  165. package/src/sequence/wallets.ts +853 -0
  166. package/test/constants.ts +62 -0
  167. package/test/recovery.test.ts +211 -0
  168. package/test/sessions.test.ts +324 -0
  169. package/test/setup.ts +63 -0
  170. package/test/transactions.test.ts +464 -0
  171. package/test/wallets.test.ts +381 -0
  172. package/tsconfig.json +10 -0
  173. package/vitest.config.ts +11 -0
@@ -0,0 +1,853 @@
1
+ import { Wallet as CoreWallet, Envelope, Signers, State } from '@0xsequence/wallet-core'
2
+ import { Config, GenericTree, Payload, SessionConfig } from '@0xsequence/wallet-primitives'
3
+ import { Address, Hex, Provider, RpcTransport } from 'ox'
4
+ import { AuthCommitment } from '../dbs/auth-commitments.js'
5
+ import { MnemonicHandler } from './handlers/mnemonic.js'
6
+ import { OtpHandler } from './handlers/otp.js'
7
+ import { Shared } from './manager.js'
8
+ import { Action } from './types/index.js'
9
+ import { Kinds, WitnessExtraSignerKind } from './types/signer.js'
10
+ import { Wallet, WalletSelectionUiHandler } from './types/wallet.js'
11
+ import { AuthCodeHandler } from './handlers/authcode.js'
12
+
13
+ export type StartSignUpWithRedirectArgs = {
14
+ kind: 'google-pkce' | 'apple'
15
+ target: string
16
+ metadata: { [key: string]: string }
17
+ }
18
+
19
+ export type CommonSignupArgs = {
20
+ noGuard?: boolean
21
+ noSessionManager?: boolean
22
+ noRecovery?: boolean
23
+ }
24
+
25
+ export type PasskeySignupArgs = CommonSignupArgs & {
26
+ kind: 'passkey'
27
+ }
28
+
29
+ export type MnemonicSignupArgs = CommonSignupArgs & {
30
+ kind: 'mnemonic'
31
+ mnemonic: string
32
+ }
33
+
34
+ export type EmailOtpSignupArgs = CommonSignupArgs & {
35
+ kind: 'email-otp'
36
+ email: string
37
+ }
38
+
39
+ export type CompleteRedirectArgs = CommonSignupArgs & {
40
+ state: string
41
+ code: string
42
+ }
43
+
44
+ export type AuthCodeSignupArgs = CommonSignupArgs & {
45
+ kind: 'google-pkce' | 'apple'
46
+ commitment: AuthCommitment
47
+ code: string
48
+ target: string
49
+ isRedirect: boolean
50
+ }
51
+
52
+ export type SignupArgs = PasskeySignupArgs | MnemonicSignupArgs | EmailOtpSignupArgs | AuthCodeSignupArgs
53
+
54
+ export type LoginToWalletArgs = {
55
+ wallet: Address.Address
56
+ }
57
+
58
+ export type LoginToMnemonicArgs = {
59
+ kind: 'mnemonic'
60
+ mnemonic: string
61
+ selectWallet: (wallets: Address.Address[]) => Promise<Address.Address>
62
+ }
63
+
64
+ export type LoginToPasskeyArgs = {
65
+ kind: 'passkey'
66
+ selectWallet: (wallets: Address.Address[]) => Promise<Address.Address>
67
+ }
68
+
69
+ export type LoginArgs = LoginToWalletArgs | LoginToMnemonicArgs | LoginToPasskeyArgs
70
+
71
+ export function isLoginToWalletArgs(args: LoginArgs): args is LoginToWalletArgs {
72
+ return 'wallet' in args
73
+ }
74
+
75
+ export function isLoginToMnemonicArgs(args: LoginArgs): args is LoginToMnemonicArgs {
76
+ return 'kind' in args && args.kind === 'mnemonic'
77
+ }
78
+
79
+ export function isLoginToPasskeyArgs(args: LoginArgs): args is LoginToPasskeyArgs {
80
+ return 'kind' in args && args.kind === 'passkey'
81
+ }
82
+
83
+ export function isAuthCodeArgs(args: SignupArgs): args is AuthCodeSignupArgs {
84
+ return 'kind' in args && (args.kind === 'google-pkce' || args.kind === 'apple')
85
+ }
86
+
87
+ function buildCappedTree(members: { address: Address.Address; imageHash?: Hex.Hex }[]): Config.Topology {
88
+ const loginMemberWeight = 1n
89
+
90
+ if (members.length === 0) {
91
+ // We need to maintain the general structure of the tree, so we can't have an empty node here
92
+ // instead, we add a dummy signer with weight 0
93
+ return {
94
+ type: 'signer',
95
+ address: '0x0000000000000000000000000000000000000000',
96
+ weight: 0n,
97
+ } as Config.SignerLeaf
98
+ }
99
+
100
+ if (members.length === 1) {
101
+ if (members[0]!.imageHash) {
102
+ return {
103
+ type: 'sapient-signer',
104
+ address: members[0]!.address,
105
+ imageHash: members[0]!.imageHash,
106
+ weight: loginMemberWeight,
107
+ } as Config.SapientSignerLeaf
108
+ } else {
109
+ return {
110
+ type: 'signer',
111
+ address: members[0]!.address,
112
+ weight: loginMemberWeight,
113
+ } as Config.SignerLeaf
114
+ }
115
+ }
116
+
117
+ return {
118
+ type: 'nested',
119
+ weight: loginMemberWeight,
120
+ threshold: 1n,
121
+ tree: Config.flatLeavesToTopology(
122
+ members.map((member) =>
123
+ member.imageHash
124
+ ? {
125
+ type: 'sapient-signer',
126
+ address: member.address,
127
+ imageHash: member.imageHash,
128
+ weight: 1n,
129
+ }
130
+ : {
131
+ type: 'signer',
132
+ address: member.address,
133
+ weight: 1n,
134
+ },
135
+ ),
136
+ ),
137
+ } as Config.NestedLeaf
138
+ }
139
+
140
+ function buildCappedTreeFromTopology(weight: bigint, topology: Config.Topology): Config.Topology {
141
+ // We may optimize this for some topology types
142
+ // but it is not worth it, because the topology
143
+ // that we will use for prod won't be optimizable
144
+ return {
145
+ type: 'nested',
146
+ weight: weight,
147
+ threshold: weight,
148
+ tree: topology,
149
+ }
150
+ }
151
+
152
+ function toConfig(
153
+ checkpoint: bigint,
154
+ loginTopology: Config.Topology,
155
+ devicesTopology: Config.Topology,
156
+ modules: Config.SapientSignerLeaf[],
157
+ guardTopology?: Config.Topology,
158
+ ): Config.Config {
159
+ if (!guardTopology) {
160
+ return {
161
+ checkpoint: checkpoint,
162
+ threshold: 1n,
163
+ topology: [[loginTopology, devicesTopology], toModulesTopology(modules)],
164
+ }
165
+ } else {
166
+ return {
167
+ checkpoint: checkpoint,
168
+ threshold: 2n,
169
+ topology: [[[loginTopology, devicesTopology], guardTopology], toModulesTopology(modules)],
170
+ }
171
+ }
172
+ }
173
+
174
+ function toModulesTopology(modules: Config.SapientSignerLeaf[]): Config.Topology {
175
+ // We always include a modules topology, even if there are no modules
176
+ // in that case we just add a signer with address 0 and no weight
177
+ if (modules.length === 0) {
178
+ return {
179
+ type: 'signer',
180
+ address: '0x0000000000000000000000000000000000000000',
181
+ weight: 0n,
182
+ } as Config.SignerLeaf
183
+ }
184
+
185
+ return Config.flatLeavesToTopology(modules)
186
+ }
187
+
188
+ function fromModulesTopology(topology: Config.Topology): Config.SapientSignerLeaf[] {
189
+ let modules: Config.SapientSignerLeaf[] = []
190
+
191
+ if (Config.isNode(topology)) {
192
+ modules = [...fromModulesTopology(topology[0]), ...fromModulesTopology(topology[1])]
193
+ } else if (Config.isSapientSignerLeaf(topology)) {
194
+ modules.push(topology)
195
+ } else if (Config.isSignerLeaf(topology)) {
196
+ // This signals that the wallet has no modules, so we just ignore it
197
+ if (topology.address !== '0x0000000000000000000000000000000000000000') {
198
+ throw new Error('signer-leaf-not-allowed-in-modules-topology')
199
+ }
200
+ } else {
201
+ throw new Error('unknown-modules-topology-format')
202
+ }
203
+
204
+ return modules
205
+ }
206
+
207
+ function fromConfig(config: Config.Config): {
208
+ loginTopology: Config.Topology
209
+ devicesTopology: Config.Topology
210
+ modules: Config.SapientSignerLeaf[]
211
+ guardTopology?: Config.Topology
212
+ } {
213
+ if (config.threshold === 1n) {
214
+ if (Config.isNode(config.topology) && Config.isNode(config.topology[0])) {
215
+ return {
216
+ loginTopology: config.topology[0][0],
217
+ devicesTopology: config.topology[0][1],
218
+ modules: fromModulesTopology(config.topology[1]),
219
+ }
220
+ } else {
221
+ throw new Error('unknown-config-format')
222
+ }
223
+ } else if (config.threshold === 2n) {
224
+ if (Config.isNode(config.topology) && Config.isNode(config.topology[0]) && Config.isNode(config.topology[0][0])) {
225
+ return {
226
+ loginTopology: config.topology[0][0][0],
227
+ devicesTopology: config.topology[0][0][1],
228
+ guardTopology: config.topology[0][1],
229
+ modules: fromModulesTopology(config.topology[1]),
230
+ }
231
+ } else {
232
+ throw new Error('unknown-config-format')
233
+ }
234
+ }
235
+
236
+ throw new Error('unknown-config-format')
237
+ }
238
+
239
+ export class Wallets {
240
+ private walletSelectionUiHandler: WalletSelectionUiHandler | null = null
241
+
242
+ constructor(private readonly shared: Shared) {}
243
+
244
+ public async exists(wallet: Address.Address): Promise<boolean> {
245
+ return this.shared.databases.manager.get(wallet).then((r) => r !== undefined)
246
+ }
247
+
248
+ public async get(walletAddress: Address.Address): Promise<Wallet | undefined> {
249
+ return await this.shared.databases.manager.get(walletAddress)
250
+ }
251
+
252
+ public async list(): Promise<Wallet[]> {
253
+ return this.shared.databases.manager.list()
254
+ }
255
+
256
+ public registerWalletSelector(handler: WalletSelectionUiHandler) {
257
+ if (this.walletSelectionUiHandler) {
258
+ throw new Error('wallet-selector-already-registered')
259
+ }
260
+ this.walletSelectionUiHandler = handler
261
+ return () => {
262
+ this.unregisterWalletSelector(handler)
263
+ }
264
+ }
265
+
266
+ public unregisterWalletSelector(handler?: WalletSelectionUiHandler) {
267
+ if (handler && this.walletSelectionUiHandler !== handler) {
268
+ throw new Error('wallet-selector-not-registered')
269
+ }
270
+ this.walletSelectionUiHandler = null
271
+ }
272
+
273
+ public onWalletsUpdate(cb: (wallets: Wallet[]) => void, trigger?: boolean) {
274
+ const undo = this.shared.databases.manager.addListener(() => {
275
+ this.list().then((wallets) => {
276
+ cb(wallets)
277
+ })
278
+ })
279
+
280
+ if (trigger) {
281
+ this.list().then((wallets) => {
282
+ cb(wallets)
283
+ })
284
+ }
285
+
286
+ return undo
287
+ }
288
+
289
+ private async prepareSignUp(args: SignupArgs): Promise<{
290
+ signer: (Signers.Signer | Signers.SapientSigner) & Signers.Witnessable
291
+ extra: WitnessExtraSignerKind
292
+ }> {
293
+ switch (args.kind) {
294
+ case 'passkey':
295
+ const passkeySigner = await Signers.Passkey.Passkey.create(this.shared.sequence.extensions, {
296
+ stateProvider: this.shared.sequence.stateProvider,
297
+ })
298
+ this.shared.modules.logger.log('Created new passkey signer:', passkeySigner.address)
299
+
300
+ return {
301
+ signer: passkeySigner,
302
+ extra: {
303
+ signerKind: Kinds.LoginPasskey,
304
+ },
305
+ }
306
+
307
+ case 'mnemonic':
308
+ const mnemonicSigner = MnemonicHandler.toSigner(args.mnemonic)
309
+ if (!mnemonicSigner) {
310
+ throw new Error('invalid-mnemonic')
311
+ }
312
+
313
+ this.shared.modules.logger.log('Created new mnemonic signer:', mnemonicSigner.address)
314
+
315
+ return {
316
+ signer: mnemonicSigner,
317
+ extra: {
318
+ signerKind: Kinds.LoginMnemonic,
319
+ },
320
+ }
321
+
322
+ case 'email-otp': {
323
+ const handler = this.shared.handlers.get(Kinds.LoginEmailOtp) as OtpHandler
324
+ if (!handler) {
325
+ throw new Error('email-otp-handler-not-registered')
326
+ }
327
+
328
+ const signer = await handler.getSigner(args.email)
329
+ this.shared.modules.logger.log('Created new email otp signer:', signer.address)
330
+
331
+ return {
332
+ signer,
333
+ extra: {
334
+ signerKind: Kinds.LoginEmailOtp,
335
+ },
336
+ }
337
+ }
338
+
339
+ case 'google-pkce':
340
+ case 'apple': {
341
+ const handler = this.shared.handlers.get('login-' + args.kind) as AuthCodeHandler
342
+ if (!handler) {
343
+ throw new Error('handler-not-registered')
344
+ }
345
+
346
+ const [signer, metadata] = await handler.completeAuth(args.commitment, args.code)
347
+ this.shared.modules.logger.log('Created new auth code pkce signer:', signer.address)
348
+
349
+ return {
350
+ signer,
351
+ extra: {
352
+ signerKind: 'login-' + args.kind,
353
+ },
354
+ }
355
+ }
356
+ }
357
+ }
358
+
359
+ async startSignUpWithRedirect(args: StartSignUpWithRedirectArgs) {
360
+ const handler = this.shared.handlers.get('login-' + args.kind) as AuthCodeHandler
361
+ if (!handler) {
362
+ throw new Error('handler-not-registered')
363
+ }
364
+ return handler.commitAuth(args.target, true)
365
+ }
366
+
367
+ async completeRedirect(args: CompleteRedirectArgs) {
368
+ const commitment = await this.shared.databases.authCommitments.get(args.state)
369
+ if (!commitment) {
370
+ throw new Error('invalid-state')
371
+ }
372
+
373
+ if (commitment.isSignUp) {
374
+ await this.signUp({
375
+ kind: commitment.kind,
376
+ commitment,
377
+ code: args.code,
378
+ noGuard: args.noGuard,
379
+ target: commitment.target,
380
+ isRedirect: true,
381
+ })
382
+ } else {
383
+ const handler = this.shared.handlers.get('login-' + commitment.kind) as AuthCodeHandler
384
+ if (!handler) {
385
+ throw new Error('handler-not-registered')
386
+ }
387
+
388
+ await handler.completeAuth(commitment, args.code)
389
+ }
390
+ return commitment.target
391
+ }
392
+
393
+ async signUp(args: SignupArgs) {
394
+ const loginSigner = await this.prepareSignUp(args)
395
+
396
+ // If there is an existing wallet callback, we check if any wallet already exist for this login signer
397
+ if (this.walletSelectionUiHandler) {
398
+ const existingWallets = await State.getWalletsFor(this.shared.sequence.stateProvider, loginSigner.signer)
399
+ if (existingWallets.length > 0) {
400
+ const result = await this.walletSelectionUiHandler({
401
+ existingWallets: existingWallets.map((w) => w.wallet),
402
+ signerAddress: await loginSigner.signer.address,
403
+ context: isAuthCodeArgs(args)
404
+ ? {
405
+ isRedirect: args.isRedirect,
406
+ target: args.target,
407
+ }
408
+ : {
409
+ isRedirect: false,
410
+ },
411
+ })
412
+
413
+ if (result) {
414
+ // A wallet was selected, we can exit early
415
+ return
416
+ }
417
+ }
418
+ } else {
419
+ console.warn('No wallet selector registered, creating a new wallet')
420
+ }
421
+
422
+ // Create the first session
423
+ const device = await this.shared.modules.devices.create()
424
+
425
+ if (!args.noGuard && !this.shared.sequence.defaultGuardTopology) {
426
+ throw new Error('guard is required for signup')
427
+ }
428
+
429
+ // Build the login tree
430
+ const loginSignerAddress = await loginSigner.signer.address
431
+ const loginTopology = buildCappedTree([
432
+ {
433
+ address: loginSignerAddress,
434
+ imageHash: Signers.isSapientSigner(loginSigner.signer) ? await loginSigner.signer.imageHash : undefined,
435
+ },
436
+ ])
437
+ const devicesTopology = buildCappedTree([{ address: device.address }])
438
+ const guardTopology = args.noGuard
439
+ ? undefined
440
+ : buildCappedTreeFromTopology(1n, this.shared.sequence.defaultGuardTopology)
441
+
442
+ // TODO: Add recovery module
443
+ // TODO: Add smart sessions module
444
+ // Placeholder
445
+ let modules: Config.SapientSignerLeaf[] = []
446
+
447
+ if (!args.noSessionManager) {
448
+ // Calculate image hash with the identity signer
449
+ const sessionsTopology = SessionConfig.emptySessionsTopology(loginSignerAddress)
450
+ // Store this tree in the state provider
451
+ const sessionsConfigTree = SessionConfig.sessionsTopologyToConfigurationTree(sessionsTopology)
452
+ this.shared.sequence.stateProvider.saveTree(sessionsConfigTree)
453
+ // Prepare the configuration leaf
454
+ const sessionsImageHash = GenericTree.hash(sessionsConfigTree)
455
+ modules.push({
456
+ ...this.shared.sequence.defaultSessionsTopology,
457
+ imageHash: sessionsImageHash,
458
+ })
459
+ }
460
+
461
+ if (!args.noRecovery) {
462
+ await this.shared.modules.recovery.initRecoveryModule(modules, device.address)
463
+ }
464
+
465
+ // Create initial configuration
466
+ const initialConfiguration = toConfig(0n, loginTopology, devicesTopology, modules, guardTopology)
467
+ console.log('initialConfiguration', initialConfiguration)
468
+
469
+ // Create wallet
470
+ const wallet = await CoreWallet.fromConfiguration(initialConfiguration, {
471
+ context: this.shared.sequence.context,
472
+ stateProvider: this.shared.sequence.stateProvider,
473
+ guest: this.shared.sequence.guest,
474
+ })
475
+
476
+ this.shared.modules.logger.log('Created new sequence wallet:', wallet.address)
477
+
478
+ // Sign witness using device signer
479
+ await this.shared.modules.devices.witness(device.address, wallet.address)
480
+
481
+ // Sign witness using the passkey signer
482
+ await loginSigner.signer.witness(this.shared.sequence.stateProvider, wallet.address, loginSigner.extra)
483
+
484
+ // Save entry in the manager db
485
+ await this.shared.databases.manager.set({
486
+ address: wallet.address,
487
+ status: 'ready',
488
+ loginDate: new Date().toISOString(),
489
+ device: device.address,
490
+ loginType: loginSigner.extra.signerKind,
491
+ useGuard: !args.noGuard,
492
+ })
493
+
494
+ return wallet.address
495
+ }
496
+
497
+ public async getConfigurationParts(address: Address.Address) {
498
+ const wallet = new CoreWallet(address, {
499
+ context: this.shared.sequence.context,
500
+ stateProvider: this.shared.sequence.stateProvider,
501
+ guest: this.shared.sequence.guest,
502
+ })
503
+
504
+ const status = await wallet.getStatus()
505
+ return fromConfig(status.configuration)
506
+ }
507
+
508
+ public async requestConfigurationUpdate(
509
+ address: Address.Address,
510
+ changes: Partial<ReturnType<typeof fromConfig>>,
511
+ action: Action,
512
+ origin?: string,
513
+ ) {
514
+ const wallet = new CoreWallet(address, {
515
+ context: this.shared.sequence.context,
516
+ stateProvider: this.shared.sequence.stateProvider,
517
+ guest: this.shared.sequence.guest,
518
+ })
519
+
520
+ const status = await wallet.getStatus()
521
+ const { loginTopology, devicesTopology, modules, guardTopology } = fromConfig(status.configuration)
522
+
523
+ const nextLoginTopology = changes.loginTopology ?? loginTopology
524
+ const nextDevicesTopology = changes.devicesTopology ?? devicesTopology
525
+ const nextModules = changes.modules ?? modules
526
+ const nextGuardTopology = changes.guardTopology ?? guardTopology
527
+
528
+ const envelope = await wallet.prepareUpdate(
529
+ toConfig(
530
+ status.configuration.checkpoint + 1n,
531
+ nextLoginTopology,
532
+ nextDevicesTopology,
533
+ nextModules,
534
+ nextGuardTopology,
535
+ ),
536
+ )
537
+
538
+ const requestId = await this.shared.modules.signatures.request(envelope, action, {
539
+ origin,
540
+ })
541
+
542
+ return requestId
543
+ }
544
+
545
+ public async completeConfigurationUpdate(requestId: string) {
546
+ const request = await this.shared.modules.signatures.get(requestId)
547
+ if (!Payload.isConfigUpdate(request.envelope.payload)) {
548
+ throw new Error('invalid-request-payload')
549
+ }
550
+
551
+ if (!Envelope.reachedThreshold(request.envelope)) {
552
+ throw new Error('insufficient-weight')
553
+ }
554
+
555
+ const wallet = new CoreWallet(request.wallet, {
556
+ context: this.shared.sequence.context,
557
+ stateProvider: this.shared.sequence.stateProvider,
558
+ guest: this.shared.sequence.guest,
559
+ })
560
+
561
+ await wallet.submitUpdate(request.envelope as Envelope.Signed<Payload.ConfigUpdate>)
562
+ await this.shared.modules.signatures.complete(requestId)
563
+ }
564
+
565
+ async login(args: LoginArgs): Promise<string | undefined> {
566
+ if (isLoginToWalletArgs(args)) {
567
+ const prevWallet = await this.exists(args.wallet)
568
+ if (prevWallet) {
569
+ throw new Error('wallet-already-logged-in')
570
+ }
571
+
572
+ const device = await this.shared.modules.devices.create()
573
+ const { devicesTopology, modules, guardTopology } = await this.getConfigurationParts(args.wallet)
574
+
575
+ // Witness the wallet
576
+ await this.shared.modules.devices.witness(device.address, args.wallet)
577
+
578
+ // Add device to devices topology
579
+ const prevDevices = Config.getSigners(devicesTopology)
580
+ if (prevDevices.sapientSigners.length > 0) {
581
+ throw new Error('found-sapient-signer-in-devices-topology')
582
+ }
583
+
584
+ if (!prevDevices.isComplete) {
585
+ throw new Error('devices-topology-incomplete')
586
+ }
587
+
588
+ const nextDevicesTopology = buildCappedTree([
589
+ ...prevDevices.signers
590
+ .filter((x) => x !== '0x0000000000000000000000000000000000000000')
591
+ .map((x) => ({ address: x })),
592
+ ...prevDevices.sapientSigners.map((x) => ({ address: x.address, imageHash: x.imageHash })),
593
+ { address: device.address },
594
+ ])
595
+
596
+ if (this.shared.modules.recovery.hasRecoveryModule(modules)) {
597
+ await this.shared.modules.recovery.addRecoverySignerToModules(modules, device.address)
598
+ }
599
+
600
+ await this.shared.databases.manager.set({
601
+ address: args.wallet,
602
+ status: 'logging-in',
603
+ loginDate: new Date().toISOString(),
604
+ device: device.address,
605
+ loginType: 'wallet',
606
+ useGuard: guardTopology !== undefined,
607
+ })
608
+
609
+ return this.requestConfigurationUpdate(
610
+ args.wallet,
611
+ {
612
+ devicesTopology: nextDevicesTopology,
613
+ modules,
614
+ },
615
+ 'login',
616
+ 'wallet-webapp',
617
+ )
618
+ }
619
+
620
+ if (isLoginToMnemonicArgs(args)) {
621
+ const mnemonicSigner = MnemonicHandler.toSigner(args.mnemonic)
622
+ if (!mnemonicSigner) {
623
+ throw new Error('invalid-mnemonic')
624
+ }
625
+
626
+ const wallets = await State.getWalletsFor(this.shared.sequence.stateProvider, mnemonicSigner)
627
+ if (wallets.length === 0) {
628
+ throw new Error('no-wallets-found')
629
+ }
630
+
631
+ const wallet = await args.selectWallet(wallets.map((w) => w.wallet))
632
+ if (!wallets.some((w) => Address.isEqual(w.wallet, wallet))) {
633
+ throw new Error('wallet-not-found')
634
+ }
635
+
636
+ return this.login({ wallet })
637
+ }
638
+
639
+ if (isLoginToPasskeyArgs(args)) {
640
+ const passkeySigner = await Signers.Passkey.Passkey.find(
641
+ this.shared.sequence.stateProvider,
642
+ this.shared.sequence.extensions,
643
+ )
644
+ if (!passkeySigner) {
645
+ throw new Error('no-passkey-found')
646
+ }
647
+
648
+ const wallets = await State.getWalletsFor(this.shared.sequence.stateProvider, passkeySigner)
649
+ if (wallets.length === 0) {
650
+ throw new Error('no-wallets-found')
651
+ }
652
+
653
+ const wallet = await args.selectWallet(wallets.map((w) => w.wallet))
654
+ if (!wallets.some((w) => Address.isEqual(w.wallet, wallet))) {
655
+ throw new Error('wallet-not-found')
656
+ }
657
+
658
+ return this.login({ wallet })
659
+ }
660
+
661
+ throw new Error('invalid-login-args')
662
+ }
663
+
664
+ async completeLogin(requestId: string) {
665
+ const request = await this.shared.modules.signatures.get(requestId)
666
+
667
+ const walletEntry = await this.shared.databases.manager.get(request.wallet)
668
+ if (!walletEntry) {
669
+ throw new Error('login-for-wallet-not-found')
670
+ }
671
+
672
+ await this.completeConfigurationUpdate(requestId)
673
+
674
+ // Save entry in the manager db
675
+ await this.shared.databases.manager.set({
676
+ ...walletEntry,
677
+ status: 'ready',
678
+ loginDate: new Date().toISOString(),
679
+ })
680
+ }
681
+
682
+ async logout<T extends { skipRemoveDevice?: boolean } | undefined = undefined>(
683
+ wallet: Address.Address,
684
+ options?: T,
685
+ ): Promise<T extends { skipRemoveDevice: true } ? undefined : string> {
686
+ const walletEntry = await this.shared.databases.manager.get(wallet)
687
+ if (!walletEntry) {
688
+ throw new Error('wallet-not-found')
689
+ }
690
+
691
+ // Prevent starting logout if already logging out or not ready
692
+ if (walletEntry.status !== 'ready') {
693
+ console.warn(`Logout called on wallet ${wallet} with status ${walletEntry.status}. Aborting.`)
694
+ throw new Error(`Wallet is not in 'ready' state for logout (current: ${walletEntry.status})`)
695
+ }
696
+
697
+ if (options?.skipRemoveDevice) {
698
+ await Promise.all([
699
+ this.shared.databases.manager.del(wallet),
700
+ this.shared.modules.devices.remove(walletEntry.device),
701
+ ])
702
+ return undefined as any
703
+ }
704
+
705
+ const device = await this.shared.modules.devices.get(walletEntry.device)
706
+ if (!device) {
707
+ throw new Error('device-not-found')
708
+ }
709
+
710
+ const { devicesTopology, modules } = await this.getConfigurationParts(wallet)
711
+ const nextDevicesTopology = buildCappedTree([
712
+ ...Config.getSigners(devicesTopology)
713
+ .signers.filter((x) => x !== '0x0000000000000000000000000000000000000000' && x !== device.address)
714
+ .map((x) => ({ address: x })),
715
+ ...Config.getSigners(devicesTopology).sapientSigners,
716
+ ])
717
+
718
+ // Remove device from the recovery topology, if it exists
719
+ if (this.shared.modules.recovery.hasRecoveryModule(modules)) {
720
+ await this.shared.modules.recovery.removeRecoverySignerFromModules(modules, device.address)
721
+ }
722
+
723
+ const requestId = await this.requestConfigurationUpdate(
724
+ wallet,
725
+ {
726
+ devicesTopology: nextDevicesTopology,
727
+ modules,
728
+ },
729
+ 'logout',
730
+ 'wallet-webapp',
731
+ )
732
+
733
+ await this.shared.databases.manager.set({ ...walletEntry, status: 'logging-out' })
734
+
735
+ return requestId as any
736
+ }
737
+
738
+ async completeLogout(requestId: string, options?: { skipValidateSave?: boolean }) {
739
+ const request = await this.shared.modules.signatures.get(requestId)
740
+ const walletEntry = await this.shared.databases.manager.get(request.wallet)
741
+ if (!walletEntry) {
742
+ throw new Error('wallet-not-found')
743
+ }
744
+
745
+ // Wallet entry should ideally be 'logging-out' here, but we proceed regardless
746
+ if (walletEntry.status !== 'logging-out') {
747
+ this.shared.modules.logger.log(
748
+ `Warning: Wallet ${request.wallet} status was ${walletEntry.status} during completeLogout.`,
749
+ )
750
+ }
751
+
752
+ await this.completeConfigurationUpdate(requestId)
753
+ await this.shared.databases.manager.del(request.wallet)
754
+ await this.shared.modules.devices.remove(walletEntry.device)
755
+ }
756
+
757
+ async getConfiguration(wallet: Address.Address) {
758
+ const walletObject = new CoreWallet(wallet, {
759
+ context: this.shared.sequence.context,
760
+ stateProvider: this.shared.sequence.stateProvider,
761
+ guest: this.shared.sequence.guest,
762
+ })
763
+
764
+ const status = await walletObject.getStatus()
765
+ const raw = fromConfig(status.configuration)
766
+
767
+ const deviceSigners = Config.getSigners(raw.devicesTopology)
768
+ const loginSigners = Config.getSigners(raw.loginTopology)
769
+
770
+ return {
771
+ devices: await this.shared.modules.signers.resolveKinds(wallet, [
772
+ ...deviceSigners.signers,
773
+ ...deviceSigners.sapientSigners,
774
+ ]),
775
+ login: await this.shared.modules.signers.resolveKinds(wallet, [
776
+ ...loginSigners.signers,
777
+ ...loginSigners.sapientSigners,
778
+ ]),
779
+ raw,
780
+ }
781
+ }
782
+
783
+ async getNonce(chainId: bigint, address: Address.Address, space: bigint) {
784
+ const wallet = new CoreWallet(address, {
785
+ context: this.shared.sequence.context,
786
+ stateProvider: this.shared.sequence.stateProvider,
787
+ guest: this.shared.sequence.guest,
788
+ })
789
+
790
+ const network = this.shared.sequence.networks.find((n) => n.chainId === chainId)
791
+ if (!network) {
792
+ throw new Error('network-not-found')
793
+ }
794
+
795
+ const provider = Provider.from(RpcTransport.fromHttp(network.rpc))
796
+ return wallet.getNonce(provider, space)
797
+ }
798
+
799
+ async getOnchainConfiguration(wallet: Address.Address, chainId: bigint) {
800
+ const walletObject = new CoreWallet(wallet, {
801
+ context: this.shared.sequence.context,
802
+ stateProvider: this.shared.sequence.stateProvider,
803
+ guest: this.shared.sequence.guest,
804
+ })
805
+
806
+ const network = this.shared.sequence.networks.find((n) => n.chainId === chainId)
807
+ if (!network) {
808
+ throw new Error('network-not-found')
809
+ }
810
+
811
+ const provider = Provider.from(RpcTransport.fromHttp(network.rpc))
812
+ const status = await walletObject.getStatus(provider)
813
+
814
+ const onchainConfiguration = await this.shared.sequence.stateProvider.getConfiguration(status.onChainImageHash)
815
+ if (!onchainConfiguration) {
816
+ throw new Error('onchain-configuration-not-found')
817
+ }
818
+
819
+ const raw = fromConfig(status.configuration)
820
+
821
+ const deviceSigners = Config.getSigners(raw.devicesTopology)
822
+ const loginSigners = Config.getSigners(raw.loginTopology)
823
+
824
+ return {
825
+ devices: await this.shared.modules.signers.resolveKinds(wallet, [
826
+ ...deviceSigners.signers,
827
+ ...deviceSigners.sapientSigners,
828
+ ]),
829
+ login: await this.shared.modules.signers.resolveKinds(wallet, [
830
+ ...loginSigners.signers,
831
+ ...loginSigners.sapientSigners,
832
+ ]),
833
+ raw,
834
+ }
835
+ }
836
+
837
+ async isUpdatedOnchain(wallet: Address.Address, chainId: bigint) {
838
+ const walletObject = new CoreWallet(wallet, {
839
+ context: this.shared.sequence.context,
840
+ stateProvider: this.shared.sequence.stateProvider,
841
+ guest: this.shared.sequence.guest,
842
+ })
843
+
844
+ const network = this.shared.sequence.networks.find((n) => n.chainId === chainId)
845
+ if (!network) {
846
+ throw new Error('network-not-found')
847
+ }
848
+
849
+ const provider = Provider.from(RpcTransport.fromHttp(network.rpc))
850
+ const onchainStatus = await walletObject.getStatus(provider)
851
+ return onchainStatus.imageHash === onchainStatus.onChainImageHash
852
+ }
853
+ }