@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,288 @@
1
+ /**
2
+ * Wallet-facing orchestration of the note-recovery primitives.
3
+ *
4
+ * After key-based recovery the local Miden store starts empty (or, in a
5
+ * shared store, its global cursors may already be past this account's
6
+ * notes). Each recovery primitive rescans one source normal forward sync
7
+ * would skip: the private-note transport backlog, the notes embedded in
8
+ * pending consume-notes proposals, and the chain's historical public notes
9
+ * for the account's tag. `Multisig.recoverNotes` runs them as a single flow
10
+ * and finishes with a normal sync so recovered notes are verified and ready
11
+ * to consume.
12
+ *
13
+ * The TS counterpart of the Rust SDK's `MultisigClient::recover_notes`,
14
+ * with matching semantics and report shape.
15
+ */
16
+
17
+ import { errorMessage } from '../connectivity.js';
18
+ import { RecoveryCancelledError } from './proposalNoteImport.js';
19
+ import type { NoteImportOutcome } from './proposalNoteImport.js';
20
+ import { BackfillRangeError, type PublicBackfillReport } from './publicNoteBackfill.js';
21
+ import type { TransportRecoveryReport } from './transportDrain.js';
22
+
23
+ /** One step of the note-recovery flow, used to attribute problems. */
24
+ export type RecoveryStep =
25
+ | 'transport-drain'
26
+ | 'proposal-import'
27
+ | 'public-backfill'
28
+ | 'sync';
29
+
30
+ /**
31
+ * A step of the recovery flow that could not run (or, for the final sync,
32
+ * did not finish). The flow always continues with the remaining steps; a
33
+ * problem here means the corresponding report field is absent (or `synced`
34
+ * is `false`) and rerunning the flow may recover more.
35
+ */
36
+ export interface RecoveryStepProblem {
37
+ /** The step that failed. */
38
+ step: RecoveryStep;
39
+ /** Human-readable cause. */
40
+ reason: string;
41
+ /**
42
+ * Whether rerunning the flow can plausibly make this step succeed.
43
+ * Failures reaching this level are I/O failures against GUARDIAN, the
44
+ * node, or the local store; all but local-store failures are marked
45
+ * retryable. The flow is idempotent, so retrying is always safe.
46
+ */
47
+ retryable: boolean;
48
+ }
49
+
50
+ /**
51
+ * Result of `Multisig.recoverNotes`.
52
+ *
53
+ * Each strategy's field holds its primitive's own report and is present
54
+ * exactly when the strategy was enabled and ran; an enabled strategy that
55
+ * could not run at all is a {@link RecoveryStepProblem} instead. Step
56
+ * problems never abort the flow.
57
+ */
58
+ export interface NoteRecoveryReport {
59
+ /** Report of the transport backlog drain, when that strategy ran. */
60
+ transport?: TransportRecoveryReport;
61
+ /**
62
+ * Per-note outcomes of the proposal-embedded import, when that strategy
63
+ * ran.
64
+ */
65
+ proposalImport?: NoteImportOutcome[];
66
+ /** Report of the historical public-note backfill, when that strategy ran. */
67
+ backfill?: PublicBackfillReport;
68
+ /** Steps that could not run or finish. Empty on a fully clean flow. */
69
+ problems: RecoveryStepProblem[];
70
+ /** Whether the final verifying sync completed. */
71
+ synced: boolean;
72
+ /**
73
+ * Total note records newly added to the local store by this flow: the
74
+ * drain's imports plus every `imported` outcome from the proposal import
75
+ * and the backfill.
76
+ */
77
+ imported: number;
78
+ /**
79
+ * Whether rerunning the flow can plausibly recover more: any step
80
+ * problem, per-note outcome, or strategy report marked retryable.
81
+ */
82
+ retryable: boolean;
83
+ }
84
+
85
+ /**
86
+ * Options for `Multisig.recoverNotes`. The default runs every strategy over
87
+ * the full chain history and syncs afterwards.
88
+ */
89
+ export interface RecoverNotesOptions {
90
+ /**
91
+ * Rescan the private-note transport backlog (the standalone
92
+ * `drainPrivateNoteBacklog`). Default `true`.
93
+ */
94
+ transportDrain?: boolean;
95
+ /**
96
+ * Import the notes embedded in pending consume-notes proposals
97
+ * (`Multisig.importNotesFromProposals`). Default `true`.
98
+ */
99
+ proposalImport?: boolean;
100
+ /**
101
+ * Scan chain history for public notes addressed at the account's tag
102
+ * (`Multisig.backfillPublicNotesByTag`). Default `true`.
103
+ */
104
+ publicBackfill?: boolean;
105
+ /** First block of the backfill scan; defaults to genesis. */
106
+ fromBlock?: number;
107
+ /** Last block of the backfill scan; defaults to the current chain tip. */
108
+ toBlock?: number;
109
+ /**
110
+ * Run a normal sync after the strategies so imported notes are verified
111
+ * and show up as consumable. Default `true`.
112
+ */
113
+ syncAfter?: boolean;
114
+ }
115
+
116
+ /**
117
+ * The guardian-switch slice of {@link RecoverNotesOptions}: only the
118
+ * proposal-embedded note import runs. Internal — the public entry point is
119
+ * `Multisig.preservePreSwitchProposalNotes`, which adds the switch-specific
120
+ * safety contract (timeout, cancellation, warnings) around this slice. The
121
+ * `satisfies` clause forces every non-range option to be listed, so adding
122
+ * a recovery strategy fails to compile until the switch path decides on it.
123
+ */
124
+ export const GUARDIAN_SWITCH_RECOVERY_OPTIONS = {
125
+ transportDrain: false,
126
+ proposalImport: true,
127
+ publicBackfill: false,
128
+ syncAfter: false,
129
+ } satisfies Required<Omit<RecoverNotesOptions, 'fromBlock' | 'toBlock'>>;
130
+
131
+ /**
132
+ * The strategy implementations `runNoteRecovery` orchestrates. `Multisig`
133
+ * wires these to the SDK primitives; tests can substitute stubs.
134
+ */
135
+ export interface NoteRecoverySteps {
136
+ /** Drain the private-note transport backlog. */
137
+ transportDrain: () => Promise<TransportRecoveryReport>;
138
+ /** Import notes embedded in pending consume-notes proposals. */
139
+ proposalImport: () => Promise<NoteImportOutcome[]>;
140
+ /** Backfill historical public notes by tag. */
141
+ publicBackfill: () => Promise<PublicBackfillReport>;
142
+ /** Run the final verifying sync. */
143
+ sync: () => Promise<void>;
144
+ }
145
+
146
+ function countImported(outcomes: readonly NoteImportOutcome[]): number {
147
+ return outcomes.filter((o) => o.status === 'imported').length;
148
+ }
149
+
150
+ /**
151
+ * Runs the enabled recovery strategies in order — transport drain, proposal
152
+ * import, public backfill, final sync — folding each strategy-level throw
153
+ * into a {@link RecoveryStepProblem} so no step failure aborts the flow.
154
+ *
155
+ * The optional `cancelled` token makes the run cooperatively cancellable:
156
+ * the orchestrator checks it before each step, and a
157
+ * {@link RecoveryCancelledError} from inside a step is recorded as one
158
+ * non-retryable problem on the interrupted step, after which no further
159
+ * step runs — cancellation never masquerades as a GUARDIAN or node outage.
160
+ *
161
+ * Throws only for an inverted backfill range (a caller error). See
162
+ * `Multisig.recoverNotes` for the wallet-facing entry point.
163
+ */
164
+ export async function runNoteRecovery(
165
+ options: RecoverNotesOptions,
166
+ steps: NoteRecoverySteps,
167
+ cancelled?: () => boolean,
168
+ ): Promise<NoteRecoveryReport> {
169
+ if (
170
+ options.fromBlock !== undefined &&
171
+ options.toBlock !== undefined &&
172
+ options.fromBlock > options.toBlock
173
+ ) {
174
+ throw new Error(
175
+ `backfill range is inverted: fromBlock ${options.fromBlock} > toBlock ${options.toBlock}`,
176
+ );
177
+ }
178
+
179
+ const report: NoteRecoveryReport = {
180
+ problems: [],
181
+ synced: false,
182
+ imported: 0,
183
+ retryable: false,
184
+ };
185
+
186
+ // Set once a cancellation is observed (token flipped, or a
187
+ // RecoveryCancelledError surfaced from a step): the remaining steps are
188
+ // skipped and exactly one problem records the interruption point.
189
+ let stopped = false;
190
+ const interrupted = (step: RecoveryStep, err: unknown): boolean => {
191
+ if (!(err instanceof RecoveryCancelledError)) {
192
+ return false;
193
+ }
194
+ report.problems.push({ step, reason: errorMessage(err), retryable: false });
195
+ stopped = true;
196
+ return true;
197
+ };
198
+ const startStep = (step: RecoveryStep): boolean => {
199
+ if (stopped) {
200
+ return false;
201
+ }
202
+ if (cancelled?.()) {
203
+ report.problems.push({
204
+ step,
205
+ reason: 'note recovery stopped before completion by its caller',
206
+ retryable: false,
207
+ });
208
+ stopped = true;
209
+ return false;
210
+ }
211
+ return true;
212
+ };
213
+
214
+ if (options.transportDrain !== false && startStep('transport-drain')) {
215
+ try {
216
+ report.transport = await steps.transportDrain();
217
+ } catch (err) {
218
+ if (!interrupted('transport-drain', err)) {
219
+ // The drain's contract: a throw means the local store itself failed,
220
+ // which a rerun is unlikely to fix.
221
+ report.problems.push({
222
+ step: 'transport-drain',
223
+ reason: errorMessage(err),
224
+ retryable: false,
225
+ });
226
+ }
227
+ }
228
+ }
229
+
230
+ if (options.proposalImport !== false && startStep('proposal-import')) {
231
+ try {
232
+ report.proposalImport = await steps.proposalImport();
233
+ } catch (err) {
234
+ if (!interrupted('proposal-import', err)) {
235
+ report.problems.push({
236
+ step: 'proposal-import',
237
+ reason: `failed to list pending proposals: ${errorMessage(err)}`,
238
+ retryable: true,
239
+ });
240
+ }
241
+ }
242
+ }
243
+
244
+ if (options.publicBackfill !== false && startStep('public-backfill')) {
245
+ try {
246
+ report.backfill = await steps.publicBackfill();
247
+ } catch (err) {
248
+ if (!interrupted('public-backfill', err)) {
249
+ // With the fully-explicit range validated above, a throw here is
250
+ // normally the chain-tip lookup (a node RPC failure, worth retrying);
251
+ // an invalid range against the resolved tip is a caller error and is
252
+ // not — mirroring the Rust orchestrator's `InvalidConfig` check.
253
+ report.problems.push({
254
+ step: 'public-backfill',
255
+ reason: errorMessage(err),
256
+ retryable: !(err instanceof BackfillRangeError),
257
+ });
258
+ }
259
+ }
260
+ }
261
+
262
+ if (options.syncAfter !== false && startStep('sync')) {
263
+ try {
264
+ await steps.sync();
265
+ report.synced = true;
266
+ } catch (err) {
267
+ if (!interrupted('sync', err)) {
268
+ report.problems.push({
269
+ step: 'sync',
270
+ reason: `recovery imports succeeded but the verifying sync failed; rerun a sync: ${errorMessage(err)}`,
271
+ retryable: true,
272
+ });
273
+ }
274
+ }
275
+ }
276
+
277
+ report.imported =
278
+ (report.transport?.imported ?? 0) +
279
+ countImported(report.proposalImport ?? []) +
280
+ countImported(report.backfill?.outcomes ?? []);
281
+ report.retryable =
282
+ report.problems.some((p) => p.retryable) ||
283
+ report.transport?.retryable === true ||
284
+ (report.proposalImport ?? []).some((o) => o.retryable) ||
285
+ report.backfill?.retryable === true;
286
+
287
+ return report;
288
+ }
@@ -0,0 +1,330 @@
1
+ /**
2
+ * Recovery primitives for restoring note state after device loss.
3
+ *
4
+ * After recovery on a fresh device the local store has no note-transport
5
+ * cursor — and in a shared dirty store another account's sync may have
6
+ * advanced the cursor past notes belonging to the newly recovered account.
7
+ * The primitives here rescan sources that normal forward sync would skip.
8
+ */
9
+
10
+ import type { MidenClient } from '@miden-sdk/miden-sdk';
11
+ import { errorMessage, isLikelyNetworkError } from '../connectivity.js';
12
+ import { isTransientRpcError } from '../rpc/errors.js';
13
+
14
+ /**
15
+ * Outcome class of a private-note transport backlog drain:
16
+ * - `completed` — the full transport backlog was scanned; every note the
17
+ * transport still holds for the tracked tags is now in the local store.
18
+ * - `unavailable` — the transport could not be consulted at all: it is
19
+ * disabled for the injected `MidenClient` (no `noteTransportUrl`
20
+ * configured) or unreachable before anything was imported (`imported` is
21
+ * always 0 — a connection lost mid-drain after partial progress reports
22
+ * `failed` instead). The rest of a recovery flow should proceed without
23
+ * transport notes.
24
+ * - `failed` — the drain started but did not finish; the backlog may be
25
+ * partially imported. `retryable` distinguishes transient failures (rerun
26
+ * the drain) from permanent ones.
27
+ */
28
+ export type TransportRecoveryStatus = 'completed' | 'unavailable' | 'failed';
29
+
30
+ /**
31
+ * Result of {@link drainPrivateNoteBacklog}.
32
+ *
33
+ * Transport problems are reported here rather than thrown so a transport
34
+ * failure never aborts the rest of a recovery flow.
35
+ */
36
+ export interface TransportRecoveryReport {
37
+ /** Outcome class of the drain. */
38
+ status: TransportRecoveryStatus;
39
+ /**
40
+ * Number of note records newly imported into the local store by this
41
+ * drain. Can be non-zero even on `failed`: batches imported before the
42
+ * failure stay imported.
43
+ */
44
+ imported: number;
45
+ /**
46
+ * Whether rerunning the drain can plausibly succeed (transient
47
+ * connectivity failures, the upstream pagination convergence guard).
48
+ * Always `false` on `completed`.
49
+ */
50
+ retryable: boolean;
51
+ /** Human-readable cause when the drain did not complete. */
52
+ reason?: string;
53
+ }
54
+
55
+ /**
56
+ * The WASM client surfaces the upstream transport errors as plain messages,
57
+ * so the message text is the only stable join key (same approach as
58
+ * `connectivity.ts`). These fragments come from miden-client's
59
+ * `NoteTransportError` `Display` impls.
60
+ */
61
+ /** Exported for the drift-guard test, which pins them against the shipped WASM binary. */
62
+ export const TRANSPORT_DISABLED_FRAGMENT = 'note transport is disabled';
63
+ /** Exported for the drift-guard test, which pins them against the shipped WASM binary. */
64
+ export const PAGINATION_GUARD_FRAGMENT = 'did not converge';
65
+ /**
66
+ * `NoteTransportError::Network`'s Display prefix — the transport service
67
+ * answered with an error. Exported for the drift-guard test.
68
+ */
69
+ export const TRANSPORT_NETWORK_FRAGMENT = 'note transport network error';
70
+ /**
71
+ * `NoteTransportError::Connection`'s Display prefix — endpoint parsing, TLS
72
+ * configuration, and actual connect failures, indiscriminately. Exported for
73
+ * the drift-guard test.
74
+ */
75
+ export const TRANSPORT_CONNECTION_FRAGMENT = 'connection error';
76
+ /**
77
+ * `RpcError::RequestError`'s Display prefix — a NODE RPC failure (each
78
+ * fetched batch is imported through the node, so this is a mid-drain import
79
+ * failure, not a transport outage). Exported for the drift-guard test.
80
+ */
81
+ export const NODE_RPC_FRAGMENT = 'grpc request failed';
82
+ /**
83
+ * `RpcError::ConnectionError`'s Display text — the NODE could not be
84
+ * reached at all, which mid-drain is the same interrupted-import class as
85
+ * {@link NODE_RPC_FRAGMENT}. Exported for the drift-guard test.
86
+ */
87
+ export const NODE_CONNECT_FRAGMENT = 'failed to connect to the Miden node';
88
+ /**
89
+ * The covered-tags bookkeeping key, mirror of miden-client's
90
+ * `NOTE_TRANSPORT_COVERED_TAGS_KEY` (the JS surface does not re-export it).
91
+ * Exported for the drift-guard test, which pins it against the shipped WASM
92
+ * binary so a silent upstream rename cannot degrade the drain to a no-op.
93
+ */
94
+ export const NOTE_TRANSPORT_COVERED_TAGS_KEY = 'note_transport_covered_tags';
95
+
96
+ /**
97
+ * Mirror of miden-client's `Client::MAX_BACKFILL_TAGS_PER_SYNC`: upstream
98
+ * backfills at most this many uncovered tags per transport sync, deferring
99
+ * the remainder to the next sync.
100
+ */
101
+ const MAX_BACKFILL_TAGS_PER_SYNC = 64;
102
+
103
+ /**
104
+ * Mirror of the Rust SDK's `CONNECT_PERMANENT_SIGNALS`: connection-failure
105
+ * wording a retry cannot fix (misconfigured endpoint, TLS/certificate
106
+ * problems). Everything else connection-shaped is the peer-still-booting
107
+ * case and stays retryable.
108
+ */
109
+ const CONNECT_PERMANENT_SIGNALS = ['certificate', 'tls', 'invalid uri', 'unsupported scheme'];
110
+ /**
111
+ * `ClientError::StoreError`'s Display prefix in the WASM error chain.
112
+ * Exported for the drift-guard test, which pins it against the shipped WASM
113
+ * binary.
114
+ */
115
+ export const STORE_ERROR_FRAGMENT = 'storage error';
116
+
117
+ /**
118
+ * IndexedDB/Dexie error names that mean the local store itself failed —
119
+ * these must reject (matching the Rust `StoreError` branch), never be folded
120
+ * into a transport report.
121
+ */
122
+ const STORE_ERROR_NAMES = ['QuotaExceededError', 'DatabaseClosedError', 'ReadOnlyError', 'TransactionInactiveError'];
123
+
124
+ /**
125
+ * Does this failure mean the local store is broken (as opposed to a
126
+ * transport/node problem)? Kept deliberately narrow: `AbortError` counts
127
+ * only when its message points at an IndexedDB transaction/database abort —
128
+ * network requests also abort, and those stay transport-classified.
129
+ */
130
+ function isLocalStoreError(err: unknown): boolean {
131
+ const message = errorMessage(err);
132
+ const lower = message.toLowerCase();
133
+ if (lower.includes(STORE_ERROR_FRAGMENT)) return true;
134
+ const name =
135
+ typeof err === 'object' && err !== null && 'name' in err && typeof err.name === 'string'
136
+ ? err.name
137
+ : undefined;
138
+ if (name !== undefined && STORE_ERROR_NAMES.includes(name)) return true;
139
+ // The WASM bridge may stringify the underlying store error into the
140
+ // message rather than preserving the name.
141
+ if (STORE_ERROR_NAMES.some((storeName) => message.includes(storeName))) return true;
142
+ if (name === 'AbortError' && (lower.includes('transaction') || lower.includes('database'))) {
143
+ return true;
144
+ }
145
+ return false;
146
+ }
147
+
148
+ interface DrainFailure {
149
+ status: TransportRecoveryStatus;
150
+ retryable: boolean;
151
+ reason: string;
152
+ /**
153
+ * `false` when the failure proves nothing was fetched (disabled transport
154
+ * throws before any scan), so the store does not need re-counting.
155
+ */
156
+ scanned: boolean;
157
+ }
158
+
159
+ function classifyDrainFailure(err: unknown): DrainFailure {
160
+ const reason = errorMessage(err);
161
+ const lower = reason.toLowerCase();
162
+ // No transport configured — retrying cannot help until the MidenClient is
163
+ // rebuilt with a `noteTransportUrl`. Thrown before anything is fetched.
164
+ if (lower.includes(TRANSPORT_DISABLED_FRAGMENT)) {
165
+ return { status: 'unavailable', retryable: false, reason, scanned: false };
166
+ }
167
+ // The upstream convergence guard tripped (the server cursor kept advancing
168
+ // for 1000 iterations without an empty batch) — a server-side bug, not an
169
+ // honest backlog. Retryable in the sense that a rerun is safe (imports are
170
+ // idempotent) and succeeds once the server recovers.
171
+ if (lower.includes(PAGINATION_GUARD_FRAGMENT)) {
172
+ return { status: 'failed', retryable: true, reason, scanned: true };
173
+ }
174
+ // A NODE RPC failure: each fetched batch is imported through the node
175
+ // (inclusion-proof lookup), so this interrupted the drain mid-way — the
176
+ // transport itself was reachable. Mirrors the Rust `ClientError::RpcError`
177
+ // arm: report `failed`, with the shared RPC classifier deciding whether a
178
+ // rerun can help.
179
+ if (lower.includes(NODE_RPC_FRAGMENT) || lower.includes(NODE_CONNECT_FRAGMENT.toLowerCase())) {
180
+ return { status: 'failed', retryable: isTransientRpcError(err), reason, scanned: true };
181
+ }
182
+ // The transport answered with an error — worth retrying once the service
183
+ // recovers.
184
+ if (lower.includes(TRANSPORT_NETWORK_FRAGMENT)) {
185
+ return { status: 'unavailable', retryable: true, reason, scanned: true };
186
+ }
187
+ // `Connection` wraps endpoint parsing, TLS configuration, and actual
188
+ // connect failures indiscriminately; permanent wording (mirroring the Rust
189
+ // SDK's cause-chain classifier) must not tell a recovery flow to loop
190
+ // retrying a client that can never connect.
191
+ if (lower.includes(TRANSPORT_CONNECTION_FRAGMENT)) {
192
+ const permanent = CONNECT_PERMANENT_SIGNALS.some((signal) => lower.includes(signal));
193
+ return { status: 'unavailable', retryable: !permanent, reason, scanned: true };
194
+ }
195
+ // Remaining connectivity-shaped wording (e.g. a raw fetch failure): the
196
+ // transport could not be reached; worth retrying once connectivity
197
+ // returns.
198
+ if (isLikelyNetworkError(err)) {
199
+ return { status: 'unavailable', retryable: true, reason, scanned: true };
200
+ }
201
+ return { status: 'failed', retryable: false, reason, scanned: true };
202
+ }
203
+
204
+ /**
205
+ * Restores the covered-tags value after a failed drain: returning it to its
206
+ * pre-drain state keeps a client that synced fine before the attempt
207
+ * syncing fine after it. Unlike the Rust twin — which merges the snapshot
208
+ * with whatever the interrupted backfill re-covered — this restores the
209
+ * snapshot verbatim (the WASM surface exposes the set only as an opaque
210
+ * value); at worst the next successful drain re-covers tags the failed
211
+ * attempt already handled, which is idempotent. A failed restoring write
212
+ * throws: the store is left with its bookkeeping cleared and subsequent
213
+ * normal syncs may re-drain (and re-fail on) old history — a local-store
214
+ * environment failure the caller must not fold into a transport report.
215
+ */
216
+ async function restoreCoveredTags(
217
+ midenClient: MidenClient,
218
+ snapshot: unknown,
219
+ drainReason: string,
220
+ ): Promise<void> {
221
+ if (snapshot === null || snapshot === undefined) return;
222
+ try {
223
+ await midenClient.settings.set(NOTE_TRANSPORT_COVERED_TAGS_KEY, snapshot);
224
+ } catch (restoreError) {
225
+ throw new Error(
226
+ `the transport drain failed (${drainReason}) and restoring the covered-tags bookkeeping also failed (${errorMessage(restoreError)}); subsequent syncs may re-drain old transport history`,
227
+ );
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Rescans the full private-note transport backlog for every tracked note tag
233
+ * and imports what it finds, regardless of the stored transport
234
+ * cursor. Counterpart of `MultisigClient::drain_private_note_backlog`
235
+ * in the Rust SDK; the WASM boundary exposes only error message text, so
236
+ * failure classification here is message-based, keyed on the upstream
237
+ * Display prefixes pinned by the drift-guard test.
238
+ *
239
+ * Use after account recovery, passing the **same** `MidenClient` instance
240
+ * that was injected into `MultisigClient`: a fresh store has no transport
241
+ * cursor, and in a shared store another account's sync may have advanced the
242
+ * cursor past this account's notes. The drain is idempotent, tag-scoped (the
243
+ * recovered account must already be in the store so its note tag is tracked
244
+ * — `MultisigClient.load` does this), and never regresses an
245
+ * already-advanced cursor.
246
+ *
247
+ * Transport recovery is bounded by the transport service's retention:
248
+ * senders may bypass the transport entirely and relayed blobs are pruned
249
+ * after the retention window, so this is a best-effort rescan, **not** a
250
+ * backup. Transport-disabled and transport-unreachable outcomes are reported
251
+ * in the {@link TransportRecoveryReport} rather than thrown; this function
252
+ * only throws when the local store itself fails.
253
+ *
254
+ * The rescan runs as many transport syncs as the upstream per-sync tag
255
+ * backfill cap requires to cover every tracked tag, and a failed drain
256
+ * restores the pre-drain covered-tags bookkeeping so normal sync keeps
257
+ * working exactly as it did before the attempt.
258
+ */
259
+ export async function drainPrivateNoteBacklog(
260
+ midenClient: MidenClient,
261
+ ): Promise<TransportRecoveryReport> {
262
+ const before = (await midenClient.notes.list()).length;
263
+ // Records are never removed by a drain, so the length delta is the count
264
+ // of newly imported records. Caveat: it is a store delta — notes imported
265
+ // by concurrent activity on the same store (a background sync, another
266
+ // tab) during the drain are attributed to it.
267
+ const importedSince = async (): Promise<number> =>
268
+ Math.max((await midenClient.notes.list()).length - before, 0);
269
+
270
+ // Snapshot the covered-tags value before clearing it: the clear is
271
+ // durable, and upstream re-marks a tag covered only after its backfill
272
+ // succeeds — so without a restore, a drain that fails on a tag with a
273
+ // permanently bad relay blob would leave every tag uncovered and make
274
+ // every subsequent normal sync re-attempt (and fail) the same backfill.
275
+ // Store errors here propagate like the count above.
276
+ const coveredSnapshot = await midenClient.settings.get(NOTE_TRANSPORT_COVERED_TAGS_KEY);
277
+ let cleared = false;
278
+
279
+ try {
280
+ // The incremental fetch doubles as the transport probe: miden-sdk 0.16's
281
+ // syncNoteTransport silently no-ops when the transport is disabled, but
282
+ // this drain must report `unavailable`, and fetchPrivate still throws
283
+ // the upstream disabled error.
284
+ await midenClient.notes.fetchPrivate();
285
+ // miden-sdk 0.16 replaced the explicit full drain with covered-tag
286
+ // bookkeeping inside syncNoteTransport: every tag not yet marked covered
287
+ // is drained from the start with a local cursor (the global cursor is
288
+ // never regressed), then the steady-state fetch runs. Clearing the
289
+ // covered-tags marker first forces that full per-tag re-drain — exactly
290
+ // the recovery semantic this primitive promises. Imports dedupe, so
291
+ // re-draining already-seen history is harmless.
292
+ await midenClient.settings.remove(NOTE_TRANSPORT_COVERED_TAGS_KEY);
293
+ cleared = true;
294
+ // Upstream backfills at most `MAX_BACKFILL_TAGS_PER_SYNC` uncovered tags
295
+ // per sync, so run enough passes to cover every tracked tag before
296
+ // reporting the backlog fully scanned.
297
+ const tagCount = (await midenClient.tags.list()).length;
298
+ const passes = Math.max(1, Math.ceil(tagCount / MAX_BACKFILL_TAGS_PER_SYNC));
299
+ for (let pass = 0; pass < passes; pass += 1) {
300
+ await midenClient.syncNoteTransport();
301
+ }
302
+ } catch (err) {
303
+ // A broken local store is an environment failure, not a transport
304
+ // outcome: the whole recovery flow needs to know, so it propagates
305
+ // (matching the Rust `StoreError` branch) instead of being folded into
306
+ // the report.
307
+ if (isLocalStoreError(err)) {
308
+ throw err;
309
+ }
310
+ const { status, retryable, reason, scanned } = classifyDrainFailure(err);
311
+ if (cleared) {
312
+ await restoreCoveredTags(midenClient, coveredSnapshot, reason);
313
+ }
314
+ // Count even when the drain failed: each fetched batch is imported as it
315
+ // arrives, so notes recovered before the failure stay in the store. A
316
+ // disabled transport throws before fetching anything, so skip the
317
+ // re-count entirely.
318
+ const imported = scanned ? await importedSince() : 0;
319
+ if (status === 'unavailable' && imported > 0) {
320
+ // `unavailable` promises "nothing was imported"; a connection lost
321
+ // mid-drain after partial progress is an interrupted drain, so report
322
+ // it as a failure — keeping the classified retryability, like the
323
+ // Rust twin.
324
+ return { status: 'failed', imported, retryable, reason };
325
+ }
326
+ return { status, imported, retryable, reason };
327
+ }
328
+
329
+ return { status: 'completed', imported: await importedSince(), retryable: false };
330
+ }
@@ -39,7 +39,10 @@ export function buildConsumeNotesTransactionRequestFromNotes(
39
39
 
40
40
  let txBuilder = new TransactionRequestBuilder();
41
41
  txBuilder = txBuilder.withInputNotes(noteAndArgsArray);
42
- txBuilder = txBuilder.withAuthArg(authSaltForBuilder);
42
+ txBuilder = txBuilder.withFeeConversionSalt(authSaltForBuilder);
43
+ // Borrows rather than consumes: the glue passes `__wbg_ptr` without taking it,
44
+ // so the handle stays ours to release once the builder has read it.
45
+ authSaltForBuilder.free?.();
43
46
 
44
47
  if (options.signatureAdviceMap) {
45
48
  txBuilder = txBuilder.extendAdviceMap(options.signatureAdviceMap);
@@ -166,7 +166,10 @@ export function buildP2idTransactionRequest(
166
166
 
167
167
  let txBuilder = new TransactionRequestBuilder();
168
168
  txBuilder = txBuilder.withOwnOutputNotes(outputNotes);
169
- txBuilder = txBuilder.withAuthArg(authSaltForBuilder);
169
+ txBuilder = txBuilder.withFeeConversionSalt(authSaltForBuilder);
170
+ // Borrows rather than consumes: the glue passes `__wbg_ptr` without taking it,
171
+ // so the handle stays ours to release once the builder has read it.
172
+ authSaltForBuilder.free?.();
170
173
 
171
174
  if (options.signatureAdviceMap) {
172
175
  txBuilder = txBuilder.extendAdviceMap(options.signatureAdviceMap);
@@ -9,11 +9,11 @@ import { getRawMidenClient } from '../raw-client.js';
9
9
  import { base64ToUint8Array, uint8ArrayToBase64 } from '../utils/encoding.js';
10
10
 
11
11
  /**
12
- * Index of the first user param carrying the auth-arg salt. The guarded-multisig
12
+ * Index of the first user param carrying the auth args. The guarded-multisig
13
13
  * auth component zeroes user params 0-2 and fills 3-6 with the auth args, matching
14
14
  * `push.0.0.0` ahead of `multisig::auth_tx` in `guarded_multisig.masm`.
15
15
  */
16
- const SALT_USER_PARAM_OFFSET = 3;
16
+ const AUTH_ARG_USER_PARAM_OFFSET = 3;
17
17
 
18
18
  /**
19
19
  * Captures a `ChainAnchor` for the request at the current sync height and
@@ -98,13 +98,19 @@ export function chainAnchorFromBase64(anchorBase64: string): ChainAnchor {
98
98
  }
99
99
 
100
100
  /**
101
- * Reads the auth-arg salt back out of a transaction summary.
101
+ * Reads the auth args back out of a transaction summary.
102
102
  *
103
103
  * Since miden-protocol 0.16-rc the summary binds seven user-defined elements
104
104
  * instead of a dedicated salt word. The guarded-multisig auth component zeroes
105
- * the leading three and passes the auth args as the trailing four, so the salt
106
- * is the tail of `userParams()`.
105
+ * the leading three and passes the auth args as the trailing four, so the auth
106
+ * args are the tail of `userParams()`.
107
+ *
108
+ * This is the auth-arg word, *not* the proposal salt. When the request declares
109
+ * a fee conversion salt, miden-client uses it to commit the native conversion
110
+ * info under `hash(CONVERSION_INFO || SALT)`. That commitment is not invertible
111
+ * to the salt. Keep the salt alongside the proposal — `ProposalMetadata.saltHex`
112
+ * — rather than trying to recover it from the summary.
107
113
  */
108
- export function summarySalt(summary: TransactionSummary): Word {
109
- return Word.newFromFelts(summary.userParams().slice(SALT_USER_PARAM_OFFSET));
114
+ export function summaryAuthArg(summary: TransactionSummary): Word {
115
+ return Word.newFromFelts(summary.userParams().slice(AUTH_ARG_USER_PARAM_OFFSET));
110
116
  }