@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,146 @@
1
+ import { Envelope, Wallet } from '@0xsequence/wallet-core'
2
+ import { Payload } from '@0xsequence/wallet-primitives'
3
+ import { Address, Bytes, Hex, Provider, RpcTransport } from 'ox'
4
+ import { v7 as uuidv7 } from 'uuid'
5
+ import { Shared } from './manager.js'
6
+ import { Message, MessageRequest, MessageRequested, MessageSigned } from './types/message-request.js'
7
+
8
+ export class Messages {
9
+ constructor(private readonly shared: Shared) {}
10
+
11
+ public async list(): Promise<Message[]> {
12
+ return this.shared.databases.messages.list()
13
+ }
14
+
15
+ public async get(messageOrSignatureId: string): Promise<Message> {
16
+ return this.getByMessageOrSignatureId(messageOrSignatureId)
17
+ }
18
+
19
+ private async getByMessageOrSignatureId(messageOrSignatureId: string): Promise<Message> {
20
+ const messages = await this.list()
21
+ const message = messages.find((m) => m.id === messageOrSignatureId || m.signatureId === messageOrSignatureId)
22
+ if (!message) {
23
+ throw new Error(`Message ${messageOrSignatureId} not found`)
24
+ }
25
+ return message
26
+ }
27
+
28
+ async request(
29
+ from: Address.Address,
30
+ message: MessageRequest,
31
+ chainId?: bigint,
32
+ options?: {
33
+ source?: string
34
+ },
35
+ ): Promise<string> {
36
+ const wallet = new Wallet(from, { stateProvider: this.shared.sequence.stateProvider })
37
+
38
+ // Prepare message payload
39
+ const envelope = await wallet.prepareMessageSignature(message, chainId ?? 0n)
40
+
41
+ // Prepare signature request
42
+ const signatureRequest = await this.shared.modules.signatures.request(envelope, 'sign-message', {
43
+ origin: options?.source,
44
+ })
45
+
46
+ const id = uuidv7()
47
+ await this.shared.databases.messages.set({
48
+ id,
49
+ wallet: from,
50
+ message,
51
+ envelope,
52
+ source: options?.source ?? 'unknown',
53
+ status: 'requested',
54
+ signatureId: signatureRequest,
55
+ } as MessageRequested)
56
+
57
+ return signatureRequest
58
+ }
59
+
60
+ async complete(messageOrSignatureId: string): Promise<string> {
61
+ const message = await this.getByMessageOrSignatureId(messageOrSignatureId)
62
+
63
+ if (message.status === 'signed') {
64
+ // Return the message signature
65
+ return message.messageSignature
66
+ }
67
+
68
+ const messageId = message.id
69
+ const signature = await this.shared.modules.signatures.get(message.signatureId)
70
+ if (!signature) {
71
+ throw new Error(`Signature ${message.signatureId} not found for message ${messageId}`)
72
+ }
73
+
74
+ if (!Payload.isMessage(message.envelope.payload) || !Payload.isMessage(signature.envelope.payload)) {
75
+ throw new Error(`Message ${messageId} is not a message payload`)
76
+ }
77
+
78
+ if (!Envelope.isSigned(signature.envelope)) {
79
+ throw new Error(`Message ${messageId} is not signed`)
80
+ }
81
+
82
+ const signatureEnvelope = signature.envelope as Envelope.Signed<Payload.Message>
83
+ const { weight, threshold } = Envelope.weightOf(signatureEnvelope)
84
+ if (weight < threshold) {
85
+ throw new Error(`Message ${messageId} has insufficient weight`)
86
+ }
87
+
88
+ // Get the provider for the message chain
89
+ let provider: Provider.Provider | undefined
90
+ if (message.envelope.chainId !== 0n) {
91
+ const network = this.shared.sequence.networks.find((network) => network.chainId === message.envelope.chainId)
92
+ if (!network) {
93
+ throw new Error(`Network not found for ${message.envelope.chainId}`)
94
+ }
95
+ const transport = RpcTransport.fromHttp(network.rpc)
96
+ provider = Provider.from(transport)
97
+ }
98
+
99
+ const wallet = new Wallet(message.wallet, { stateProvider: this.shared.sequence.stateProvider })
100
+ const messageSignature = Hex.from(await wallet.buildMessageSignature(signatureEnvelope, provider))
101
+
102
+ await this.shared.databases.messages.set({
103
+ ...message,
104
+ envelope: signature.envelope,
105
+ status: 'signed',
106
+ messageSignature,
107
+ } as MessageSigned)
108
+ await this.shared.modules.signatures.complete(signature.id)
109
+
110
+ return messageSignature
111
+ }
112
+
113
+ onMessagesUpdate(cb: (messages: Message[]) => void, trigger?: boolean) {
114
+ const undo = this.shared.databases.messages.addListener(() => {
115
+ this.list().then((l) => cb(l))
116
+ })
117
+
118
+ if (trigger) {
119
+ this.list().then((l) => cb(l))
120
+ }
121
+
122
+ return undo
123
+ }
124
+
125
+ onMessageUpdate(messageId: string, cb: (message: Message) => void, trigger?: boolean) {
126
+ const undo = this.shared.databases.messages.addListener(() => {
127
+ this.get(messageId).then((t) => cb(t))
128
+ })
129
+
130
+ if (trigger) {
131
+ this.get(messageId).then((t) => cb(t))
132
+ }
133
+
134
+ return undo
135
+ }
136
+
137
+ async delete(messageOrSignatureId: string) {
138
+ try {
139
+ const message = await this.getByMessageOrSignatureId(messageOrSignatureId)
140
+ await this.shared.databases.signatures.del(message.signatureId)
141
+ await this.shared.databases.messages.del(message.id)
142
+ } catch (error) {
143
+ // Ignore
144
+ }
145
+ }
146
+ }
@@ -0,0 +1,429 @@
1
+ import { Config, Extensions, GenericTree, Payload } from '@0xsequence/wallet-primitives'
2
+ import { Shared } from './manager.js'
3
+ import { Address, Hex, Provider, RpcTransport } from 'ox'
4
+ import { Kinds, RecoverySigner } from './types/signer.js'
5
+ import { Envelope } from '@0xsequence/wallet-core'
6
+ import { QueuedRecoveryPayload } from './types/recovery.js'
7
+ import { Actions } from './types/index.js'
8
+ import { MnemonicHandler } from './handlers/mnemonic.js'
9
+
10
+ export class Recovery {
11
+ constructor(private readonly shared: Shared) {}
12
+
13
+ initialize() {
14
+ this.shared.modules.cron.registerJob(
15
+ 'update-queued-recovery-payloads',
16
+ 5 * 60 * 1000, // 5 minutes
17
+ async () => {
18
+ this.shared.modules.logger.log('Running job: update-queued-recovery-payloads')
19
+ await this.updateQueuedRecoveryPayloads()
20
+ },
21
+ )
22
+ this.shared.modules.logger.log('Recovery module initialized and job registered.')
23
+ }
24
+
25
+ private async updateRecoveryModule(
26
+ modules: Config.SapientSignerLeaf[],
27
+ transformer: (leaves: Extensions.Recovery.RecoveryLeaf[]) => Extensions.Recovery.RecoveryLeaf[],
28
+ ) {
29
+ const ext = this.shared.sequence.extensions.recovery
30
+ const idx = modules.findIndex((m) => m.address === ext)
31
+ if (idx === -1) {
32
+ return
33
+ }
34
+
35
+ const sapientSigner = modules[idx]
36
+ if (!sapientSigner) {
37
+ throw new Error('recovery-module-not-found')
38
+ }
39
+
40
+ const genericTree = await this.shared.sequence.stateProvider.getTree(sapientSigner.imageHash)
41
+ if (!genericTree) {
42
+ throw new Error('recovery-module-tree-not-found')
43
+ }
44
+
45
+ const tree = Extensions.Recovery.fromGenericTree(genericTree)
46
+ const { leaves, isComplete } = Extensions.Recovery.getRecoveryLeaves(tree)
47
+ if (!isComplete) {
48
+ throw new Error('recovery-module-tree-incomplete')
49
+ }
50
+
51
+ const nextTree = Extensions.Recovery.fromRecoveryLeaves(transformer(leaves))
52
+ const nextGeneric = Extensions.Recovery.toGenericTree(nextTree)
53
+ await this.shared.sequence.stateProvider.saveTree(nextGeneric)
54
+ if (!modules[idx]) {
55
+ throw new Error('recovery-module-not-found-(unreachable)')
56
+ }
57
+
58
+ modules[idx].imageHash = GenericTree.hash(nextGeneric)
59
+ }
60
+
61
+ public async initRecoveryModule(modules: Config.SapientSignerLeaf[], address: Address.Address) {
62
+ if (this.hasRecoveryModule(modules)) {
63
+ throw new Error('recovery-module-already-initialized')
64
+ }
65
+
66
+ const recoveryTree = Extensions.Recovery.fromRecoveryLeaves([
67
+ {
68
+ type: 'leaf' as const,
69
+ signer: address,
70
+ requiredDeltaTime: this.shared.sequence.defaultRecoverySettings.requiredDeltaTime,
71
+ minTimestamp: this.shared.sequence.defaultRecoverySettings.minTimestamp,
72
+ },
73
+ ])
74
+
75
+ const recoveryGenericTree = Extensions.Recovery.toGenericTree(recoveryTree)
76
+ await this.shared.sequence.stateProvider.saveTree(recoveryGenericTree)
77
+
78
+ const recoveryImageHash = GenericTree.hash(recoveryGenericTree)
79
+
80
+ modules.push({
81
+ type: 'sapient-signer',
82
+ address: this.shared.sequence.extensions.recovery,
83
+ weight: 255n,
84
+ imageHash: recoveryImageHash,
85
+ } as Config.SapientSignerLeaf)
86
+ }
87
+
88
+ hasRecoveryModule(modules: Config.SapientSignerLeaf[]): boolean {
89
+ return modules.some((m) => Address.isEqual(m.address, this.shared.sequence.extensions.recovery))
90
+ }
91
+
92
+ async addRecoverySignerToModules(modules: Config.SapientSignerLeaf[], address: Address.Address) {
93
+ if (!this.hasRecoveryModule(modules)) {
94
+ throw new Error('recovery-module-not-enabled')
95
+ }
96
+
97
+ await this.updateRecoveryModule(modules, (leaves) => {
98
+ if (leaves.some((l) => Address.isEqual(l.signer, address))) {
99
+ return leaves
100
+ }
101
+
102
+ const filtered = leaves.filter((l) => !Address.isEqual(l.signer, '0x0000000000000000000000000000000000000000'))
103
+
104
+ return [
105
+ ...filtered,
106
+ {
107
+ type: 'leaf',
108
+ signer: address,
109
+ requiredDeltaTime: this.shared.sequence.defaultRecoverySettings.requiredDeltaTime,
110
+ minTimestamp: this.shared.sequence.defaultRecoverySettings.minTimestamp,
111
+ },
112
+ ]
113
+ })
114
+ }
115
+
116
+ async removeRecoverySignerFromModules(modules: Config.SapientSignerLeaf[], address: Address.Address) {
117
+ if (!this.hasRecoveryModule(modules)) {
118
+ throw new Error('recovery-module-not-enabled')
119
+ }
120
+
121
+ await this.updateRecoveryModule(modules, (leaves) => {
122
+ const next = leaves.filter((l) => l.signer !== address)
123
+ if (next.length === 0) {
124
+ return [
125
+ {
126
+ type: 'leaf',
127
+ signer: '0x0000000000000000000000000000000000000000',
128
+ requiredDeltaTime: 0n,
129
+ minTimestamp: 0n,
130
+ },
131
+ ]
132
+ }
133
+
134
+ return next
135
+ })
136
+ }
137
+
138
+ async addRecoveryMnemonic(wallet: Address.Address, mnemonic: string) {
139
+ const signer = MnemonicHandler.toSigner(mnemonic)
140
+ if (!signer) {
141
+ throw new Error('invalid-mnemonic')
142
+ }
143
+
144
+ await signer.witness(this.shared.sequence.stateProvider, wallet, {
145
+ isForRecovery: true,
146
+ signerKind: Kinds.LoginMnemonic,
147
+ })
148
+
149
+ return this.addRecoverySigner(wallet, signer.address)
150
+ }
151
+
152
+ async addRecoverySigner(wallet: Address.Address, address: Address.Address) {
153
+ const { modules } = await this.shared.modules.wallets.getConfigurationParts(wallet)
154
+ await this.addRecoverySignerToModules(modules, address)
155
+ return this.shared.modules.wallets.requestConfigurationUpdate(
156
+ wallet,
157
+ {
158
+ modules,
159
+ },
160
+ Actions.AddRecoverySigner,
161
+ 'wallet-webapp',
162
+ )
163
+ }
164
+
165
+ async removeRecoverySigner(wallet: Address.Address, address: Address.Address) {
166
+ const { modules } = await this.shared.modules.wallets.getConfigurationParts(wallet)
167
+ await this.removeRecoverySignerFromModules(modules, address)
168
+ return this.shared.modules.wallets.requestConfigurationUpdate(
169
+ wallet,
170
+ { modules },
171
+ Actions.RemoveRecoverySigner,
172
+ 'wallet-webapp',
173
+ )
174
+ }
175
+
176
+ async completeRecoveryUpdate(requestId: string) {
177
+ const request = await this.shared.modules.signatures.get(requestId)
178
+ if (request.action !== 'add-recovery-signer' && request.action !== 'remove-recovery-signer') {
179
+ throw new Error('invalid-recovery-update-action')
180
+ }
181
+
182
+ return this.shared.modules.wallets.completeConfigurationUpdate(requestId)
183
+ }
184
+
185
+ async getRecoverySigners(address: Address.Address): Promise<RecoverySigner[] | undefined> {
186
+ const { raw } = await this.shared.modules.wallets.getConfiguration(address)
187
+ const recoveryLeaf = raw.modules.find((m) => Address.isEqual(m.address, this.shared.sequence.extensions.recovery))
188
+ if (!recoveryLeaf) {
189
+ return undefined
190
+ }
191
+
192
+ const recoveryGenericTree = await this.shared.sequence.stateProvider.getTree(recoveryLeaf.imageHash)
193
+ if (!recoveryGenericTree) {
194
+ throw new Error('recovery-module-tree-not-found')
195
+ }
196
+
197
+ const recoveryTree = Extensions.Recovery.fromGenericTree(recoveryGenericTree)
198
+ const { leaves, isComplete } = Extensions.Recovery.getRecoveryLeaves(recoveryTree)
199
+ if (!isComplete) {
200
+ throw new Error('recovery-module-tree-incomplete')
201
+ }
202
+
203
+ const kos = await this.shared.modules.signers.resolveKinds(
204
+ address,
205
+ leaves.map((l) => l.signer),
206
+ )
207
+
208
+ return leaves
209
+ .filter((l) => !Address.isEqual(l.signer, '0x0000000000000000000000000000000000000000'))
210
+ .map((l) => ({
211
+ address: l.signer,
212
+ kind: kos.find((s) => Address.isEqual(s.address, l.signer))?.kind || 'unknown',
213
+ isRecovery: true,
214
+ minTimestamp: l.minTimestamp,
215
+ requiredDeltaTime: l.requiredDeltaTime,
216
+ }))
217
+ }
218
+
219
+ async queueRecoveryPayload(wallet: Address.Address, chainId: bigint, payload: Payload.Calls) {
220
+ const signers = await this.getRecoverySigners(wallet)
221
+ if (!signers) {
222
+ throw new Error('recovery-signers-not-found')
223
+ }
224
+
225
+ const recoveryPayload = Payload.toRecovery(payload)
226
+ const simulatedTopology = Config.flatLeavesToTopology(
227
+ signers.map((s) => ({
228
+ type: 'signer',
229
+ address: s.address,
230
+ weight: 1n,
231
+ })),
232
+ )
233
+
234
+ // Save both versions of the payload in parallel
235
+ await Promise.all([
236
+ this.shared.sequence.stateProvider.savePayload(wallet, payload, chainId),
237
+ this.shared.sequence.stateProvider.savePayload(wallet, recoveryPayload, chainId),
238
+ ])
239
+
240
+ const requestId = await this.shared.modules.signatures.request(
241
+ {
242
+ wallet,
243
+ chainId,
244
+ configuration: {
245
+ threshold: 1n,
246
+ checkpoint: 0n,
247
+ topology: simulatedTopology,
248
+ },
249
+ payload: recoveryPayload,
250
+ },
251
+ 'recovery',
252
+ )
253
+
254
+ return requestId
255
+ }
256
+
257
+ // TODO: Handle this transaction instead of just returning the to and data
258
+ async completeRecoveryPayload(requestId: string): Promise<{ to: Address.Address; data: Hex.Hex }> {
259
+ const signature = await this.shared.modules.signatures.get(requestId)
260
+ if (signature.action !== 'recovery' || !Payload.isRecovery(signature.envelope.payload)) {
261
+ throw new Error('invalid-recovery-payload')
262
+ }
263
+
264
+ if (!Envelope.isSigned(signature.envelope)) {
265
+ throw new Error('recovery-payload-not-signed')
266
+ }
267
+
268
+ const { weight, threshold } = Envelope.weightOf(signature.envelope)
269
+ if (weight < threshold) {
270
+ throw new Error('recovery-payload-insufficient-weight')
271
+ }
272
+
273
+ // Find any valid signature
274
+ const validSignature = signature.envelope.signatures[0]
275
+ if (Envelope.isSapientSignature(validSignature)) {
276
+ throw new Error('recovery-payload-sapient-signatures-not-supported')
277
+ }
278
+
279
+ if (!validSignature) {
280
+ throw new Error('recovery-payload-no-valid-signature')
281
+ }
282
+
283
+ const calldata = Extensions.Recovery.encodeCalldata(
284
+ signature.wallet,
285
+ signature.envelope.payload,
286
+ validSignature.address,
287
+ validSignature.signature,
288
+ )
289
+
290
+ return {
291
+ to: this.shared.sequence.extensions.recovery,
292
+ data: calldata,
293
+ }
294
+ }
295
+
296
+ async getQueuedRecoveryPayloads(wallet?: Address.Address): Promise<QueuedRecoveryPayload[]> {
297
+ const all = await this.shared.databases.recovery.list()
298
+ if (wallet) {
299
+ return all.filter((p) => Address.isEqual(p.wallet, wallet))
300
+ }
301
+
302
+ return all
303
+ }
304
+
305
+ onQueuedRecoveryPayloadsUpdate(
306
+ wallet: Address.Address | undefined,
307
+ cb: (payloads: QueuedRecoveryPayload[]) => void,
308
+ trigger?: boolean,
309
+ ) {
310
+ if (trigger) {
311
+ this.getQueuedRecoveryPayloads(wallet).then(cb)
312
+ }
313
+
314
+ return this.shared.databases.recovery.addListener(() => {
315
+ this.getQueuedRecoveryPayloads(wallet).then(cb)
316
+ })
317
+ }
318
+
319
+ async updateQueuedRecoveryPayloads(): Promise<void> {
320
+ const wallets = await this.shared.modules.wallets.list()
321
+ if (wallets.length === 0) {
322
+ return
323
+ }
324
+
325
+ // Create providers for each network
326
+ const providers = this.shared.sequence.networks.map((network) => ({
327
+ chainId: network.chainId,
328
+ provider: Provider.from(RpcTransport.fromHttp(network.rpc)),
329
+ }))
330
+
331
+ const seenInThisRun = new Set<string>()
332
+
333
+ for (const wallet of wallets) {
334
+ // See if they have any recover signers
335
+ const signers = await this.getRecoverySigners(wallet.address)
336
+ if (!signers || signers.length === 0) {
337
+ continue
338
+ }
339
+
340
+ // Now we need to fetch, for each signer and network, any queued recovery payloads
341
+ // TODO: This may benefit from multicall, but it is not urgent, as this happens in the background
342
+ for (const signer of signers) {
343
+ for (const { chainId, provider } of providers) {
344
+ const totalPayloads = await Extensions.Recovery.totalQueuedPayloads(
345
+ provider,
346
+ this.shared.sequence.extensions.recovery,
347
+ wallet.address,
348
+ signer.address,
349
+ )
350
+
351
+ for (let i = 0n; i < totalPayloads; i++) {
352
+ const payloadHash = await Extensions.Recovery.queuedPayloadHashOf(
353
+ provider,
354
+ this.shared.sequence.extensions.recovery,
355
+ wallet.address,
356
+ signer.address,
357
+ i,
358
+ )
359
+
360
+ const timestamp = await Extensions.Recovery.timestampForQueuedPayload(
361
+ provider,
362
+ this.shared.sequence.extensions.recovery,
363
+ wallet.address,
364
+ signer.address,
365
+ payloadHash,
366
+ )
367
+
368
+ const payload = await this.shared.sequence.stateProvider.getPayload(payloadHash)
369
+
370
+ // If ready, we need to check if it was executed already
371
+ // for this, we check if the wallet 77nonce for the given space
372
+ // is greater than the nonce in the payload
373
+ if (timestamp < Date.now() / 1000 && payload && Payload.isCalls(payload.payload)) {
374
+ const nonce = await this.shared.modules.wallets.getNonce(chainId, wallet.address, payload.payload.space)
375
+ if (nonce > i) {
376
+ continue
377
+ }
378
+ }
379
+
380
+ // The id is the index + signer address + chainId + wallet address
381
+ const id = `${i}-${signer.address}-${chainId}-${wallet.address}`
382
+
383
+ // Create a new payload
384
+ const payloadEntry: QueuedRecoveryPayload = {
385
+ id,
386
+ index: i,
387
+ recoveryModule: this.shared.sequence.extensions.recovery,
388
+ wallet: wallet.address,
389
+ signer: signer.address,
390
+ chainId,
391
+ startTimestamp: timestamp,
392
+ endTimestamp: timestamp + signer.requiredDeltaTime,
393
+ payloadHash,
394
+ payload: payload?.payload,
395
+ }
396
+
397
+ await this.shared.databases.recovery.set(payloadEntry)
398
+ seenInThisRun.add(payloadEntry.id)
399
+ }
400
+ }
401
+ }
402
+
403
+ // Delete any unseen queued payloads as they are no longer relevant
404
+ const allQueuedPayloads = await this.shared.databases.recovery.list()
405
+ for (const payload of allQueuedPayloads) {
406
+ if (!seenInThisRun.has(payload.id)) {
407
+ await this.shared.databases.recovery.del(payload.id)
408
+ }
409
+ }
410
+ }
411
+ }
412
+
413
+ async encodeRecoverySignature(imageHash: Hex.Hex, signer: Address.Address) {
414
+ const genericTree = await this.shared.sequence.stateProvider.getTree(imageHash)
415
+ if (!genericTree) {
416
+ throw new Error('recovery-module-tree-not-found')
417
+ }
418
+
419
+ const tree = Extensions.Recovery.fromGenericTree(genericTree)
420
+ const allSigners = Extensions.Recovery.getRecoveryLeaves(tree).leaves.map((l) => l.signer)
421
+
422
+ if (!allSigners.includes(signer)) {
423
+ throw new Error('signer-not-found-in-recovery-module')
424
+ }
425
+
426
+ const trimmed = Extensions.Recovery.trimTopology(tree, signer)
427
+ return Extensions.Recovery.encodeTopology(trimmed)
428
+ }
429
+ }