@openzeppelin/miden-multisig-client 0.14.8 → 0.14.9
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 +47 -0
- package/dist/multisig/proposal/execution.js +3 -2
- package/dist/multisig/proposal/execution.js.map +1 -1
- package/dist/multisig.d.ts +28 -5
- package/dist/multisig.d.ts.map +1 -1
- package/dist/multisig.js +173 -5
- package/dist/multisig.js.map +1 -1
- package/dist/multisig.test.js +110 -0
- package/dist/multisig.test.js.map +1 -1
- package/dist/proposal/metadata.d.ts.map +1 -1
- package/dist/proposal/metadata.js +14 -5
- package/dist/proposal/metadata.js.map +1 -1
- package/dist/proposal/metadata.test.js +44 -0
- package/dist/proposal/metadata.test.js.map +1 -1
- package/dist/types/proposal.d.ts +16 -5
- package/dist/types/proposal.d.ts.map +1 -1
- package/dist/types/proposal.js.map +1 -1
- package/package.json +2 -2
- package/src/multisig.test.ts +139 -0
- package/src/multisig.ts +260 -5
- package/src/proposal/metadata.test.ts +53 -1
- package/src/proposal/metadata.ts +15 -6
- package/src/types/proposal.ts +24 -9
- package/src/multisig/proposal/execution.ts +0 -385
- package/src/multisig/proposal/parser.ts +0 -191
package/src/proposal/metadata.ts
CHANGED
|
@@ -48,8 +48,9 @@ export class ProposalMetadataCodec {
|
|
|
48
48
|
targetThreshold: metadata.targetThreshold,
|
|
49
49
|
signerCommitments: metadata.targetSignerCommitments,
|
|
50
50
|
};
|
|
51
|
-
case '
|
|
52
|
-
|
|
51
|
+
case 'custom':
|
|
52
|
+
// Round-trip the original server label rather than the 'custom' bucket.
|
|
53
|
+
return { ...base, proposalType: metadata.rawProposalType };
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
56
|
|
|
@@ -120,12 +121,18 @@ export class ProposalMetadataCodec {
|
|
|
120
121
|
}
|
|
121
122
|
return {
|
|
122
123
|
...base,
|
|
123
|
-
proposalType: guardian.proposalType,
|
|
124
|
+
proposalType: guardian.proposalType as 'add_signer' | 'remove_signer' | 'change_threshold',
|
|
124
125
|
targetThreshold: guardian.targetThreshold,
|
|
125
126
|
targetSignerCommitments: guardian.signerCommitments,
|
|
126
127
|
};
|
|
127
128
|
default:
|
|
128
|
-
|
|
129
|
+
// Any proposal type the SDK does not model collapses to the 'custom'
|
|
130
|
+
// bucket while preserving the original label (issue #266).
|
|
131
|
+
return {
|
|
132
|
+
...base,
|
|
133
|
+
proposalType: 'custom',
|
|
134
|
+
rawProposalType: guardian.proposalType,
|
|
135
|
+
};
|
|
129
136
|
}
|
|
130
137
|
}
|
|
131
138
|
|
|
@@ -162,8 +169,10 @@ export class ProposalMetadataCodec {
|
|
|
162
169
|
throw new Error('p2id proposal metadata is incomplete');
|
|
163
170
|
}
|
|
164
171
|
return metadata;
|
|
165
|
-
case '
|
|
166
|
-
|
|
172
|
+
case 'custom':
|
|
173
|
+
// Custom proposals are opaque to the SDK; nothing to validate beyond
|
|
174
|
+
// the base fields. They can be listed/signed/exported, not built here.
|
|
175
|
+
return metadata;
|
|
167
176
|
}
|
|
168
177
|
}
|
|
169
178
|
}
|
package/src/types/proposal.ts
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ProposalSignature,
|
|
3
|
-
ProposalType as GuardianProposalType,
|
|
4
|
-
SignatureScheme,
|
|
5
|
-
} from '@openzeppelin/guardian-client';
|
|
1
|
+
import type { ProposalSignature, SignatureScheme } from '@openzeppelin/guardian-client';
|
|
6
2
|
import type { ProcedureName } from '../procedures.js';
|
|
7
3
|
|
|
8
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Closed set of proposal types the multisig SDK models behaviorally, plus the
|
|
6
|
+
* `'custom'` bucket for any server-defined type the SDK does not model (issue
|
|
7
|
+
* #266). Defined explicitly (not derived from the now-arbitrary guardian-client
|
|
8
|
+
* wire union) so the exhaustive switches in the metadata codec stay sound.
|
|
9
|
+
*/
|
|
10
|
+
export type ProposalType =
|
|
11
|
+
| 'add_signer'
|
|
12
|
+
| 'remove_signer'
|
|
13
|
+
| 'change_threshold'
|
|
14
|
+
| 'update_procedure_threshold'
|
|
15
|
+
| 'switch_guardian'
|
|
16
|
+
| 'consume_notes'
|
|
17
|
+
| 'p2id'
|
|
18
|
+
| 'custom';
|
|
9
19
|
|
|
10
20
|
export type ProposalStatus = 'pending' | 'ready' | 'finalized';
|
|
11
21
|
|
|
@@ -79,8 +89,13 @@ export interface P2IdProposalMetadata extends BaseProposalMetadata {
|
|
|
79
89
|
amount: string;
|
|
80
90
|
}
|
|
81
91
|
|
|
82
|
-
export interface
|
|
83
|
-
proposalType: '
|
|
92
|
+
export interface CustomProposalMetadata extends BaseProposalMetadata {
|
|
93
|
+
proposalType: 'custom';
|
|
94
|
+
/** Original server-defined proposal label, e.g. "b2agg" (issue #266). Mirrors
|
|
95
|
+
* Rust `ProposalMetadata.proposal_type`; it is what lets a custom proposal
|
|
96
|
+
* round-trip back to GUARDIAN/export, so it is required in the domain model.
|
|
97
|
+
* Any wire-level optionality is resolved in the parser/codec boundary. */
|
|
98
|
+
rawProposalType: string;
|
|
84
99
|
}
|
|
85
100
|
|
|
86
101
|
export type ProposalMetadata =
|
|
@@ -89,7 +104,7 @@ export type ProposalMetadata =
|
|
|
89
104
|
| UpdateProcedureThresholdProposalMetadata
|
|
90
105
|
| ConsumeNotesProposalMetadata
|
|
91
106
|
| P2IdProposalMetadata
|
|
92
|
-
|
|
|
107
|
+
| CustomProposalMetadata;
|
|
93
108
|
|
|
94
109
|
export interface Proposal {
|
|
95
110
|
id: string;
|
|
@@ -1,385 +0,0 @@
|
|
|
1
|
-
import { type DeltaObject, type GuardianHttpClient } from '@openzeppelin/guardian-client';
|
|
2
|
-
import type {
|
|
3
|
-
MidenClient,
|
|
4
|
-
TransactionProver,
|
|
5
|
-
TransactionRequest,
|
|
6
|
-
} from '@miden-sdk/miden-sdk';
|
|
7
|
-
import {
|
|
8
|
-
AdviceMap,
|
|
9
|
-
FeltArray,
|
|
10
|
-
Signature,
|
|
11
|
-
TransactionSummary,
|
|
12
|
-
Word,
|
|
13
|
-
} from '@miden-sdk/miden-sdk';
|
|
14
|
-
import type { ProposalType, TransactionProposal } from '../../types.js';
|
|
15
|
-
import {
|
|
16
|
-
buildConsumeNotesTransactionRequest,
|
|
17
|
-
buildP2idTransactionRequest,
|
|
18
|
-
buildUpdateGuardianTransactionRequest,
|
|
19
|
-
buildUpdateProcedureThresholdTransactionRequest,
|
|
20
|
-
buildUpdateSignersTransactionRequest,
|
|
21
|
-
} from '../../transaction.js';
|
|
22
|
-
import { base64ToUint8Array, normalizeHexWord } from '../../utils/encoding.js';
|
|
23
|
-
import {
|
|
24
|
-
buildSignatureAdviceEntry,
|
|
25
|
-
signatureHexToBytes,
|
|
26
|
-
tryComputeEcdsaCommitmentHex,
|
|
27
|
-
} from '../../utils/signature.js';
|
|
28
|
-
import { computeCommitmentFromTxSummary } from '../helpers.js';
|
|
29
|
-
|
|
30
|
-
interface ResolveExecutionSourceResult {
|
|
31
|
-
delta?: DeltaObject;
|
|
32
|
-
txSummaryBase64: string;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
interface PrepareExecutionResult {
|
|
36
|
-
txSummary: TransactionSummary;
|
|
37
|
-
saltHex: string;
|
|
38
|
-
txCommitmentHex: string;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
interface ExecuteProposalWorkflowParams {
|
|
42
|
-
proposal: TransactionProposal;
|
|
43
|
-
accountId: string;
|
|
44
|
-
threshold: number;
|
|
45
|
-
signerCommitments: string[];
|
|
46
|
-
guardianCommitment: string;
|
|
47
|
-
guardianPublicKey?: string;
|
|
48
|
-
signatureScheme: 'falcon' | 'ecdsa';
|
|
49
|
-
getEffectiveThreshold: (proposalType: ProposalType) => number;
|
|
50
|
-
guardian: GuardianHttpClient;
|
|
51
|
-
midenClient: MidenClient;
|
|
52
|
-
transactionProver?: TransactionProver | null;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export async function executeProposalWorkflow(
|
|
56
|
-
params: ExecuteProposalWorkflowParams,
|
|
57
|
-
): Promise<void> {
|
|
58
|
-
ensureProposalReady(
|
|
59
|
-
params.proposal,
|
|
60
|
-
params.threshold,
|
|
61
|
-
params.getEffectiveThreshold,
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
const isSwitchGuardian = params.proposal.metadata.proposalType === 'switch_guardian';
|
|
65
|
-
const executionSource = await resolveExecutionSource(
|
|
66
|
-
params.guardian,
|
|
67
|
-
params.accountId,
|
|
68
|
-
params.proposal,
|
|
69
|
-
isSwitchGuardian,
|
|
70
|
-
);
|
|
71
|
-
const executionData = prepareExecutionData(executionSource.txSummaryBase64);
|
|
72
|
-
|
|
73
|
-
const adviceMap = buildCosignerAdviceMap(
|
|
74
|
-
params.proposal,
|
|
75
|
-
params.signerCommitments,
|
|
76
|
-
executionData.txCommitmentHex,
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
if (!isSwitchGuardian && executionSource.delta) {
|
|
80
|
-
await appendGuardianAckAdvice(
|
|
81
|
-
params.guardian,
|
|
82
|
-
executionSource.delta,
|
|
83
|
-
params.guardianCommitment,
|
|
84
|
-
params.guardianPublicKey,
|
|
85
|
-
params.signatureScheme,
|
|
86
|
-
executionData.txCommitmentHex,
|
|
87
|
-
adviceMap,
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const finalRequest = await buildFinalRequest(
|
|
92
|
-
params,
|
|
93
|
-
executionData.saltHex,
|
|
94
|
-
adviceMap,
|
|
95
|
-
);
|
|
96
|
-
await submitTransaction(
|
|
97
|
-
params.midenClient,
|
|
98
|
-
params.accountId,
|
|
99
|
-
finalRequest,
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
export async function createTransactionProposalRequest(
|
|
105
|
-
params: ExecuteProposalWorkflowParams,
|
|
106
|
-
): Promise<TransactionRequest> {
|
|
107
|
-
ensureProposalReady(
|
|
108
|
-
params.proposal,
|
|
109
|
-
params.threshold,
|
|
110
|
-
params.getEffectiveThreshold,
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
const isSwitchGuardian = params.proposal.metadata.proposalType === 'switch_guardian';
|
|
114
|
-
const executionSource = await resolveExecutionSource(
|
|
115
|
-
params.guardian,
|
|
116
|
-
params.accountId,
|
|
117
|
-
params.proposal,
|
|
118
|
-
isSwitchGuardian,
|
|
119
|
-
);
|
|
120
|
-
const executionData = prepareExecutionData(executionSource.txSummaryBase64);
|
|
121
|
-
|
|
122
|
-
const adviceMap = buildCosignerAdviceMap(
|
|
123
|
-
params.proposal,
|
|
124
|
-
params.signerCommitments,
|
|
125
|
-
executionData.txCommitmentHex,
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
if (!isSwitchGuardian && executionSource.delta) {
|
|
129
|
-
await appendGuardianAckAdvice(
|
|
130
|
-
params.guardian,
|
|
131
|
-
executionSource.delta,
|
|
132
|
-
params.guardianCommitment,
|
|
133
|
-
params.guardianPublicKey,
|
|
134
|
-
params.signatureScheme,
|
|
135
|
-
executionData.txCommitmentHex,
|
|
136
|
-
adviceMap,
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return await buildFinalRequest(
|
|
141
|
-
params,
|
|
142
|
-
executionData.saltHex,
|
|
143
|
-
adviceMap,
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function ensureProposalReady(
|
|
148
|
-
proposal: TransactionProposal,
|
|
149
|
-
defaultThreshold: number,
|
|
150
|
-
getEffectiveThreshold: (proposalType: ProposalType) => number,
|
|
151
|
-
): void {
|
|
152
|
-
const proposalType = proposal.metadata?.proposalType;
|
|
153
|
-
const effectiveThreshold = proposalType
|
|
154
|
-
? getEffectiveThreshold(proposalType)
|
|
155
|
-
: defaultThreshold;
|
|
156
|
-
|
|
157
|
-
if (proposal.signatures.length < effectiveThreshold) {
|
|
158
|
-
throw new Error('Proposal is not ready for execution. Still pending signatures.');
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
async function resolveExecutionSource(
|
|
163
|
-
guardian: GuardianHttpClient,
|
|
164
|
-
accountId: string,
|
|
165
|
-
proposal: TransactionProposal,
|
|
166
|
-
isSwitchGuardian: boolean,
|
|
167
|
-
): Promise<ResolveExecutionSourceResult> {
|
|
168
|
-
if (isSwitchGuardian) {
|
|
169
|
-
return { txSummaryBase64: proposal.txSummary };
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const deltas = await guardian.getDeltaProposals(accountId);
|
|
173
|
-
const delta = deltas.find(
|
|
174
|
-
(d) => computeCommitmentFromTxSummary(d.deltaPayload.txSummary.data) === proposal.commitment,
|
|
175
|
-
);
|
|
176
|
-
|
|
177
|
-
if (!delta) {
|
|
178
|
-
throw new Error(`Proposal not found on server: ${proposal.commitment}`);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
return {
|
|
182
|
-
delta,
|
|
183
|
-
txSummaryBase64: delta.deltaPayload.txSummary.data,
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function prepareExecutionData(txSummaryBase64: string): PrepareExecutionResult {
|
|
188
|
-
const txSummaryBytes = base64ToUint8Array(txSummaryBase64);
|
|
189
|
-
const txSummary = TransactionSummary.deserialize(txSummaryBytes);
|
|
190
|
-
const saltHex = txSummary.salt().toHex();
|
|
191
|
-
const txCommitmentHex = txSummary.toCommitment().toHex();
|
|
192
|
-
return { txSummary, saltHex, txCommitmentHex };
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function buildCosignerAdviceMap(
|
|
196
|
-
proposal: TransactionProposal,
|
|
197
|
-
signerCommitments: string[],
|
|
198
|
-
txCommitmentHex: string,
|
|
199
|
-
): AdviceMap {
|
|
200
|
-
const adviceMap = new AdviceMap();
|
|
201
|
-
const normalizedSignerCommitments = new Set(
|
|
202
|
-
signerCommitments.map((c) => normalizeHexWord(c)),
|
|
203
|
-
);
|
|
204
|
-
|
|
205
|
-
for (const cosignerSig of proposal.signatures) {
|
|
206
|
-
let signerCommitmentHex = normalizeHexWord(cosignerSig.signerId);
|
|
207
|
-
if (cosignerSig.signature.scheme === 'ecdsa' && cosignerSig.signature.publicKey) {
|
|
208
|
-
const derived = tryComputeEcdsaCommitmentHex(cosignerSig.signature.publicKey);
|
|
209
|
-
if (derived && derived !== signerCommitmentHex) {
|
|
210
|
-
if (!normalizedSignerCommitments.has(derived)) {
|
|
211
|
-
throw new Error(
|
|
212
|
-
`ECDSA public key commitment mismatch: derived commitment ${derived} is not in signerCommitments.`,
|
|
213
|
-
);
|
|
214
|
-
}
|
|
215
|
-
signerCommitmentHex = derived;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
const signerCommitment = Word.fromHex(signerCommitmentHex);
|
|
220
|
-
const sigBytes = signatureHexToBytes(
|
|
221
|
-
cosignerSig.signature.signature,
|
|
222
|
-
cosignerSig.signature.scheme,
|
|
223
|
-
);
|
|
224
|
-
const signature = Signature.deserialize(sigBytes);
|
|
225
|
-
const txCommitment = Word.fromHex(normalizeHexWord(txCommitmentHex));
|
|
226
|
-
const ecdsaPublicKey =
|
|
227
|
-
cosignerSig.signature.scheme === 'ecdsa'
|
|
228
|
-
? cosignerSig.signature.publicKey
|
|
229
|
-
: undefined;
|
|
230
|
-
const isEcdsa = cosignerSig.signature.scheme === 'ecdsa' && Boolean(ecdsaPublicKey);
|
|
231
|
-
|
|
232
|
-
const { key, values } = buildSignatureAdviceEntry(
|
|
233
|
-
signerCommitment,
|
|
234
|
-
txCommitment,
|
|
235
|
-
signature,
|
|
236
|
-
ecdsaPublicKey,
|
|
237
|
-
isEcdsa ? cosignerSig.signature.signature : undefined,
|
|
238
|
-
);
|
|
239
|
-
adviceMap.insert(key, new FeltArray(values));
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
return adviceMap;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
async function appendGuardianAckAdvice(
|
|
246
|
-
guardian: GuardianHttpClient,
|
|
247
|
-
delta: DeltaObject,
|
|
248
|
-
guardianCommitmentHex: string,
|
|
249
|
-
guardianPublicKey: string | undefined,
|
|
250
|
-
defaultAckScheme: 'falcon' | 'ecdsa',
|
|
251
|
-
txCommitmentHex: string,
|
|
252
|
-
adviceMap: AdviceMap,
|
|
253
|
-
): Promise<void> {
|
|
254
|
-
const executionDelta = {
|
|
255
|
-
...delta,
|
|
256
|
-
deltaPayload: delta.deltaPayload.txSummary,
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
const pushResult = await guardian.pushDelta(executionDelta);
|
|
260
|
-
const ackSigHex = pushResult.ackSig;
|
|
261
|
-
if (!ackSigHex) {
|
|
262
|
-
throw new Error('GUARDIAN did not return acknowledgment signature');
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
const guardianAckScheme: 'ecdsa' | 'falcon' =
|
|
266
|
-
(pushResult.ackScheme as 'ecdsa' | 'falcon') || defaultAckScheme;
|
|
267
|
-
const ackPubkey = pushResult.ackPubkey || guardianPublicKey;
|
|
268
|
-
const normalizedGuardianCommitment = normalizeHexWord(guardianCommitmentHex);
|
|
269
|
-
|
|
270
|
-
if (guardianAckScheme === 'ecdsa' && ackPubkey) {
|
|
271
|
-
const derived = tryComputeEcdsaCommitmentHex(ackPubkey);
|
|
272
|
-
if (derived && derived !== normalizedGuardianCommitment) {
|
|
273
|
-
throw new Error('GUARDIAN public key commitment mismatch');
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
const guardianCommitment = Word.fromHex(normalizedGuardianCommitment);
|
|
278
|
-
const ackSigBytes = signatureHexToBytes(ackSigHex, guardianAckScheme);
|
|
279
|
-
const ackSignature = Signature.deserialize(ackSigBytes);
|
|
280
|
-
const txCommitment = Word.fromHex(normalizeHexWord(txCommitmentHex));
|
|
281
|
-
const isAckEcdsa = guardianAckScheme === 'ecdsa' && ackPubkey;
|
|
282
|
-
const { key, values } = buildSignatureAdviceEntry(
|
|
283
|
-
guardianCommitment,
|
|
284
|
-
txCommitment,
|
|
285
|
-
ackSignature,
|
|
286
|
-
isAckEcdsa ? ackPubkey : undefined,
|
|
287
|
-
isAckEcdsa ? ackSigHex : undefined,
|
|
288
|
-
);
|
|
289
|
-
adviceMap.insert(key, new FeltArray(values));
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
async function buildFinalRequest(
|
|
293
|
-
params: ExecuteProposalWorkflowParams,
|
|
294
|
-
saltHex: string,
|
|
295
|
-
adviceMap: AdviceMap,
|
|
296
|
-
): Promise<TransactionRequest> {
|
|
297
|
-
const metadata = params.proposal.metadata;
|
|
298
|
-
const normalizedSalt = Word.fromHex(normalizeHexWord(saltHex));
|
|
299
|
-
|
|
300
|
-
switch (metadata.proposalType) {
|
|
301
|
-
case 'consume_notes': {
|
|
302
|
-
if (!metadata.noteIds || metadata.noteIds.length === 0) {
|
|
303
|
-
throw new Error(
|
|
304
|
-
'Proposal missing noteIds. Was it created with createConsumeNotesProposal?',
|
|
305
|
-
);
|
|
306
|
-
}
|
|
307
|
-
const { request } = await buildConsumeNotesTransactionRequest(
|
|
308
|
-
params.midenClient,
|
|
309
|
-
metadata.noteIds,
|
|
310
|
-
{ salt: normalizedSalt, signatureAdviceMap: adviceMap },
|
|
311
|
-
);
|
|
312
|
-
return request;
|
|
313
|
-
}
|
|
314
|
-
case 'switch_guardian': {
|
|
315
|
-
if (!metadata.newGuardianPubkey) {
|
|
316
|
-
throw new Error(
|
|
317
|
-
'Proposal missing newGuardianPubkey. Was it created with createSwitchGuardianProposal?',
|
|
318
|
-
);
|
|
319
|
-
}
|
|
320
|
-
const { request } = await buildUpdateGuardianTransactionRequest(
|
|
321
|
-
params.midenClient,
|
|
322
|
-
metadata.newGuardianPubkey,
|
|
323
|
-
{
|
|
324
|
-
salt: normalizedSalt,
|
|
325
|
-
signatureAdviceMap: adviceMap,
|
|
326
|
-
signatureScheme: params.signatureScheme,
|
|
327
|
-
},
|
|
328
|
-
);
|
|
329
|
-
return request;
|
|
330
|
-
}
|
|
331
|
-
case 'update_procedure_threshold': {
|
|
332
|
-
const { request } = await buildUpdateProcedureThresholdTransactionRequest(
|
|
333
|
-
params.midenClient,
|
|
334
|
-
metadata.targetProcedure,
|
|
335
|
-
metadata.targetThreshold,
|
|
336
|
-
{
|
|
337
|
-
salt: normalizedSalt,
|
|
338
|
-
signatureAdviceMap: adviceMap,
|
|
339
|
-
signatureScheme: params.signatureScheme,
|
|
340
|
-
},
|
|
341
|
-
);
|
|
342
|
-
return request;
|
|
343
|
-
}
|
|
344
|
-
case 'p2id': {
|
|
345
|
-
if (!metadata.recipientId || !metadata.faucetId || !metadata.amount) {
|
|
346
|
-
throw new Error(
|
|
347
|
-
'Proposal missing P2ID metadata (recipientId, faucetId, amount). Was it created with createP2idProposal?',
|
|
348
|
-
);
|
|
349
|
-
}
|
|
350
|
-
const { request } = buildP2idTransactionRequest(
|
|
351
|
-
params.accountId,
|
|
352
|
-
metadata.recipientId,
|
|
353
|
-
metadata.faucetId,
|
|
354
|
-
BigInt(metadata.amount),
|
|
355
|
-
{ salt: normalizedSalt, signatureAdviceMap: adviceMap },
|
|
356
|
-
);
|
|
357
|
-
return request;
|
|
358
|
-
}
|
|
359
|
-
case 'unknown':
|
|
360
|
-
throw new Error(
|
|
361
|
-
'Cannot execute proposal with unknown type. The proposal must have been imported without proper metadata.',
|
|
362
|
-
);
|
|
363
|
-
default: {
|
|
364
|
-
const { request } = await buildUpdateSignersTransactionRequest(
|
|
365
|
-
params.midenClient,
|
|
366
|
-
metadata.targetThreshold,
|
|
367
|
-
metadata.targetSignerCommitments,
|
|
368
|
-
{
|
|
369
|
-
salt: normalizedSalt,
|
|
370
|
-
signatureAdviceMap: adviceMap,
|
|
371
|
-
signatureScheme: params.signatureScheme,
|
|
372
|
-
},
|
|
373
|
-
);
|
|
374
|
-
return request;
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
async function submitTransaction(
|
|
380
|
-
midenClient: MidenClient,
|
|
381
|
-
accountIdHex: string,
|
|
382
|
-
request: TransactionRequest,
|
|
383
|
-
): Promise<void> {
|
|
384
|
-
await midenClient.transactions.submit(accountIdHex, request);
|
|
385
|
-
}
|
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
DeltaObject,
|
|
3
|
-
DeltaStatus,
|
|
4
|
-
ProposalMetadata as GuardianProposalMetadata,
|
|
5
|
-
} from '@openzeppelin/guardian-client';
|
|
6
|
-
import type {
|
|
7
|
-
ProposalMetadata,
|
|
8
|
-
ProposalType,
|
|
9
|
-
TransactionProposal,
|
|
10
|
-
TransactionProposalSignature,
|
|
11
|
-
TransactionProposalStatus,
|
|
12
|
-
} from '../../types.js';
|
|
13
|
-
|
|
14
|
-
export function buildGuardianMetadata(metadata: ProposalMetadata): GuardianProposalMetadata {
|
|
15
|
-
const base: GuardianProposalMetadata = {
|
|
16
|
-
proposalType: metadata.proposalType,
|
|
17
|
-
description: metadata.description,
|
|
18
|
-
salt: metadata.saltHex,
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
switch (metadata.proposalType) {
|
|
22
|
-
case 'consume_notes':
|
|
23
|
-
return {
|
|
24
|
-
...base,
|
|
25
|
-
noteIds: metadata.noteIds,
|
|
26
|
-
};
|
|
27
|
-
case 'p2id':
|
|
28
|
-
return {
|
|
29
|
-
...base,
|
|
30
|
-
recipientId: metadata.recipientId,
|
|
31
|
-
faucetId: metadata.faucetId,
|
|
32
|
-
amount: metadata.amount,
|
|
33
|
-
};
|
|
34
|
-
case 'switch_guardian':
|
|
35
|
-
return {
|
|
36
|
-
...base,
|
|
37
|
-
targetThreshold: metadata.targetThreshold,
|
|
38
|
-
signerCommitments: metadata.targetSignerCommitments,
|
|
39
|
-
newGuardianPubkey: metadata.newGuardianPubkey,
|
|
40
|
-
newGuardianEndpoint: metadata.newGuardianEndpoint,
|
|
41
|
-
};
|
|
42
|
-
case 'add_signer':
|
|
43
|
-
case 'remove_signer':
|
|
44
|
-
case 'change_threshold':
|
|
45
|
-
return {
|
|
46
|
-
...base,
|
|
47
|
-
targetThreshold: metadata.targetThreshold,
|
|
48
|
-
signerCommitments: metadata.targetSignerCommitments,
|
|
49
|
-
};
|
|
50
|
-
case 'unknown':
|
|
51
|
-
return base;
|
|
52
|
-
default:
|
|
53
|
-
return base;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export function fromGuardianMetadata(guardian: GuardianProposalMetadata): ProposalMetadata | undefined {
|
|
58
|
-
if (!guardian.proposalType) return undefined;
|
|
59
|
-
|
|
60
|
-
const base = {
|
|
61
|
-
description: guardian.description ?? '',
|
|
62
|
-
saltHex: guardian.salt,
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
switch (guardian.proposalType) {
|
|
66
|
-
case 'p2id':
|
|
67
|
-
return {
|
|
68
|
-
...base,
|
|
69
|
-
proposalType: 'p2id',
|
|
70
|
-
recipientId: guardian.recipientId ?? '',
|
|
71
|
-
faucetId: guardian.faucetId ?? '',
|
|
72
|
-
amount: guardian.amount ?? '0',
|
|
73
|
-
};
|
|
74
|
-
case 'consume_notes':
|
|
75
|
-
return {
|
|
76
|
-
...base,
|
|
77
|
-
proposalType: 'consume_notes',
|
|
78
|
-
noteIds: guardian.noteIds ?? [],
|
|
79
|
-
};
|
|
80
|
-
case 'switch_guardian':
|
|
81
|
-
return {
|
|
82
|
-
...base,
|
|
83
|
-
proposalType: 'switch_guardian',
|
|
84
|
-
newGuardianPubkey: guardian.newGuardianPubkey ?? '',
|
|
85
|
-
newGuardianEndpoint: guardian.newGuardianEndpoint,
|
|
86
|
-
targetThreshold: guardian.targetThreshold,
|
|
87
|
-
targetSignerCommitments: guardian.signerCommitments,
|
|
88
|
-
};
|
|
89
|
-
case 'add_signer':
|
|
90
|
-
case 'remove_signer':
|
|
91
|
-
case 'change_threshold':
|
|
92
|
-
return {
|
|
93
|
-
...base,
|
|
94
|
-
proposalType: guardian.proposalType,
|
|
95
|
-
targetThreshold: guardian.targetThreshold ?? 0,
|
|
96
|
-
targetSignerCommitments: guardian.signerCommitments ?? [],
|
|
97
|
-
};
|
|
98
|
-
default:
|
|
99
|
-
return undefined;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export function deltaStatusToProposalStatus(
|
|
104
|
-
status: DeltaStatus,
|
|
105
|
-
signaturesRequired: number,
|
|
106
|
-
): TransactionProposalStatus {
|
|
107
|
-
switch (status.status) {
|
|
108
|
-
case 'pending': {
|
|
109
|
-
const signaturesCollected = status.cosignerSigs.length;
|
|
110
|
-
if (signaturesCollected >= signaturesRequired) {
|
|
111
|
-
return { type: 'ready' };
|
|
112
|
-
}
|
|
113
|
-
return {
|
|
114
|
-
type: 'pending',
|
|
115
|
-
signaturesCollected,
|
|
116
|
-
signaturesRequired,
|
|
117
|
-
signers: status.cosignerSigs.map((s) => s.signerId),
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
case 'candidate':
|
|
121
|
-
return { type: 'ready' };
|
|
122
|
-
case 'canonical':
|
|
123
|
-
case 'discarded':
|
|
124
|
-
return { type: 'finalized' };
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
interface DeltaToProposalParams {
|
|
129
|
-
delta: DeltaObject;
|
|
130
|
-
proposalId: string;
|
|
131
|
-
metadata: ProposalMetadata;
|
|
132
|
-
signaturesRequired: number;
|
|
133
|
-
existingSignatures?: TransactionProposalSignature[];
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
export function deltaToProposal({
|
|
137
|
-
delta,
|
|
138
|
-
proposalId,
|
|
139
|
-
metadata,
|
|
140
|
-
signaturesRequired,
|
|
141
|
-
existingSignatures,
|
|
142
|
-
}: DeltaToProposalParams): TransactionProposal {
|
|
143
|
-
const status = deltaStatusToProposalStatus(delta.status, signaturesRequired);
|
|
144
|
-
|
|
145
|
-
const signaturesFromStatus =
|
|
146
|
-
delta.status.status === 'pending'
|
|
147
|
-
? delta.status.cosignerSigs.map((s) => ({
|
|
148
|
-
signerId: s.signerId,
|
|
149
|
-
signature: s.signature,
|
|
150
|
-
timestamp: s.timestamp,
|
|
151
|
-
}))
|
|
152
|
-
: [];
|
|
153
|
-
|
|
154
|
-
const signaturesMap = new Map<string, TransactionProposalSignature>();
|
|
155
|
-
for (const sig of existingSignatures ?? []) {
|
|
156
|
-
signaturesMap.set(sig.signerId, sig);
|
|
157
|
-
}
|
|
158
|
-
for (const sig of signaturesFromStatus) {
|
|
159
|
-
signaturesMap.set(sig.signerId, sig);
|
|
160
|
-
}
|
|
161
|
-
const signatures = Array.from(signaturesMap.values());
|
|
162
|
-
|
|
163
|
-
return {
|
|
164
|
-
id: proposalId,
|
|
165
|
-
commitment: proposalId,
|
|
166
|
-
accountId: delta.accountId,
|
|
167
|
-
nonce: delta.nonce,
|
|
168
|
-
status,
|
|
169
|
-
txSummary: delta.deltaPayload.txSummary.data,
|
|
170
|
-
signatures,
|
|
171
|
-
metadata,
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
export function resolveMetadata(
|
|
176
|
-
delta: DeltaObject,
|
|
177
|
-
existingMetadata?: ProposalMetadata,
|
|
178
|
-
): ProposalMetadata | undefined {
|
|
179
|
-
if (existingMetadata) return existingMetadata;
|
|
180
|
-
if (!delta.deltaPayload.metadata) return undefined;
|
|
181
|
-
return fromGuardianMetadata(delta.deltaPayload.metadata);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
export function signatureRequirementForProposal(
|
|
185
|
-
metadata: ProposalMetadata,
|
|
186
|
-
defaultThreshold: number,
|
|
187
|
-
getEffectiveThreshold: (proposalType: ProposalType) => number,
|
|
188
|
-
): number {
|
|
189
|
-
if (metadata.proposalType === 'unknown') return defaultThreshold;
|
|
190
|
-
return getEffectiveThreshold(metadata.proposalType);
|
|
191
|
-
}
|