@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.
- package/README.md +46 -4
- package/dist/account/builder.d.ts +3 -4
- package/dist/account/builder.d.ts.map +1 -1
- package/dist/account/builder.js +6 -13
- package/dist/account/builder.js.map +1 -1
- package/dist/client.d.ts +11 -4
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +8 -3
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +14 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/multisig.d.ts +40 -9
- package/dist/multisig.d.ts.map +1 -1
- package/dist/multisig.js +97 -108
- package/dist/multisig.js.map +1 -1
- package/dist/procedures.d.ts +5 -5
- package/dist/procedures.js +5 -5
- package/dist/recovery/proposalNoteImport.d.ts +5 -6
- package/dist/recovery/proposalNoteImport.d.ts.map +1 -1
- package/dist/recovery/proposalNoteImport.js +12 -13
- package/dist/recovery/proposalNoteImport.js.map +1 -1
- package/dist/recovery/publicNoteBackfill.d.ts +2 -3
- package/dist/recovery/publicNoteBackfill.d.ts.map +1 -1
- package/dist/recovery/publicNoteBackfill.js +4 -5
- package/dist/recovery/publicNoteBackfill.js.map +1 -1
- package/dist/transaction/authArgs.d.ts +2 -3
- package/dist/transaction/authArgs.d.ts.map +1 -1
- package/dist/transaction/authArgs.js +7 -14
- package/dist/transaction/authArgs.js.map +1 -1
- package/dist/transaction/consumeNotes.d.ts +4 -12
- package/dist/transaction/consumeNotes.d.ts.map +1 -1
- package/dist/transaction/consumeNotes.js +10 -4
- package/dist/transaction/consumeNotes.js.map +1 -1
- package/dist/transaction/noteAuthentication.d.ts +2 -3
- package/dist/transaction/noteAuthentication.d.ts.map +1 -1
- package/dist/transaction/noteAuthentication.js +11 -11
- package/dist/transaction/noteAuthentication.js.map +1 -1
- package/dist/transaction/options.d.ts +0 -7
- package/dist/transaction/options.d.ts.map +1 -1
- package/dist/transaction/p2id.d.ts +2 -3
- package/dist/transaction/p2id.d.ts.map +1 -1
- package/dist/transaction/p2id.js.map +1 -1
- package/dist/transaction/summary.d.ts +8 -14
- package/dist/transaction/summary.d.ts.map +1 -1
- package/dist/transaction/summary.js +64 -20
- package/dist/transaction/summary.js.map +1 -1
- package/dist/transaction/updateGuardian.d.ts +3 -7
- package/dist/transaction/updateGuardian.d.ts.map +1 -1
- package/dist/transaction/updateGuardian.js +3 -4
- package/dist/transaction/updateGuardian.js.map +1 -1
- package/dist/transaction/updateProcedureThreshold.d.ts +3 -8
- package/dist/transaction/updateProcedureThreshold.d.ts.map +1 -1
- package/dist/transaction/updateProcedureThreshold.js +3 -4
- package/dist/transaction/updateProcedureThreshold.js.map +1 -1
- package/dist/transaction/updateSigners.d.ts +3 -8
- package/dist/transaction/updateSigners.d.ts.map +1 -1
- package/dist/transaction/updateSigners.js +3 -4
- package/dist/transaction/updateSigners.js.map +1 -1
- package/package.json +4 -4
- package/src/account/builder.ts +6 -13
- package/src/client.ts +13 -10
- package/src/config.ts +14 -0
- package/src/index.ts +2 -3
- package/src/multisig.ts +125 -143
- package/src/procedures.ts +5 -5
- package/src/recovery/proposalNoteImport.ts +13 -15
- package/src/recovery/publicNoteBackfill.ts +6 -6
- package/src/transaction/authArgs.ts +15 -21
- package/src/transaction/consumeNotes.ts +6 -34
- package/src/transaction/noteAuthentication.ts +18 -13
- package/src/transaction/options.ts +0 -9
- package/src/transaction/p2id.ts +2 -3
- package/src/transaction/summary.ts +27 -69
- package/src/transaction/updateGuardian.ts +5 -23
- package/src/transaction/updateProcedureThreshold.ts +5 -25
- package/src/transaction/updateSigners.ts +5 -22
- package/dist/raw-client.d.ts +0 -14
- package/dist/raw-client.d.ts.map +0 -1
- package/dist/raw-client.js +0 -60
- package/dist/raw-client.js.map +0 -1
- 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,
|
|
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
|
|
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
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
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
|
-
*
|
|
614
|
-
*
|
|
615
|
-
* is
|
|
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<
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
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
|
-
|
|
1092
|
+
this.midenClient,
|
|
1065
1093
|
targetThreshold,
|
|
1066
1094
|
targetSignerCommitments,
|
|
1067
1095
|
this.proposalRequestOptions(options),
|
|
1068
1096
|
);
|
|
1069
1097
|
|
|
1070
|
-
const { summary, anchor } = await executeForSummary(
|
|
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
|
-
|
|
1150
|
+
this.midenClient,
|
|
1124
1151
|
targetThreshold,
|
|
1125
1152
|
targetSignerCommitments,
|
|
1126
1153
|
this.proposalRequestOptions(options),
|
|
1127
1154
|
);
|
|
1128
1155
|
|
|
1129
|
-
const { summary, anchor } = await executeForSummary(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
1244
|
+
this.midenClient,
|
|
1220
1245
|
targetProcedure,
|
|
1221
1246
|
targetThreshold,
|
|
1222
1247
|
this.proposalRequestOptions(options),
|
|
1223
1248
|
);
|
|
1224
1249
|
|
|
1225
|
-
const { summary, anchor } = await executeForSummary(
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
1437
|
+
this.midenClient,
|
|
1416
1438
|
fetchedNotes,
|
|
1417
1439
|
this.proposalRequestOptions(options),
|
|
1418
1440
|
);
|
|
1419
1441
|
|
|
1420
|
-
const { summary, anchor } = await executeForSummary(
|
|
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
|
-
|
|
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(
|
|
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
|
|
1512
|
-
*
|
|
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
|
|
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
|
|
1524
|
-
|
|
1525
|
-
const
|
|
1526
|
-
|
|
1527
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: '
|
|
15
|
-
update_procedure_threshold: '
|
|
16
|
-
auth_tx: '
|
|
14
|
+
update_signers: '0x0f664cdaae422fe43bb45d959c7e469b0c855ad1fc4e79fddd730ec3cb983c6e',
|
|
15
|
+
update_procedure_threshold: '0xa32cd13808fd8fb91adb3dceaed4d8faebc3bd80fa49c2903be4b8d7bf89ba77',
|
|
16
|
+
auth_tx: '0x71ba7380c6138d5e80e911094a9767ed0fa5c8ffefcf6a776754bfc75bc163b9',
|
|
17
17
|
update_guardian: '0x93dedb135fd5bb7112c07aacf4a5680ddc76e45ecf043bfc42ea735b6d971911',
|
|
18
|
-
send_asset: '
|
|
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
|
-
* // '
|
|
36
|
+
* // '0xf261e7bdd1faee5db3b0abe4bb67b153fbca6ece3e456b83eff98697f21f6a97'
|
|
37
37
|
* ```
|
|
38
38
|
*/
|
|
39
39
|
export function getProcedureRoot(name: ProcedureName): string {
|