@openzeppelin/miden-multisig-client 0.18.0-rc.2 → 0.18.0-rc.3

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 (87) hide show
  1. package/README.md +46 -4
  2. package/dist/account/builder.d.ts +3 -4
  3. package/dist/account/builder.d.ts.map +1 -1
  4. package/dist/account/builder.js +6 -13
  5. package/dist/account/builder.js.map +1 -1
  6. package/dist/client.d.ts +11 -4
  7. package/dist/client.d.ts.map +1 -1
  8. package/dist/client.js +8 -3
  9. package/dist/client.js.map +1 -1
  10. package/dist/config.d.ts +3 -0
  11. package/dist/config.d.ts.map +1 -0
  12. package/dist/config.js +14 -0
  13. package/dist/config.js.map +1 -0
  14. package/dist/index.d.ts +3 -3
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +1 -1
  17. package/dist/index.js.map +1 -1
  18. package/dist/multisig.d.ts +40 -9
  19. package/dist/multisig.d.ts.map +1 -1
  20. package/dist/multisig.js +97 -108
  21. package/dist/multisig.js.map +1 -1
  22. package/dist/procedures.d.ts +5 -5
  23. package/dist/procedures.js +5 -5
  24. package/dist/recovery/proposalNoteImport.d.ts +5 -6
  25. package/dist/recovery/proposalNoteImport.d.ts.map +1 -1
  26. package/dist/recovery/proposalNoteImport.js +12 -13
  27. package/dist/recovery/proposalNoteImport.js.map +1 -1
  28. package/dist/recovery/publicNoteBackfill.d.ts +2 -3
  29. package/dist/recovery/publicNoteBackfill.d.ts.map +1 -1
  30. package/dist/recovery/publicNoteBackfill.js +4 -5
  31. package/dist/recovery/publicNoteBackfill.js.map +1 -1
  32. package/dist/transaction/authArgs.d.ts +2 -3
  33. package/dist/transaction/authArgs.d.ts.map +1 -1
  34. package/dist/transaction/authArgs.js +7 -14
  35. package/dist/transaction/authArgs.js.map +1 -1
  36. package/dist/transaction/consumeNotes.d.ts +4 -12
  37. package/dist/transaction/consumeNotes.d.ts.map +1 -1
  38. package/dist/transaction/consumeNotes.js +10 -4
  39. package/dist/transaction/consumeNotes.js.map +1 -1
  40. package/dist/transaction/noteAuthentication.d.ts +2 -3
  41. package/dist/transaction/noteAuthentication.d.ts.map +1 -1
  42. package/dist/transaction/noteAuthentication.js +11 -11
  43. package/dist/transaction/noteAuthentication.js.map +1 -1
  44. package/dist/transaction/options.d.ts +0 -7
  45. package/dist/transaction/options.d.ts.map +1 -1
  46. package/dist/transaction/p2id.d.ts +2 -3
  47. package/dist/transaction/p2id.d.ts.map +1 -1
  48. package/dist/transaction/p2id.js.map +1 -1
  49. package/dist/transaction/summary.d.ts +8 -14
  50. package/dist/transaction/summary.d.ts.map +1 -1
  51. package/dist/transaction/summary.js +64 -20
  52. package/dist/transaction/summary.js.map +1 -1
  53. package/dist/transaction/updateGuardian.d.ts +3 -7
  54. package/dist/transaction/updateGuardian.d.ts.map +1 -1
  55. package/dist/transaction/updateGuardian.js +3 -4
  56. package/dist/transaction/updateGuardian.js.map +1 -1
  57. package/dist/transaction/updateProcedureThreshold.d.ts +3 -8
  58. package/dist/transaction/updateProcedureThreshold.d.ts.map +1 -1
  59. package/dist/transaction/updateProcedureThreshold.js +3 -4
  60. package/dist/transaction/updateProcedureThreshold.js.map +1 -1
  61. package/dist/transaction/updateSigners.d.ts +3 -8
  62. package/dist/transaction/updateSigners.d.ts.map +1 -1
  63. package/dist/transaction/updateSigners.js +3 -4
  64. package/dist/transaction/updateSigners.js.map +1 -1
  65. package/package.json +4 -4
  66. package/src/account/builder.ts +6 -13
  67. package/src/client.ts +13 -10
  68. package/src/config.ts +14 -0
  69. package/src/index.ts +2 -3
  70. package/src/multisig.ts +125 -143
  71. package/src/procedures.ts +5 -5
  72. package/src/recovery/proposalNoteImport.ts +13 -15
  73. package/src/recovery/publicNoteBackfill.ts +6 -6
  74. package/src/transaction/authArgs.ts +15 -21
  75. package/src/transaction/consumeNotes.ts +6 -34
  76. package/src/transaction/noteAuthentication.ts +18 -13
  77. package/src/transaction/options.ts +0 -9
  78. package/src/transaction/p2id.ts +2 -3
  79. package/src/transaction/summary.ts +27 -69
  80. package/src/transaction/updateGuardian.ts +5 -23
  81. package/src/transaction/updateProcedureThreshold.ts +5 -25
  82. package/src/transaction/updateSigners.ts +5 -22
  83. package/dist/raw-client.d.ts +0 -14
  84. package/dist/raw-client.d.ts.map +0 -1
  85. package/dist/raw-client.js +0 -60
  86. package/dist/raw-client.js.map +0 -1
  87. package/src/raw-client.ts +0 -89
package/src/multisig.ts CHANGED
@@ -18,10 +18,7 @@ import type {
18
18
  } from './types.js';
19
19
  import { ProposalSaltMalformedError } from './multisig/authArgErrors.js';
20
20
  import type { ProcedureName } from './procedures.js';
21
- import type {
22
- MidenClient,
23
- WasmWebClient,
24
- } from '@miden-sdk/miden-sdk';
21
+ import type { MidenClient, OutputNoteRecord } from '@miden-sdk/miden-sdk';
25
22
  import {
26
23
  Account,
27
24
  AccountId,
@@ -113,11 +110,7 @@ import {
113
110
  type NoteRecoverySteps,
114
111
  type RecoverNotesOptions,
115
112
  } from './recovery/recoverNotes.js';
116
- import {
117
- getRawMidenClient,
118
- getTransactionProver,
119
- requireMidenRpcEndpoint,
120
- } from './raw-client.js';
113
+ import { requireMidenRpcEndpoint } from './config.js';
121
114
  import {
122
115
  resolveProverConfig,
123
116
  type ResolvedProverConfig,
@@ -145,6 +138,20 @@ export interface AccountState {
145
138
  updatedAt: string;
146
139
  }
147
140
 
141
+ /**
142
+ * Outcome of {@link Multisig.syncState}.
143
+ *
144
+ * `'guardian'`: GUARDIAN's canonical nonce was above the local nonce (or the
145
+ * local store had no account, or the same nonce carried a different
146
+ * commitment), so the state was fetched and reconciled; `state` is what
147
+ * GUARDIAN served. `'local'`: GUARDIAN's canonical nonce was below the local
148
+ * nonce, or equal to it at the same commitment, so the state fetch was
149
+ * skipped and the local account stands.
150
+ */
151
+ export type SyncStateResult =
152
+ | { source: 'guardian'; state: AccountState }
153
+ | { source: 'local'; localNonce: bigint; guardianNonce: bigint };
154
+
148
155
  export interface AccountStateVerificationResult {
149
156
  accountId: string;
150
157
  localCommitment: string;
@@ -327,7 +334,6 @@ export class Multisig {
327
334
  private guardian: GuardianHttpClient;
328
335
  private readonly signer: Signer;
329
336
  private readonly midenClient: MidenClient;
330
- private readonly rawClientPromise: Promise<WasmWebClient>;
331
337
  private readonly proverWorkflow: ProverWorkflow;
332
338
  private readonly rpcConfig: ResolvedRpcConfig;
333
339
  private readonly _accountId: string;
@@ -376,10 +382,9 @@ export class Multisig {
376
382
  this.midenClient = midenClient;
377
383
  this._accountId = accountId ?? (account ? accountIdToHex(account) : '');
378
384
  this.midenRpcEndpoint = requireMidenRpcEndpoint(midenRpcEndpoint);
379
- this.rawClientPromise = getRawMidenClient(midenClient, this.midenRpcEndpoint);
380
385
  this.proverWorkflow = new ProverWorkflow(
381
386
  this.midenClient,
382
- proverConfig ?? resolveProverConfig(undefined, getTransactionProver(midenClient)),
387
+ proverConfig ?? resolveProverConfig(undefined, midenClient.defaultProver),
383
388
  );
384
389
  this.rpcConfig = rpcConfig ?? resolveRpcConfig(undefined);
385
390
  }
@@ -388,10 +393,6 @@ export class Multisig {
388
393
  return this.midenRpcEndpoint;
389
394
  }
390
395
 
391
- private async getRawClient(): Promise<WasmWebClient> {
392
- return this.rawClientPromise;
393
- }
394
-
395
396
  private proposalFactory(): ProposalFactory {
396
397
  return new ProposalFactory({
397
398
  accountId: this._accountId,
@@ -428,7 +429,7 @@ export class Multisig {
428
429
  }
429
430
 
430
431
  /**
431
- * Resolve the account from the web client's store, falling back to the
432
+ * Resolve the account from the Miden client's store, falling back to the
432
433
  * `account` snapshot when the store has no record.
433
434
  *
434
435
  * Transaction execution reads the store, and other flows (e.g. consume-notes
@@ -436,12 +437,11 @@ export class Multisig {
436
437
  * source from the store to see the same state execution will.
437
438
  */
438
439
  async getStoreAccount(): Promise<Account> {
439
- const webClient = await this.getRawClient();
440
- const stored = await retryRpcRead(
441
- () => webClient.getAccount(AccountId.fromHex(this._accountId)),
442
- this.rpcConfig,
443
- );
444
- return stored ?? this.account;
440
+ return (await this.readStoreAccount()) ?? this.account;
441
+ }
442
+
443
+ private readStoreAccount(): Promise<Account | null> {
444
+ return retryRpcRead(() => this.midenClient.accounts.get(this._accountId), this.rpcConfig);
445
445
  }
446
446
 
447
447
  /**
@@ -610,24 +610,37 @@ export class Multisig {
610
610
  /**
611
611
  * Sync account state from GUARDIAN into the local Miden client store.
612
612
  *
613
- * If the GUARDIAN commitment differs from the local commitment (or the account
614
- * is missing locally) and the GUARDIAN state is safe to import, the local store
615
- * is overwritten with the GUARDIAN state. When the GUARDIAN is merely *behind*
613
+ * Starts with the canonical-nonce pre-check (OpenZeppelin/guardian#191):
614
+ * GUARDIAN reports the nonce and commitment of its canonical state, and
615
+ * when that nonce is not above the local account's — at a matching
616
+ * commitment when equal — nothing newer exists to pull, so the state
617
+ * fetch is skipped and the local account stands (`source: 'local'`).
618
+ *
619
+ * Otherwise the state is fetched (`source: 'guardian'`). If the GUARDIAN
620
+ * commitment differs from the local commitment (or the account is missing
621
+ * locally) and the GUARDIAN state is safe to import, the local store is
622
+ * overwritten with the GUARDIAN state. When the GUARDIAN is merely *behind*
616
623
  * local — e.g. the pushed execution delta has not been canonicalized yet
617
624
  * (see OpenZeppelin/guardian#316) — the local state is already ahead and
618
625
  * on-chain-verifiable, so it is kept as authoritative. Either way, config is
619
626
  * refreshed from the resulting account so callers reading `Multisig.account`
620
627
  * (e.g. the UI) observe the current state instead of a stale snapshot.
621
628
  */
622
- async syncState(): Promise<AccountState> {
629
+ async syncState(): Promise<SyncStateResult> {
630
+ const localAccount = await this.readStoreAccount();
631
+
632
+ if (localAccount) {
633
+ const head = await this.guardian.getCanonicalNonce(this._accountId);
634
+ const localNonce = localAccount.nonce().asInt();
635
+ const guardianNonce = BigInt(head.nonce);
636
+ if (this.guardianIsNotAhead(localAccount, localNonce, head.commitment, guardianNonce)) {
637
+ this.refreshConfigFromAccount(localAccount);
638
+ return { source: 'local', localNonce, guardianNonce };
639
+ }
640
+ }
641
+
623
642
  const state = await this.fetchState();
624
- const accountId = AccountId.fromHex(this._accountId);
625
- const webClient = await this.getRawClient();
626
- const localAccount = await retryRpcRead(
627
- () => webClient.getAccount(accountId),
628
- this.rpcConfig,
629
- );
630
- let accountForConfigRefresh: Account | null = localAccount ?? null;
643
+ let accountForConfigRefresh: Account | null = localAccount;
631
644
 
632
645
  const guardianCommitment = normalizeHexWord(state.commitment);
633
646
  const localCommitment = localAccount
@@ -637,24 +650,44 @@ export class Multisig {
637
650
  if (!localAccount || localCommitment !== guardianCommitment) {
638
651
  const accountBytes = base64ToUint8Array(state.stateDataBase64);
639
652
  const incomingAccount = Account.deserialize(accountBytes);
640
- if (await this.isSafeToOverwriteLocalState(incomingAccount, localAccount)) {
641
- await webClient.newAccount(incomingAccount, true);
653
+ if (await this.isSafeToOverwriteLocalState(incomingAccount, localAccount ?? undefined)) {
654
+ await this.midenClient.accounts.insert({ account: incomingAccount, overwrite: true });
642
655
  accountForConfigRefresh = incomingAccount;
643
656
  }
644
657
  }
645
658
 
646
659
  this.refreshConfigFromAccount(accountForConfigRefresh);
647
660
 
648
- return state;
661
+ return { source: 'guardian', state };
662
+ }
663
+
664
+ /**
665
+ * The pre-check decision: GUARDIAN has nothing newer when its canonical
666
+ * nonce is below the local nonce, or equal to it at the same commitment.
667
+ * An equal nonce with a different commitment is divergence, which the
668
+ * full fetch reports (see `isSafeToOverwriteLocalState`).
669
+ */
670
+ private guardianIsNotAhead(
671
+ localAccount: Account,
672
+ localNonce: bigint,
673
+ guardianCommitment: string,
674
+ guardianNonce: bigint,
675
+ ): boolean {
676
+ if (guardianNonce < localNonce) {
677
+ return true;
678
+ }
679
+ if (guardianNonce > localNonce) {
680
+ return false;
681
+ }
682
+ return (
683
+ normalizeHexWord(guardianCommitment) ===
684
+ normalizeHexWord(localAccount.to_commitment().toHex())
685
+ );
649
686
  }
650
687
 
651
688
  async verifyStateCommitment(): Promise<AccountStateVerificationResult> {
652
689
  const accountId = AccountId.fromHex(this._accountId);
653
- const webClient = await this.getRawClient();
654
- const localAccount = await retryRpcRead(
655
- () => webClient.getAccount(accountId),
656
- this.rpcConfig,
657
- );
690
+ const localAccount = await this.readStoreAccount();
658
691
 
659
692
  if (!localAccount) {
660
693
  throw new Error(
@@ -725,14 +758,10 @@ export class Multisig {
725
758
  * fail.
726
759
  */
727
760
  private async syncNetworkOnly(): Promise<Account | null> {
728
- const webClient = await this.getRawClient();
729
- await retryRpcRead(() => webClient.syncState(), this.rpcConfig);
730
- const account = await retryRpcRead(
731
- () => webClient.getAccount(AccountId.fromHex(this._accountId)),
732
- this.rpcConfig,
733
- );
734
- this.refreshConfigFromAccount(account ?? null);
735
- return account ?? null;
761
+ await this.syncChain();
762
+ const account = await this.readStoreAccount();
763
+ this.refreshConfigFromAccount(account);
764
+ return account;
736
765
  }
737
766
 
738
767
  private refreshConfigFromAccount(account: Account | null): void {
@@ -1047,7 +1076,6 @@ export class Multisig {
1047
1076
  ...legacyArgs: never[]
1048
1077
  ): Promise<Proposal> {
1049
1078
  const proposalNonce = resolveProposalNonce('createAddSignerProposal', options, legacyArgs);
1050
- const webClient = await this.getRawClient();
1051
1079
  const targetThreshold = options.newThreshold ?? this.threshold;
1052
1080
  const targetSignerCommitments = [...this.signerCommitments, newCommitment];
1053
1081
  // What `update_signers_and_threshold` rejects on-chain, and what the auth
@@ -1061,13 +1089,13 @@ export class Multisig {
1061
1089
  this.warnOnOverrideDilution(targetSignerCommitments.length);
1062
1090
 
1063
1091
  const { request, salt } = await buildUpdateSignersTransactionRequest(
1064
- webClient,
1092
+ this.midenClient,
1065
1093
  targetThreshold,
1066
1094
  targetSignerCommitments,
1067
1095
  this.proposalRequestOptions(options),
1068
1096
  );
1069
1097
 
1070
- const { summary, anchor } = await executeForSummary(webClient, this._accountId, request);
1098
+ const { summary, anchor } = await executeForSummary(this.midenClient, this._accountId, request);
1071
1099
  const chainAnchor = chainAnchorToBase64(anchor);
1072
1100
  anchor.free();
1073
1101
  const summaryBase64 = uint8ArrayToBase64(summary.serialize());
@@ -1098,7 +1126,6 @@ export class Multisig {
1098
1126
  ...legacyArgs: never[]
1099
1127
  ): Promise<Proposal> {
1100
1128
  const proposalNonce = resolveProposalNonce('createRemoveSignerProposal', options, legacyArgs);
1101
- const webClient = await this.getRawClient();
1102
1129
  const normalizedRemove = signerToRemove.toLowerCase();
1103
1130
  const targetSignerCommitments = this.signerCommitments.filter(
1104
1131
  (c) => c.toLowerCase() !== normalizedRemove
@@ -1120,13 +1147,13 @@ export class Multisig {
1120
1147
  }
1121
1148
 
1122
1149
  const { request, salt } = await buildUpdateSignersTransactionRequest(
1123
- webClient,
1150
+ this.midenClient,
1124
1151
  targetThreshold,
1125
1152
  targetSignerCommitments,
1126
1153
  this.proposalRequestOptions(options),
1127
1154
  );
1128
1155
 
1129
- const { summary, anchor } = await executeForSummary(webClient, this._accountId, request);
1156
+ const { summary, anchor } = await executeForSummary(this.midenClient, this._accountId, request);
1130
1157
  const chainAnchor = chainAnchorToBase64(anchor);
1131
1158
  anchor.free();
1132
1159
  const summaryBase64 = uint8ArrayToBase64(summary.serialize());
@@ -1155,7 +1182,6 @@ export class Multisig {
1155
1182
  options: CreateProposalOptions = {},
1156
1183
  ): Promise<Proposal> {
1157
1184
  const proposalNonce = resolveProposalNonce('createChangeThresholdProposal', options);
1158
- const webClient = await this.getRawClient();
1159
1185
  if (newThreshold < 1 || newThreshold > this.signerCommitments.length) {
1160
1186
  throw new Error(
1161
1187
  `Invalid threshold ${newThreshold}. Must be between 1 and ${this.signerCommitments.length}`
@@ -1167,13 +1193,13 @@ export class Multisig {
1167
1193
  }
1168
1194
 
1169
1195
  const { request, salt } = await buildUpdateSignersTransactionRequest(
1170
- webClient,
1196
+ this.midenClient,
1171
1197
  newThreshold,
1172
1198
  this.signerCommitments,
1173
1199
  this.proposalRequestOptions(options),
1174
1200
  );
1175
1201
 
1176
- const { summary, anchor } = await executeForSummary(webClient, this._accountId, request);
1202
+ const { summary, anchor } = await executeForSummary(this.midenClient, this._accountId, request);
1177
1203
  const chainAnchor = chainAnchorToBase64(anchor);
1178
1204
  anchor.free();
1179
1205
  const summaryBase64 = uint8ArrayToBase64(summary.serialize());
@@ -1197,7 +1223,6 @@ export class Multisig {
1197
1223
  options: CreateProposalOptions = {},
1198
1224
  ): Promise<Proposal> {
1199
1225
  const proposalNonce = resolveProposalNonce('createUpdateProcedureThresholdProposal', options);
1200
- const webClient = await this.getRawClient();
1201
1226
  if (targetThreshold < 0 || targetThreshold > this.signerCommitments.length) {
1202
1227
  throw new Error(
1203
1228
  `Invalid threshold ${targetThreshold}. Must be between 0 and ${this.signerCommitments.length}`
@@ -1216,13 +1241,13 @@ export class Multisig {
1216
1241
  }
1217
1242
 
1218
1243
  const { request, salt } = await buildUpdateProcedureThresholdTransactionRequest(
1219
- webClient,
1244
+ this.midenClient,
1220
1245
  targetProcedure,
1221
1246
  targetThreshold,
1222
1247
  this.proposalRequestOptions(options),
1223
1248
  );
1224
1249
 
1225
- const { summary, anchor } = await executeForSummary(webClient, this._accountId, request);
1250
+ const { summary, anchor } = await executeForSummary(this.midenClient, this._accountId, request);
1226
1251
  const chainAnchor = chainAnchorToBase64(anchor);
1227
1252
  anchor.free();
1228
1253
  const summaryBase64 = uint8ArrayToBase64(summary.serialize());
@@ -1279,7 +1304,6 @@ export class Multisig {
1279
1304
  newGuardianPubkey: string,
1280
1305
  approvalExpirationDelta: number | undefined,
1281
1306
  ): Promise<{ summaryBase64: string; metadata: ProposalMetadata }> {
1282
- const webClient = await this.getRawClient();
1283
1307
  // What `auth_tx_guarded_multisig` asserts after a guardian rotation, checked
1284
1308
  // before any signature is collected.
1285
1309
  validateMultisigConfig({
@@ -1290,7 +1314,7 @@ export class Multisig {
1290
1314
  await this.verifyGuardianEndpointCommitment(newGuardianEndpoint, newGuardianPubkey);
1291
1315
 
1292
1316
  const { request, salt } = await buildUpdateGuardianTransactionRequest(
1293
- webClient,
1317
+ this.midenClient,
1294
1318
  newGuardianPubkey,
1295
1319
  {
1296
1320
  accountId: this._accountId,
@@ -1299,7 +1323,7 @@ export class Multisig {
1299
1323
  },
1300
1324
  );
1301
1325
 
1302
- const { summary, anchor } = await executeForSummary(webClient, this._accountId, request);
1326
+ const { summary, anchor } = await executeForSummary(this.midenClient, this._accountId, request);
1303
1327
  const chainAnchor = chainAnchorToBase64(anchor);
1304
1328
  anchor.free();
1305
1329
  const summaryBase64 = uint8ArrayToBase64(summary.serialize());
@@ -1390,16 +1414,14 @@ export class Multisig {
1390
1414
  options: CreateProposalOptions = {},
1391
1415
  ): Promise<Proposal> {
1392
1416
  const proposalNonce = resolveProposalNonce('createConsumeNotesProposal', options);
1393
- const webClient = await this.getRawClient();
1394
1417
  if (noteIds.length === 0) {
1395
1418
  throw new Error('At least one note ID is required');
1396
1419
  }
1397
1420
 
1398
1421
  // Fetch notes locally (proposer has them per FR-012); embed for v2 verification.
1399
- const rawClient = await getRawMidenClient(webClient);
1400
1422
  const fetchedNotes: Note[] = [];
1401
1423
  for (const noteIdHex of noteIds) {
1402
- const inputNoteRecord = await rawClient.getInputNote(noteIdHex);
1424
+ const inputNoteRecord = await this.midenClient.notes.get(noteIdHex);
1403
1425
  if (!inputNoteRecord) {
1404
1426
  throw new LegacyConsumeNotesNoteMissingError(noteIdHex);
1405
1427
  }
@@ -1412,12 +1434,12 @@ export class Multisig {
1412
1434
  const embeddedNotes = fetchedNotes.map((n) => noteToBase64(n));
1413
1435
 
1414
1436
  const { request, salt } = await buildConsumeNotesTransactionRequestFromNotes(
1415
- webClient,
1437
+ this.midenClient,
1416
1438
  fetchedNotes,
1417
1439
  this.proposalRequestOptions(options),
1418
1440
  );
1419
1441
 
1420
- const { summary, anchor } = await executeForSummary(webClient, this._accountId, request);
1442
+ const { summary, anchor } = await executeForSummary(this.midenClient, this._accountId, request);
1421
1443
  const chainAnchor = chainAnchorToBase64(anchor);
1422
1444
  anchor.free();
1423
1445
  const summaryBase64 = uint8ArrayToBase64(summary.serialize());
@@ -1464,7 +1486,6 @@ export class Multisig {
1464
1486
  ...legacyArgs: never[]
1465
1487
  ): Promise<Proposal> {
1466
1488
  const proposalNonce = resolveProposalNonce('createP2idProposal', options, legacyArgs);
1467
- const webClient = await this.getRawClient();
1468
1489
  if (amount <= 0n) {
1469
1490
  throw new Error('Amount must be greater than 0');
1470
1491
  }
@@ -1473,7 +1494,7 @@ export class Multisig {
1473
1494
  // CreateP2idProposalOptions can't be silently dropped before the builder.
1474
1495
  const { nonce: _nonce, ...noteOptions } = options;
1475
1496
  const { request, salt } = await buildP2idTransactionRequest(
1476
- webClient,
1497
+ this.midenClient,
1477
1498
  this._accountId,
1478
1499
  recipientId,
1479
1500
  faucetId,
@@ -1481,7 +1502,7 @@ export class Multisig {
1481
1502
  noteOptions,
1482
1503
  );
1483
1504
 
1484
- const { summary, anchor } = await executeForSummary(webClient, this._accountId, request);
1505
+ const { summary, anchor } = await executeForSummary(this.midenClient, this._accountId, request);
1485
1506
  const chainAnchor = chainAnchorToBase64(anchor);
1486
1507
  anchor.free();
1487
1508
  const summaryBase64 = uint8ArrayToBase64(summary.serialize());
@@ -1508,49 +1529,25 @@ export class Multisig {
1508
1529
  /**
1509
1530
  * Get notes that can be consumed by this multisig account.
1510
1531
  *
1511
- * Returns a list of notes that are committed on-chain and can be consumed
1512
- * immediately by the multisig account.
1532
+ * Returns the notes the Miden client's last sync screened as consumable by
1533
+ * this account right now; block-locked notes are left out.
1513
1534
  */
1514
1535
  async getConsumableNotes(): Promise<ConsumableNote[]> {
1515
- const accountId = AccountId.fromHex(this._accountId);
1516
- const webClient = await this.getRawClient();
1536
+ const records = await this.midenClient.notes.listAvailable({ account: this._accountId });
1517
1537
 
1518
- // Get consumable notes for this account
1519
- const consumableRecords = await webClient.getConsumableNotes(accountId);
1520
-
1521
- // Convert to our simplified ConsumableNote type
1522
1538
  const notes: ConsumableNote[] = [];
1523
- for (const record of consumableRecords) {
1524
- const inputNote = record.inputNoteRecord();
1525
- const consumability = record.noteConsumability();
1526
-
1527
- // Only include notes that can be consumed now (consumableAfterBlock is undefined/null)
1528
- const canConsumeNow = consumability.some(
1529
- (c) => c.accountId().toString().toLowerCase() === this._accountId.toLowerCase() &&
1530
- c.consumptionStatus().consumableAfterBlock() === undefined
1531
- );
1532
-
1533
- if (canConsumeNow) {
1534
- // Miden 0.15: InputNoteRecord.id() is `NoteId | undefined`; skip id-less records.
1535
- const id = inputNote.id();
1536
- if (id === undefined) {
1537
- continue;
1538
- }
1539
- const noteId = id.toString();
1540
- const details = inputNote.details();
1541
- const fungibleAssets = details.assets().fungibleAssets();
1542
-
1543
- // Extract assets
1544
- const assets: NoteAsset[] = [];
1545
- for (const asset of fungibleAssets) {
1546
- assets.push({
1547
- faucetId: asset.faucetId().toString(),
1548
- amount: asset.amount(),
1549
- });
1550
- }
1551
-
1552
- notes.push({ id: noteId, assets });
1539
+ for (const inputNote of records) {
1540
+ // Miden 0.15: InputNoteRecord.id() is `NoteId | undefined`; skip id-less records.
1541
+ const id = inputNote.id();
1542
+ if (id === undefined) {
1543
+ continue;
1553
1544
  }
1545
+ const assets: NoteAsset[] = inputNote
1546
+ .details()
1547
+ .assets()
1548
+ .fungibleAssets()
1549
+ .map((asset) => ({ faucetId: asset.faucetId().toString(), amount: asset.amount() }));
1550
+ notes.push({ id: id.toString(), assets });
1554
1551
  }
1555
1552
 
1556
1553
  return notes;
@@ -1574,12 +1571,11 @@ export class Multisig {
1574
1571
  * @returns Serialized note file bytes
1575
1572
  */
1576
1573
  async exportNoteToBytes(noteId: string): Promise<Uint8Array> {
1577
- const webClient = await this.getRawClient();
1578
1574
  const trimmedNoteId = noteId.trim();
1579
1575
 
1580
- let record;
1576
+ let record: OutputNoteRecord | undefined;
1581
1577
  try {
1582
- record = await webClient.getOutputNote(trimmedNoteId);
1578
+ [record] = await this.midenClient.notes.listSent({ ids: [trimmedNoteId] });
1583
1579
  } catch (err) {
1584
1580
  const detail = err instanceof Error ? err.message : String(err);
1585
1581
  throw new Error(
@@ -1595,7 +1591,7 @@ export class Multisig {
1595
1591
  const format = record.inclusionProof()
1596
1592
  ? NoteExportFormat.Full
1597
1593
  : NoteExportFormat.Details;
1598
- const noteFile = await webClient.exportNoteFile(trimmedNoteId, format);
1594
+ const noteFile = await this.midenClient.notes.export(trimmedNoteId, { format });
1599
1595
  return noteFile.serialize();
1600
1596
  }
1601
1597
 
@@ -1642,8 +1638,6 @@ export class Multisig {
1642
1638
  * commitment for a details-only file
1643
1639
  */
1644
1640
  async importNoteFromBytes(noteBytes: Uint8Array): Promise<string> {
1645
- const webClient = await this.getRawClient();
1646
-
1647
1641
  let noteFile: NoteFile;
1648
1642
  try {
1649
1643
  noteFile = NoteFile.deserialize(noteBytes);
@@ -1652,7 +1646,7 @@ export class Multisig {
1652
1646
  throw new Error(`failed to decode note file: ${detail}`);
1653
1647
  }
1654
1648
 
1655
- return webClient.importNoteFile(noteFile);
1649
+ return this.midenClient.notes.import(noteFile);
1656
1650
  }
1657
1651
 
1658
1652
  /**
@@ -2160,10 +2154,7 @@ export class Multisig {
2160
2154
  * syncing first when this client is still below the block the request binds.
2161
2155
  */
2162
2156
  private async submitAtTip(request: TransactionRequest): Promise<void> {
2163
- const webClient = await this.getRawClient();
2164
- await prepareTipExecution(webClient, request, () =>
2165
- retryRpcRead(() => webClient.syncState(), this.rpcConfig),
2166
- );
2157
+ await prepareTipExecution(this.midenClient, request, () => this.syncChain());
2167
2158
  await this.proverWorkflow.submit(AccountId.fromHex(this._accountId), request);
2168
2159
  }
2169
2160
 
@@ -2173,16 +2164,12 @@ export class Multisig {
2173
2164
  * store's sync height, which a node prunes about 50 blocks later.
2174
2165
  */
2175
2166
  private async syncChain(): Promise<void> {
2176
- const webClient = await this.getRawClient();
2177
- await retryRpcRead(() => webClient.syncState(), this.rpcConfig);
2167
+ await retryRpcRead(() => this.midenClient.syncChain(), this.rpcConfig);
2178
2168
  }
2179
2169
 
2180
2170
  /** {@link syncToBoundBlock} with this client's RPC retry policy. */
2181
2171
  private async syncToBoundBlock(boundBlockNum: number): Promise<void> {
2182
- const webClient = await this.getRawClient();
2183
- await syncToBoundBlock(webClient, boundBlockNum, () =>
2184
- retryRpcRead(() => webClient.syncState(), this.rpcConfig),
2185
- );
2172
+ await syncToBoundBlock(this.midenClient, boundBlockNum, () => this.syncChain());
2186
2173
  }
2187
2174
 
2188
2175
  /**
@@ -2213,9 +2200,8 @@ export class Multisig {
2213
2200
  );
2214
2201
  }
2215
2202
 
2216
- const webClient = await this.getRawClient();
2217
2203
  const request = deserializeTransactionRequest(transactionRequestBytes);
2218
- const { summary, anchor } = await executeForSummary(webClient, this._accountId, request);
2204
+ const { summary, anchor } = await executeForSummary(this.midenClient, this._accountId, request);
2219
2205
  const chainAnchor = chainAnchorToBase64(anchor);
2220
2206
  anchor.free();
2221
2207
  const summaryBase64 = uint8ArrayToBase64(summary.serialize());
@@ -2298,8 +2284,7 @@ export class Multisig {
2298
2284
  } finally {
2299
2285
  anchor.free();
2300
2286
  }
2301
- const webClient = await this.getRawClient();
2302
- const derived = await executeForSummaryAtTip(webClient, this._accountId, bindingRequest);
2287
+ const derived = await executeForSummaryAtTip(this.midenClient, this._accountId, bindingRequest);
2303
2288
  const derivedCommitmentHex = normalizeHexWord(derived.toCommitment().toHex());
2304
2289
  if (derivedCommitmentHex !== signedCommitmentHex) {
2305
2290
  throw new Error(
@@ -2874,7 +2859,6 @@ export class Multisig {
2874
2859
  // to the block the summary binds; a cosigner that has only just loaded
2875
2860
  // the account has not.
2876
2861
  await this.syncToBoundBlock(binding.boundBlockNum);
2877
- const webClient = await this.getRawClient();
2878
2862
 
2879
2863
  // A consume-notes summary commits to *authenticated* consumption (see
2880
2864
  // ensureNotesAuthenticated), which miden-client decides from this store
@@ -2888,7 +2872,7 @@ export class Multisig {
2888
2872
  }
2889
2873
 
2890
2874
  const request = await this.buildTransactionRequestFromMetadata(proposal.metadata, binding);
2891
- const reconstructed = await executeForSummaryAtTip(webClient, this._accountId, request);
2875
+ const reconstructed = await executeForSummaryAtTip(this.midenClient, this._accountId, request);
2892
2876
  const reconstructedCommitment = normalizeHexWord(reconstructed.toCommitment().toHex());
2893
2877
 
2894
2878
  if (reconstructedCommitment !== txSummaryCommitment) {
@@ -2978,8 +2962,7 @@ export class Multisig {
2978
2962
  if (expirationBlockNum === undefined) {
2979
2963
  return;
2980
2964
  }
2981
- const webClient = await this.getRawClient();
2982
- const syncHeight = await webClient.getSyncHeight();
2965
+ const syncHeight = await this.midenClient.getSyncHeight();
2983
2966
  if (syncHeight >= expirationBlockNum) {
2984
2967
  throw new Error(
2985
2968
  `Proposal ${proposalId} approval expired at block ${expirationBlockNum}; the chain is at ` +
@@ -3018,13 +3001,12 @@ export class Multisig {
3018
3001
  metadata: ProposalMetadata,
3019
3002
  requestOptions: MultisigRequestOptions,
3020
3003
  ): Promise<TransactionRequest> {
3021
- const webClient = await this.getRawClient();
3022
3004
  switch (metadata.proposalType) {
3023
3005
  case 'add_signer':
3024
3006
  case 'remove_signer':
3025
3007
  case 'change_threshold': {
3026
3008
  const { request } = await buildUpdateSignersTransactionRequest(
3027
- webClient,
3009
+ this.midenClient,
3028
3010
  metadata.targetThreshold,
3029
3011
  metadata.targetSignerCommitments,
3030
3012
  requestOptions,
@@ -3033,7 +3015,7 @@ export class Multisig {
3033
3015
  }
3034
3016
  case 'switch_guardian': {
3035
3017
  const { request } = await buildUpdateGuardianTransactionRequest(
3036
- webClient,
3018
+ this.midenClient,
3037
3019
  metadata.newGuardianPubkey,
3038
3020
  requestOptions,
3039
3021
  );
@@ -3041,7 +3023,7 @@ export class Multisig {
3041
3023
  }
3042
3024
  case 'update_procedure_threshold': {
3043
3025
  const { request } = await buildUpdateProcedureThresholdTransactionRequest(
3044
- webClient,
3026
+ this.midenClient,
3045
3027
  metadata.targetProcedure,
3046
3028
  metadata.targetThreshold,
3047
3029
  requestOptions,
@@ -3054,7 +3036,7 @@ export class Multisig {
3054
3036
  if (version === CONSUME_NOTES_METADATA_VERSION_V2) {
3055
3037
  const decoded = decodeEmbeddedConsumeNotes(metadata);
3056
3038
  const { request } = await buildConsumeNotesTransactionRequestFromNotes(
3057
- webClient,
3039
+ this.midenClient,
3058
3040
  decoded,
3059
3041
  requestOptions,
3060
3042
  );
@@ -3067,7 +3049,7 @@ export class Multisig {
3067
3049
  throw new UnsupportedMetadataVersionError(version);
3068
3050
  }
3069
3051
  const { request } = await buildConsumeNotesTransactionRequest(
3070
- webClient,
3052
+ this.midenClient,
3071
3053
  metadata.noteIds,
3072
3054
  requestOptions,
3073
3055
  );
@@ -3077,7 +3059,7 @@ export class Multisig {
3077
3059
  }
3078
3060
  case 'p2id': {
3079
3061
  const { request } = await buildP2idTransactionRequest(
3080
- webClient,
3062
+ this.midenClient,
3081
3063
  this._accountId,
3082
3064
  metadata.recipientId,
3083
3065
  metadata.faucetId,
package/src/procedures.ts CHANGED
@@ -11,11 +11,11 @@
11
11
  * human-readable encoding and should not be copied into this table.
12
12
  */
13
13
  export const PROCEDURE_ROOTS = {
14
- update_signers: '0xe39b380d435dd42206fd625fcddfd26a379a2312cfef0271e9b5f18cbdec67e5',
15
- update_procedure_threshold: '0x5de3563f30c5dd130da49c8fdd86d867fed6dbbd928363021c53ef99d8034bac',
16
- auth_tx: '0xf988ff88c7a9c2104d77862d580239ec40e060a9b4d2d96028135abeb8cc58bc',
14
+ update_signers: '0x0f664cdaae422fe43bb45d959c7e469b0c855ad1fc4e79fddd730ec3cb983c6e',
15
+ update_procedure_threshold: '0xa32cd13808fd8fb91adb3dceaed4d8faebc3bd80fa49c2903be4b8d7bf89ba77',
16
+ auth_tx: '0x71ba7380c6138d5e80e911094a9767ed0fa5c8ffefcf6a776754bfc75bc163b9',
17
17
  update_guardian: '0x93dedb135fd5bb7112c07aacf4a5680ddc76e45ecf043bfc42ea735b6d971911',
18
- send_asset: '0x936e9920bffd7f458cc9ba2c4bbcc018fc4d3561511d79635955129268039dd7',
18
+ send_asset: '0xf261e7bdd1faee5db3b0abe4bb67b153fbca6ece3e456b83eff98697f21f6a97',
19
19
  receive_asset: '0xd7416b798a70aabbca510c3cd0f48ba35473b5d76dc302375157c6f563fffc15',
20
20
  } as const;
21
21
 
@@ -33,7 +33,7 @@ export type ProcedureName = keyof typeof PROCEDURE_ROOTS;
33
33
  * @example
34
34
  * ```typescript
35
35
  * const root = getProcedureRoot('send_asset');
36
- * // '0x936e9920bffd7f458cc9ba2c4bbcc018fc4d3561511d79635955129268039dd7'
36
+ * // '0xf261e7bdd1faee5db3b0abe4bb67b153fbca6ece3e456b83eff98697f21f6a97'
37
37
  * ```
38
38
  */
39
39
  export function getProcedureRoot(name: ProcedureName): string {