@openzeppelin/miden-multisig-client 0.17.0-rc.2 → 0.17.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 (278) hide show
  1. package/README.md +172 -47
  2. package/dist/account/builder.d.ts +2 -2
  3. package/dist/account/builder.d.ts.map +1 -1
  4. package/dist/account/builder.js +49 -24
  5. package/dist/account/builder.js.map +1 -1
  6. package/dist/connectivity.d.ts +2 -0
  7. package/dist/connectivity.d.ts.map +1 -1
  8. package/dist/connectivity.js +7 -4
  9. package/dist/connectivity.js.map +1 -1
  10. package/dist/index.d.ts +8 -1
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +7 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/multisig/authArgErrors.d.ts +59 -0
  15. package/dist/multisig/authArgErrors.d.ts.map +1 -0
  16. package/dist/multisig/authArgErrors.js +115 -0
  17. package/dist/multisig/authArgErrors.js.map +1 -0
  18. package/dist/multisig/helpers.d.ts +5 -0
  19. package/dist/multisig/helpers.d.ts.map +1 -1
  20. package/dist/multisig/helpers.js +7 -2
  21. package/dist/multisig/helpers.js.map +1 -1
  22. package/dist/multisig.d.ts +140 -0
  23. package/dist/multisig.d.ts.map +1 -1
  24. package/dist/multisig.js +419 -12
  25. package/dist/multisig.js.map +1 -1
  26. package/dist/procedures.d.ts +1 -1
  27. package/dist/procedures.js +1 -1
  28. package/dist/recovery/proposalNoteImport.d.ts +173 -0
  29. package/dist/recovery/proposalNoteImport.d.ts.map +1 -0
  30. package/dist/recovery/proposalNoteImport.js +411 -0
  31. package/dist/recovery/proposalNoteImport.js.map +1 -0
  32. package/dist/recovery/publicNoteBackfill.d.ts +168 -0
  33. package/dist/recovery/publicNoteBackfill.d.ts.map +1 -0
  34. package/dist/recovery/publicNoteBackfill.js +368 -0
  35. package/dist/recovery/publicNoteBackfill.js.map +1 -0
  36. package/dist/recovery/recoverNotes.d.ts +147 -0
  37. package/dist/recovery/recoverNotes.d.ts.map +1 -0
  38. package/dist/recovery/recoverNotes.js +163 -0
  39. package/dist/recovery/recoverNotes.js.map +1 -0
  40. package/dist/recovery/transportDrain.d.ts +124 -0
  41. package/dist/recovery/transportDrain.d.ts.map +1 -0
  42. package/dist/recovery/transportDrain.js +262 -0
  43. package/dist/recovery/transportDrain.js.map +1 -0
  44. package/dist/transaction/consumeNotes.d.ts.map +1 -1
  45. package/dist/transaction/consumeNotes.js +4 -1
  46. package/dist/transaction/consumeNotes.js.map +1 -1
  47. package/dist/transaction/p2id.d.ts.map +1 -1
  48. package/dist/transaction/p2id.js +4 -1
  49. package/dist/transaction/p2id.js.map +1 -1
  50. package/dist/transaction/summary.d.ts +10 -4
  51. package/dist/transaction/summary.d.ts.map +1 -1
  52. package/dist/transaction/summary.js +13 -7
  53. package/dist/transaction/summary.js.map +1 -1
  54. package/dist/transaction/updateGuardian.js +4 -1
  55. package/dist/transaction/updateGuardian.js.map +1 -1
  56. package/dist/transaction/updateProcedureThreshold.js +4 -1
  57. package/dist/transaction/updateProcedureThreshold.js.map +1 -1
  58. package/dist/transaction/updateSigners.js +4 -1
  59. package/dist/transaction/updateSigners.js.map +1 -1
  60. package/dist/transaction.d.ts +1 -1
  61. package/dist/transaction.d.ts.map +1 -1
  62. package/dist/transaction.js +1 -1
  63. package/dist/transaction.js.map +1 -1
  64. package/package.json +14 -10
  65. package/src/account/builder.ts +87 -35
  66. package/src/connectivity.ts +9 -4
  67. package/src/index.ts +34 -1
  68. package/src/multisig/authArgErrors.ts +138 -0
  69. package/src/multisig/helpers.ts +7 -2
  70. package/src/multisig.ts +513 -20
  71. package/src/procedures.ts +1 -1
  72. package/src/recovery/proposalNoteImport.ts +548 -0
  73. package/src/recovery/publicNoteBackfill.ts +502 -0
  74. package/src/recovery/recoverNotes.ts +288 -0
  75. package/src/recovery/transportDrain.ts +330 -0
  76. package/src/transaction/consumeNotes.ts +4 -1
  77. package/src/transaction/p2id.ts +4 -1
  78. package/src/transaction/summary.ts +13 -7
  79. package/src/transaction/updateGuardian.ts +4 -1
  80. package/src/transaction/updateProcedureThreshold.ts +4 -1
  81. package/src/transaction/updateSigners.ts +4 -1
  82. package/src/transaction.ts +1 -1
  83. package/dist/account/builder.test.d.ts +0 -2
  84. package/dist/account/builder.test.d.ts.map +0 -1
  85. package/dist/account/builder.test.js +0 -166
  86. package/dist/account/builder.test.js.map +0 -1
  87. package/dist/account/masm/account-components/auth.d.ts +0 -2
  88. package/dist/account/masm/account-components/auth.d.ts.map +0 -1
  89. package/dist/account/masm/account-components/auth.js +0 -46
  90. package/dist/account/masm/account-components/auth.js.map +0 -1
  91. package/dist/account/masm/index.d.ts +0 -2
  92. package/dist/account/masm/index.d.ts.map +0 -1
  93. package/dist/account/masm/index.js +0 -4
  94. package/dist/account/masm/index.js.map +0 -1
  95. package/dist/account/masm.d.ts +0 -2
  96. package/dist/account/masm.d.ts.map +0 -1
  97. package/dist/account/masm.js +0 -4
  98. package/dist/account/masm.js.map +0 -1
  99. package/dist/account/storage.test.d.ts +0 -2
  100. package/dist/account/storage.test.d.ts.map +0 -1
  101. package/dist/account/storage.test.js +0 -73
  102. package/dist/account/storage.test.js.map +0 -1
  103. package/dist/client.test.d.ts +0 -2
  104. package/dist/client.test.d.ts.map +0 -1
  105. package/dist/client.test.js +0 -380
  106. package/dist/client.test.js.map +0 -1
  107. package/dist/connectivity.test.d.ts +0 -2
  108. package/dist/connectivity.test.d.ts.map +0 -1
  109. package/dist/connectivity.test.js +0 -61
  110. package/dist/connectivity.test.js.map +0 -1
  111. package/dist/inspector.test.d.ts +0 -2
  112. package/dist/inspector.test.d.ts.map +0 -1
  113. package/dist/inspector.test.js +0 -425
  114. package/dist/inspector.test.js.map +0 -1
  115. package/dist/lookupAuth.test.d.ts +0 -2
  116. package/dist/lookupAuth.test.d.ts.map +0 -1
  117. package/dist/lookupAuth.test.js +0 -138
  118. package/dist/lookupAuth.test.js.map +0 -1
  119. package/dist/multisig/consumeNotesErrors.test.d.ts +0 -2
  120. package/dist/multisig/consumeNotesErrors.test.d.ts.map +0 -1
  121. package/dist/multisig/consumeNotesErrors.test.js +0 -28
  122. package/dist/multisig/consumeNotesErrors.test.js.map +0 -1
  123. package/dist/multisig/helpers.test.d.ts +0 -2
  124. package/dist/multisig/helpers.test.d.ts.map +0 -1
  125. package/dist/multisig/helpers.test.js +0 -94
  126. package/dist/multisig/helpers.test.js.map +0 -1
  127. package/dist/multisig.test.d.ts +0 -2
  128. package/dist/multisig.test.d.ts.map +0 -1
  129. package/dist/multisig.test.js +0 -4112
  130. package/dist/multisig.test.js.map +0 -1
  131. package/dist/proposal/factory.test.d.ts +0 -2
  132. package/dist/proposal/factory.test.d.ts.map +0 -1
  133. package/dist/proposal/factory.test.js +0 -32
  134. package/dist/proposal/factory.test.js.map +0 -1
  135. package/dist/proposal/metadata.test.d.ts +0 -2
  136. package/dist/proposal/metadata.test.d.ts.map +0 -1
  137. package/dist/proposal/metadata.test.js +0 -193
  138. package/dist/proposal/metadata.test.js.map +0 -1
  139. package/dist/prover/config.test.d.ts +0 -2
  140. package/dist/prover/config.test.d.ts.map +0 -1
  141. package/dist/prover/config.test.js +0 -54
  142. package/dist/prover/config.test.js.map +0 -1
  143. package/dist/prover/errors.test.d.ts +0 -2
  144. package/dist/prover/errors.test.d.ts.map +0 -1
  145. package/dist/prover/errors.test.js +0 -29
  146. package/dist/prover/errors.test.js.map +0 -1
  147. package/dist/prover/retry.test.d.ts +0 -2
  148. package/dist/prover/retry.test.d.ts.map +0 -1
  149. package/dist/prover/retry.test.js +0 -20
  150. package/dist/prover/retry.test.js.map +0 -1
  151. package/dist/prover/workflow.test.d.ts +0 -2
  152. package/dist/prover/workflow.test.d.ts.map +0 -1
  153. package/dist/prover/workflow.test.js +0 -105
  154. package/dist/prover/workflow.test.js.map +0 -1
  155. package/dist/raw-client.test.d.ts +0 -2
  156. package/dist/raw-client.test.d.ts.map +0 -1
  157. package/dist/raw-client.test.js +0 -111
  158. package/dist/raw-client.test.js.map +0 -1
  159. package/dist/rpc/config.test.d.ts +0 -2
  160. package/dist/rpc/config.test.d.ts.map +0 -1
  161. package/dist/rpc/config.test.js +0 -24
  162. package/dist/rpc/config.test.js.map +0 -1
  163. package/dist/rpc/errors.test.d.ts +0 -2
  164. package/dist/rpc/errors.test.d.ts.map +0 -1
  165. package/dist/rpc/errors.test.js +0 -34
  166. package/dist/rpc/errors.test.js.map +0 -1
  167. package/dist/rpc/retry.test.d.ts +0 -2
  168. package/dist/rpc/retry.test.d.ts.map +0 -1
  169. package/dist/rpc/retry.test.js +0 -98
  170. package/dist/rpc/retry.test.js.map +0 -1
  171. package/dist/signers/ecdsa.test.d.ts +0 -2
  172. package/dist/signers/ecdsa.test.d.ts.map +0 -1
  173. package/dist/signers/ecdsa.test.js +0 -88
  174. package/dist/signers/ecdsa.test.js.map +0 -1
  175. package/dist/signers/falcon.test.d.ts +0 -2
  176. package/dist/signers/falcon.test.d.ts.map +0 -1
  177. package/dist/signers/falcon.test.js +0 -161
  178. package/dist/signers/falcon.test.js.map +0 -1
  179. package/dist/signers/miden-wallet.ecdsa-recovery.test.d.ts +0 -2
  180. package/dist/signers/miden-wallet.ecdsa-recovery.test.d.ts.map +0 -1
  181. package/dist/signers/miden-wallet.ecdsa-recovery.test.js +0 -148
  182. package/dist/signers/miden-wallet.ecdsa-recovery.test.js.map +0 -1
  183. package/dist/signers/miden-wallet.test.d.ts +0 -2
  184. package/dist/signers/miden-wallet.test.d.ts.map +0 -1
  185. package/dist/signers/miden-wallet.test.js +0 -164
  186. package/dist/signers/miden-wallet.test.js.map +0 -1
  187. package/dist/signers/para.test.d.ts +0 -2
  188. package/dist/signers/para.test.d.ts.map +0 -1
  189. package/dist/signers/para.test.js +0 -146
  190. package/dist/signers/para.test.js.map +0 -1
  191. package/dist/transaction/index.d.ts +0 -8
  192. package/dist/transaction/index.d.ts.map +0 -1
  193. package/dist/transaction/index.js +0 -7
  194. package/dist/transaction/index.js.map +0 -1
  195. package/dist/transaction/p2id.test.d.ts +0 -2
  196. package/dist/transaction/p2id.test.d.ts.map +0 -1
  197. package/dist/transaction/p2id.test.js +0 -246
  198. package/dist/transaction/p2id.test.js.map +0 -1
  199. package/dist/transaction/rpoRandomCoin.test.d.ts +0 -2
  200. package/dist/transaction/rpoRandomCoin.test.d.ts.map +0 -1
  201. package/dist/transaction/rpoRandomCoin.test.js +0 -52
  202. package/dist/transaction/rpoRandomCoin.test.js.map +0 -1
  203. package/dist/transaction/summary.test.d.ts +0 -2
  204. package/dist/transaction/summary.test.d.ts.map +0 -1
  205. package/dist/transaction/summary.test.js +0 -26
  206. package/dist/transaction/summary.test.js.map +0 -1
  207. package/dist/transaction.test.d.ts +0 -2
  208. package/dist/transaction.test.d.ts.map +0 -1
  209. package/dist/transaction.test.js +0 -122
  210. package/dist/transaction.test.js.map +0 -1
  211. package/dist/types/proposal.test.d.ts +0 -2
  212. package/dist/types/proposal.test.d.ts.map +0 -1
  213. package/dist/types/proposal.test.js +0 -56
  214. package/dist/types/proposal.test.js.map +0 -1
  215. package/dist/utils/digest.test.d.ts +0 -2
  216. package/dist/utils/digest.test.d.ts.map +0 -1
  217. package/dist/utils/digest.test.js +0 -48
  218. package/dist/utils/digest.test.js.map +0 -1
  219. package/dist/utils/ecdsa.test.d.ts +0 -2
  220. package/dist/utils/ecdsa.test.d.ts.map +0 -1
  221. package/dist/utils/ecdsa.test.js +0 -131
  222. package/dist/utils/ecdsa.test.js.map +0 -1
  223. package/dist/utils/encoding.test.d.ts +0 -2
  224. package/dist/utils/encoding.test.d.ts.map +0 -1
  225. package/dist/utils/encoding.test.js +0 -185
  226. package/dist/utils/encoding.test.js.map +0 -1
  227. package/dist/utils/key.test.d.ts +0 -2
  228. package/dist/utils/key.test.d.ts.map +0 -1
  229. package/dist/utils/key.test.js +0 -82
  230. package/dist/utils/key.test.js.map +0 -1
  231. package/dist/utils/signature.test.d.ts +0 -2
  232. package/dist/utils/signature.test.d.ts.map +0 -1
  233. package/dist/utils/signature.test.js +0 -164
  234. package/dist/utils/signature.test.js.map +0 -1
  235. package/dist/utils/word.test.d.ts +0 -2
  236. package/dist/utils/word.test.d.ts.map +0 -1
  237. package/dist/utils/word.test.js +0 -54
  238. package/dist/utils/word.test.js.map +0 -1
  239. package/masm/account_components/auth/guarded_multisig.masm +0 -42
  240. package/src/account/builder.test.ts +0 -238
  241. package/src/account/masm/account-components/auth.ts +0 -46
  242. package/src/account/masm/index.ts +0 -4
  243. package/src/account/masm.ts +0 -4
  244. package/src/account/storage.test.ts +0 -90
  245. package/src/client.test.ts +0 -473
  246. package/src/connectivity.test.ts +0 -67
  247. package/src/inspector.test.ts +0 -542
  248. package/src/lookupAuth.test.ts +0 -185
  249. package/src/multisig/consumeNotesErrors.test.ts +0 -43
  250. package/src/multisig/helpers.test.ts +0 -114
  251. package/src/multisig.test.ts +0 -5008
  252. package/src/proposal/factory.test.ts +0 -40
  253. package/src/proposal/metadata.test.ts +0 -235
  254. package/src/prover/config.test.ts +0 -90
  255. package/src/prover/errors.test.ts +0 -61
  256. package/src/prover/retry.test.ts +0 -38
  257. package/src/prover/workflow.test.ts +0 -145
  258. package/src/raw-client.test.ts +0 -171
  259. package/src/rpc/config.test.ts +0 -45
  260. package/src/rpc/errors.test.ts +0 -69
  261. package/src/rpc/retry.test.ts +0 -144
  262. package/src/signers/ecdsa.test.ts +0 -111
  263. package/src/signers/falcon.test.ts +0 -197
  264. package/src/signers/miden-wallet.ecdsa-recovery.test.ts +0 -201
  265. package/src/signers/miden-wallet.test.ts +0 -206
  266. package/src/signers/para.test.ts +0 -186
  267. package/src/transaction/index.ts +0 -13
  268. package/src/transaction/p2id.test.ts +0 -371
  269. package/src/transaction/rpoRandomCoin.test.ts +0 -64
  270. package/src/transaction/summary.test.ts +0 -32
  271. package/src/transaction.test.ts +0 -142
  272. package/src/types/proposal.test.ts +0 -68
  273. package/src/utils/digest.test.ts +0 -55
  274. package/src/utils/ecdsa.test.ts +0 -164
  275. package/src/utils/encoding.test.ts +0 -233
  276. package/src/utils/key.test.ts +0 -91
  277. package/src/utils/signature.test.ts +0 -210
  278. package/src/utils/word.test.ts +0 -64
@@ -0,0 +1,411 @@
1
+ /**
2
+ * Recovery primitives.
3
+ *
4
+ * After key-based recovery the local Miden store starts empty, so notes the
5
+ * account was in the middle of consuming are gone. v2 `consume_notes`
6
+ * proposals embed the serialized notes they consume, which makes
7
+ * pending proposals opportunistic recovery material: this module rebuilds
8
+ * importable notes from those embedded bytes plus a node-fetched inclusion
9
+ * proof, without needing the node to hold the note body — so it works for
10
+ * private notes too.
11
+ */
12
+ import { Endpoint, InputNote, InputNoteState, Note, NoteDetails, NoteFile, NoteFilter, NoteFilterTypes, RpcClient, } from '@miden-sdk/miden-sdk';
13
+ import { getRawMidenClient, requireMidenRpcEndpoint } from '../raw-client.js';
14
+ import { resolveRpcConfig } from '../rpc/config.js';
15
+ import { isTransientRpcError } from '../rpc/errors.js';
16
+ import { retryRpcRead } from '../rpc/retry.js';
17
+ import { isConsumeNotesV2 } from '../types/proposal.js';
18
+ import { noteFromBase64, normalizeHexWord } from '../utils/encoding.js';
19
+ /**
20
+ * Thrown by the recovery cancellation checkpoints; `runNoteRecovery` stops
21
+ * on it instead of misreporting a step failure. The message avoids
22
+ * 'cancelled'/'timeout' wording on purpose — the RPC retry classifier
23
+ * treats those fragments as transient, and cancellation must not retry.
24
+ */
25
+ export class RecoveryCancelledError extends Error {
26
+ constructor() {
27
+ super('note recovery stopped before completion by its caller');
28
+ this.name = 'RecoveryCancelledError';
29
+ }
30
+ }
31
+ /** Throws {@link RecoveryCancelledError} when the token reports cancelled. */
32
+ export function throwIfCancelled(cancelled) {
33
+ if (cancelled?.()) {
34
+ throw new RecoveryCancelledError();
35
+ }
36
+ }
37
+ /** Shared by the recovery primitives (proposal import and backfill). */
38
+ export function errorDetail(error) {
39
+ return error instanceof Error ? error.message : String(error);
40
+ }
41
+ /** Details key: recipient digest + canonical FUNGIBLE asset list. This
42
+ * approximates the details commitment — which the WASM record surface does
43
+ * not expose — as closely as the surface allows: `fungibleAssets()` silently
44
+ * omits non-fungible assets and no complete accessor or commitment exists,
45
+ * so two notes sharing a recipient digest and fungible assets but differing
46
+ * only in non-fungible assets collide (the Rust SDK, keyed on the real
47
+ * `NoteDetailsCommitment`, does not). Latent until NFA-bearing notes reach
48
+ * these flows; closing it needs an upstream `NoteAssets` commitment/NFA
49
+ * accessor. Recipient digest alone would be worse: distinct notes can share
50
+ * a recipient while carrying different fungible assets. */
51
+ export function detailsKeyOf(recipientDigestHex, assets) {
52
+ const fingerprint = assets
53
+ .fungibleAssets()
54
+ .map((asset) => `${normalizeHexWord(asset.faucetId().toString())}:${asset.amount()}`)
55
+ .sort()
56
+ .join(',');
57
+ return `${recipientDigestHex}|${fingerprint}`;
58
+ }
59
+ function recordKeys(record) {
60
+ const keys = [];
61
+ const recordId = record.id();
62
+ if (recordId) {
63
+ keys.push(normalizeHexWord(recordId.toString()));
64
+ }
65
+ const details = record.details();
66
+ keys.push(detailsKeyOf(normalizeHexWord(details.recipient().digest().toHex()), details.assets()));
67
+ return keys;
68
+ }
69
+ /**
70
+ * Scans the store once and keys every record by note ID *and* details key:
71
+ * records the store keeps without metadata (a note details import in
72
+ * expected state, or a note observed as consumed on chain) expose neither a
73
+ * note ID nor a nullifier, and an ID-only lookup would keep re-importing
74
+ * them forever. (The WASM NoteFilter has no details-commitment variant,
75
+ * unlike the Rust SDK, so the store is scanned once and keyed both ways.)
76
+ * Shared by the recovery primitives (proposal import and backfill).
77
+ */
78
+ export async function collectExistingRecords(webClient) {
79
+ const existing = new Map();
80
+ const records = await webClient.getInputNotes(new NoteFilter(NoteFilterTypes.All));
81
+ for (const record of records) {
82
+ for (const key of recordKeys(record)) {
83
+ existing.set(key, record);
84
+ }
85
+ }
86
+ return existing;
87
+ }
88
+ /**
89
+ * Imports one note with its inclusion proof and classifies the result.
90
+ * Upstream note-import batches are atomic, which is why callers import
91
+ * individually — one bad note must not sink the rest. Returns the outcome
92
+ * and whether the import succeeded (input for the batched consumed-state
93
+ * re-check).
94
+ */
95
+ export async function importNoteWithProof(webClient, source, idHex, note, proof) {
96
+ try {
97
+ const inputNote = InputNote.authenticated(note, proof);
98
+ await webClient.importNoteFile(NoteFile.fromInputNote(inputNote));
99
+ return {
100
+ outcome: { identifier: idHex, source, status: 'imported' },
101
+ wasImported: true,
102
+ };
103
+ }
104
+ catch (error) {
105
+ return {
106
+ outcome: {
107
+ identifier: idHex,
108
+ source,
109
+ status: 'failed',
110
+ retryable: isTransientRpcError(error),
111
+ reason: `failed to import note: ${errorDetail(error)}`,
112
+ },
113
+ wasImported: false,
114
+ };
115
+ }
116
+ }
117
+ /**
118
+ * Re-classifies provisionally `imported` outcomes from the records the
119
+ * import actually left behind. A note the chain had already nullified is
120
+ * stored as consumption history, not as a consumable note — reported as
121
+ * `already-consumed`. A note whose inclusion proof failed verification
122
+ * against the authenticated block header is stored in `Invalid` state by
123
+ * upstream while the import still resolves — reported as `failed`, because
124
+ * "recovered" notes that can never be consumed must not count as recovered.
125
+ * Records are matched by note ID when they expose one, and by the (lossy,
126
+ * fungible-assets-only) details key otherwise — a chain-consumed record is
127
+ * stored without metadata, so the approximation is the only join available;
128
+ * it can only misstate the status here, never skip an import. One batched
129
+ * store read covers every imported note. A failed check downgrades nothing;
130
+ * it flags the outcome's classification as unconfirmed instead.
131
+ */
132
+ export async function reclassifyConsumedImports(webClient, imported, outcomes) {
133
+ if (imported.length === 0) {
134
+ return;
135
+ }
136
+ try {
137
+ const records = await webClient.getInputNotes(new NoteFilter(NoteFilterTypes.All));
138
+ const byId = new Map();
139
+ const byDetailsKey = new Map();
140
+ for (const record of records) {
141
+ const recordId = record.id();
142
+ if (recordId) {
143
+ byId.set(normalizeHexWord(recordId.toString()), record);
144
+ }
145
+ else {
146
+ const details = record.details();
147
+ byDetailsKey.set(detailsKeyOf(normalizeHexWord(details.recipient().digest().toHex()), details.assets()), record);
148
+ }
149
+ }
150
+ for (const entry of imported) {
151
+ const record = byId.get(entry.idHex) ?? byDetailsKey.get(entry.detailsKey);
152
+ if (!record) {
153
+ continue;
154
+ }
155
+ if (record.isConsumed()) {
156
+ outcomes[entry.index] = {
157
+ ...outcomes[entry.index],
158
+ status: 'already-consumed',
159
+ reason: 'note was already consumed on chain; recorded as consumption history',
160
+ };
161
+ }
162
+ else if (record.state() === InputNoteState.Invalid) {
163
+ outcomes[entry.index] = {
164
+ ...outcomes[entry.index],
165
+ status: 'failed',
166
+ retryable: false,
167
+ reason: "the note's inclusion proof failed verification against the authenticated block header; the record is stored as invalid and the note is not consumable",
168
+ };
169
+ }
170
+ }
171
+ }
172
+ catch (error) {
173
+ // The imports themselves succeeded; stay `imported` but flag that the
174
+ // post-import state check is unknown.
175
+ for (const entry of imported) {
176
+ outcomes[entry.index] = {
177
+ ...outcomes[entry.index],
178
+ reason: `imported, but the post-import state check failed (${errorDetail(error)}); run sync to confirm the note's status`,
179
+ };
180
+ }
181
+ }
182
+ }
183
+ /**
184
+ * Imports the notes embedded in v2 `consume_notes` proposals into the local
185
+ * Miden store, typically after key-based recovery rebuilt the
186
+ * proposal list (`syncProposals`) but left the note store empty.
187
+ *
188
+ * Proposals are opportunistic recovery material, not a backup: v1 proposals
189
+ * carry no note bytes, and proposals disappear once canonicalized, so only
190
+ * notes still mid-consumption are recoverable this way.
191
+ *
192
+ * Per note: decode the embedded bytes, skip notes the store already tracks,
193
+ * fetch the on-chain inclusion proof, and import the note individually
194
+ * (upstream note-import batches are atomic, so one bad note must not sink the
195
+ * rest). A note the chain does not know yet is recorded as expected with its
196
+ * tag tracked so a later sync picks it up, and is reported as
197
+ * `not-committed`/retryable. A note the chain has already nullified is
198
+ * recorded as consumption history and reported `already-consumed`.
199
+ *
200
+ * The returned outcomes cover every unique embedded note — a note embedded by
201
+ * several proposals yields one outcome, not one per embedding — and this
202
+ * function does not throw for per-note problems.
203
+ *
204
+ * @example
205
+ * ```typescript
206
+ * const proposals = await multisig.syncProposals();
207
+ * const outcomes = await importNotesFromProposals(midenClient, proposals, {
208
+ * midenRpcEndpoint: 'https://rpc.testnet.miden.io',
209
+ * });
210
+ * await multisig.syncState();
211
+ * ```
212
+ */
213
+ export async function importNotesFromProposals(midenClient, proposals, options) {
214
+ const midenRpcEndpoint = requireMidenRpcEndpoint(options.midenRpcEndpoint);
215
+ const rpcConfig = resolveRpcConfig(options.rpc);
216
+ throwIfCancelled(options.cancelled);
217
+ const webClient = await getRawMidenClient(midenClient, midenRpcEndpoint);
218
+ const outcomes = [];
219
+ // Decode, validate, and deduplicate embedded notes (the same note may be
220
+ // embedded by several proposals). Undecodable entries, embeddings past the
221
+ // declared note-id list, and embeddings whose decoded ID disagrees with
222
+ // the declared one become isolated `invalid` outcomes with a positional
223
+ // identifier: recovery runs automatically over synced proposals, so the
224
+ // per-index ID binding is what keeps a malformed or adversarial proposal
225
+ // from smuggling arbitrary notes (and, for uncommitted ones, persistent
226
+ // expected records and tag registrations) into the local store.
227
+ const decoded = [];
228
+ const seen = new Set();
229
+ for (const proposal of proposals) {
230
+ const metadata = proposal.metadata;
231
+ if (metadata.proposalType !== 'consume_notes' || !isConsumeNotesV2(metadata)) {
232
+ continue;
233
+ }
234
+ const embedded = metadata.notes ?? [];
235
+ const declaredIds = metadata.noteIds ?? [];
236
+ for (let index = 0; index < embedded.length; index += 1) {
237
+ const identifier = `proposal ${proposal.id} notes[${index}]`;
238
+ const declaredId = declaredIds[index];
239
+ if (declaredId === undefined) {
240
+ outcomes.push({
241
+ identifier,
242
+ source: 'proposal',
243
+ status: 'invalid',
244
+ reason: "embedded note has no matching entry in the proposal's declared note ids",
245
+ });
246
+ continue;
247
+ }
248
+ // The try covers every per-note WASM accessor, so a payload that
249
+ // deserializes but traps on use is isolated like any other bad note.
250
+ let candidate;
251
+ try {
252
+ const note = noteFromBase64(embedded[index], Note);
253
+ candidate = {
254
+ note,
255
+ idHex: normalizeHexWord(note.id().toString()),
256
+ detailsKey: detailsKeyOf(normalizeHexWord(note.recipient().digest().toHex()), note.assets()),
257
+ };
258
+ }
259
+ catch (error) {
260
+ outcomes.push({
261
+ identifier,
262
+ source: 'proposal',
263
+ status: 'invalid',
264
+ reason: `failed to decode embedded note: ${errorDetail(error)}`,
265
+ });
266
+ continue;
267
+ }
268
+ if (candidate.idHex !== normalizeHexWord(declaredId)) {
269
+ outcomes.push({
270
+ identifier,
271
+ source: 'proposal',
272
+ status: 'invalid',
273
+ reason: `embedded note decodes to ${candidate.idHex} but the proposal declares ${declaredId}`,
274
+ });
275
+ continue;
276
+ }
277
+ if (seen.has(candidate.idHex)) {
278
+ continue;
279
+ }
280
+ seen.add(candidate.idHex);
281
+ decoded.push(candidate);
282
+ }
283
+ }
284
+ if (decoded.length === 0) {
285
+ return outcomes;
286
+ }
287
+ let existing;
288
+ try {
289
+ existing = await collectExistingRecords(webClient);
290
+ }
291
+ catch (error) {
292
+ const reason = `failed to read local store: ${errorDetail(error)}`;
293
+ for (const candidate of decoded) {
294
+ outcomes.push({
295
+ identifier: candidate.idHex,
296
+ source: 'proposal',
297
+ status: 'failed',
298
+ reason,
299
+ });
300
+ }
301
+ return outcomes;
302
+ }
303
+ // Skip notes the store already tracks — but only on an exact note-ID
304
+ // match. A details-key match is a lossy approximation (see
305
+ // {@link detailsKeyOf}), so a candidate that only matches a metadata-less
306
+ // record proceeds to import: the upstream import dedupes exactly by the
307
+ // real details commitment, upgrading or no-oping in place, so importing
308
+ // "again" is safe while pre-skipping on the approximation could silently
309
+ // drop a genuinely new note.
310
+ const pending = [];
311
+ for (const candidate of decoded) {
312
+ const record = existing.get(candidate.idHex);
313
+ if (record) {
314
+ outcomes.push({
315
+ identifier: candidate.idHex,
316
+ source: 'proposal',
317
+ status: record.isConsumed() ? 'already-consumed' : 'already-present',
318
+ });
319
+ continue;
320
+ }
321
+ pending.push(candidate);
322
+ }
323
+ if (pending.length === 0) {
324
+ return outcomes;
325
+ }
326
+ // One round trip for all missing notes; only the import itself is per-note.
327
+ // The node returns proofs for private notes too, so the locally-held bytes
328
+ // are the only body this path ever needs.
329
+ const proofs = new Map();
330
+ throwIfCancelled(options.cancelled);
331
+ try {
332
+ const rpcClient = new RpcClient(new Endpoint(midenRpcEndpoint));
333
+ // Inside the retried closure, so a token flip between attempts stops
334
+ // the retry loop instead of letting backoff attempts outlive the
335
+ // deadline.
336
+ const fetchedNotes = await retryRpcRead(() => {
337
+ throwIfCancelled(options.cancelled);
338
+ return rpcClient.getNotesById(pending.map((candidate) => candidate.note.id()));
339
+ }, rpcConfig);
340
+ for (const fetched of fetchedNotes) {
341
+ proofs.set(normalizeHexWord(fetched.noteId.toString()), fetched.inclusionProof);
342
+ }
343
+ }
344
+ catch (error) {
345
+ if (error instanceof RecoveryCancelledError) {
346
+ throw error;
347
+ }
348
+ const retryable = isTransientRpcError(error);
349
+ const reason = `failed to fetch inclusion proofs: ${errorDetail(error)}`;
350
+ for (const candidate of pending) {
351
+ outcomes.push({
352
+ identifier: candidate.idHex,
353
+ source: 'proposal',
354
+ status: 'failed',
355
+ retryable,
356
+ reason,
357
+ });
358
+ }
359
+ return outcomes;
360
+ }
361
+ // Provisionally `imported` outcomes, re-classified in one batched
362
+ // post-import state check below.
363
+ const imported = [];
364
+ for (const candidate of pending) {
365
+ throwIfCancelled(options.cancelled);
366
+ const proof = proofs.get(candidate.idHex);
367
+ if (proof) {
368
+ const { outcome, wasImported } = await importNoteWithProof(webClient, 'proposal', candidate.idHex, candidate.note, proof);
369
+ if (wasImported) {
370
+ imported.push({
371
+ index: outcomes.length,
372
+ idHex: candidate.idHex,
373
+ detailsKey: candidate.detailsKey,
374
+ });
375
+ }
376
+ outcomes.push(outcome);
377
+ }
378
+ else {
379
+ try {
380
+ // Mirror the Rust SDK's `NoteFile::ExpectedNote` + sync-hint import:
381
+ // the tag rides in the note file itself, so upstream registers a
382
+ // note-source tag that sync uses to discover the commitment and
383
+ // removes once the note commits. (An explicit `addTag` would instead
384
+ // create a permanent user-source tag that the transport backfill
385
+ // also re-drains, and that would outlive even a failed import.)
386
+ const details = new NoteDetails(candidate.note.assets(), candidate.note.recipient());
387
+ await webClient.importNoteFile(NoteFile.fromExpectedNote(details, candidate.note.metadata().tag(), 0));
388
+ outcomes.push({
389
+ identifier: candidate.idHex,
390
+ source: 'proposal',
391
+ status: 'not-committed',
392
+ retryable: true,
393
+ reason: 'note not yet committed on chain; recorded as expected so a later sync picks it up',
394
+ });
395
+ }
396
+ catch (error) {
397
+ outcomes.push({
398
+ identifier: candidate.idHex,
399
+ source: 'proposal',
400
+ status: 'failed',
401
+ retryable: isTransientRpcError(error),
402
+ reason: `failed to record expected note: ${errorDetail(error)}`,
403
+ });
404
+ }
405
+ }
406
+ }
407
+ throwIfCancelled(options.cancelled);
408
+ await reclassifyConsumedImports(webClient, imported, outcomes);
409
+ return outcomes;
410
+ }
411
+ //# sourceMappingURL=proposalNoteImport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proposalNoteImport.js","sourceRoot":"","sources":["../../src/recovery/proposalNoteImport.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Historical public-note backfill by tag.
3
+ *
4
+ * Public notes addressed to an account are on chain, but normal forward sync
5
+ * starts from the store's **global** cursor: in a shared dirty store the
6
+ * cursor may already be past blocks containing a recovered account's notes,
7
+ * and a fresh store has no efficient path to them at all. This module
8
+ * rescans a historical block range with the account's standard note tag and
9
+ * imports what it finds with on-chain inclusion proofs, without ever
10
+ * touching the global sync height.
11
+ */
12
+ import { AccountId, type Note } from '@miden-sdk/miden-sdk';
13
+ import { type NoteImportOutcome } from './proposalNoteImport.js';
14
+ import { type RawClientSource } from '../raw-client.js';
15
+ import { type RpcConfig } from '../rpc/config.js';
16
+ /**
17
+ * `RpcError::PaginationError`'s Display prefix in the WASM error chain: the
18
+ * node caps internal pagination per `syncNotes` request, and the scan splits
19
+ * the range client-side when it trips. Exported for the drift-guard test,
20
+ * which pins it against the shipped WASM binary.
21
+ */
22
+ export declare const RPC_PAGINATION_FRAGMENT = "rpc pagination error";
23
+ /**
24
+ * The requested scan range is invalid (out-of-range bound, or inverted
25
+ * against the resolved chain tip) — a caller error that retrying cannot fix,
26
+ * as opposed to the transient chain-tip-lookup failures this function also
27
+ * throws. `Multisig.recoverNotes` keys its problem retryability on this,
28
+ * mirroring the Rust orchestrator's `InvalidConfig` check.
29
+ */
30
+ export declare class BackfillRangeError extends Error {
31
+ }
32
+ /** Verdict of the static relevance screen. */
33
+ export type ScreenVerdict = 'relevant' | 'irrelevant' | 'unscreenable';
34
+ /**
35
+ * Static relevance screen for a discovered public note. The Rust SDK screens
36
+ * with the execution-based `NoteScreener` normal sync uses; the WASM surface
37
+ * does not expose it, so this mirrors its verdict for the well-known note
38
+ * scripts: a note is `relevant` when it is a P2ID/P2IDE note whose target
39
+ * (or P2IDE reclaimer) is the scanned account, and `irrelevant` when it is
40
+ * one of those scripts addressed at someone else. Notes with other scripts
41
+ * are `unscreenable` — this screen cannot judge them, and they are
42
+ * conservatively not imported (tags are shared, truncated filters, and
43
+ * importing unscreened tag matches would let anyone pollute the store), but
44
+ * the report counts them separately so "screened out" and "not screenable"
45
+ * stay distinguishable. Exported for the drift-guard test, which pins the
46
+ * root and storage-layout assumptions against real WASM-built notes.
47
+ */
48
+ export declare function screenNoteForAccount(note: Note, account: AccountId): ScreenVerdict;
49
+ /** A contiguous block range, inclusive on both ends. */
50
+ export interface BlockRange {
51
+ /** First block of the range. */
52
+ from: number;
53
+ /** Last block of the range. */
54
+ to: number;
55
+ }
56
+ /**
57
+ * Result of {@link backfillPublicNotesByTag}.
58
+ *
59
+ * Scan problems are reported here rather than thrown so a partially failing
60
+ * scan never aborts the rest of a recovery flow: notes discovered in the
61
+ * covered ranges are imported regardless.
62
+ */
63
+ export interface PublicBackfillReport {
64
+ /** First block of the requested scan range. */
65
+ scannedFrom: number;
66
+ /** Last block of the requested scan range. */
67
+ scannedTo: number;
68
+ /** Unique tag-matching notes the scan discovered, of every visibility. */
69
+ discovered: number;
70
+ /** Unique non-public matches skipped: the chain does not hold their
71
+ * bodies, so they cannot be rebuilt from a scan. Private notes are covered
72
+ * by the transport drain and proposal-import primitives instead. */
73
+ skippedPrivate: number;
74
+ /** Unique public matches the relevance screen rejected: tags are
75
+ * best-effort, truncated filters, so unrelated notes can carry this
76
+ * account's tag. Like normal sync, only notes the account could actually
77
+ * consume are imported; the rest are counted here. (This SDK screens
78
+ * statically against the well-known P2ID/P2IDE scripts; the Rust SDK uses
79
+ * the execution-based screener.) */
80
+ skippedIrrelevant: number;
81
+ /** Unique public matches this SDK's static screen could not judge (custom
82
+ * note scripts). They are conservatively not imported, but counted apart
83
+ * from `skippedIrrelevant` so callers can tell "screened out" from "not
84
+ * screenable". Always `0` in the Rust SDK, whose execution-based screener
85
+ * judges every note. */
86
+ skippedUnscreenable: number;
87
+ /** One outcome per unique public note that passed the relevance screen —
88
+ * `outcomes.length === discovered - skippedPrivate - skippedIrrelevant -
89
+ * skippedUnscreenable`. Screened-out, unscreenable, and private matches
90
+ * get no outcome, only their counters. */
91
+ outcomes: NoteImportOutcome[];
92
+ /** Sub-ranges of `[scannedFrom, scannedTo]` the scan could not cover (RPC
93
+ * failures, or the scan budget ran out while splitting around the node's
94
+ * pagination cap). Empty when the whole range was scanned. Notes committed
95
+ * in these ranges may be missing from `outcomes`. */
96
+ uncovered: BlockRange[];
97
+ /** Whether rerunning the backfill can plausibly improve the result: cover
98
+ * `uncovered` ranges, or retry outcomes whose own `retryable` flag is set.
99
+ * Always `false` when the scan fully covered the range and no outcome is
100
+ * retryable. */
101
+ retryable: boolean;
102
+ /** Human-readable cause when the scan did not cover the whole range. */
103
+ reason?: string;
104
+ }
105
+ export interface BackfillPublicNotesOptions {
106
+ /** Hex ID of the account whose standard note tag should be scanned. */
107
+ accountId: string;
108
+ /** Miden node RPC endpoint used for the scan and body fetches. Must point
109
+ * at the same network as the injected Miden client. */
110
+ midenRpcEndpoint: string;
111
+ /** First block of the scan range (default: genesis). */
112
+ fromBlock?: number;
113
+ /** Last block of the scan range (default: the current chain tip). */
114
+ toBlock?: number;
115
+ /** Node RPC read-retry configuration (defaults match the rest of the SDK). */
116
+ rpc?: RpcConfig;
117
+ }
118
+ /**
119
+ * Scans a historical block range for public notes addressed at an account's
120
+ * standard note tag and imports what it finds with their on-chain inclusion
121
+ * proofs. Counterpart of
122
+ * `MultisigClient::backfill_public_notes_by_tag` in the Rust SDK.
123
+ *
124
+ * Use after account recovery: normal forward sync starts from the store's
125
+ * **global** cursor, so in a shared dirty store the cursor may already be
126
+ * past blocks containing the recovered account's notes, and a fresh store
127
+ * would need to replay the whole chain state to see them. The scan is
128
+ * tag-scoped and its cost grows with the number of matching notes, not the
129
+ * range length, which makes genesis an acceptable default lower
130
+ * bound. The global sync height is never touched — run normal sync
131
+ * afterwards to verify the imported notes. The store must have synced at
132
+ * least once (`Multisig.recoverNotes` syncs the chain before this strategy
133
+ * runs): importing a proof into a store that has never seen the chain
134
+ * fails, and such failures surface as `failed` outcomes.
135
+ *
136
+ * Notes are discovered by tag only — a best-effort filter: notes sent with
137
+ * unrelated custom tags are outside this scan's guarantee, and, like normal
138
+ * sync, every new discovery is screened for relevance before import —
139
+ * tag-colliding notes the account cannot consume are counted as
140
+ * `skippedIrrelevant` instead of polluting the store. This SDK screens
141
+ * statically against the well-known P2ID/P2IDE scripts (the WASM surface
142
+ * does not expose the execution-based screener the Rust SDK uses), so notes
143
+ * with custom scripts are conservatively not imported and counted as
144
+ * `skippedUnscreenable`. Only public notes can be
145
+ * rebuilt from chain data; private matches are counted as `skippedPrivate`
146
+ * and are covered by the transport drain and proposal-import primitives
147
+ * instead.
148
+ *
149
+ * A range dense enough to trip the node's internal pagination cap is split
150
+ * client-side and rescanned as narrower requests; ranges that still cannot
151
+ * be covered are reported in {@link PublicBackfillReport.uncovered} rather
152
+ * than failing the recovery flow. This function throws only when the scan
153
+ * range itself cannot be established (chain-tip lookup failed, an invalid
154
+ * account ID, a block bound that is not a u32 integer, or
155
+ * `fromBlock > toBlock`).
156
+ *
157
+ * Prefer the `Multisig.backfillPublicNotesByTag` convenience method, which
158
+ * reuses the client's endpoint and retry configuration.
159
+ *
160
+ * @example
161
+ * ```typescript
162
+ * const report = await multisig.backfillPublicNotesByTag();
163
+ * console.log(report.discovered, 'discovered,', report.outcomes.length, 'public');
164
+ * await multisig.syncState(); // verifies the imported notes
165
+ * ```
166
+ */
167
+ export declare function backfillPublicNotesByTag(midenClient: RawClientSource, options: BackfillPublicNotesOptions): Promise<PublicBackfillReport>;
168
+ //# sourceMappingURL=publicNoteBackfill.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publicNoteBackfill.d.ts","sourceRoot":"","sources":["../../src/recovery/publicNoteBackfill.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EACL,SAAS,EAIT,KAAK,IAAI,EAMV,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EAKL,KAAK,iBAAiB,EAEvB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAA8C,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACpG,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAKpE;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,yBAAyB,CAAC;AAc9D;;;;;;GAMG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;CAAG;AAUhD,8CAA8C;AAC9C,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,YAAY,GAAG,cAAc,CAAC;AAEvE;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,GAAG,aAAa,CAmBlF;AAED,wDAAwD;AACxD,MAAM,WAAW,UAAU;IACzB,gCAAgC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,+BAA+B;IAC/B,EAAE,EAAE,MAAM,CAAC;CACZ;AAED;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB;IACnC,+CAA+C;IAC/C,WAAW,EAAE,MAAM,CAAC;IACpB,8CAA8C;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,UAAU,EAAE,MAAM,CAAC;IACnB;;wEAEoE;IACpE,cAAc,EAAE,MAAM,CAAC;IACvB;;;;;wCAKoC;IACpC,iBAAiB,EAAE,MAAM,CAAC;IAC1B;;;;4BAIwB;IACxB,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;;8CAG0C;IAC1C,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B;;;yDAGqD;IACrD,SAAS,EAAE,UAAU,EAAE,CAAC;IACxB;;;oBAGgB;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,0BAA0B;IACzC,uEAAuE;IACvE,SAAS,EAAE,MAAM,CAAC;IAClB;2DACuD;IACvD,gBAAgB,EAAE,MAAM,CAAC;IACzB,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8EAA8E;IAC9E,GAAG,CAAC,EAAE,SAAS,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,wBAAsB,wBAAwB,CAC5C,WAAW,EAAE,eAAe,EAC5B,OAAO,EAAE,0BAA0B,GAClC,OAAO,CAAC,oBAAoB,CAAC,CAsQ/B"}