@openzeppelin/miden-multisig-client 0.12.6 → 0.12.8
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 +70 -75
- package/dist/account/builder.d.ts +0 -18
- package/dist/account/builder.d.ts.map +1 -1
- package/dist/account/builder.js +16 -24
- package/dist/account/builder.js.map +1 -1
- package/dist/account/masm.d.ts +2 -0
- package/dist/account/masm.d.ts.map +1 -1
- package/dist/account/masm.js +428 -1
- package/dist/account/masm.js.map +1 -1
- package/dist/account/storage.d.ts.map +1 -1
- package/dist/account/storage.js +5 -6
- package/dist/account/storage.js.map +1 -1
- package/dist/client.d.ts +5 -57
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +7 -53
- package/dist/client.js.map +1 -1
- package/dist/client.test.js +28 -0
- package/dist/client.test.js.map +1 -1
- package/dist/index.d.ts +3 -46
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -45
- package/dist/index.js.map +1 -1
- package/dist/inspector.d.ts +4 -30
- package/dist/inspector.d.ts.map +1 -1
- package/dist/inspector.js +13 -43
- package/dist/inspector.js.map +1 -1
- package/dist/multisig/helpers.d.ts.map +1 -1
- package/dist/multisig/helpers.js.map +1 -1
- package/dist/multisig.d.ts +39 -161
- package/dist/multisig.d.ts.map +1 -1
- package/dist/multisig.js +190 -241
- package/dist/multisig.js.map +1 -1
- package/dist/multisig.test.js +41 -31
- package/dist/multisig.test.js.map +1 -1
- package/dist/procedures.d.ts +20 -47
- package/dist/procedures.d.ts.map +1 -1
- package/dist/procedures.js +17 -50
- package/dist/procedures.js.map +1 -1
- package/dist/signer.d.ts +11 -26
- package/dist/signer.d.ts.map +1 -1
- package/dist/signer.js +41 -26
- package/dist/signer.js.map +1 -1
- package/dist/transaction/options.d.ts +2 -0
- package/dist/transaction/options.d.ts.map +1 -1
- package/dist/transaction/updatePsm.d.ts.map +1 -1
- package/dist/transaction/updatePsm.js +21 -3
- package/dist/transaction/updatePsm.js.map +1 -1
- package/dist/transaction/updateSigners.d.ts.map +1 -1
- package/dist/transaction/updateSigners.js +21 -3
- package/dist/transaction/updateSigners.js.map +1 -1
- package/dist/transaction.d.ts.map +1 -1
- package/dist/transaction.js +0 -2
- package/dist/transaction.js.map +1 -1
- package/dist/transaction.test.js +5 -0
- package/dist/transaction.test.js.map +1 -1
- package/dist/types/proposal.d.ts +16 -8
- package/dist/types/proposal.d.ts.map +1 -1
- package/dist/types.d.ts +18 -14
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/signature.d.ts +10 -2
- package/dist/utils/signature.d.ts.map +1 -1
- package/dist/utils/signature.js +77 -5
- package/dist/utils/signature.js.map +1 -1
- package/dist/utils/signature.test.js +16 -1
- package/dist/utils/signature.test.js.map +1 -1
- package/masm/multisig_ecdsa.masm +424 -0
- package/masm/psm_ecdsa.masm +179 -0
- package/package.json +2 -2
- package/src/account/builder.ts +18 -25
- package/src/account/masm.ts +433 -2
- package/src/account/storage.ts +5 -6
- package/src/client.test.ts +36 -0
- package/src/client.ts +10 -59
- package/src/index.ts +13 -62
- package/src/inspector.ts +15 -44
- package/src/multisig/helpers.ts +1 -2
- package/src/multisig.test.ts +45 -34
- package/src/multisig.ts +246 -273
- package/src/procedures.ts +21 -56
- package/src/signer.ts +48 -28
- package/src/transaction/options.ts +2 -0
- package/src/transaction/updatePsm.ts +24 -3
- package/src/transaction/updateSigners.ts +25 -3
- package/src/transaction.test.ts +6 -0
- package/src/transaction.ts +0 -2
- package/src/types/proposal.ts +15 -8
- package/src/types.ts +26 -16
- package/src/utils/signature.test.ts +24 -1
- package/src/utils/signature.ts +86 -4
package/dist/multisig.js
CHANGED
|
@@ -1,50 +1,47 @@
|
|
|
1
|
-
|
|
2
|
-
* Multisig class representing a created or loaded multisig account.
|
|
3
|
-
*
|
|
4
|
-
* This class wraps a Miden SDK Account and provides PSM integration
|
|
5
|
-
* for proposal management.
|
|
6
|
-
*/
|
|
1
|
+
import { AccountInspector } from './inspector.js';
|
|
7
2
|
import { Account, AccountId, AdviceMap, FeltArray, Signature, TransactionSummary, Word, } from '@demox-labs/miden-sdk';
|
|
8
3
|
import { executeForSummary, buildUpdateSignersTransactionRequest, buildUpdatePsmTransactionRequest, buildConsumeNotesTransactionRequest, buildP2idTransactionRequest, } from './transaction.js';
|
|
9
4
|
import { base64ToUint8Array, uint8ArrayToBase64, normalizeHexWord, } from './utils/encoding.js';
|
|
10
|
-
import { buildSignatureAdviceEntry, signatureHexToBytes } from './utils/signature.js';
|
|
5
|
+
import { buildSignatureAdviceEntry, signatureHexToBytes, tryComputeEcdsaCommitmentHex } from './utils/signature.js';
|
|
11
6
|
import { computeCommitmentFromTxSummary, accountIdToHex } from './multisig/helpers.js';
|
|
12
|
-
/**
|
|
13
|
-
* Represents a multisig account with PSM integration.
|
|
14
|
-
*/
|
|
15
7
|
export class Multisig {
|
|
16
8
|
account;
|
|
17
9
|
threshold;
|
|
18
10
|
signerCommitments;
|
|
19
11
|
psmCommitment;
|
|
12
|
+
psmPublicKey;
|
|
20
13
|
procedureThresholds;
|
|
14
|
+
signatureScheme;
|
|
21
15
|
psm;
|
|
22
16
|
signer;
|
|
23
17
|
webClient;
|
|
24
18
|
_accountId;
|
|
25
19
|
proposals = new Map();
|
|
26
20
|
constructor(account, config, psm, signer, webClient, accountId) {
|
|
21
|
+
if (config.signatureScheme && config.signatureScheme !== signer.scheme) {
|
|
22
|
+
throw new Error(`signature scheme mismatch: config=${config.signatureScheme} signer=${signer.scheme}`);
|
|
23
|
+
}
|
|
27
24
|
this.account = account;
|
|
28
25
|
this.threshold = config.threshold;
|
|
29
26
|
this.signerCommitments = config.signerCommitments;
|
|
30
27
|
this.psmCommitment = config.psmCommitment;
|
|
28
|
+
this.psmPublicKey = config.psmPublicKey;
|
|
29
|
+
this.signatureScheme = config.signatureScheme ?? signer.scheme;
|
|
31
30
|
this.procedureThresholds = new Map((config.procedureThresholds ?? []).map((pt) => [pt.procedure, pt.threshold]));
|
|
32
31
|
this.psm = psm;
|
|
33
32
|
this.signer = signer;
|
|
34
33
|
this.webClient = webClient;
|
|
35
34
|
this._accountId = accountId ?? (account ? accountIdToHex(account) : '');
|
|
36
35
|
}
|
|
37
|
-
/** The account ID as a string */
|
|
38
36
|
get accountId() {
|
|
39
37
|
return this._accountId;
|
|
40
38
|
}
|
|
41
|
-
/** The signer's commitment */
|
|
42
39
|
get signerCommitment() {
|
|
43
40
|
return this.signer.commitment;
|
|
44
41
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
42
|
+
setPsmPublicKey(pubkey) {
|
|
43
|
+
this.psmPublicKey = pubkey;
|
|
44
|
+
}
|
|
48
45
|
getProposalProcedure(proposalType) {
|
|
49
46
|
switch (proposalType) {
|
|
50
47
|
case 'p2id':
|
|
@@ -61,13 +58,6 @@ export class Multisig {
|
|
|
61
58
|
return null;
|
|
62
59
|
}
|
|
63
60
|
}
|
|
64
|
-
/**
|
|
65
|
-
* Get the effective threshold for a given proposal type.
|
|
66
|
-
* Returns the procedure-specific threshold if configured, otherwise the default threshold.
|
|
67
|
-
*
|
|
68
|
-
* @param proposalType - The type of proposal
|
|
69
|
-
* @returns The threshold that applies to this proposal type
|
|
70
|
-
*/
|
|
71
61
|
getEffectiveThreshold(proposalType) {
|
|
72
62
|
if (this.procedureThresholds.size === 0) {
|
|
73
63
|
return this.threshold;
|
|
@@ -78,20 +68,10 @@ export class Multisig {
|
|
|
78
68
|
}
|
|
79
69
|
return this.procedureThresholds.get(procedure) ?? this.threshold;
|
|
80
70
|
}
|
|
81
|
-
/**
|
|
82
|
-
* Update the PSM client used by this Multisig instance.
|
|
83
|
-
*
|
|
84
|
-
* @param psmClient - The new PSM HTTP client
|
|
85
|
-
*/
|
|
86
71
|
setPsmClient(psmClient) {
|
|
87
72
|
this.psm = psmClient;
|
|
88
73
|
this.psm.setSigner(this.signer);
|
|
89
74
|
}
|
|
90
|
-
/**
|
|
91
|
-
* Fetch the current account state from PSM.
|
|
92
|
-
*
|
|
93
|
-
* @returns The account state including commitment and serialized data
|
|
94
|
-
*/
|
|
95
75
|
async fetchState() {
|
|
96
76
|
const state = await this.psm.getState(this._accountId);
|
|
97
77
|
return {
|
|
@@ -100,14 +80,9 @@ export class Multisig {
|
|
|
100
80
|
stateDataBase64: state.stateJson.data,
|
|
101
81
|
createdAt: state.createdAt,
|
|
102
82
|
updatedAt: state.updatedAt,
|
|
83
|
+
authScheme: state.authScheme,
|
|
103
84
|
};
|
|
104
85
|
}
|
|
105
|
-
/**
|
|
106
|
-
* Sync account state from PSM into the local WebClient store.
|
|
107
|
-
*
|
|
108
|
-
* If the PSM commitment differs from the local commitment (or the account
|
|
109
|
-
* is missing locally), the local store is overwritten with the PSM state.
|
|
110
|
-
*/
|
|
111
86
|
async syncState() {
|
|
112
87
|
const state = await this.fetchState();
|
|
113
88
|
const accountId = AccountId.fromHex(this._accountId);
|
|
@@ -123,19 +98,10 @@ export class Multisig {
|
|
|
123
98
|
}
|
|
124
99
|
return state;
|
|
125
100
|
}
|
|
126
|
-
/**
|
|
127
|
-
* Register this multisig account on the PSM server.
|
|
128
|
-
*
|
|
129
|
-
* The initial state must be the serialized Account bytes (base64-encoded).
|
|
130
|
-
* If not provided, the account's serialize() method is used.
|
|
131
|
-
*
|
|
132
|
-
* @param initialStateBase64 - Optional base64-encoded serialized Account.¡
|
|
133
|
-
*/
|
|
134
101
|
async registerOnPsm(initialStateBase64) {
|
|
135
102
|
if (!this.account && !initialStateBase64) {
|
|
136
103
|
throw new Error('Cannot register on PSM: no account available and no initial state provided');
|
|
137
104
|
}
|
|
138
|
-
// Serialize the account to bytes and base64-encode
|
|
139
105
|
let stateData;
|
|
140
106
|
if (initialStateBase64) {
|
|
141
107
|
stateData = initialStateBase64;
|
|
@@ -144,11 +110,9 @@ export class Multisig {
|
|
|
144
110
|
const accountBytes = this.account.serialize();
|
|
145
111
|
stateData = uint8ArrayToBase64(accountBytes);
|
|
146
112
|
}
|
|
147
|
-
const auth =
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
},
|
|
151
|
-
};
|
|
113
|
+
const auth = this.signer.scheme === 'ecdsa'
|
|
114
|
+
? { MidenEcdsa: { cosigner_commitments: this.signerCommitments } }
|
|
115
|
+
: { MidenFalconRpo: { cosigner_commitments: this.signerCommitments } };
|
|
152
116
|
const response = await this.psm.configure({
|
|
153
117
|
accountId: this._accountId,
|
|
154
118
|
auth,
|
|
@@ -157,14 +121,37 @@ export class Multisig {
|
|
|
157
121
|
if (!response.success) {
|
|
158
122
|
throw new Error(`Failed to register on PSM: ${response.message}`);
|
|
159
123
|
}
|
|
124
|
+
if (response.ackCommitment && this.psmCommitment) {
|
|
125
|
+
const onChain = normalizeHexWord(this.psmCommitment);
|
|
126
|
+
const server = normalizeHexWord(response.ackCommitment);
|
|
127
|
+
if (onChain !== server) {
|
|
128
|
+
throw new Error(`PSM commitment mismatch: on-chain=${onChain}, server=${server}. ` +
|
|
129
|
+
`Re-create the account with getPubkey('${this.signatureScheme}') to get the correct PSM commitment.`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
async syncAll() {
|
|
134
|
+
const proposals = await this.syncTransactionProposals();
|
|
135
|
+
const state = await this.syncState();
|
|
136
|
+
const notes = await this.getConsumableNotes();
|
|
137
|
+
const config = AccountInspector.fromBase64(state.stateDataBase64, this.signatureScheme);
|
|
138
|
+
return { proposals, state, notes, config };
|
|
160
139
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
140
|
+
async getAccountConfig() {
|
|
141
|
+
const state = await this.syncState();
|
|
142
|
+
return AccountInspector.fromBase64(state.stateDataBase64, this.signatureScheme);
|
|
143
|
+
}
|
|
144
|
+
async switchPsm(psmClient) {
|
|
145
|
+
this.setPsmClient(psmClient);
|
|
146
|
+
const state = await this.fetchState();
|
|
147
|
+
await this.registerOnPsm(state.stateDataBase64);
|
|
148
|
+
}
|
|
149
|
+
async syncTransactionProposals() {
|
|
165
150
|
const deltas = await this.psm.getDeltaProposals(this._accountId);
|
|
151
|
+
const serverProposalIds = new Set();
|
|
166
152
|
for (const delta of deltas) {
|
|
167
153
|
const proposalId = computeCommitmentFromTxSummary(delta.deltaPayload.txSummary.data);
|
|
154
|
+
serverProposalIds.add(proposalId);
|
|
168
155
|
const existingProposal = this.proposals.get(proposalId);
|
|
169
156
|
const resolvedMetadata = existingProposal?.metadata ??
|
|
170
157
|
(delta.deltaPayload.metadata
|
|
@@ -176,21 +163,19 @@ export class Multisig {
|
|
|
176
163
|
const proposal = this.deltaToProposal(delta, proposalId, resolvedMetadata, existingProposal?.signatures);
|
|
177
164
|
this.proposals.set(proposal.id, proposal);
|
|
178
165
|
}
|
|
166
|
+
for (const [id, proposal] of this.proposals) {
|
|
167
|
+
if (serverProposalIds.has(id))
|
|
168
|
+
continue;
|
|
169
|
+
const isLocalOnly = proposal.metadata?.proposalType === 'switch_psm' && proposal.status.type !== 'finalized';
|
|
170
|
+
if (!isLocalOnly) {
|
|
171
|
+
this.proposals.delete(id);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
179
174
|
return Array.from(this.proposals.values());
|
|
180
175
|
}
|
|
181
|
-
|
|
182
|
-
* List all known proposals
|
|
183
|
-
*/
|
|
184
|
-
listProposals() {
|
|
176
|
+
listTransactionProposals() {
|
|
185
177
|
return Array.from(this.proposals.values());
|
|
186
178
|
}
|
|
187
|
-
/**
|
|
188
|
-
* Create a new proposal.
|
|
189
|
-
*
|
|
190
|
-
* @param nonce - The nonce for this transaction
|
|
191
|
-
* @param txSummaryBase64 - Base64-encoded transaction summary
|
|
192
|
-
* @param metadata - Optional metadata for execution (target config, salt, etc.)
|
|
193
|
-
*/
|
|
194
179
|
async createProposal(nonce, txSummaryBase64, metadata) {
|
|
195
180
|
const psmMetadata = this.buildPsmMetadata(metadata);
|
|
196
181
|
const response = await this.psm.pushDeltaProposal({
|
|
@@ -206,20 +191,13 @@ export class Multisig {
|
|
|
206
191
|
this.proposals.set(proposal.id, proposal);
|
|
207
192
|
return proposal;
|
|
208
193
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
*
|
|
212
|
-
* @param newCommitment - Commitment of the new signer (hex)
|
|
213
|
-
* @param nonce - Optional proposal nonce (defaults to Date.now())
|
|
214
|
-
* @param newThreshold - Optional new threshold (defaults to current threshold)
|
|
215
|
-
*/
|
|
216
|
-
async createAddSignerProposal(newCommitment, nonce, newThreshold) {
|
|
217
|
-
const targetThreshold = newThreshold ?? this.threshold;
|
|
194
|
+
async createAddSignerProposal(newCommitment, options) {
|
|
195
|
+
const targetThreshold = options?.newThreshold ?? this.threshold;
|
|
218
196
|
const targetSignerCommitments = [...this.signerCommitments, newCommitment];
|
|
219
|
-
const { request, salt } = await buildUpdateSignersTransactionRequest(this.webClient, targetThreshold, targetSignerCommitments);
|
|
197
|
+
const { request, salt } = await buildUpdateSignersTransactionRequest(this.webClient, targetThreshold, targetSignerCommitments, { signatureScheme: this.signatureScheme });
|
|
220
198
|
const summary = await executeForSummary(this.webClient, this._accountId, request);
|
|
221
199
|
const summaryBase64 = uint8ArrayToBase64(summary.serialize());
|
|
222
|
-
const proposalNonce = nonce ?? Date.now();
|
|
200
|
+
const proposalNonce = options?.nonce ?? Date.now();
|
|
223
201
|
const metadata = {
|
|
224
202
|
proposalType: 'add_signer',
|
|
225
203
|
targetThreshold,
|
|
@@ -227,16 +205,10 @@ export class Multisig {
|
|
|
227
205
|
saltHex: salt.toHex(),
|
|
228
206
|
description: `Add signer ${newCommitment.slice(0, 10)}...`,
|
|
229
207
|
};
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
*
|
|
235
|
-
* @param signerToRemove - Commitment of the signer to remove (hex)
|
|
236
|
-
* @param nonce - Optional proposal nonce (defaults to Date.now())
|
|
237
|
-
* @param newThreshold - Optional new threshold (defaults to min of current threshold and new signer count)
|
|
238
|
-
*/
|
|
239
|
-
async createRemoveSignerProposal(signerToRemove, nonce, newThreshold) {
|
|
208
|
+
const proposal = await this.createProposal(proposalNonce, summaryBase64, metadata);
|
|
209
|
+
return this.syncAfterCreate(proposal);
|
|
210
|
+
}
|
|
211
|
+
async createRemoveSignerProposal(signerToRemove, options) {
|
|
240
212
|
const normalizedRemove = signerToRemove.toLowerCase();
|
|
241
213
|
const signerExists = this.signerCommitments.some((c) => c.toLowerCase() === normalizedRemove);
|
|
242
214
|
if (!signerExists) {
|
|
@@ -246,14 +218,14 @@ export class Multisig {
|
|
|
246
218
|
if (targetSignerCommitments.length === 0) {
|
|
247
219
|
throw new Error('Cannot remove the last signer');
|
|
248
220
|
}
|
|
249
|
-
const targetThreshold = newThreshold ?? Math.min(this.threshold, targetSignerCommitments.length);
|
|
221
|
+
const targetThreshold = options?.newThreshold ?? Math.min(this.threshold, targetSignerCommitments.length);
|
|
250
222
|
if (targetThreshold < 1 || targetThreshold > targetSignerCommitments.length) {
|
|
251
223
|
throw new Error(`Invalid threshold ${targetThreshold}. Must be between 1 and ${targetSignerCommitments.length}`);
|
|
252
224
|
}
|
|
253
|
-
const { request, salt } = await buildUpdateSignersTransactionRequest(this.webClient, targetThreshold, targetSignerCommitments);
|
|
225
|
+
const { request, salt } = await buildUpdateSignersTransactionRequest(this.webClient, targetThreshold, targetSignerCommitments, { signatureScheme: this.signatureScheme });
|
|
254
226
|
const summary = await executeForSummary(this.webClient, this._accountId, request);
|
|
255
227
|
const summaryBase64 = uint8ArrayToBase64(summary.serialize());
|
|
256
|
-
const proposalNonce = nonce ?? Date.now();
|
|
228
|
+
const proposalNonce = options?.nonce ?? Date.now();
|
|
257
229
|
const metadata = {
|
|
258
230
|
proposalType: 'remove_signer',
|
|
259
231
|
targetThreshold,
|
|
@@ -261,25 +233,20 @@ export class Multisig {
|
|
|
261
233
|
saltHex: salt.toHex(),
|
|
262
234
|
description: `Remove signer ${signerToRemove.slice(0, 10)}...`,
|
|
263
235
|
};
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
*
|
|
269
|
-
* @param newThreshold - The new threshold value
|
|
270
|
-
* @param nonce - Optional proposal nonce (defaults to Date.now())
|
|
271
|
-
*/
|
|
272
|
-
async createChangeThresholdProposal(newThreshold, nonce) {
|
|
236
|
+
const proposal = await this.createProposal(proposalNonce, summaryBase64, metadata);
|
|
237
|
+
return this.syncAfterCreate(proposal);
|
|
238
|
+
}
|
|
239
|
+
async createChangeThresholdProposal(newThreshold, options) {
|
|
273
240
|
if (newThreshold < 1 || newThreshold > this.signerCommitments.length) {
|
|
274
241
|
throw new Error(`Invalid threshold ${newThreshold}. Must be between 1 and ${this.signerCommitments.length}`);
|
|
275
242
|
}
|
|
276
243
|
if (newThreshold === this.threshold) {
|
|
277
244
|
throw new Error('New threshold is the same as current threshold');
|
|
278
245
|
}
|
|
279
|
-
const { request, salt } = await buildUpdateSignersTransactionRequest(this.webClient, newThreshold, this.signerCommitments);
|
|
246
|
+
const { request, salt } = await buildUpdateSignersTransactionRequest(this.webClient, newThreshold, this.signerCommitments, { signatureScheme: this.signatureScheme });
|
|
280
247
|
const summary = await executeForSummary(this.webClient, this._accountId, request);
|
|
281
248
|
const summaryBase64 = uint8ArrayToBase64(summary.serialize());
|
|
282
|
-
const proposalNonce = nonce ?? Date.now();
|
|
249
|
+
const proposalNonce = options?.nonce ?? Date.now();
|
|
283
250
|
const metadata = {
|
|
284
251
|
proposalType: 'change_threshold',
|
|
285
252
|
targetThreshold: newThreshold,
|
|
@@ -287,20 +254,14 @@ export class Multisig {
|
|
|
287
254
|
saltHex: salt.toHex(),
|
|
288
255
|
description: `Change threshold from ${this.threshold} to ${newThreshold}`,
|
|
289
256
|
};
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
* @param newPsmEndpoint - The new PSM server endpoint URL
|
|
296
|
-
* @param newPsmPubkey - The new PSM server's public key commitment (hex)
|
|
297
|
-
* @param nonce - Optional proposal nonce (defaults to Date.now())
|
|
298
|
-
*/
|
|
299
|
-
async createSwitchPsmProposal(newPsmEndpoint, newPsmPubkey, nonce) {
|
|
300
|
-
const { request, salt } = await buildUpdatePsmTransactionRequest(this.webClient, newPsmPubkey);
|
|
257
|
+
const proposal = await this.createProposal(proposalNonce, summaryBase64, metadata);
|
|
258
|
+
return this.syncAfterCreate(proposal);
|
|
259
|
+
}
|
|
260
|
+
async createSwitchPsmProposal(newPsmEndpoint, newPsmPubkey, options) {
|
|
261
|
+
const { request, salt } = await buildUpdatePsmTransactionRequest(this.webClient, newPsmPubkey, { signatureScheme: this.signatureScheme });
|
|
301
262
|
const summary = await executeForSummary(this.webClient, this._accountId, request);
|
|
302
263
|
const summaryBase64 = uint8ArrayToBase64(summary.serialize());
|
|
303
|
-
const proposalNonce = nonce ?? Date.now();
|
|
264
|
+
const proposalNonce = options?.nonce ?? Date.now();
|
|
304
265
|
const metadata = {
|
|
305
266
|
proposalType: 'switch_psm',
|
|
306
267
|
saltHex: salt.toHex(),
|
|
@@ -311,6 +272,7 @@ export class Multisig {
|
|
|
311
272
|
const proposalId = computeCommitmentFromTxSummary(summaryBase64);
|
|
312
273
|
const proposal = {
|
|
313
274
|
id: proposalId,
|
|
275
|
+
commitment: proposalId,
|
|
314
276
|
accountId: this._accountId,
|
|
315
277
|
nonce: proposalNonce,
|
|
316
278
|
status: { type: 'pending', signaturesCollected: 0, signaturesRequired: this.threshold, signers: [] },
|
|
@@ -319,46 +281,34 @@ export class Multisig {
|
|
|
319
281
|
metadata,
|
|
320
282
|
};
|
|
321
283
|
this.proposals.set(proposal.id, proposal);
|
|
322
|
-
|
|
284
|
+
const proposals = this.listTransactionProposals();
|
|
285
|
+
return { proposal, proposals };
|
|
323
286
|
}
|
|
324
|
-
|
|
325
|
-
* Create a "consume notes" proposal to consume notes sent to the multisig account.
|
|
326
|
-
*
|
|
327
|
-
* @param noteIds - IDs of the notes to consume (hex strings)
|
|
328
|
-
* @param nonce - Optional proposal nonce (defaults to Date.now())
|
|
329
|
-
*/
|
|
330
|
-
async createConsumeNotesProposal(noteIds, nonce) {
|
|
287
|
+
async createConsumeNotesProposal(noteIds, options) {
|
|
331
288
|
if (noteIds.length === 0) {
|
|
332
289
|
throw new Error('At least one note ID is required');
|
|
333
290
|
}
|
|
334
291
|
const { request, salt } = buildConsumeNotesTransactionRequest(noteIds);
|
|
335
292
|
const summary = await executeForSummary(this.webClient, this._accountId, request);
|
|
336
293
|
const summaryBase64 = uint8ArrayToBase64(summary.serialize());
|
|
337
|
-
const proposalNonce = nonce ?? Date.now();
|
|
294
|
+
const proposalNonce = options?.nonce ?? Date.now();
|
|
338
295
|
const metadata = {
|
|
339
296
|
proposalType: 'consume_notes',
|
|
340
297
|
noteIds,
|
|
341
298
|
saltHex: salt.toHex(),
|
|
342
299
|
description: `Consume ${noteIds.length} note(s)`,
|
|
343
300
|
};
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
*
|
|
349
|
-
* @param recipientId - Account ID of the recipient (hex string)
|
|
350
|
-
* @param faucetId - Faucet/token account ID (hex string)
|
|
351
|
-
* @param amount - Amount to send
|
|
352
|
-
* @param nonce - Optional proposal nonce (defaults to Date.now())
|
|
353
|
-
*/
|
|
354
|
-
async createP2idProposal(recipientId, faucetId, amount, nonce) {
|
|
301
|
+
const proposal = await this.createProposal(proposalNonce, summaryBase64, metadata);
|
|
302
|
+
return this.syncAfterCreate(proposal);
|
|
303
|
+
}
|
|
304
|
+
async createSendProposal(recipientId, faucetId, amount, options) {
|
|
355
305
|
if (amount <= 0n) {
|
|
356
306
|
throw new Error('Amount must be greater than 0');
|
|
357
307
|
}
|
|
358
308
|
const { request, salt } = buildP2idTransactionRequest(this._accountId, recipientId, faucetId, amount);
|
|
359
309
|
const summary = await executeForSummary(this.webClient, this._accountId, request);
|
|
360
310
|
const summaryBase64 = uint8ArrayToBase64(summary.serialize());
|
|
361
|
-
const proposalNonce = nonce ?? Date.now();
|
|
311
|
+
const proposalNonce = options?.nonce ?? Date.now();
|
|
362
312
|
const metadata = {
|
|
363
313
|
proposalType: 'p2id',
|
|
364
314
|
saltHex: salt.toHex(),
|
|
@@ -367,31 +317,22 @@ export class Multisig {
|
|
|
367
317
|
amount: amount.toString(),
|
|
368
318
|
description: `Send ${amount} to ${recipientId.slice(0, 10)}...`,
|
|
369
319
|
};
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
* Get notes that can be consumed by this multisig account.
|
|
374
|
-
*
|
|
375
|
-
* Returns a list of notes that are committed on-chain and can be consumed
|
|
376
|
-
* immediately by the multisig account.
|
|
377
|
-
*/
|
|
320
|
+
const proposal = await this.createProposal(proposalNonce, summaryBase64, metadata);
|
|
321
|
+
return this.syncAfterCreate(proposal);
|
|
322
|
+
}
|
|
378
323
|
async getConsumableNotes() {
|
|
379
324
|
const accountId = AccountId.fromHex(this._accountId);
|
|
380
|
-
// Get consumable notes for this account
|
|
381
325
|
const consumableRecords = await this.webClient.getConsumableNotes(accountId);
|
|
382
|
-
// Convert to our simplified ConsumableNote type
|
|
383
326
|
const notes = [];
|
|
384
327
|
for (const record of consumableRecords) {
|
|
385
328
|
const inputNote = record.inputNoteRecord();
|
|
386
329
|
const consumability = record.noteConsumability();
|
|
387
|
-
// Only include notes that can be consumed now (consumableAfterBlock is undefined/null)
|
|
388
330
|
const canConsumeNow = consumability.some((c) => c.accountId().toString().toLowerCase() === this._accountId.toLowerCase() &&
|
|
389
331
|
c.consumableAfterBlock() === undefined);
|
|
390
332
|
if (canConsumeNow) {
|
|
391
333
|
const noteId = inputNote.id().toString();
|
|
392
334
|
const details = inputNote.details();
|
|
393
335
|
const fungibleAssets = details.assets().fungibleAssets();
|
|
394
|
-
// Extract assets
|
|
395
336
|
const assets = [];
|
|
396
337
|
for (const asset of fungibleAssets) {
|
|
397
338
|
assets.push({
|
|
@@ -404,42 +345,47 @@ export class Multisig {
|
|
|
404
345
|
}
|
|
405
346
|
return notes;
|
|
406
347
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
* @param proposalId - The proposal commitment/ID (this is also what gets signed)
|
|
414
|
-
*/
|
|
415
|
-
async signProposal(proposalId) {
|
|
416
|
-
const existingProposal = this.proposals.get(proposalId);
|
|
417
|
-
const signatureHex = this.signer.signCommitment(proposalId);
|
|
418
|
-
const signature = {
|
|
419
|
-
scheme: 'falcon',
|
|
420
|
-
signature: signatureHex,
|
|
421
|
-
};
|
|
348
|
+
async signTransactionProposal(commitment) {
|
|
349
|
+
const existingProposal = this.proposals.get(commitment);
|
|
350
|
+
const signatureHex = this.signer.signCommitment(commitment);
|
|
351
|
+
const signature = this.signer.scheme === 'ecdsa'
|
|
352
|
+
? { scheme: 'ecdsa', signature: signatureHex, publicKey: this.signer.publicKey }
|
|
353
|
+
: { scheme: 'falcon', signature: signatureHex };
|
|
422
354
|
const delta = await this.psm.signDeltaProposal({
|
|
423
355
|
accountId: this._accountId,
|
|
424
|
-
commitment
|
|
356
|
+
commitment,
|
|
425
357
|
signature,
|
|
426
358
|
});
|
|
427
|
-
const proposal = this.deltaToProposal(delta,
|
|
359
|
+
const proposal = this.deltaToProposal(delta, commitment, undefined, existingProposal?.signatures);
|
|
428
360
|
if (existingProposal?.metadata) {
|
|
429
361
|
proposal.metadata = existingProposal.metadata;
|
|
430
362
|
}
|
|
431
363
|
this.proposals.set(proposal.id, proposal);
|
|
432
|
-
return
|
|
364
|
+
return this.syncTransactionProposals();
|
|
433
365
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
366
|
+
async signTransactionProposalExternal(params) {
|
|
367
|
+
const { commitment, signature: signatureHex, publicKey, scheme } = params;
|
|
368
|
+
const resolvedScheme = scheme ?? this.signatureScheme;
|
|
369
|
+
const existingProposal = this.proposals.get(commitment);
|
|
370
|
+
const signature = resolvedScheme === 'ecdsa'
|
|
371
|
+
? { scheme: 'ecdsa', signature: signatureHex, publicKey: publicKey }
|
|
372
|
+
: { scheme: 'falcon', signature: signatureHex };
|
|
373
|
+
const delta = await this.psm.signDeltaProposal({
|
|
374
|
+
accountId: this._accountId,
|
|
375
|
+
commitment,
|
|
376
|
+
signature,
|
|
377
|
+
});
|
|
378
|
+
const proposal = this.deltaToProposal(delta, commitment, undefined, existingProposal?.signatures);
|
|
379
|
+
if (existingProposal?.metadata) {
|
|
380
|
+
proposal.metadata = existingProposal.metadata;
|
|
381
|
+
}
|
|
382
|
+
this.proposals.set(proposal.id, proposal);
|
|
383
|
+
return this.syncTransactionProposals();
|
|
384
|
+
}
|
|
385
|
+
async executeTransactionProposal(commitment) {
|
|
386
|
+
const proposal = this.proposals.get(commitment);
|
|
441
387
|
if (!proposal) {
|
|
442
|
-
throw new Error(`Proposal not found: ${
|
|
388
|
+
throw new Error(`Proposal not found: ${commitment}`);
|
|
443
389
|
}
|
|
444
390
|
const proposalType = proposal.metadata?.proposalType;
|
|
445
391
|
const effectiveThreshold = proposalType
|
|
@@ -456,9 +402,9 @@ export class Multisig {
|
|
|
456
402
|
}
|
|
457
403
|
else {
|
|
458
404
|
const deltas = await this.psm.getDeltaProposals(this._accountId);
|
|
459
|
-
delta = deltas.find((d) => computeCommitmentFromTxSummary(d.deltaPayload.txSummary.data) ===
|
|
405
|
+
delta = deltas.find((d) => computeCommitmentFromTxSummary(d.deltaPayload.txSummary.data) === commitment);
|
|
460
406
|
if (!delta) {
|
|
461
|
-
throw new Error(`Proposal not found on server: ${
|
|
407
|
+
throw new Error(`Proposal not found on server: ${commitment}`);
|
|
462
408
|
}
|
|
463
409
|
txSummaryBase64 = delta.deltaPayload.txSummary.data;
|
|
464
410
|
}
|
|
@@ -467,12 +413,24 @@ export class Multisig {
|
|
|
467
413
|
const saltHex = txSummary.salt().toHex();
|
|
468
414
|
const txCommitmentHex = txSummary.toCommitment().toHex();
|
|
469
415
|
const adviceMap = new AdviceMap();
|
|
416
|
+
const normalizedSignerCommitments = new Set(this.signerCommitments.map((c) => normalizeHexWord(c)));
|
|
470
417
|
for (const cosignerSig of proposal.signatures) {
|
|
471
|
-
|
|
472
|
-
|
|
418
|
+
let signerCommitmentHex = normalizeHexWord(cosignerSig.signerId);
|
|
419
|
+
if (cosignerSig.signature.scheme === 'ecdsa' && cosignerSig.signature.publicKey) {
|
|
420
|
+
const derived = tryComputeEcdsaCommitmentHex(cosignerSig.signature.publicKey);
|
|
421
|
+
if (derived && derived !== signerCommitmentHex) {
|
|
422
|
+
if (!normalizedSignerCommitments.has(derived)) {
|
|
423
|
+
throw new Error(`ECDSA public key commitment mismatch: derived commitment ${derived} is not in signerCommitments.`);
|
|
424
|
+
}
|
|
425
|
+
signerCommitmentHex = derived;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
const signerCommitment = Word.fromHex(signerCommitmentHex);
|
|
429
|
+
const sigBytes = signatureHexToBytes(cosignerSig.signature.signature, cosignerSig.signature.scheme);
|
|
473
430
|
const signature = Signature.deserialize(sigBytes);
|
|
474
431
|
const txCommitment = Word.fromHex(normalizeHexWord(txCommitmentHex));
|
|
475
|
-
const
|
|
432
|
+
const isEcdsa = cosignerSig.signature.scheme === 'ecdsa' && cosignerSig.signature.publicKey;
|
|
433
|
+
const { key, values } = buildSignatureAdviceEntry(signerCommitment, txCommitment, signature, isEcdsa ? cosignerSig.signature.publicKey : undefined, isEcdsa ? cosignerSig.signature.signature : undefined);
|
|
476
434
|
adviceMap.insert(key, new FeltArray(values));
|
|
477
435
|
}
|
|
478
436
|
if (!isSwitchPsm && delta) {
|
|
@@ -485,11 +443,21 @@ export class Multisig {
|
|
|
485
443
|
if (!ackSigHex) {
|
|
486
444
|
throw new Error('PSM did not return acknowledgment signature');
|
|
487
445
|
}
|
|
488
|
-
const
|
|
489
|
-
const
|
|
446
|
+
const psmAckScheme = pushResult.ackScheme || this.signatureScheme;
|
|
447
|
+
const psmAckPubkey = pushResult.ackPubkey || this.psmPublicKey;
|
|
448
|
+
const psmCommitmentHex = normalizeHexWord(this.psmCommitment);
|
|
449
|
+
if (psmAckScheme === 'ecdsa' && psmAckPubkey) {
|
|
450
|
+
const derived = tryComputeEcdsaCommitmentHex(psmAckPubkey);
|
|
451
|
+
if (derived && derived !== psmCommitmentHex) {
|
|
452
|
+
throw new Error(`PSM public key commitment mismatch`);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
const psmCommitment = Word.fromHex(psmCommitmentHex);
|
|
456
|
+
const ackSigBytes = signatureHexToBytes(ackSigHex, psmAckScheme);
|
|
490
457
|
const ackSignature = Signature.deserialize(ackSigBytes);
|
|
491
458
|
const txCommitmentForAck = Word.fromHex(normalizeHexWord(txCommitmentHex));
|
|
492
|
-
const
|
|
459
|
+
const isAckEcdsa = psmAckScheme === 'ecdsa' && psmAckPubkey;
|
|
460
|
+
const { key: ackKey, values: ackValues } = buildSignatureAdviceEntry(psmCommitment, txCommitmentForAck, ackSignature, isAckEcdsa ? psmAckPubkey : undefined, isAckEcdsa ? ackSigHex : undefined);
|
|
493
461
|
adviceMap.insert(ackKey, new FeltArray(ackValues));
|
|
494
462
|
}
|
|
495
463
|
const metadata = proposal.metadata;
|
|
@@ -510,7 +478,11 @@ export class Multisig {
|
|
|
510
478
|
if (!metadata.newPsmPubkey) {
|
|
511
479
|
throw new Error('Proposal missing newPsmPubkey. Was it created with createSwitchPsmProposal?');
|
|
512
480
|
}
|
|
513
|
-
const { request } = await buildUpdatePsmTransactionRequest(this.webClient, metadata.newPsmPubkey, {
|
|
481
|
+
const { request } = await buildUpdatePsmTransactionRequest(this.webClient, metadata.newPsmPubkey, {
|
|
482
|
+
salt: Word.fromHex(normalizeHexWord(saltHex)),
|
|
483
|
+
signatureAdviceMap: adviceMap,
|
|
484
|
+
signatureScheme: this.signatureScheme,
|
|
485
|
+
});
|
|
514
486
|
finalRequest = request;
|
|
515
487
|
break;
|
|
516
488
|
}
|
|
@@ -526,7 +498,11 @@ export class Multisig {
|
|
|
526
498
|
throw new Error('Cannot execute proposal with unknown type. The proposal must have been imported without proper metadata.');
|
|
527
499
|
}
|
|
528
500
|
default: {
|
|
529
|
-
const { request } = await buildUpdateSignersTransactionRequest(this.webClient, metadata.targetThreshold, metadata.targetSignerCommitments, {
|
|
501
|
+
const { request } = await buildUpdateSignersTransactionRequest(this.webClient, metadata.targetThreshold, metadata.targetSignerCommitments, {
|
|
502
|
+
salt: Word.fromHex(normalizeHexWord(saltHex)),
|
|
503
|
+
signatureAdviceMap: adviceMap,
|
|
504
|
+
signatureScheme: this.signatureScheme,
|
|
505
|
+
});
|
|
530
506
|
finalRequest = request;
|
|
531
507
|
break;
|
|
532
508
|
}
|
|
@@ -538,39 +514,10 @@ export class Multisig {
|
|
|
538
514
|
await this.webClient.applyTransaction(result, submissionHeight);
|
|
539
515
|
proposal.status = { type: 'finalized' };
|
|
540
516
|
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
*/
|
|
544
|
-
async exportProposal(proposalId) {
|
|
545
|
-
const deltas = await this.psm.getDeltaProposals(this._accountId);
|
|
546
|
-
const delta = deltas.find((d) => computeCommitmentFromTxSummary(d.deltaPayload.txSummary.data) === proposalId);
|
|
547
|
-
if (!delta) {
|
|
548
|
-
throw new Error(`Proposal not found: ${proposalId}`);
|
|
549
|
-
}
|
|
550
|
-
const signatures = delta.status.status === 'pending'
|
|
551
|
-
? delta.status.cosignerSigs.map((s) => ({
|
|
552
|
-
commitment: s.signerId,
|
|
553
|
-
signatureHex: s.signature.signature,
|
|
554
|
-
}))
|
|
555
|
-
: [];
|
|
556
|
-
return {
|
|
557
|
-
accountId: delta.accountId,
|
|
558
|
-
nonce: delta.nonce,
|
|
559
|
-
commitment: proposalId,
|
|
560
|
-
txSummaryBase64: delta.deltaPayload.txSummary.data,
|
|
561
|
-
signatures,
|
|
562
|
-
};
|
|
563
|
-
}
|
|
564
|
-
/**
|
|
565
|
-
* Export a proposal to JSON for side-channel sharing.
|
|
566
|
-
*
|
|
567
|
-
* @param proposalId - The proposal commitment/ID
|
|
568
|
-
* @returns JSON string that can be shared and imported by other signers
|
|
569
|
-
*/
|
|
570
|
-
exportProposalToJson(proposalId) {
|
|
571
|
-
const proposal = this.proposals.get(proposalId);
|
|
517
|
+
exportTransactionProposalToJson(commitment) {
|
|
518
|
+
const proposal = this.proposals.get(commitment);
|
|
572
519
|
if (!proposal) {
|
|
573
|
-
throw new Error(`Proposal not found in local cache: ${
|
|
520
|
+
throw new Error(`Proposal not found in local cache: ${commitment}`);
|
|
574
521
|
}
|
|
575
522
|
const exported = {
|
|
576
523
|
accountId: proposal.accountId,
|
|
@@ -586,13 +533,7 @@ export class Multisig {
|
|
|
586
533
|
};
|
|
587
534
|
return JSON.stringify(exported, null, 2);
|
|
588
535
|
}
|
|
589
|
-
|
|
590
|
-
* Import a proposal from JSON (exported via exportProposalToJson).
|
|
591
|
-
*
|
|
592
|
-
* @param json - JSON string from exportProposalToJson
|
|
593
|
-
* @returns The imported proposal
|
|
594
|
-
*/
|
|
595
|
-
importProposal(json) {
|
|
536
|
+
importTransactionProposal(json) {
|
|
596
537
|
const exported = JSON.parse(json);
|
|
597
538
|
if (!exported.accountId || !exported.txSummaryBase64 || !exported.commitment) {
|
|
598
539
|
throw new Error('Invalid proposal JSON: missing required fields');
|
|
@@ -620,45 +561,40 @@ export class Multisig {
|
|
|
620
561
|
};
|
|
621
562
|
const proposal = {
|
|
622
563
|
id: exported.commitment,
|
|
564
|
+
commitment: exported.commitment,
|
|
623
565
|
accountId: exported.accountId,
|
|
624
566
|
nonce: exported.nonce,
|
|
625
567
|
status,
|
|
626
568
|
txSummary: exported.txSummaryBase64,
|
|
627
569
|
signatures: exported.signatures.map((s) => ({
|
|
628
570
|
signerId: s.commitment,
|
|
629
|
-
signature: { scheme:
|
|
571
|
+
signature: { scheme: this.signer.scheme, signature: s.signatureHex },
|
|
630
572
|
timestamp: s.timestamp || new Date().toISOString(),
|
|
631
573
|
})),
|
|
632
574
|
metadata,
|
|
633
575
|
};
|
|
634
576
|
this.proposals.set(proposal.id, proposal);
|
|
635
|
-
|
|
577
|
+
const proposals = this.listTransactionProposals();
|
|
578
|
+
return { proposal, proposals };
|
|
636
579
|
}
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
*
|
|
640
|
-
* @param proposalId - The proposal commitment/ID
|
|
641
|
-
* @returns Updated JSON string with the new signature included
|
|
642
|
-
*/
|
|
643
|
-
signProposalOffline(proposalId) {
|
|
644
|
-
const proposal = this.proposals.get(proposalId);
|
|
580
|
+
signTransactionProposalOffline(commitment) {
|
|
581
|
+
const proposal = this.proposals.get(commitment);
|
|
645
582
|
if (!proposal) {
|
|
646
|
-
throw new Error(`Proposal not found: ${
|
|
583
|
+
throw new Error(`Proposal not found: ${commitment}`);
|
|
647
584
|
}
|
|
648
|
-
// Check if already signed
|
|
649
585
|
const alreadySigned = proposal.signatures.some((s) => s.signerId.toLowerCase() === this.signer.commitment.toLowerCase());
|
|
650
586
|
if (alreadySigned) {
|
|
651
587
|
throw new Error('You have already signed this proposal');
|
|
652
588
|
}
|
|
653
|
-
|
|
654
|
-
const
|
|
655
|
-
|
|
589
|
+
const signatureHex = this.signer.signCommitment(commitment);
|
|
590
|
+
const sigEntry = this.signer.scheme === 'ecdsa'
|
|
591
|
+
? { scheme: 'ecdsa', signature: signatureHex, publicKey: this.signer.publicKey }
|
|
592
|
+
: { scheme: 'falcon', signature: signatureHex };
|
|
656
593
|
proposal.signatures.push({
|
|
657
594
|
signerId: this.signer.commitment,
|
|
658
|
-
signature:
|
|
595
|
+
signature: sigEntry,
|
|
659
596
|
timestamp: new Date().toISOString(),
|
|
660
597
|
});
|
|
661
|
-
// Update status
|
|
662
598
|
const signaturesCollected = proposal.signatures.length;
|
|
663
599
|
const proposalType = proposal.metadata?.proposalType;
|
|
664
600
|
const effectiveThreshold = proposalType
|
|
@@ -675,8 +611,20 @@ export class Multisig {
|
|
|
675
611
|
signers: proposal.signatures.map((s) => s.signerId),
|
|
676
612
|
};
|
|
677
613
|
}
|
|
678
|
-
|
|
679
|
-
|
|
614
|
+
return this.exportTransactionProposalToJson(commitment);
|
|
615
|
+
}
|
|
616
|
+
async syncAfterCreate(proposal) {
|
|
617
|
+
let proposals;
|
|
618
|
+
try {
|
|
619
|
+
proposals = await this.syncTransactionProposals();
|
|
620
|
+
if (!proposals.find((p) => p.id === proposal.id)) {
|
|
621
|
+
proposals = [...proposals, proposal];
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
catch {
|
|
625
|
+
proposals = this.listTransactionProposals();
|
|
626
|
+
}
|
|
627
|
+
return { proposal, proposals };
|
|
680
628
|
}
|
|
681
629
|
deltaToProposal(delta, proposalId, metadata, existingSignatures) {
|
|
682
630
|
const resolvedMetadata = metadata ??
|
|
@@ -702,6 +650,7 @@ export class Multisig {
|
|
|
702
650
|
const signatures = Array.from(signaturesMap.values());
|
|
703
651
|
return {
|
|
704
652
|
id: proposalId,
|
|
653
|
+
commitment: proposalId,
|
|
705
654
|
accountId: delta.accountId,
|
|
706
655
|
nonce: delta.nonce,
|
|
707
656
|
status,
|