@openzeppelin/miden-multisig-client 0.15.2 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/README.md +37 -1
  2. package/dist/account/builder.d.ts +2 -1
  3. package/dist/account/builder.d.ts.map +1 -1
  4. package/dist/account/builder.js +1 -0
  5. package/dist/account/builder.js.map +1 -1
  6. package/dist/account/builder.test.js +2 -2
  7. package/dist/account/builder.test.js.map +1 -1
  8. package/dist/client.d.ts +17 -5
  9. package/dist/client.d.ts.map +1 -1
  10. package/dist/client.js +14 -7
  11. package/dist/client.js.map +1 -1
  12. package/dist/client.test.js +46 -15
  13. package/dist/client.test.js.map +1 -1
  14. package/dist/connectivity.d.ts +38 -0
  15. package/dist/connectivity.d.ts.map +1 -0
  16. package/dist/connectivity.js +97 -0
  17. package/dist/connectivity.js.map +1 -0
  18. package/dist/connectivity.test.d.ts +2 -0
  19. package/dist/connectivity.test.d.ts.map +1 -0
  20. package/dist/connectivity.test.js +61 -0
  21. package/dist/connectivity.test.js.map +1 -0
  22. package/dist/index.d.ts +14 -3
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +13 -3
  25. package/dist/index.js.map +1 -1
  26. package/dist/multisig.d.ts +130 -8
  27. package/dist/multisig.d.ts.map +1 -1
  28. package/dist/multisig.js +220 -24
  29. package/dist/multisig.js.map +1 -1
  30. package/dist/multisig.test.js +427 -89
  31. package/dist/multisig.test.js.map +1 -1
  32. package/dist/proposal/factory.d.ts.map +1 -1
  33. package/dist/proposal/factory.js +10 -0
  34. package/dist/proposal/factory.js.map +1 -1
  35. package/dist/proposal/factory.test.d.ts +2 -0
  36. package/dist/proposal/factory.test.d.ts.map +1 -0
  37. package/dist/proposal/factory.test.js +32 -0
  38. package/dist/proposal/factory.test.js.map +1 -0
  39. package/dist/proposal/metadata.d.ts.map +1 -1
  40. package/dist/proposal/metadata.js +12 -0
  41. package/dist/proposal/metadata.js.map +1 -1
  42. package/dist/proposal/metadata.test.js +49 -0
  43. package/dist/proposal/metadata.test.js.map +1 -1
  44. package/dist/prover/config.d.ts +16 -0
  45. package/dist/prover/config.d.ts.map +1 -0
  46. package/dist/prover/config.js +54 -0
  47. package/dist/prover/config.js.map +1 -0
  48. package/dist/prover/config.test.d.ts +2 -0
  49. package/dist/prover/config.test.d.ts.map +1 -0
  50. package/dist/prover/config.test.js +54 -0
  51. package/dist/prover/config.test.js.map +1 -0
  52. package/dist/prover/errors.d.ts +2 -0
  53. package/dist/prover/errors.d.ts.map +1 -0
  54. package/dist/prover/errors.js +158 -0
  55. package/dist/prover/errors.js.map +1 -0
  56. package/dist/prover/errors.test.d.ts +2 -0
  57. package/dist/prover/errors.test.d.ts.map +1 -0
  58. package/dist/prover/errors.test.js +24 -0
  59. package/dist/prover/errors.test.js.map +1 -0
  60. package/dist/prover/retry.d.ts +10 -0
  61. package/dist/prover/retry.d.ts.map +1 -0
  62. package/dist/prover/retry.js +32 -0
  63. package/dist/prover/retry.js.map +1 -0
  64. package/dist/prover/retry.test.d.ts +2 -0
  65. package/dist/prover/retry.test.d.ts.map +1 -0
  66. package/dist/prover/retry.test.js +20 -0
  67. package/dist/prover/retry.test.js.map +1 -0
  68. package/dist/prover/workflow.d.ts +11 -0
  69. package/dist/prover/workflow.d.ts.map +1 -0
  70. package/dist/prover/workflow.js +18 -0
  71. package/dist/prover/workflow.js.map +1 -0
  72. package/dist/prover/workflow.test.d.ts +2 -0
  73. package/dist/prover/workflow.test.d.ts.map +1 -0
  74. package/dist/prover/workflow.test.js +80 -0
  75. package/dist/prover/workflow.test.js.map +1 -0
  76. package/dist/raw-client.d.ts +2 -2
  77. package/dist/raw-client.d.ts.map +1 -1
  78. package/dist/raw-client.js +14 -4
  79. package/dist/raw-client.js.map +1 -1
  80. package/dist/raw-client.test.js +25 -3
  81. package/dist/raw-client.test.js.map +1 -1
  82. package/dist/transaction/consumeNotes.d.ts +6 -2
  83. package/dist/transaction/consumeNotes.d.ts.map +1 -1
  84. package/dist/transaction/consumeNotes.js +0 -4
  85. package/dist/transaction/consumeNotes.js.map +1 -1
  86. package/dist/transaction/options.d.ts +3 -0
  87. package/dist/transaction/options.d.ts.map +1 -1
  88. package/dist/transaction/p2id.d.ts +27 -2
  89. package/dist/transaction/p2id.d.ts.map +1 -1
  90. package/dist/transaction/p2id.js +56 -4
  91. package/dist/transaction/p2id.js.map +1 -1
  92. package/dist/transaction/p2id.test.js +66 -6
  93. package/dist/transaction/p2id.test.js.map +1 -1
  94. package/dist/transaction/summary.d.ts +2 -1
  95. package/dist/transaction/summary.d.ts.map +1 -1
  96. package/dist/transaction/summary.js.map +1 -1
  97. package/dist/transaction/updateGuardian.d.ts +6 -2
  98. package/dist/transaction/updateGuardian.d.ts.map +1 -1
  99. package/dist/transaction/updateGuardian.js.map +1 -1
  100. package/dist/transaction/updateProcedureThreshold.d.ts +7 -2
  101. package/dist/transaction/updateProcedureThreshold.d.ts.map +1 -1
  102. package/dist/transaction/updateProcedureThreshold.js.map +1 -1
  103. package/dist/transaction/updateSigners.d.ts +7 -2
  104. package/dist/transaction/updateSigners.d.ts.map +1 -1
  105. package/dist/transaction/updateSigners.js.map +1 -1
  106. package/dist/transaction.d.ts +1 -1
  107. package/dist/transaction.d.ts.map +1 -1
  108. package/dist/transaction.js +1 -1
  109. package/dist/transaction.js.map +1 -1
  110. package/dist/types/proposal.d.ts +5 -0
  111. package/dist/types/proposal.d.ts.map +1 -1
  112. package/dist/types/proposal.js +3 -0
  113. package/dist/types/proposal.js.map +1 -1
  114. package/package.json +3 -3
  115. package/src/account/builder.test.ts +19 -11
  116. package/src/account/builder.ts +2 -1
  117. package/src/client.test.ts +67 -15
  118. package/src/client.ts +35 -12
  119. package/src/connectivity.test.ts +67 -0
  120. package/src/connectivity.ts +111 -0
  121. package/src/index.ts +25 -1
  122. package/src/multisig.test.ts +526 -94
  123. package/src/multisig.ts +270 -25
  124. package/src/proposal/factory.test.ts +40 -0
  125. package/src/proposal/factory.ts +11 -1
  126. package/src/proposal/metadata.test.ts +60 -0
  127. package/src/proposal/metadata.ts +14 -0
  128. package/src/prover/config.test.ts +90 -0
  129. package/src/prover/config.ts +78 -0
  130. package/src/prover/errors.test.ts +53 -0
  131. package/src/prover/errors.ts +178 -0
  132. package/src/prover/retry.test.ts +38 -0
  133. package/src/prover/retry.ts +46 -0
  134. package/src/prover/test-node.d.ts +6 -0
  135. package/src/prover/workflow.test.ts +109 -0
  136. package/src/prover/workflow.ts +19 -0
  137. package/src/raw-client.test.ts +41 -3
  138. package/src/raw-client.ts +15 -5
  139. package/src/transaction/consumeNotes.ts +11 -1
  140. package/src/transaction/options.ts +4 -0
  141. package/src/transaction/p2id.test.ts +98 -8
  142. package/src/transaction/p2id.ts +91 -12
  143. package/src/transaction/summary.ts +12 -0
  144. package/src/transaction/updateGuardian.ts +11 -1
  145. package/src/transaction/updateProcedureThreshold.ts +13 -1
  146. package/src/transaction/updateSigners.ts +13 -1
  147. package/src/transaction.ts +4 -0
  148. package/src/types/proposal.ts +9 -0
@@ -2,10 +2,11 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import { Multisig } from './multisig.js';
3
3
  import { GuardianHttpClient } from '@openzeppelin/guardian-client';
4
4
  import { buildUpdateProcedureThresholdTransactionRequest, buildUpdateGuardianTransactionRequest, buildUpdateSignersTransactionRequest, executeForSummary, } from './transaction.js';
5
- const { mockRpcGetAccountDetails, mockAccountDeserialize, mockDetectConfig } = vi.hoisted(() => ({
5
+ const { mockRpcGetAccountDetails, mockAccountDeserialize, mockDetectConfig, mockNoteFileDeserialize } = vi.hoisted(() => ({
6
6
  mockRpcGetAccountDetails: vi.fn(),
7
7
  mockAccountDeserialize: vi.fn(),
8
8
  mockDetectConfig: vi.fn(),
9
+ mockNoteFileDeserialize: vi.fn(),
9
10
  }));
10
11
  // Mock the Miden SDK
11
12
  vi.mock('@miden-sdk/miden-sdk', () => ({
@@ -15,6 +16,18 @@ vi.mock('@miden-sdk/miden-sdk', () => ({
15
16
  AccountId: {
16
17
  fromHex: vi.fn((hex) => ({ toString: () => hex })),
17
18
  },
19
+ NoteType: {
20
+ Private: 0,
21
+ Public: 1,
22
+ },
23
+ NoteExportFormat: {
24
+ Id: 0,
25
+ Full: 1,
26
+ Details: 2,
27
+ },
28
+ NoteFile: {
29
+ deserialize: mockNoteFileDeserialize,
30
+ },
18
31
  TransactionSummary: {
19
32
  deserialize: vi.fn().mockReturnValue({
20
33
  toCommitment: () => ({
@@ -79,6 +92,19 @@ vi.mock('./transaction.js', () => ({
79
92
  request: {},
80
93
  salt: { toHex: () => '0x' + 'd'.repeat(64) },
81
94
  }),
95
+ buildP2idNoteFromMetadata: vi.fn().mockReturnValue({
96
+ id: () => ({ toString: () => '0x' + 'ab'.repeat(32) }),
97
+ }),
98
+ // Mirrors the real implementations against the mocked NoteType values
99
+ // (Private = 0, Public = 1).
100
+ parseP2idNoteType: vi.fn((value) => {
101
+ if (value === undefined || value === 'public')
102
+ return 1;
103
+ if (value === 'private')
104
+ return 0;
105
+ throw new Error(`unsupported metadata.noteType '${value}': expected 'public' or 'private'`);
106
+ }),
107
+ p2idNoteTypeToMetadata: vi.fn((noteType) => (noteType === 0 ? 'private' : undefined)),
82
108
  }));
83
109
  vi.mock('./utils/signature.js', async () => {
84
110
  const actual = await vi.importActual('./utils/signature.js');
@@ -106,6 +132,7 @@ vi.mock('./inspector.js', () => ({
106
132
  // Mock fetch for GUARDIAN client
107
133
  const mockFetch = vi.fn();
108
134
  vi.stubGlobal('fetch', mockFetch);
135
+ const MIDEN_RPC_ENDPOINT = 'https://rpc.devnet.miden.io';
109
136
  function mockedAccount(commitmentHex, nonce = 0) {
110
137
  return {
111
138
  commitment: () => ({
@@ -124,6 +151,9 @@ describe('Multisig', () => {
124
151
  let mockSigner;
125
152
  let mockAccount;
126
153
  let mockWebClient;
154
+ function createTestMultisig(config, signer = mockSigner, accountId, proverConfig) {
155
+ return new Multisig(mockAccount, config, guardian, signer, mockWebClient, accountId, MIDEN_RPC_ENDPOINT, proverConfig);
156
+ }
127
157
  beforeEach(() => {
128
158
  mockFetch.mockReset();
129
159
  vi.mocked(executeForSummary).mockResolvedValue({
@@ -176,13 +206,36 @@ describe('Multisig', () => {
176
206
  submitNewTransaction: vi.fn(),
177
207
  submitNewTransactionWithProver: vi.fn(),
178
208
  transactions: {
179
- submit: vi.fn(),
209
+ executeRequest: vi.fn(),
180
210
  },
181
211
  getConsumableNotes: vi.fn().mockResolvedValue([]),
182
212
  syncState: vi.fn(),
183
213
  getAccount: vi.fn().mockResolvedValue(null),
184
214
  newAccount: vi.fn(),
185
215
  };
216
+ mockWebClient.transactions.executeRequest.mockImplementation(async (accountId, request) => {
217
+ const result = await mockWebClient.executeTransaction(accountId, request);
218
+ return {
219
+ result,
220
+ prove: async (options) => {
221
+ const proof = options?.prover === undefined
222
+ ? await mockWebClient.proveTransaction(result)
223
+ : await mockWebClient.proveTransaction(result, options.prover);
224
+ return {
225
+ proof,
226
+ result,
227
+ submit: async () => {
228
+ const blockNumber = await mockWebClient.submitProvenTransaction(proof, result);
229
+ return {
230
+ blockNumber,
231
+ result,
232
+ apply: () => mockWebClient.applyTransaction(result, blockNumber),
233
+ };
234
+ },
235
+ };
236
+ },
237
+ };
238
+ });
186
239
  });
187
240
  describe('constructor', () => {
188
241
  it('should create Multisig with account', () => {
@@ -191,7 +244,7 @@ describe('Multisig', () => {
191
244
  signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
192
245
  guardianCommitment: '0x' + 'c'.repeat(64),
193
246
  };
194
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
247
+ const multisig = createTestMultisig(config);
195
248
  expect(multisig.threshold).toBe(2);
196
249
  expect(multisig.signerCommitments).toEqual(config.signerCommitments);
197
250
  expect(multisig.guardianCommitment).toBe(config.guardianCommitment);
@@ -204,10 +257,18 @@ describe('Multisig', () => {
204
257
  guardianCommitment: '0x' + 'c'.repeat(64),
205
258
  };
206
259
  const accountId = '0x' + 'd'.repeat(30);
207
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient, accountId);
260
+ const multisig = createTestMultisig(config, mockSigner, accountId);
208
261
  expect(multisig.account).toBe(mockAccount);
209
262
  expect(multisig.accountId).toBe(accountId);
210
263
  });
264
+ it('should reject a missing Miden RPC endpoint immediately', () => {
265
+ const config = {
266
+ threshold: 2,
267
+ signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
268
+ guardianCommitment: '0x' + 'c'.repeat(64),
269
+ };
270
+ expect(() => new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient, undefined, undefined)).toThrow('missing required configuration: midenRpcEndpoint');
271
+ });
211
272
  });
212
273
  describe('accountId', () => {
213
274
  it('should return account ID from account', () => {
@@ -216,7 +277,7 @@ describe('Multisig', () => {
216
277
  signerCommitments: ['0x' + 'a'.repeat(64)],
217
278
  guardianCommitment: '0x' + 'c'.repeat(64),
218
279
  };
219
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
280
+ const multisig = createTestMultisig(config);
220
281
  expect(multisig.accountId).toBe('0x' + 'a'.repeat(30));
221
282
  });
222
283
  it('should return provided account ID when constructor override is set', () => {
@@ -226,7 +287,7 @@ describe('Multisig', () => {
226
287
  guardianCommitment: '0x' + 'c'.repeat(64),
227
288
  };
228
289
  const accountId = '0x' + 'e'.repeat(30);
229
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient, accountId);
290
+ const multisig = createTestMultisig(config, mockSigner, accountId);
230
291
  expect(multisig.accountId).toBe(accountId);
231
292
  });
232
293
  });
@@ -237,7 +298,7 @@ describe('Multisig', () => {
237
298
  signerCommitments: ['0x' + 'a'.repeat(64)],
238
299
  guardianCommitment: '0x' + 'c'.repeat(64),
239
300
  };
240
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
301
+ const multisig = createTestMultisig(config);
241
302
  expect(multisig.signerCommitment).toBe(mockSigner.commitment);
242
303
  });
243
304
  });
@@ -248,7 +309,7 @@ describe('Multisig', () => {
248
309
  signerCommitments: ['0x' + 'a'.repeat(64)],
249
310
  guardianCommitment: '0x' + 'c'.repeat(64),
250
311
  };
251
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
312
+ const multisig = createTestMultisig(config);
252
313
  mockFetch.mockResolvedValueOnce({
253
314
  ok: true,
254
315
  json: async () => ({
@@ -399,14 +460,15 @@ describe('Multisig', () => {
399
460
  await expect(multisig.syncState()).rejects.toThrow('Refusing to overwrite local state');
400
461
  expect(mockWebClient.newAccount).not.toHaveBeenCalled();
401
462
  });
402
- it('should throw when incoming state nonce is lower than local nonce', async () => {
463
+ it('keeps local state and refreshes config from it when GUARDIAN nonce is behind local', async () => {
403
464
  const config = {
404
465
  threshold: 1,
405
466
  signerCommitments: ['0x' + 'a'.repeat(64)],
406
467
  guardianCommitment: '0x' + 'c'.repeat(64),
407
468
  };
408
469
  const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient, undefined, 'https://rpc.devnet.miden.io');
409
- mockWebClient.getAccount.mockResolvedValueOnce(mockedAccount('0x' + 'a'.repeat(64), 3));
470
+ const localAccount = mockedAccount('0x' + 'a'.repeat(64), 3);
471
+ mockWebClient.getAccount.mockResolvedValueOnce(localAccount);
410
472
  mockAccountDeserialize.mockReturnValueOnce(mockedAccount('0x' + 'b'.repeat(64), 2));
411
473
  mockFetch.mockResolvedValueOnce({
412
474
  ok: true,
@@ -418,8 +480,23 @@ describe('Multisig', () => {
418
480
  updated_at: '2024-01-02T00:00:00Z',
419
481
  }),
420
482
  });
421
- await expect(multisig.syncState()).rejects.toThrow('incoming nonce 2 is not greater than local nonce 3');
483
+ mockDetectConfig.mockReturnValueOnce({
484
+ threshold: 2,
485
+ numSigners: 2,
486
+ signerCommitments: ['0x' + '1'.repeat(64), '0x' + '2'.repeat(64)],
487
+ guardianEnabled: true,
488
+ guardianCommitment: '0x' + 'd'.repeat(64),
489
+ vaultBalances: [],
490
+ procedureThresholds: new Map(),
491
+ });
492
+ // GUARDIAN behind local (nonce 2 < 3): no throw, local kept, no overwrite,
493
+ // and the decision needs no on-chain round-trip.
494
+ await expect(multisig.syncState()).resolves.toBeDefined();
422
495
  expect(mockWebClient.newAccount).not.toHaveBeenCalled();
496
+ expect(mockRpcGetAccountDetails).not.toHaveBeenCalled();
497
+ // Config refreshed from the authoritative local account (UI unfreezes).
498
+ expect(multisig.account).toBe(localAccount);
499
+ expect(multisig.threshold).toBe(2);
423
500
  });
424
501
  it('should throw when incoming state nonce equals local nonce but commitment differs', async () => {
425
502
  const config = {
@@ -440,8 +517,35 @@ describe('Multisig', () => {
440
517
  updated_at: '2024-01-02T00:00:00Z',
441
518
  }),
442
519
  });
443
- await expect(multisig.syncState()).rejects.toThrow('incoming nonce 2 is not greater than local nonce 2');
520
+ await expect(multisig.syncState()).rejects.toThrow('incoming nonce 2 equals local nonce 2 but commitments differ');
521
+ expect(mockWebClient.newAccount).not.toHaveBeenCalled();
522
+ });
523
+ it('unfreezes Multisig.account after execute when GUARDIAN still lags by one nonce (regression, #343)', async () => {
524
+ const config = {
525
+ threshold: 1,
526
+ signerCommitments: ['0x' + 'a'.repeat(64)],
527
+ guardianCommitment: '0x' + 'c'.repeat(64),
528
+ };
529
+ const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient, undefined, 'https://rpc.devnet.miden.io');
530
+ // Post-execute: local advanced to nonce 1, GUARDIAN still reports nonce 0
531
+ // (candidate not canonicalized yet). Before the fix this threw and left
532
+ // Multisig.account frozen at the pre-execute snapshot.
533
+ const localAccount = mockedAccount('0x' + 'a'.repeat(64), 1);
534
+ mockWebClient.getAccount.mockResolvedValueOnce(localAccount);
535
+ mockAccountDeserialize.mockReturnValueOnce(mockedAccount('0x' + 'b'.repeat(64), 0));
536
+ mockFetch.mockResolvedValueOnce({
537
+ ok: true,
538
+ json: async () => ({
539
+ account_id: multisig.accountId,
540
+ commitment: '0x' + 'b'.repeat(64),
541
+ state_json: { data: 'AQID' },
542
+ created_at: '2024-01-01T00:00:00Z',
543
+ updated_at: '2024-01-02T00:00:00Z',
544
+ }),
545
+ });
546
+ await expect(multisig.syncState()).resolves.toBeDefined();
444
547
  expect(mockWebClient.newAccount).not.toHaveBeenCalled();
548
+ expect(multisig.account).toBe(localAccount);
445
549
  });
446
550
  });
447
551
  describe('verifyStateCommitment', () => {
@@ -490,7 +594,7 @@ describe('Multisig', () => {
490
594
  signerCommitments: ['0x' + 'a'.repeat(64)],
491
595
  guardianCommitment: '0x' + 'c'.repeat(64),
492
596
  };
493
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
597
+ const multisig = createTestMultisig(config);
494
598
  mockFetch.mockResolvedValueOnce({
495
599
  ok: true,
496
600
  json: async () => ({
@@ -513,7 +617,7 @@ describe('Multisig', () => {
513
617
  scheme: 'ecdsa',
514
618
  };
515
619
  guardian.setSigner(ecdsaSigner);
516
- const multisig = new Multisig(mockAccount, config, guardian, ecdsaSigner, mockWebClient);
620
+ const multisig = createTestMultisig(config, ecdsaSigner);
517
621
  mockFetch.mockResolvedValueOnce({
518
622
  ok: true,
519
623
  json: async () => ({
@@ -537,7 +641,7 @@ describe('Multisig', () => {
537
641
  signerCommitments: ['0x' + 'a'.repeat(64)],
538
642
  guardianCommitment: '0x' + 'c'.repeat(64),
539
643
  };
540
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient, '0x' + 'e'.repeat(30));
644
+ const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient, '0x' + 'e'.repeat(30), MIDEN_RPC_ENDPOINT);
541
645
  mockFetch.mockResolvedValueOnce({
542
646
  ok: true,
543
647
  json: async () => ({
@@ -553,7 +657,7 @@ describe('Multisig', () => {
553
657
  signerCommitments: ['0x' + 'a'.repeat(64)],
554
658
  guardianCommitment: '0x' + 'c'.repeat(64),
555
659
  };
556
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
660
+ const multisig = createTestMultisig(config);
557
661
  mockFetch.mockResolvedValueOnce({
558
662
  ok: true,
559
663
  json: async () => ({
@@ -571,7 +675,7 @@ describe('Multisig', () => {
571
675
  signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
572
676
  guardianCommitment: '0x' + 'c'.repeat(64),
573
677
  };
574
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
678
+ const multisig = createTestMultisig(config);
575
679
  const mockProposals = [
576
680
  {
577
681
  account_id: '0x' + 'a'.repeat(30),
@@ -616,7 +720,7 @@ describe('Multisig', () => {
616
720
  signerCommitments: ['0x' + 'a'.repeat(64)],
617
721
  guardianCommitment: '0x' + 'c'.repeat(64),
618
722
  };
619
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
723
+ const multisig = createTestMultisig(config);
620
724
  const mockProposals = [
621
725
  {
622
726
  account_id: '0x' + 'a'.repeat(30),
@@ -659,7 +763,7 @@ describe('Multisig', () => {
659
763
  signerCommitments: ['0x' + 'a'.repeat(64)],
660
764
  guardianCommitment: '0x' + 'c'.repeat(64),
661
765
  };
662
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
766
+ const multisig = createTestMultisig(config);
663
767
  mockFetch.mockResolvedValueOnce({
664
768
  ok: true,
665
769
  json: async () => ({
@@ -701,7 +805,7 @@ describe('Multisig', () => {
701
805
  signerCommitments: ['0x' + 'a'.repeat(64)],
702
806
  guardianCommitment: '0x' + 'c'.repeat(64),
703
807
  };
704
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
808
+ const multisig = createTestMultisig(config);
705
809
  const mockProposals = [
706
810
  {
707
811
  account_id: '0x' + 'a'.repeat(30),
@@ -743,7 +847,7 @@ describe('Multisig', () => {
743
847
  signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
744
848
  guardianCommitment: '0x' + 'c'.repeat(64),
745
849
  };
746
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
850
+ const multisig = createTestMultisig(config);
747
851
  const mockProposals = [
748
852
  {
749
853
  account_id: '0x' + 'a'.repeat(30),
@@ -792,7 +896,7 @@ describe('Multisig', () => {
792
896
  signerCommitments: ['0x' + 'a'.repeat(64)],
793
897
  guardianCommitment: '0x' + 'c'.repeat(64),
794
898
  };
795
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
899
+ const multisig = createTestMultisig(config);
796
900
  expect(multisig.listProposals()).toEqual([]);
797
901
  });
798
902
  });
@@ -803,7 +907,7 @@ describe('Multisig', () => {
803
907
  signerCommitments: ['0x' + 'a'.repeat(64)],
804
908
  guardianCommitment: '0x' + 'c'.repeat(64),
805
909
  };
806
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
910
+ const multisig = createTestMultisig(config);
807
911
  const mockDelta = {
808
912
  account_id: '0x' + 'a'.repeat(30),
809
913
  nonce: 1,
@@ -841,7 +945,7 @@ describe('Multisig', () => {
841
945
  signerCommitments: ['0x' + 'a'.repeat(64)],
842
946
  guardianCommitment: '0x' + 'c'.repeat(64),
843
947
  };
844
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
948
+ const multisig = createTestMultisig(config);
845
949
  const mockDelta = {
846
950
  account_id: '0x' + 'a'.repeat(30),
847
951
  nonce: 1,
@@ -877,7 +981,7 @@ describe('Multisig', () => {
877
981
  signerCommitments: ['0x' + 'a'.repeat(64)],
878
982
  guardianCommitment: '0x' + 'c'.repeat(64),
879
983
  };
880
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
984
+ const multisig = createTestMultisig(config);
881
985
  const mockDelta = {
882
986
  account_id: '0x' + 'a'.repeat(30),
883
987
  nonce: 1,
@@ -927,7 +1031,7 @@ describe('Multisig', () => {
927
1031
  signerCommitments: ['0x' + 'a'.repeat(64)],
928
1032
  guardianCommitment: '0x' + 'c'.repeat(64),
929
1033
  };
930
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1034
+ const multisig = createTestMultisig(config);
931
1035
  const mockDelta = {
932
1036
  account_id: '0x' + 'a'.repeat(30),
933
1037
  nonce: 1,
@@ -960,6 +1064,178 @@ describe('Multisig', () => {
960
1064
  const proposal = await multisig.createP2idProposal('0xrecipient', '0xfaucet', 100n, 1);
961
1065
  expect(proposal.metadata.description).toBe('Send 100 of asset 0xfaucet... to 0xrecipien...');
962
1066
  });
1067
+ it('threads a private noteType into the request and wire metadata (issue #322)', async () => {
1068
+ const { executeForSummary, buildP2idTransactionRequest } = await import('./transaction.js');
1069
+ const { NoteType } = await import('@miden-sdk/miden-sdk');
1070
+ vi.mocked(executeForSummary).mockResolvedValue({
1071
+ toCommitment: () => ({
1072
+ toHex: () => '0x' + 'c'.repeat(64),
1073
+ }),
1074
+ serialize: () => new Uint8Array([1, 2, 3]),
1075
+ });
1076
+ const config = {
1077
+ threshold: 1,
1078
+ signerCommitments: ['0x' + 'a'.repeat(64)],
1079
+ guardianCommitment: '0x' + 'c'.repeat(64),
1080
+ };
1081
+ const multisig = createTestMultisig(config);
1082
+ const mockDelta = {
1083
+ account_id: '0x' + 'a'.repeat(30),
1084
+ nonce: 1,
1085
+ prev_commitment: '0x' + 'b'.repeat(64),
1086
+ delta_payload: {
1087
+ tx_summary: { data: 'AQID' },
1088
+ signatures: [],
1089
+ metadata: {
1090
+ proposal_type: 'p2id',
1091
+ recipient_id: '0xrecipient',
1092
+ faucet_id: '0xfaucet',
1093
+ amount: '100',
1094
+ note_type: 'private',
1095
+ description: '',
1096
+ },
1097
+ },
1098
+ status: {
1099
+ status: 'pending',
1100
+ timestamp: '2024-01-01T00:00:00Z',
1101
+ proposer_id: '0x' + 'c'.repeat(64),
1102
+ cosigner_sigs: [],
1103
+ },
1104
+ };
1105
+ mockFetch.mockResolvedValueOnce({
1106
+ ok: true,
1107
+ json: async () => ({
1108
+ delta: mockDelta,
1109
+ commitment: '0x' + 'c'.repeat(64),
1110
+ }),
1111
+ });
1112
+ const proposal = await multisig.createP2idProposal('0xrecipient', '0xfaucet', 100n, 1, {
1113
+ noteType: NoteType.Private,
1114
+ });
1115
+ // Propose path builds the private note...
1116
+ expect(vi.mocked(buildP2idTransactionRequest)).toHaveBeenCalledWith(expect.any(String), '0xrecipient', '0xfaucet', 100n, expect.anything(), { noteType: NoteType.Private });
1117
+ // ...and the rebuild-from-metadata path parses note_type back to Private.
1118
+ const lastCall = vi.mocked(buildP2idTransactionRequest).mock.calls.at(-1);
1119
+ expect(lastCall[5]).toMatchObject({ noteType: NoteType.Private });
1120
+ // The pushed wire metadata carries note_type so cosigners rebuild the
1121
+ // same private note at verification/execution.
1122
+ const pushBody = JSON.parse(mockFetch.mock.calls.at(-1)[1].body);
1123
+ expect(pushBody.delta_payload.metadata.note_type).toBe('private');
1124
+ expect(proposal.metadata.proposalType).toBe('p2id');
1125
+ expect(proposal.metadata.noteType).toBe('private');
1126
+ });
1127
+ });
1128
+ describe('exportNoteToBytes / importNoteFromBytes (issue #356)', () => {
1129
+ const config = {
1130
+ threshold: 1,
1131
+ signerCommitments: ['0x' + '1'.repeat(64)],
1132
+ guardianCommitment: '0x' + '3'.repeat(64),
1133
+ };
1134
+ it('exports the full note with proof when the inclusion proof is known', async () => {
1135
+ const noteFile = { serialize: () => new Uint8Array([9, 9, 9]) };
1136
+ mockWebClient.getOutputNote = vi.fn().mockResolvedValue({
1137
+ inclusionProof: () => ({}),
1138
+ });
1139
+ mockWebClient.exportNoteFile = vi.fn().mockResolvedValue(noteFile);
1140
+ const multisig = createTestMultisig(config);
1141
+ const bytes = await multisig.exportNoteToBytes('0x' + 'ab'.repeat(32));
1142
+ expect(bytes).toEqual(new Uint8Array([9, 9, 9]));
1143
+ // NoteExportFormat.Full = 1 in the SDK mock
1144
+ expect(mockWebClient.exportNoteFile).toHaveBeenCalledWith('0x' + 'ab'.repeat(32), 1);
1145
+ });
1146
+ it('falls back to a details-only export before the note commits on chain', async () => {
1147
+ const noteFile = { serialize: () => new Uint8Array([7]) };
1148
+ mockWebClient.getOutputNote = vi.fn().mockResolvedValue({
1149
+ inclusionProof: () => undefined,
1150
+ });
1151
+ mockWebClient.exportNoteFile = vi.fn().mockResolvedValue(noteFile);
1152
+ const multisig = createTestMultisig(config);
1153
+ await multisig.exportNoteToBytes(' 0x' + 'ab'.repeat(32) + ' ');
1154
+ // NoteExportFormat.Details = 2 in the SDK mock; the id is trimmed
1155
+ expect(mockWebClient.exportNoteFile).toHaveBeenCalledWith('0x' + 'ab'.repeat(32), 2);
1156
+ });
1157
+ it('rejects exporting a note the local store does not know', async () => {
1158
+ mockWebClient.getOutputNote = vi.fn().mockRejectedValue(new Error('no such note'));
1159
+ mockWebClient.exportNoteFile = vi.fn();
1160
+ const multisig = createTestMultisig(config);
1161
+ await expect(multisig.exportNoteToBytes('0x' + 'ab'.repeat(32))).rejects.toThrow(/not found in the local store/);
1162
+ expect(mockWebClient.exportNoteFile).not.toHaveBeenCalled();
1163
+ });
1164
+ it('rejects exporting when the store resolves no record', async () => {
1165
+ mockWebClient.getOutputNote = vi.fn().mockResolvedValue(undefined);
1166
+ mockWebClient.exportNoteFile = vi.fn();
1167
+ const multisig = createTestMultisig(config);
1168
+ await expect(multisig.exportNoteToBytes('0x' + 'ab'.repeat(32))).rejects.toThrow(/not found in the local store/);
1169
+ expect(mockWebClient.exportNoteFile).not.toHaveBeenCalled();
1170
+ });
1171
+ it('imports note file bytes and returns the resolved identifier', async () => {
1172
+ const decoded = { marker: 'note-file' };
1173
+ mockNoteFileDeserialize.mockReturnValue(decoded);
1174
+ mockWebClient.importNoteFile = vi.fn().mockResolvedValue('0x' + 'cd'.repeat(32));
1175
+ const multisig = createTestMultisig(config);
1176
+ const noteId = await multisig.importNoteFromBytes(new Uint8Array([1, 2, 3]));
1177
+ expect(mockNoteFileDeserialize).toHaveBeenCalledWith(new Uint8Array([1, 2, 3]));
1178
+ expect(mockWebClient.importNoteFile).toHaveBeenCalledWith(decoded);
1179
+ expect(noteId).toBe('0x' + 'cd'.repeat(32));
1180
+ });
1181
+ it('rejects bytes that do not decode as a note file', async () => {
1182
+ mockNoteFileDeserialize.mockImplementation(() => {
1183
+ throw new Error('bad bytes');
1184
+ });
1185
+ mockWebClient.importNoteFile = vi.fn();
1186
+ const multisig = createTestMultisig(config);
1187
+ await expect(multisig.importNoteFromBytes(new Uint8Array([0]))).rejects.toThrow(/failed to decode note file: bad bytes/);
1188
+ expect(mockWebClient.importNoteFile).not.toHaveBeenCalled();
1189
+ });
1190
+ });
1191
+ describe('exportNoteToFile / importNoteFromFile (issue #356)', () => {
1192
+ const config = {
1193
+ threshold: 1,
1194
+ signerCommitments: ['0x' + '1'.repeat(64)],
1195
+ guardianCommitment: '0x' + '3'.repeat(64),
1196
+ };
1197
+ it('rejects exportNoteToFile outside a browser environment', async () => {
1198
+ const multisig = createTestMultisig(config);
1199
+ await expect(multisig.exportNoteToFile('0x' + 'ab'.repeat(32))).rejects.toThrow(/requires a browser environment/);
1200
+ });
1201
+ it('imports from a File/Blob by delegating to importNoteFromBytes', async () => {
1202
+ const decoded = { marker: 'note-file' };
1203
+ mockNoteFileDeserialize.mockReturnValue(decoded);
1204
+ mockWebClient.importNoteFile = vi.fn().mockResolvedValue('0x' + 'cd'.repeat(32));
1205
+ const multisig = createTestMultisig(config);
1206
+ const noteId = await multisig.importNoteFromFile(new Blob([new Uint8Array([1, 2, 3])]));
1207
+ expect(mockNoteFileDeserialize).toHaveBeenCalledWith(new Uint8Array([1, 2, 3]));
1208
+ expect(noteId).toBe('0x' + 'cd'.repeat(32));
1209
+ });
1210
+ });
1211
+ describe('getP2idNoteId (issue #356)', () => {
1212
+ const config = {
1213
+ threshold: 1,
1214
+ signerCommitments: ['0x' + '1'.repeat(64)],
1215
+ guardianCommitment: '0x' + '3'.repeat(64),
1216
+ };
1217
+ it('computes the deterministic note ID from p2id proposal metadata', async () => {
1218
+ const multisig = createTestMultisig(config);
1219
+ const proposal = {
1220
+ metadata: {
1221
+ proposalType: 'p2id',
1222
+ recipientId: '0x' + 'b'.repeat(30),
1223
+ faucetId: '0x' + 'c'.repeat(30),
1224
+ amount: '100',
1225
+ saltHex: '0x' + 'd'.repeat(64),
1226
+ noteType: 'private',
1227
+ },
1228
+ };
1229
+ const noteId = await multisig.getP2idNoteId(proposal);
1230
+ expect(noteId).toBe('0x' + 'ab'.repeat(32));
1231
+ });
1232
+ it('rejects non-p2id proposals', async () => {
1233
+ const multisig = createTestMultisig(config);
1234
+ const proposal = {
1235
+ metadata: { proposalType: 'consume_notes' },
1236
+ };
1237
+ await expect(multisig.getP2idNoteId(proposal)).rejects.toThrow(/requires a P2ID proposal/);
1238
+ });
963
1239
  });
964
1240
  describe('createChangeThresholdProposal', () => {
965
1241
  it('passes the signer scheme to update-signers requests', async () => {
@@ -1007,7 +1283,7 @@ describe('Multisig', () => {
1007
1283
  commitment: '0x' + 'c'.repeat(64),
1008
1284
  }),
1009
1285
  });
1010
- const multisig = new Multisig(mockAccount, config, guardian, ecdsaSigner, mockWebClient);
1286
+ const multisig = createTestMultisig(config, ecdsaSigner);
1011
1287
  await multisig.createChangeThresholdProposal(2, 1);
1012
1288
  expect(buildUpdateSignersTransactionRequest).toHaveBeenCalledWith(mockWebClient, 2, config.signerCommitments, { signatureScheme: 'ecdsa' });
1013
1289
  });
@@ -1022,7 +1298,7 @@ describe('Multisig', () => {
1022
1298
  signerCommitments: ['0x' + 'a'.repeat(64)],
1023
1299
  guardianCommitment: '0x' + 'c'.repeat(64),
1024
1300
  };
1025
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1301
+ const multisig = createTestMultisig(config);
1026
1302
  const newGuardianPubkey = '0x' + '1'.repeat(64);
1027
1303
  mockFetch.mockResolvedValueOnce({
1028
1304
  ok: true,
@@ -1062,7 +1338,7 @@ describe('Multisig', () => {
1062
1338
  signerCommitments: ['0x' + 'a'.repeat(64)],
1063
1339
  guardianCommitment: '0x' + 'c'.repeat(64),
1064
1340
  };
1065
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1341
+ const multisig = createTestMultisig(config);
1066
1342
  mockFetch.mockResolvedValueOnce({
1067
1343
  ok: true,
1068
1344
  json: async () => ({ commitment: '0x' + '2'.repeat(64) }),
@@ -1084,7 +1360,7 @@ describe('Multisig', () => {
1084
1360
  signerCommitments: ['0x' + 'a'.repeat(64)],
1085
1361
  guardianCommitment: '0x' + 'c'.repeat(64),
1086
1362
  };
1087
- const multisig = new Multisig(mockAccount, config, guardian, ecdsaSigner, mockWebClient);
1363
+ const multisig = createTestMultisig(config, ecdsaSigner);
1088
1364
  const newGuardianCommitment = '0x' + '1'.repeat(64);
1089
1365
  mockFetch.mockResolvedValueOnce({
1090
1366
  ok: true,
@@ -1126,7 +1402,7 @@ describe('Multisig', () => {
1126
1402
  signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
1127
1403
  guardianCommitment: '0x' + 'c'.repeat(64),
1128
1404
  };
1129
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1405
+ const multisig = createTestMultisig(config);
1130
1406
  const mockDelta = {
1131
1407
  account_id: '0x' + 'a'.repeat(30),
1132
1408
  nonce: 1,
@@ -1181,7 +1457,7 @@ describe('Multisig', () => {
1181
1457
  signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
1182
1458
  guardianCommitment: '0x' + 'c'.repeat(64),
1183
1459
  };
1184
- const multisig = new Multisig(mockAccount, config, guardian, ecdsaSigner, mockWebClient);
1460
+ const multisig = createTestMultisig(config, ecdsaSigner);
1185
1461
  const mockDelta = {
1186
1462
  account_id: '0x' + 'a'.repeat(30),
1187
1463
  nonce: 1,
@@ -1221,7 +1497,7 @@ describe('Multisig', () => {
1221
1497
  signerCommitments: [mockSigner.commitment],
1222
1498
  guardianCommitment: '0x' + 'c'.repeat(64),
1223
1499
  };
1224
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1500
+ const multisig = createTestMultisig(config);
1225
1501
  // First create a proposal
1226
1502
  const mockDelta = {
1227
1503
  account_id: '0x' + 'a'.repeat(30),
@@ -1290,7 +1566,7 @@ describe('Multisig', () => {
1290
1566
  signerCommitments: ['0x' + 'a'.repeat(64)],
1291
1567
  guardianCommitment: '0x' + 'c'.repeat(64),
1292
1568
  };
1293
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1569
+ const multisig = createTestMultisig(config);
1294
1570
  const mockDelta = {
1295
1571
  account_id: '0x' + 'a'.repeat(30),
1296
1572
  nonce: 1,
@@ -1332,7 +1608,7 @@ describe('Multisig', () => {
1332
1608
  signerCommitments: [mockSigner.commitment],
1333
1609
  guardianCommitment: '0x' + 'c'.repeat(64),
1334
1610
  };
1335
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1611
+ const multisig = createTestMultisig(config);
1336
1612
  const proposalId = '0x' + 'd'.repeat(64);
1337
1613
  mockFetch.mockResolvedValueOnce({
1338
1614
  ok: true,
@@ -1373,7 +1649,7 @@ describe('Multisig', () => {
1373
1649
  signerCommitments: ['0x' + 'a'.repeat(64)],
1374
1650
  guardianCommitment: '0x' + 'c'.repeat(64),
1375
1651
  };
1376
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1652
+ const multisig = createTestMultisig(config);
1377
1653
  vi.mocked(executeForSummary).mockResolvedValueOnce({
1378
1654
  toCommitment: () => ({
1379
1655
  toHex: () => '0x' + 'f'.repeat(64),
@@ -1401,7 +1677,7 @@ describe('Multisig', () => {
1401
1677
  signerCommitments: ['0x' + 'a'.repeat(64)],
1402
1678
  guardianCommitment: '0x' + 'c'.repeat(64),
1403
1679
  };
1404
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1680
+ const multisig = createTestMultisig(config);
1405
1681
  vi.mocked(executeForSummary).mockResolvedValueOnce({
1406
1682
  toCommitment: () => ({
1407
1683
  toHex: () => '0x' + 'c'.repeat(64),
@@ -1441,7 +1717,7 @@ describe('Multisig', () => {
1441
1717
  signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
1442
1718
  guardianCommitment: '0x' + 'c'.repeat(64),
1443
1719
  };
1444
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1720
+ const multisig = createTestMultisig(config);
1445
1721
  const mockProposals = [
1446
1722
  {
1447
1723
  account_id: '0x' + 'a'.repeat(30),
@@ -1488,7 +1764,7 @@ describe('Multisig', () => {
1488
1764
  signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
1489
1765
  guardianCommitment: '0x' + 'c'.repeat(64),
1490
1766
  };
1491
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1767
+ const multisig = createTestMultisig(config);
1492
1768
  const publicKey = '0x' + 'd'.repeat(66);
1493
1769
  mockFetch.mockResolvedValueOnce({
1494
1770
  ok: true,
@@ -1541,12 +1817,18 @@ describe('Multisig', () => {
1541
1817
  signerCommitments: ['0x' + 'a'.repeat(64)],
1542
1818
  guardianCommitment: '0x' + 'c'.repeat(64),
1543
1819
  };
1544
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1820
+ const multisig = createTestMultisig(config);
1545
1821
  mockFetch.mockResolvedValueOnce({
1546
1822
  ok: false,
1547
1823
  status: 404,
1548
1824
  statusText: 'Not Found',
1549
- text: async () => 'Proposal not found',
1825
+ // Feature 009: only a conforming { code, message, meta } envelope is
1826
+ // folded into the error message; raw text bodies are dropped.
1827
+ text: async () => JSON.stringify({
1828
+ code: 'GUARDIAN_PROPOSAL_NOT_FOUND',
1829
+ message: 'Proposal not found',
1830
+ meta: { retryable: false },
1831
+ }),
1550
1832
  });
1551
1833
  await expect(multisig.exportProposal('0x' + 'nonexistent'.repeat(5))).rejects.toThrow('Proposal not found');
1552
1834
  });
@@ -1558,7 +1840,7 @@ describe('Multisig', () => {
1558
1840
  signerCommitments: ['0x' + 'a'.repeat(64)],
1559
1841
  guardianCommitment: '0x' + 'c'.repeat(64),
1560
1842
  };
1561
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1843
+ const multisig = createTestMultisig(config);
1562
1844
  const exported = {
1563
1845
  accountId: multisig.accountId,
1564
1846
  nonce: 1,
@@ -1585,7 +1867,7 @@ describe('Multisig', () => {
1585
1867
  signerCommitments: ['0x' + 'a'.repeat(64)],
1586
1868
  guardianCommitment: '0x' + 'c'.repeat(64),
1587
1869
  };
1588
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1870
+ const multisig = createTestMultisig(config);
1589
1871
  const publicKey = '0x' + 'd'.repeat(66);
1590
1872
  const proposal = await multisig.importProposal(JSON.stringify({
1591
1873
  accountId: multisig.accountId,
@@ -1626,7 +1908,7 @@ describe('Multisig', () => {
1626
1908
  signerCommitments: ['0x' + 'a'.repeat(64)],
1627
1909
  guardianCommitment: '0x' + 'c'.repeat(64),
1628
1910
  };
1629
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1911
+ const multisig = createTestMultisig(config);
1630
1912
  await expect(multisig.importProposal(JSON.stringify({
1631
1913
  accountId: multisig.accountId,
1632
1914
  nonce: 1,
@@ -1653,7 +1935,7 @@ describe('Multisig', () => {
1653
1935
  signerCommitments: ['0x' + 'a'.repeat(64), mockSigner.commitment],
1654
1936
  guardianCommitment: '0x' + 'c'.repeat(64),
1655
1937
  };
1656
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1938
+ const multisig = createTestMultisig(config);
1657
1939
  const exported = {
1658
1940
  accountId: multisig.accountId,
1659
1941
  nonce: 1,
@@ -1689,7 +1971,7 @@ describe('Multisig', () => {
1689
1971
  scheme: 'ecdsa',
1690
1972
  publicKey: '0x' + '2'.repeat(66),
1691
1973
  };
1692
- const multisig = new Multisig(mockAccount, config, guardian, ecdsaSigner, mockWebClient);
1974
+ const multisig = createTestMultisig(config, ecdsaSigner);
1693
1975
  const cachedProposalId = '0x' + 'c'.repeat(64);
1694
1976
  const requestedProposalId = '0x' + 'C'.repeat(64);
1695
1977
  const cosignerPubkey = '0x' + '3'.repeat(66);
@@ -1781,7 +2063,7 @@ describe('Multisig', () => {
1781
2063
  signerCommitments: ['0x' + 'a'.repeat(64)],
1782
2064
  guardianCommitment: '0x' + 'c'.repeat(64),
1783
2065
  };
1784
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
2066
+ const multisig = createTestMultisig(config);
1785
2067
  const proposalId = '0x' + 'c'.repeat(64);
1786
2068
  const newGuardianPubkey = '0x' + '1'.repeat(64);
1787
2069
  const finalRequest = { kind: 'final-switch-guardian-request' };
@@ -1829,7 +2111,7 @@ describe('Multisig', () => {
1829
2111
  signerCommitments: ['0x' + 'a'.repeat(64)],
1830
2112
  guardianCommitment: '0x' + 'c'.repeat(64),
1831
2113
  };
1832
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
2114
+ const multisig = createTestMultisig(config);
1833
2115
  await expect(multisig.createTransactionProposalRequest('0x' + 'nonexistent'.repeat(5))).rejects.toThrow('Proposal not found');
1834
2116
  });
1835
2117
  it('should throw if proposal is still pending', async () => {
@@ -1838,7 +2120,7 @@ describe('Multisig', () => {
1838
2120
  signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
1839
2121
  guardianCommitment: '0x' + 'c'.repeat(64),
1840
2122
  };
1841
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
2123
+ const multisig = createTestMultisig(config);
1842
2124
  const mockProposals = [
1843
2125
  {
1844
2126
  account_id: '0x' + 'a'.repeat(30),
@@ -1881,7 +2163,7 @@ describe('Multisig', () => {
1881
2163
  signerCommitments: ['0x' + 'a'.repeat(64)],
1882
2164
  guardianCommitment: '0x' + 'c'.repeat(64),
1883
2165
  };
1884
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
2166
+ const multisig = createTestMultisig(config);
1885
2167
  const proposalId = '0x' + 'c'.repeat(64);
1886
2168
  vi.mocked(executeForSummary).mockResolvedValueOnce({
1887
2169
  toCommitment: () => ({
@@ -1917,7 +2199,7 @@ describe('Multisig', () => {
1917
2199
  signerCommitments: ['0x' + 'a'.repeat(64)],
1918
2200
  guardianCommitment: '0x' + 'c'.repeat(64),
1919
2201
  };
1920
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
2202
+ const multisig = createTestMultisig(config);
1921
2203
  const proposalId = '0x' + 'c'.repeat(64);
1922
2204
  multisig.proposals.set(proposalId, {
1923
2205
  id: proposalId,
@@ -1952,7 +2234,7 @@ describe('Multisig', () => {
1952
2234
  signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
1953
2235
  guardianCommitment: '0x' + 'c'.repeat(64),
1954
2236
  };
1955
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
2237
+ const multisig = createTestMultisig(config);
1956
2238
  const proposalId = '0x' + 'c'.repeat(64);
1957
2239
  multisig.proposals.set(proposalId, {
1958
2240
  id: proposalId,
@@ -2012,7 +2294,7 @@ describe('Multisig', () => {
2012
2294
  signerCommitments: ['0x' + 'a'.repeat(64)],
2013
2295
  guardianCommitment: '0x' + 'c'.repeat(64),
2014
2296
  };
2015
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
2297
+ const multisig = createTestMultisig(config);
2016
2298
  const proposalId = '0x' + 'c'.repeat(64);
2017
2299
  const finalRequest = { kind: 'fresh-message-word-request' };
2018
2300
  vi.mocked(buildUpdateSignersTransactionRequest)
@@ -2105,7 +2387,7 @@ describe('Multisig', () => {
2105
2387
  signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
2106
2388
  guardianCommitment: '0x' + 'c'.repeat(64),
2107
2389
  };
2108
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
2390
+ const multisig = createTestMultisig(config);
2109
2391
  const proposalId = '0x' + 'c'.repeat(64);
2110
2392
  multisig.proposals.set(proposalId, {
2111
2393
  id: proposalId,
@@ -2142,7 +2424,7 @@ describe('Multisig', () => {
2142
2424
  signerCommitments: ['0x' + 'a'.repeat(64)],
2143
2425
  guardianCommitment: '0x' + 'c'.repeat(64),
2144
2426
  };
2145
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
2427
+ const multisig = createTestMultisig(config);
2146
2428
  await expect(multisig.executeProposal('0x' + 'nonexistent'.repeat(5))).rejects.toThrow('Proposal not found');
2147
2429
  });
2148
2430
  it('should throw if proposal is still pending', async () => {
@@ -2151,7 +2433,7 @@ describe('Multisig', () => {
2151
2433
  signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
2152
2434
  guardianCommitment: '0x' + 'c'.repeat(64),
2153
2435
  };
2154
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
2436
+ const multisig = createTestMultisig(config);
2155
2437
  // Sync with pending proposal (only 1 signature)
2156
2438
  const mockProposals = [
2157
2439
  {
@@ -2196,7 +2478,7 @@ describe('Multisig', () => {
2196
2478
  signerCommitments: ['0x' + 'a'.repeat(64)],
2197
2479
  guardianCommitment: '0x' + 'c'.repeat(64),
2198
2480
  };
2199
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
2481
+ const multisig = createTestMultisig(config);
2200
2482
  const readyDelta = {
2201
2483
  account_id: '0x' + 'a'.repeat(30),
2202
2484
  nonce: 1,
@@ -2258,7 +2540,11 @@ describe('Multisig', () => {
2258
2540
  scheme: 'ecdsa',
2259
2541
  publicKey: '0x' + '2'.repeat(66),
2260
2542
  };
2261
- const multisig = new Multisig(mockAccount, config, guardian, ecdsaSigner, mockWebClient);
2543
+ const multisig = createTestMultisig(config, ecdsaSigner, undefined, {
2544
+ kind: 'remote',
2545
+ maxAttempts: 2,
2546
+ createProver: () => ({}),
2547
+ });
2262
2548
  const proposalId = '0x' + 'c'.repeat(64);
2263
2549
  const cosignerPubkey = '0x' + '3'.repeat(66);
2264
2550
  const ackPubkey = '0x' + '4'.repeat(66);
@@ -2321,9 +2607,22 @@ describe('Multisig', () => {
2321
2607
  ack_scheme: 'ecdsa',
2322
2608
  }),
2323
2609
  });
2324
- mockWebClient.transactions.submit.mockResolvedValueOnce({});
2325
- await expect(multisig.executeProposal(proposalId)).resolves.toBeUndefined();
2326
- expect(mockWebClient.transactions.submit).toHaveBeenCalledTimes(1);
2610
+ mockWebClient.proveTransaction
2611
+ .mockRejectedValueOnce(Object.assign(new Error('unavailable'), { code: 'Unavailable' }))
2612
+ .mockResolvedValueOnce({});
2613
+ vi.useFakeTimers();
2614
+ try {
2615
+ const execution = multisig.executeProposal(proposalId);
2616
+ await vi.runAllTimersAsync();
2617
+ await expect(execution).resolves.toBeUndefined();
2618
+ }
2619
+ finally {
2620
+ vi.useRealTimers();
2621
+ }
2622
+ expect(mockWebClient.executeTransaction).toHaveBeenCalledTimes(1);
2623
+ expect(mockWebClient.proveTransaction).toHaveBeenCalledTimes(2);
2624
+ expect(mockWebClient.submitProvenTransaction).toHaveBeenCalledTimes(1);
2625
+ expect(mockWebClient.applyTransaction).toHaveBeenCalledTimes(1);
2327
2626
  expect(vi.mocked(signatureHexToBytes)).toHaveBeenNthCalledWith(1, cosignerSignature, 'ecdsa');
2328
2627
  expect(vi.mocked(signatureHexToBytes)).toHaveBeenNthCalledWith(2, ackSignature, 'ecdsa');
2329
2628
  expect(vi.mocked(buildSignatureAdviceEntry)).toHaveBeenNthCalledWith(1, expect.anything(), expect.anything(), expect.anything(), cosignerPubkey, cosignerSignature);
@@ -2344,7 +2643,7 @@ describe('Multisig', () => {
2344
2643
  scheme: 'ecdsa',
2345
2644
  publicKey: '0x' + '2'.repeat(66),
2346
2645
  };
2347
- const multisig = new Multisig(mockAccount, config, guardian, ecdsaSigner, mockWebClient);
2646
+ const multisig = createTestMultisig(config, ecdsaSigner);
2348
2647
  const proposalId = '0x' + 'c'.repeat(64);
2349
2648
  const cosignerPubkey = '0x' + '3'.repeat(66);
2350
2649
  const ackPubkey = '0x' + '4'.repeat(66);
@@ -2404,8 +2703,11 @@ describe('Multisig', () => {
2404
2703
  ack_scheme: 'ecdsa',
2405
2704
  }),
2406
2705
  });
2407
- mockWebClient.transactions.submit.mockResolvedValueOnce({});
2408
2706
  await expect(multisig.executeProposal(proposalId)).resolves.toBeUndefined();
2707
+ expect(mockWebClient.executeTransaction).toHaveBeenCalledTimes(1);
2708
+ expect(mockWebClient.proveTransaction).toHaveBeenCalledTimes(1);
2709
+ expect(mockWebClient.submitProvenTransaction).toHaveBeenCalledTimes(1);
2710
+ expect(mockWebClient.applyTransaction).toHaveBeenCalledTimes(1);
2409
2711
  expect(vi.mocked(signatureHexToBytes)).toHaveBeenNthCalledWith(1, cosignerSignature, 'ecdsa');
2410
2712
  expect(vi.mocked(signatureHexToBytes)).toHaveBeenNthCalledWith(2, ackSignature, 'ecdsa');
2411
2713
  expect(vi.mocked(buildSignatureAdviceEntry)).toHaveBeenNthCalledWith(1, expect.anything(), expect.anything(), expect.anything(), cosignerPubkey, cosignerSignature);
@@ -2417,7 +2719,7 @@ describe('Multisig', () => {
2417
2719
  signerCommitments: ['0x' + 'a'.repeat(64)],
2418
2720
  guardianCommitment: '0x' + 'c'.repeat(64),
2419
2721
  };
2420
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
2722
+ const multisig = createTestMultisig(config);
2421
2723
  const proposalId = '0x' + 'c'.repeat(64);
2422
2724
  const newGuardianPubkey = '0x' + '1'.repeat(64);
2423
2725
  multisig.proposals.set(proposalId, {
@@ -2485,9 +2787,11 @@ describe('Multisig', () => {
2485
2787
  ok: true,
2486
2788
  json: async () => ({ success: true, message: 'ok', ack_pubkey: '0x' + 'f'.repeat(64) }),
2487
2789
  });
2488
- mockWebClient.transactions.submit.mockResolvedValueOnce({});
2489
2790
  await expect(multisig.executeProposal(proposalId)).resolves.toBeUndefined();
2490
- expect(mockWebClient.transactions.submit).toHaveBeenCalledTimes(1);
2791
+ expect(mockWebClient.executeTransaction).toHaveBeenCalledTimes(1);
2792
+ expect(mockWebClient.proveTransaction).toHaveBeenCalledTimes(1);
2793
+ expect(mockWebClient.submitProvenTransaction).toHaveBeenCalledTimes(1);
2794
+ expect(mockWebClient.applyTransaction).toHaveBeenCalledTimes(1);
2491
2795
  });
2492
2796
  it('should still switch GUARDIAN when the pre-switch canonicalization push fails', async () => {
2493
2797
  const config = {
@@ -2495,7 +2799,7 @@ describe('Multisig', () => {
2495
2799
  signerCommitments: ['0x' + 'a'.repeat(64)],
2496
2800
  guardianCommitment: '0x' + 'c'.repeat(64),
2497
2801
  };
2498
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
2802
+ const multisig = createTestMultisig(config);
2499
2803
  const proposalId = '0x' + 'c'.repeat(64);
2500
2804
  const newGuardianPubkey = '0x' + '1'.repeat(64);
2501
2805
  multisig.proposals.set(proposalId, {
@@ -2531,9 +2835,11 @@ describe('Multisig', () => {
2531
2835
  ok: true,
2532
2836
  json: async () => ({ success: true, message: 'ok', ack_pubkey: '0x' + 'f'.repeat(64) }),
2533
2837
  });
2534
- mockWebClient.transactions.submit.mockResolvedValueOnce({});
2535
2838
  await expect(multisig.executeProposal(proposalId)).resolves.toBeUndefined();
2536
- expect(mockWebClient.transactions.submit).toHaveBeenCalledTimes(1);
2839
+ expect(mockWebClient.executeTransaction).toHaveBeenCalledTimes(1);
2840
+ expect(mockWebClient.proveTransaction).toHaveBeenCalledTimes(1);
2841
+ expect(mockWebClient.submitProvenTransaction).toHaveBeenCalledTimes(1);
2842
+ expect(mockWebClient.applyTransaction).toHaveBeenCalledTimes(1);
2537
2843
  });
2538
2844
  it('should reject switch_guardian execution when endpoint commitment mismatches', async () => {
2539
2845
  const config = {
@@ -2541,7 +2847,7 @@ describe('Multisig', () => {
2541
2847
  signerCommitments: ['0x' + 'a'.repeat(64)],
2542
2848
  guardianCommitment: '0x' + 'c'.repeat(64),
2543
2849
  };
2544
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
2850
+ const multisig = createTestMultisig(config);
2545
2851
  const proposalId = '0x' + 'c'.repeat(64);
2546
2852
  multisig.proposals.set(proposalId, {
2547
2853
  id: proposalId,
@@ -2576,7 +2882,7 @@ describe('Multisig', () => {
2576
2882
  signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
2577
2883
  guardianCommitment: '0x' + 'c'.repeat(64),
2578
2884
  };
2579
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
2885
+ const multisig = createTestMultisig(config);
2580
2886
  const proposalId = '0x' + 'c'.repeat(64);
2581
2887
  multisig.proposals.set(proposalId, {
2582
2888
  id: proposalId,
@@ -2621,7 +2927,7 @@ describe('Multisig', () => {
2621
2927
  signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
2622
2928
  guardianCommitment: '0x' + 'c'.repeat(64),
2623
2929
  };
2624
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
2930
+ const multisig = createTestMultisig(config);
2625
2931
  const proposalId = '0x' + 'c'.repeat(64);
2626
2932
  multisig.proposals.set(proposalId, {
2627
2933
  id: proposalId,
@@ -2651,6 +2957,38 @@ describe('Multisig', () => {
2651
2957
  await expect(multisig.executeProposal(proposalId)).rejects.toThrow('Duplicate advice-map key detected');
2652
2958
  });
2653
2959
  });
2960
+ describe('submitTransaction', () => {
2961
+ it('uses the configured total proof-attempt budget without repeating other stages', async () => {
2962
+ const multisig = createTestMultisig({
2963
+ threshold: 1,
2964
+ signerCommitments: ['0x' + 'a'.repeat(64)],
2965
+ guardianCommitment: '0x' + 'c'.repeat(64),
2966
+ }, mockSigner, undefined, {
2967
+ kind: 'remote',
2968
+ maxAttempts: 4,
2969
+ createProver: () => ({}),
2970
+ });
2971
+ const transient = Object.assign(new Error('unavailable'), { code: 'Unavailable' });
2972
+ mockWebClient.proveTransaction
2973
+ .mockRejectedValueOnce(transient)
2974
+ .mockRejectedValueOnce(transient)
2975
+ .mockRejectedValueOnce(transient)
2976
+ .mockResolvedValueOnce({});
2977
+ vi.useFakeTimers();
2978
+ try {
2979
+ const submission = multisig.submitTransaction({});
2980
+ await vi.runAllTimersAsync();
2981
+ await submission;
2982
+ }
2983
+ finally {
2984
+ vi.useRealTimers();
2985
+ }
2986
+ expect(mockWebClient.executeTransaction).toHaveBeenCalledTimes(1);
2987
+ expect(mockWebClient.proveTransaction).toHaveBeenCalledTimes(4);
2988
+ expect(mockWebClient.submitProvenTransaction).toHaveBeenCalledTimes(1);
2989
+ expect(mockWebClient.applyTransaction).toHaveBeenCalledTimes(1);
2990
+ });
2991
+ });
2654
2992
  describe('prepareCustomExecution', () => {
2655
2993
  const requestBytes = new Uint8Array([9, 8, 7]);
2656
2994
  function customDelta(proposalType, cosignerSigs) {
@@ -2687,7 +3025,7 @@ describe('Multisig', () => {
2687
3025
  signerCommitments: ['0x' + 'a'.repeat(64)],
2688
3026
  guardianCommitment: '0x' + 'c'.repeat(64),
2689
3027
  };
2690
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
3028
+ const multisig = createTestMultisig(config);
2691
3029
  const builtinDelta = {
2692
3030
  ...customDelta('change_threshold', [falconSig('0x' + 'a'.repeat(64))]),
2693
3031
  };
@@ -2709,7 +3047,7 @@ describe('Multisig', () => {
2709
3047
  signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
2710
3048
  guardianCommitment: '0x' + 'c'.repeat(64),
2711
3049
  };
2712
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
3050
+ const multisig = createTestMultisig(config);
2713
3051
  mockFetch.mockResolvedValueOnce({
2714
3052
  ok: true,
2715
3053
  json: async () => customDelta('b2agg', [falconSig('0x' + 'a'.repeat(64))]),
@@ -2722,7 +3060,7 @@ describe('Multisig', () => {
2722
3060
  signerCommitments: ['0x' + 'a'.repeat(64)],
2723
3061
  guardianCommitment: '0x' + 'c'.repeat(64),
2724
3062
  };
2725
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
3063
+ const multisig = createTestMultisig(config);
2726
3064
  // Signed commitment comes from TransactionSummary.deserialize -> 'c' * 64.
2727
3065
  // Make the binding request derive a different commitment so the check fails.
2728
3066
  vi.mocked(executeForSummary).mockResolvedValueOnce({
@@ -2743,7 +3081,7 @@ describe('Multisig', () => {
2743
3081
  signerCommitments: ['0x' + 'a'.repeat(64)],
2744
3082
  guardianCommitment: '0x' + 'c'.repeat(64),
2745
3083
  };
2746
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
3084
+ const multisig = createTestMultisig(config);
2747
3085
  const ready = customDelta('b2agg', [falconSig('0x' + 'a'.repeat(64))]);
2748
3086
  // getDeltaProposal
2749
3087
  mockFetch.mockResolvedValueOnce({
@@ -2765,7 +3103,7 @@ describe('Multisig', () => {
2765
3103
  signerCommitments: ['0x' + 'a'.repeat(64)],
2766
3104
  guardianCommitment: '0x' + 'c'.repeat(64),
2767
3105
  };
2768
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
3106
+ const multisig = createTestMultisig(config);
2769
3107
  // Create a proposal with metadata
2770
3108
  const mockDelta = {
2771
3109
  account_id: '0x' + 'a'.repeat(30),
@@ -2818,7 +3156,7 @@ describe('Multisig', () => {
2818
3156
  signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
2819
3157
  guardianCommitment: '0x' + 'c'.repeat(64),
2820
3158
  };
2821
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
3159
+ const multisig = createTestMultisig(config);
2822
3160
  // Sync proposals - no local proposals exist
2823
3161
  const mockProposals = [
2824
3162
  {
@@ -2859,7 +3197,7 @@ describe('Multisig', () => {
2859
3197
  signerCommitments: ['0x' + 'a'.repeat(64)],
2860
3198
  guardianCommitment: '0x' + 'c'.repeat(64),
2861
3199
  };
2862
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
3200
+ const multisig = createTestMultisig(config);
2863
3201
  const mockDelta = {
2864
3202
  account_id: '0x' + 'a'.repeat(30),
2865
3203
  nonce: 1,
@@ -2901,7 +3239,7 @@ describe('Multisig', () => {
2901
3239
  signerCommitments: ['0x' + 'a'.repeat(64)],
2902
3240
  guardianCommitment: '0x' + 'c'.repeat(64),
2903
3241
  };
2904
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
3242
+ const multisig = createTestMultisig(config);
2905
3243
  const mockDelta = {
2906
3244
  account_id: '0x' + 'a'.repeat(30),
2907
3245
  nonce: 1,
@@ -2945,7 +3283,7 @@ describe('Multisig', () => {
2945
3283
  signerCommitments: ['0x' + 'a'.repeat(64)],
2946
3284
  guardianCommitment: '0x' + 'c'.repeat(64),
2947
3285
  };
2948
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
3286
+ const multisig = createTestMultisig(config);
2949
3287
  const mockDelta = {
2950
3288
  account_id: '0x' + 'a'.repeat(30),
2951
3289
  nonce: 1,
@@ -2990,7 +3328,7 @@ describe('Multisig', () => {
2990
3328
  signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
2991
3329
  guardianCommitment: '0x' + 'c'.repeat(64),
2992
3330
  };
2993
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
3331
+ const multisig = createTestMultisig(config);
2994
3332
  // First sync with 1 signature (pending)
2995
3333
  const mockProposalsPending = [
2996
3334
  {
@@ -3074,7 +3412,7 @@ describe('Multisig', () => {
3074
3412
  signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64), '0x' + 'c'.repeat(64)],
3075
3413
  guardianCommitment: '0x' + 'd'.repeat(64),
3076
3414
  };
3077
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
3415
+ const multisig = createTestMultisig(config);
3078
3416
  expect(multisig.threshold).toBe(3);
3079
3417
  });
3080
3418
  it('should expose signerCommitments', () => {
@@ -3084,7 +3422,7 @@ describe('Multisig', () => {
3084
3422
  signerCommitments,
3085
3423
  guardianCommitment: '0x' + 'd'.repeat(64),
3086
3424
  };
3087
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
3425
+ const multisig = createTestMultisig(config);
3088
3426
  expect(multisig.signerCommitments).toEqual(signerCommitments);
3089
3427
  });
3090
3428
  it('should expose guardianCommitment', () => {
@@ -3094,7 +3432,7 @@ describe('Multisig', () => {
3094
3432
  signerCommitments: ['0x' + 'a'.repeat(64)],
3095
3433
  guardianCommitment,
3096
3434
  };
3097
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
3435
+ const multisig = createTestMultisig(config);
3098
3436
  expect(multisig.guardianCommitment).toBe(guardianCommitment);
3099
3437
  });
3100
3438
  it('should expose account when provided', () => {
@@ -3103,7 +3441,7 @@ describe('Multisig', () => {
3103
3441
  signerCommitments: ['0x' + 'a'.repeat(64)],
3104
3442
  guardianCommitment: '0x' + 'd'.repeat(64),
3105
3443
  };
3106
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
3444
+ const multisig = createTestMultisig(config);
3107
3445
  expect(multisig.account).toBe(mockAccount);
3108
3446
  });
3109
3447
  });
@@ -3114,7 +3452,7 @@ describe('Multisig', () => {
3114
3452
  signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
3115
3453
  guardianCommitment: '0x' + 'c'.repeat(64),
3116
3454
  };
3117
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
3455
+ const multisig = createTestMultisig(config);
3118
3456
  // Simulates a GUARDIAN response with canonical snake_case metadata
3119
3457
  const rustProposals = [
3120
3458
  {
@@ -3158,7 +3496,7 @@ describe('Multisig', () => {
3158
3496
  signerCommitments: ['0x' + 'a'.repeat(64)],
3159
3497
  guardianCommitment: '0x' + 'c'.repeat(64),
3160
3498
  };
3161
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
3499
+ const multisig = createTestMultisig(config);
3162
3500
  // P2ID proposal with canonical snake_case fields
3163
3501
  const p2idProposals = [
3164
3502
  {
@@ -3209,7 +3547,7 @@ describe('Multisig', () => {
3209
3547
  signerCommitments: ['0x' + 'a'.repeat(64)],
3210
3548
  guardianCommitment: '0x' + 'c'.repeat(64),
3211
3549
  };
3212
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
3550
+ const multisig = createTestMultisig(config);
3213
3551
  const switchGuardianProposals = [
3214
3552
  {
3215
3553
  account_id: '0x' + 'a'.repeat(30),