@openzeppelin/miden-multisig-client 0.15.2 → 0.16.0

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 (98) hide show
  1. package/README.md +25 -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 +9 -5
  9. package/dist/client.d.ts.map +1 -1
  10. package/dist/client.js +5 -5
  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 +9 -3
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +9 -3
  25. package/dist/index.js.map +1 -1
  26. package/dist/multisig.d.ts +68 -7
  27. package/dist/multisig.d.ts.map +1 -1
  28. package/dist/multisig.js +90 -18
  29. package/dist/multisig.js.map +1 -1
  30. package/dist/multisig.test.js +214 -79
  31. package/dist/multisig.test.js.map +1 -1
  32. package/dist/proposal/metadata.d.ts.map +1 -1
  33. package/dist/proposal/metadata.js +12 -0
  34. package/dist/proposal/metadata.js.map +1 -1
  35. package/dist/proposal/metadata.test.js +49 -0
  36. package/dist/proposal/metadata.test.js.map +1 -1
  37. package/dist/raw-client.d.ts +2 -2
  38. package/dist/raw-client.d.ts.map +1 -1
  39. package/dist/raw-client.js +14 -4
  40. package/dist/raw-client.js.map +1 -1
  41. package/dist/raw-client.test.js +25 -3
  42. package/dist/raw-client.test.js.map +1 -1
  43. package/dist/transaction/consumeNotes.d.ts +6 -2
  44. package/dist/transaction/consumeNotes.d.ts.map +1 -1
  45. package/dist/transaction/consumeNotes.js +0 -4
  46. package/dist/transaction/consumeNotes.js.map +1 -1
  47. package/dist/transaction/options.d.ts +3 -0
  48. package/dist/transaction/options.d.ts.map +1 -1
  49. package/dist/transaction/p2id.d.ts +20 -2
  50. package/dist/transaction/p2id.d.ts.map +1 -1
  51. package/dist/transaction/p2id.js +46 -3
  52. package/dist/transaction/p2id.js.map +1 -1
  53. package/dist/transaction/p2id.test.js +66 -6
  54. package/dist/transaction/p2id.test.js.map +1 -1
  55. package/dist/transaction/summary.d.ts +2 -1
  56. package/dist/transaction/summary.d.ts.map +1 -1
  57. package/dist/transaction/summary.js.map +1 -1
  58. package/dist/transaction/updateGuardian.d.ts +6 -2
  59. package/dist/transaction/updateGuardian.d.ts.map +1 -1
  60. package/dist/transaction/updateGuardian.js.map +1 -1
  61. package/dist/transaction/updateProcedureThreshold.d.ts +7 -2
  62. package/dist/transaction/updateProcedureThreshold.d.ts.map +1 -1
  63. package/dist/transaction/updateProcedureThreshold.js.map +1 -1
  64. package/dist/transaction/updateSigners.d.ts +7 -2
  65. package/dist/transaction/updateSigners.d.ts.map +1 -1
  66. package/dist/transaction/updateSigners.js.map +1 -1
  67. package/dist/transaction.d.ts +1 -1
  68. package/dist/transaction.d.ts.map +1 -1
  69. package/dist/transaction.js +1 -1
  70. package/dist/transaction.js.map +1 -1
  71. package/dist/types/proposal.d.ts +5 -0
  72. package/dist/types/proposal.d.ts.map +1 -1
  73. package/dist/types/proposal.js +3 -0
  74. package/dist/types/proposal.js.map +1 -1
  75. package/package.json +3 -3
  76. package/src/account/builder.test.ts +19 -11
  77. package/src/account/builder.ts +2 -1
  78. package/src/client.test.ts +67 -15
  79. package/src/client.ts +18 -10
  80. package/src/connectivity.test.ts +67 -0
  81. package/src/connectivity.ts +111 -0
  82. package/src/index.ts +20 -1
  83. package/src/multisig.test.ts +267 -80
  84. package/src/multisig.ts +109 -20
  85. package/src/proposal/metadata.test.ts +60 -0
  86. package/src/proposal/metadata.ts +14 -0
  87. package/src/raw-client.test.ts +41 -3
  88. package/src/raw-client.ts +15 -5
  89. package/src/transaction/consumeNotes.ts +11 -1
  90. package/src/transaction/options.ts +4 -0
  91. package/src/transaction/p2id.test.ts +98 -8
  92. package/src/transaction/p2id.ts +64 -4
  93. package/src/transaction/summary.ts +12 -0
  94. package/src/transaction/updateGuardian.ts +11 -1
  95. package/src/transaction/updateProcedureThreshold.ts +13 -1
  96. package/src/transaction/updateSigners.ts +13 -1
  97. package/src/transaction.ts +3 -0
  98. package/src/types/proposal.ts +9 -0
@@ -15,6 +15,10 @@ vi.mock('@miden-sdk/miden-sdk', () => ({
15
15
  AccountId: {
16
16
  fromHex: vi.fn((hex) => ({ toString: () => hex })),
17
17
  },
18
+ NoteType: {
19
+ Private: 0,
20
+ Public: 1,
21
+ },
18
22
  TransactionSummary: {
19
23
  deserialize: vi.fn().mockReturnValue({
20
24
  toCommitment: () => ({
@@ -79,6 +83,16 @@ vi.mock('./transaction.js', () => ({
79
83
  request: {},
80
84
  salt: { toHex: () => '0x' + 'd'.repeat(64) },
81
85
  }),
86
+ // Mirrors the real implementations against the mocked NoteType values
87
+ // (Private = 0, Public = 1).
88
+ parseP2idNoteType: vi.fn((value) => {
89
+ if (value === undefined || value === 'public')
90
+ return 1;
91
+ if (value === 'private')
92
+ return 0;
93
+ throw new Error(`unsupported metadata.noteType '${value}': expected 'public' or 'private'`);
94
+ }),
95
+ p2idNoteTypeToMetadata: vi.fn((noteType) => (noteType === 0 ? 'private' : undefined)),
82
96
  }));
83
97
  vi.mock('./utils/signature.js', async () => {
84
98
  const actual = await vi.importActual('./utils/signature.js');
@@ -106,6 +120,7 @@ vi.mock('./inspector.js', () => ({
106
120
  // Mock fetch for GUARDIAN client
107
121
  const mockFetch = vi.fn();
108
122
  vi.stubGlobal('fetch', mockFetch);
123
+ const MIDEN_RPC_ENDPOINT = 'https://rpc.devnet.miden.io';
109
124
  function mockedAccount(commitmentHex, nonce = 0) {
110
125
  return {
111
126
  commitment: () => ({
@@ -124,6 +139,9 @@ describe('Multisig', () => {
124
139
  let mockSigner;
125
140
  let mockAccount;
126
141
  let mockWebClient;
142
+ function createTestMultisig(config, signer = mockSigner, accountId) {
143
+ return new Multisig(mockAccount, config, guardian, signer, mockWebClient, accountId, MIDEN_RPC_ENDPOINT);
144
+ }
127
145
  beforeEach(() => {
128
146
  mockFetch.mockReset();
129
147
  vi.mocked(executeForSummary).mockResolvedValue({
@@ -191,7 +209,7 @@ describe('Multisig', () => {
191
209
  signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
192
210
  guardianCommitment: '0x' + 'c'.repeat(64),
193
211
  };
194
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
212
+ const multisig = createTestMultisig(config);
195
213
  expect(multisig.threshold).toBe(2);
196
214
  expect(multisig.signerCommitments).toEqual(config.signerCommitments);
197
215
  expect(multisig.guardianCommitment).toBe(config.guardianCommitment);
@@ -204,10 +222,18 @@ describe('Multisig', () => {
204
222
  guardianCommitment: '0x' + 'c'.repeat(64),
205
223
  };
206
224
  const accountId = '0x' + 'd'.repeat(30);
207
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient, accountId);
225
+ const multisig = createTestMultisig(config, mockSigner, accountId);
208
226
  expect(multisig.account).toBe(mockAccount);
209
227
  expect(multisig.accountId).toBe(accountId);
210
228
  });
229
+ it('should reject a missing Miden RPC endpoint immediately', () => {
230
+ const config = {
231
+ threshold: 2,
232
+ signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
233
+ guardianCommitment: '0x' + 'c'.repeat(64),
234
+ };
235
+ expect(() => new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient, undefined, undefined)).toThrow('missing required configuration: midenRpcEndpoint');
236
+ });
211
237
  });
212
238
  describe('accountId', () => {
213
239
  it('should return account ID from account', () => {
@@ -216,7 +242,7 @@ describe('Multisig', () => {
216
242
  signerCommitments: ['0x' + 'a'.repeat(64)],
217
243
  guardianCommitment: '0x' + 'c'.repeat(64),
218
244
  };
219
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
245
+ const multisig = createTestMultisig(config);
220
246
  expect(multisig.accountId).toBe('0x' + 'a'.repeat(30));
221
247
  });
222
248
  it('should return provided account ID when constructor override is set', () => {
@@ -226,7 +252,7 @@ describe('Multisig', () => {
226
252
  guardianCommitment: '0x' + 'c'.repeat(64),
227
253
  };
228
254
  const accountId = '0x' + 'e'.repeat(30);
229
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient, accountId);
255
+ const multisig = createTestMultisig(config, mockSigner, accountId);
230
256
  expect(multisig.accountId).toBe(accountId);
231
257
  });
232
258
  });
@@ -237,7 +263,7 @@ describe('Multisig', () => {
237
263
  signerCommitments: ['0x' + 'a'.repeat(64)],
238
264
  guardianCommitment: '0x' + 'c'.repeat(64),
239
265
  };
240
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
266
+ const multisig = createTestMultisig(config);
241
267
  expect(multisig.signerCommitment).toBe(mockSigner.commitment);
242
268
  });
243
269
  });
@@ -248,7 +274,7 @@ describe('Multisig', () => {
248
274
  signerCommitments: ['0x' + 'a'.repeat(64)],
249
275
  guardianCommitment: '0x' + 'c'.repeat(64),
250
276
  };
251
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
277
+ const multisig = createTestMultisig(config);
252
278
  mockFetch.mockResolvedValueOnce({
253
279
  ok: true,
254
280
  json: async () => ({
@@ -399,14 +425,15 @@ describe('Multisig', () => {
399
425
  await expect(multisig.syncState()).rejects.toThrow('Refusing to overwrite local state');
400
426
  expect(mockWebClient.newAccount).not.toHaveBeenCalled();
401
427
  });
402
- it('should throw when incoming state nonce is lower than local nonce', async () => {
428
+ it('keeps local state and refreshes config from it when GUARDIAN nonce is behind local', async () => {
403
429
  const config = {
404
430
  threshold: 1,
405
431
  signerCommitments: ['0x' + 'a'.repeat(64)],
406
432
  guardianCommitment: '0x' + 'c'.repeat(64),
407
433
  };
408
434
  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));
435
+ const localAccount = mockedAccount('0x' + 'a'.repeat(64), 3);
436
+ mockWebClient.getAccount.mockResolvedValueOnce(localAccount);
410
437
  mockAccountDeserialize.mockReturnValueOnce(mockedAccount('0x' + 'b'.repeat(64), 2));
411
438
  mockFetch.mockResolvedValueOnce({
412
439
  ok: true,
@@ -418,8 +445,23 @@ describe('Multisig', () => {
418
445
  updated_at: '2024-01-02T00:00:00Z',
419
446
  }),
420
447
  });
421
- await expect(multisig.syncState()).rejects.toThrow('incoming nonce 2 is not greater than local nonce 3');
448
+ mockDetectConfig.mockReturnValueOnce({
449
+ threshold: 2,
450
+ numSigners: 2,
451
+ signerCommitments: ['0x' + '1'.repeat(64), '0x' + '2'.repeat(64)],
452
+ guardianEnabled: true,
453
+ guardianCommitment: '0x' + 'd'.repeat(64),
454
+ vaultBalances: [],
455
+ procedureThresholds: new Map(),
456
+ });
457
+ // GUARDIAN behind local (nonce 2 < 3): no throw, local kept, no overwrite,
458
+ // and the decision needs no on-chain round-trip.
459
+ await expect(multisig.syncState()).resolves.toBeDefined();
422
460
  expect(mockWebClient.newAccount).not.toHaveBeenCalled();
461
+ expect(mockRpcGetAccountDetails).not.toHaveBeenCalled();
462
+ // Config refreshed from the authoritative local account (UI unfreezes).
463
+ expect(multisig.account).toBe(localAccount);
464
+ expect(multisig.threshold).toBe(2);
423
465
  });
424
466
  it('should throw when incoming state nonce equals local nonce but commitment differs', async () => {
425
467
  const config = {
@@ -440,9 +482,36 @@ describe('Multisig', () => {
440
482
  updated_at: '2024-01-02T00:00:00Z',
441
483
  }),
442
484
  });
443
- await expect(multisig.syncState()).rejects.toThrow('incoming nonce 2 is not greater than local nonce 2');
485
+ await expect(multisig.syncState()).rejects.toThrow('incoming nonce 2 equals local nonce 2 but commitments differ');
444
486
  expect(mockWebClient.newAccount).not.toHaveBeenCalled();
445
487
  });
488
+ it('unfreezes Multisig.account after execute when GUARDIAN still lags by one nonce (regression, #343)', async () => {
489
+ const config = {
490
+ threshold: 1,
491
+ signerCommitments: ['0x' + 'a'.repeat(64)],
492
+ guardianCommitment: '0x' + 'c'.repeat(64),
493
+ };
494
+ const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient, undefined, 'https://rpc.devnet.miden.io');
495
+ // Post-execute: local advanced to nonce 1, GUARDIAN still reports nonce 0
496
+ // (candidate not canonicalized yet). Before the fix this threw and left
497
+ // Multisig.account frozen at the pre-execute snapshot.
498
+ const localAccount = mockedAccount('0x' + 'a'.repeat(64), 1);
499
+ mockWebClient.getAccount.mockResolvedValueOnce(localAccount);
500
+ mockAccountDeserialize.mockReturnValueOnce(mockedAccount('0x' + 'b'.repeat(64), 0));
501
+ mockFetch.mockResolvedValueOnce({
502
+ ok: true,
503
+ json: async () => ({
504
+ account_id: multisig.accountId,
505
+ commitment: '0x' + 'b'.repeat(64),
506
+ state_json: { data: 'AQID' },
507
+ created_at: '2024-01-01T00:00:00Z',
508
+ updated_at: '2024-01-02T00:00:00Z',
509
+ }),
510
+ });
511
+ await expect(multisig.syncState()).resolves.toBeDefined();
512
+ expect(mockWebClient.newAccount).not.toHaveBeenCalled();
513
+ expect(multisig.account).toBe(localAccount);
514
+ });
446
515
  });
447
516
  describe('verifyStateCommitment', () => {
448
517
  it('should pass when local and on-chain commitments match', async () => {
@@ -490,7 +559,7 @@ describe('Multisig', () => {
490
559
  signerCommitments: ['0x' + 'a'.repeat(64)],
491
560
  guardianCommitment: '0x' + 'c'.repeat(64),
492
561
  };
493
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
562
+ const multisig = createTestMultisig(config);
494
563
  mockFetch.mockResolvedValueOnce({
495
564
  ok: true,
496
565
  json: async () => ({
@@ -513,7 +582,7 @@ describe('Multisig', () => {
513
582
  scheme: 'ecdsa',
514
583
  };
515
584
  guardian.setSigner(ecdsaSigner);
516
- const multisig = new Multisig(mockAccount, config, guardian, ecdsaSigner, mockWebClient);
585
+ const multisig = createTestMultisig(config, ecdsaSigner);
517
586
  mockFetch.mockResolvedValueOnce({
518
587
  ok: true,
519
588
  json: async () => ({
@@ -537,7 +606,7 @@ describe('Multisig', () => {
537
606
  signerCommitments: ['0x' + 'a'.repeat(64)],
538
607
  guardianCommitment: '0x' + 'c'.repeat(64),
539
608
  };
540
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient, '0x' + 'e'.repeat(30));
609
+ const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient, '0x' + 'e'.repeat(30), MIDEN_RPC_ENDPOINT);
541
610
  mockFetch.mockResolvedValueOnce({
542
611
  ok: true,
543
612
  json: async () => ({
@@ -553,7 +622,7 @@ describe('Multisig', () => {
553
622
  signerCommitments: ['0x' + 'a'.repeat(64)],
554
623
  guardianCommitment: '0x' + 'c'.repeat(64),
555
624
  };
556
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
625
+ const multisig = createTestMultisig(config);
557
626
  mockFetch.mockResolvedValueOnce({
558
627
  ok: true,
559
628
  json: async () => ({
@@ -571,7 +640,7 @@ describe('Multisig', () => {
571
640
  signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
572
641
  guardianCommitment: '0x' + 'c'.repeat(64),
573
642
  };
574
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
643
+ const multisig = createTestMultisig(config);
575
644
  const mockProposals = [
576
645
  {
577
646
  account_id: '0x' + 'a'.repeat(30),
@@ -616,7 +685,7 @@ describe('Multisig', () => {
616
685
  signerCommitments: ['0x' + 'a'.repeat(64)],
617
686
  guardianCommitment: '0x' + 'c'.repeat(64),
618
687
  };
619
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
688
+ const multisig = createTestMultisig(config);
620
689
  const mockProposals = [
621
690
  {
622
691
  account_id: '0x' + 'a'.repeat(30),
@@ -659,7 +728,7 @@ describe('Multisig', () => {
659
728
  signerCommitments: ['0x' + 'a'.repeat(64)],
660
729
  guardianCommitment: '0x' + 'c'.repeat(64),
661
730
  };
662
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
731
+ const multisig = createTestMultisig(config);
663
732
  mockFetch.mockResolvedValueOnce({
664
733
  ok: true,
665
734
  json: async () => ({
@@ -701,7 +770,7 @@ describe('Multisig', () => {
701
770
  signerCommitments: ['0x' + 'a'.repeat(64)],
702
771
  guardianCommitment: '0x' + 'c'.repeat(64),
703
772
  };
704
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
773
+ const multisig = createTestMultisig(config);
705
774
  const mockProposals = [
706
775
  {
707
776
  account_id: '0x' + 'a'.repeat(30),
@@ -743,7 +812,7 @@ describe('Multisig', () => {
743
812
  signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
744
813
  guardianCommitment: '0x' + 'c'.repeat(64),
745
814
  };
746
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
815
+ const multisig = createTestMultisig(config);
747
816
  const mockProposals = [
748
817
  {
749
818
  account_id: '0x' + 'a'.repeat(30),
@@ -792,7 +861,7 @@ describe('Multisig', () => {
792
861
  signerCommitments: ['0x' + 'a'.repeat(64)],
793
862
  guardianCommitment: '0x' + 'c'.repeat(64),
794
863
  };
795
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
864
+ const multisig = createTestMultisig(config);
796
865
  expect(multisig.listProposals()).toEqual([]);
797
866
  });
798
867
  });
@@ -803,7 +872,7 @@ describe('Multisig', () => {
803
872
  signerCommitments: ['0x' + 'a'.repeat(64)],
804
873
  guardianCommitment: '0x' + 'c'.repeat(64),
805
874
  };
806
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
875
+ const multisig = createTestMultisig(config);
807
876
  const mockDelta = {
808
877
  account_id: '0x' + 'a'.repeat(30),
809
878
  nonce: 1,
@@ -841,7 +910,7 @@ describe('Multisig', () => {
841
910
  signerCommitments: ['0x' + 'a'.repeat(64)],
842
911
  guardianCommitment: '0x' + 'c'.repeat(64),
843
912
  };
844
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
913
+ const multisig = createTestMultisig(config);
845
914
  const mockDelta = {
846
915
  account_id: '0x' + 'a'.repeat(30),
847
916
  nonce: 1,
@@ -877,7 +946,7 @@ describe('Multisig', () => {
877
946
  signerCommitments: ['0x' + 'a'.repeat(64)],
878
947
  guardianCommitment: '0x' + 'c'.repeat(64),
879
948
  };
880
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
949
+ const multisig = createTestMultisig(config);
881
950
  const mockDelta = {
882
951
  account_id: '0x' + 'a'.repeat(30),
883
952
  nonce: 1,
@@ -927,7 +996,7 @@ describe('Multisig', () => {
927
996
  signerCommitments: ['0x' + 'a'.repeat(64)],
928
997
  guardianCommitment: '0x' + 'c'.repeat(64),
929
998
  };
930
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
999
+ const multisig = createTestMultisig(config);
931
1000
  const mockDelta = {
932
1001
  account_id: '0x' + 'a'.repeat(30),
933
1002
  nonce: 1,
@@ -960,6 +1029,66 @@ describe('Multisig', () => {
960
1029
  const proposal = await multisig.createP2idProposal('0xrecipient', '0xfaucet', 100n, 1);
961
1030
  expect(proposal.metadata.description).toBe('Send 100 of asset 0xfaucet... to 0xrecipien...');
962
1031
  });
1032
+ it('threads a private noteType into the request and wire metadata (issue #322)', async () => {
1033
+ const { executeForSummary, buildP2idTransactionRequest } = await import('./transaction.js');
1034
+ const { NoteType } = await import('@miden-sdk/miden-sdk');
1035
+ vi.mocked(executeForSummary).mockResolvedValue({
1036
+ toCommitment: () => ({
1037
+ toHex: () => '0x' + 'c'.repeat(64),
1038
+ }),
1039
+ serialize: () => new Uint8Array([1, 2, 3]),
1040
+ });
1041
+ const config = {
1042
+ threshold: 1,
1043
+ signerCommitments: ['0x' + 'a'.repeat(64)],
1044
+ guardianCommitment: '0x' + 'c'.repeat(64),
1045
+ };
1046
+ const multisig = createTestMultisig(config);
1047
+ const mockDelta = {
1048
+ account_id: '0x' + 'a'.repeat(30),
1049
+ nonce: 1,
1050
+ prev_commitment: '0x' + 'b'.repeat(64),
1051
+ delta_payload: {
1052
+ tx_summary: { data: 'AQID' },
1053
+ signatures: [],
1054
+ metadata: {
1055
+ proposal_type: 'p2id',
1056
+ recipient_id: '0xrecipient',
1057
+ faucet_id: '0xfaucet',
1058
+ amount: '100',
1059
+ note_type: 'private',
1060
+ description: '',
1061
+ },
1062
+ },
1063
+ status: {
1064
+ status: 'pending',
1065
+ timestamp: '2024-01-01T00:00:00Z',
1066
+ proposer_id: '0x' + 'c'.repeat(64),
1067
+ cosigner_sigs: [],
1068
+ },
1069
+ };
1070
+ mockFetch.mockResolvedValueOnce({
1071
+ ok: true,
1072
+ json: async () => ({
1073
+ delta: mockDelta,
1074
+ commitment: '0x' + 'c'.repeat(64),
1075
+ }),
1076
+ });
1077
+ const proposal = await multisig.createP2idProposal('0xrecipient', '0xfaucet', 100n, 1, {
1078
+ noteType: NoteType.Private,
1079
+ });
1080
+ // Propose path builds the private note...
1081
+ expect(vi.mocked(buildP2idTransactionRequest)).toHaveBeenCalledWith(expect.any(String), '0xrecipient', '0xfaucet', 100n, expect.anything(), { noteType: NoteType.Private });
1082
+ // ...and the rebuild-from-metadata path parses note_type back to Private.
1083
+ const lastCall = vi.mocked(buildP2idTransactionRequest).mock.calls.at(-1);
1084
+ expect(lastCall[5]).toMatchObject({ noteType: NoteType.Private });
1085
+ // The pushed wire metadata carries note_type so cosigners rebuild the
1086
+ // same private note at verification/execution.
1087
+ const pushBody = JSON.parse(mockFetch.mock.calls.at(-1)[1].body);
1088
+ expect(pushBody.delta_payload.metadata.note_type).toBe('private');
1089
+ expect(proposal.metadata.proposalType).toBe('p2id');
1090
+ expect(proposal.metadata.noteType).toBe('private');
1091
+ });
963
1092
  });
964
1093
  describe('createChangeThresholdProposal', () => {
965
1094
  it('passes the signer scheme to update-signers requests', async () => {
@@ -1007,7 +1136,7 @@ describe('Multisig', () => {
1007
1136
  commitment: '0x' + 'c'.repeat(64),
1008
1137
  }),
1009
1138
  });
1010
- const multisig = new Multisig(mockAccount, config, guardian, ecdsaSigner, mockWebClient);
1139
+ const multisig = createTestMultisig(config, ecdsaSigner);
1011
1140
  await multisig.createChangeThresholdProposal(2, 1);
1012
1141
  expect(buildUpdateSignersTransactionRequest).toHaveBeenCalledWith(mockWebClient, 2, config.signerCommitments, { signatureScheme: 'ecdsa' });
1013
1142
  });
@@ -1022,7 +1151,7 @@ describe('Multisig', () => {
1022
1151
  signerCommitments: ['0x' + 'a'.repeat(64)],
1023
1152
  guardianCommitment: '0x' + 'c'.repeat(64),
1024
1153
  };
1025
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1154
+ const multisig = createTestMultisig(config);
1026
1155
  const newGuardianPubkey = '0x' + '1'.repeat(64);
1027
1156
  mockFetch.mockResolvedValueOnce({
1028
1157
  ok: true,
@@ -1062,7 +1191,7 @@ describe('Multisig', () => {
1062
1191
  signerCommitments: ['0x' + 'a'.repeat(64)],
1063
1192
  guardianCommitment: '0x' + 'c'.repeat(64),
1064
1193
  };
1065
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1194
+ const multisig = createTestMultisig(config);
1066
1195
  mockFetch.mockResolvedValueOnce({
1067
1196
  ok: true,
1068
1197
  json: async () => ({ commitment: '0x' + '2'.repeat(64) }),
@@ -1084,7 +1213,7 @@ describe('Multisig', () => {
1084
1213
  signerCommitments: ['0x' + 'a'.repeat(64)],
1085
1214
  guardianCommitment: '0x' + 'c'.repeat(64),
1086
1215
  };
1087
- const multisig = new Multisig(mockAccount, config, guardian, ecdsaSigner, mockWebClient);
1216
+ const multisig = createTestMultisig(config, ecdsaSigner);
1088
1217
  const newGuardianCommitment = '0x' + '1'.repeat(64);
1089
1218
  mockFetch.mockResolvedValueOnce({
1090
1219
  ok: true,
@@ -1126,7 +1255,7 @@ describe('Multisig', () => {
1126
1255
  signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
1127
1256
  guardianCommitment: '0x' + 'c'.repeat(64),
1128
1257
  };
1129
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1258
+ const multisig = createTestMultisig(config);
1130
1259
  const mockDelta = {
1131
1260
  account_id: '0x' + 'a'.repeat(30),
1132
1261
  nonce: 1,
@@ -1181,7 +1310,7 @@ describe('Multisig', () => {
1181
1310
  signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
1182
1311
  guardianCommitment: '0x' + 'c'.repeat(64),
1183
1312
  };
1184
- const multisig = new Multisig(mockAccount, config, guardian, ecdsaSigner, mockWebClient);
1313
+ const multisig = createTestMultisig(config, ecdsaSigner);
1185
1314
  const mockDelta = {
1186
1315
  account_id: '0x' + 'a'.repeat(30),
1187
1316
  nonce: 1,
@@ -1221,7 +1350,7 @@ describe('Multisig', () => {
1221
1350
  signerCommitments: [mockSigner.commitment],
1222
1351
  guardianCommitment: '0x' + 'c'.repeat(64),
1223
1352
  };
1224
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1353
+ const multisig = createTestMultisig(config);
1225
1354
  // First create a proposal
1226
1355
  const mockDelta = {
1227
1356
  account_id: '0x' + 'a'.repeat(30),
@@ -1290,7 +1419,7 @@ describe('Multisig', () => {
1290
1419
  signerCommitments: ['0x' + 'a'.repeat(64)],
1291
1420
  guardianCommitment: '0x' + 'c'.repeat(64),
1292
1421
  };
1293
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1422
+ const multisig = createTestMultisig(config);
1294
1423
  const mockDelta = {
1295
1424
  account_id: '0x' + 'a'.repeat(30),
1296
1425
  nonce: 1,
@@ -1332,7 +1461,7 @@ describe('Multisig', () => {
1332
1461
  signerCommitments: [mockSigner.commitment],
1333
1462
  guardianCommitment: '0x' + 'c'.repeat(64),
1334
1463
  };
1335
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1464
+ const multisig = createTestMultisig(config);
1336
1465
  const proposalId = '0x' + 'd'.repeat(64);
1337
1466
  mockFetch.mockResolvedValueOnce({
1338
1467
  ok: true,
@@ -1373,7 +1502,7 @@ describe('Multisig', () => {
1373
1502
  signerCommitments: ['0x' + 'a'.repeat(64)],
1374
1503
  guardianCommitment: '0x' + 'c'.repeat(64),
1375
1504
  };
1376
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1505
+ const multisig = createTestMultisig(config);
1377
1506
  vi.mocked(executeForSummary).mockResolvedValueOnce({
1378
1507
  toCommitment: () => ({
1379
1508
  toHex: () => '0x' + 'f'.repeat(64),
@@ -1401,7 +1530,7 @@ describe('Multisig', () => {
1401
1530
  signerCommitments: ['0x' + 'a'.repeat(64)],
1402
1531
  guardianCommitment: '0x' + 'c'.repeat(64),
1403
1532
  };
1404
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1533
+ const multisig = createTestMultisig(config);
1405
1534
  vi.mocked(executeForSummary).mockResolvedValueOnce({
1406
1535
  toCommitment: () => ({
1407
1536
  toHex: () => '0x' + 'c'.repeat(64),
@@ -1441,7 +1570,7 @@ describe('Multisig', () => {
1441
1570
  signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
1442
1571
  guardianCommitment: '0x' + 'c'.repeat(64),
1443
1572
  };
1444
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1573
+ const multisig = createTestMultisig(config);
1445
1574
  const mockProposals = [
1446
1575
  {
1447
1576
  account_id: '0x' + 'a'.repeat(30),
@@ -1488,7 +1617,7 @@ describe('Multisig', () => {
1488
1617
  signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
1489
1618
  guardianCommitment: '0x' + 'c'.repeat(64),
1490
1619
  };
1491
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1620
+ const multisig = createTestMultisig(config);
1492
1621
  const publicKey = '0x' + 'd'.repeat(66);
1493
1622
  mockFetch.mockResolvedValueOnce({
1494
1623
  ok: true,
@@ -1541,12 +1670,18 @@ describe('Multisig', () => {
1541
1670
  signerCommitments: ['0x' + 'a'.repeat(64)],
1542
1671
  guardianCommitment: '0x' + 'c'.repeat(64),
1543
1672
  };
1544
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1673
+ const multisig = createTestMultisig(config);
1545
1674
  mockFetch.mockResolvedValueOnce({
1546
1675
  ok: false,
1547
1676
  status: 404,
1548
1677
  statusText: 'Not Found',
1549
- text: async () => 'Proposal not found',
1678
+ // Feature 009: only a conforming { code, message, meta } envelope is
1679
+ // folded into the error message; raw text bodies are dropped.
1680
+ text: async () => JSON.stringify({
1681
+ code: 'GUARDIAN_PROPOSAL_NOT_FOUND',
1682
+ message: 'Proposal not found',
1683
+ meta: { retryable: false },
1684
+ }),
1550
1685
  });
1551
1686
  await expect(multisig.exportProposal('0x' + 'nonexistent'.repeat(5))).rejects.toThrow('Proposal not found');
1552
1687
  });
@@ -1558,7 +1693,7 @@ describe('Multisig', () => {
1558
1693
  signerCommitments: ['0x' + 'a'.repeat(64)],
1559
1694
  guardianCommitment: '0x' + 'c'.repeat(64),
1560
1695
  };
1561
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1696
+ const multisig = createTestMultisig(config);
1562
1697
  const exported = {
1563
1698
  accountId: multisig.accountId,
1564
1699
  nonce: 1,
@@ -1585,7 +1720,7 @@ describe('Multisig', () => {
1585
1720
  signerCommitments: ['0x' + 'a'.repeat(64)],
1586
1721
  guardianCommitment: '0x' + 'c'.repeat(64),
1587
1722
  };
1588
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1723
+ const multisig = createTestMultisig(config);
1589
1724
  const publicKey = '0x' + 'd'.repeat(66);
1590
1725
  const proposal = await multisig.importProposal(JSON.stringify({
1591
1726
  accountId: multisig.accountId,
@@ -1626,7 +1761,7 @@ describe('Multisig', () => {
1626
1761
  signerCommitments: ['0x' + 'a'.repeat(64)],
1627
1762
  guardianCommitment: '0x' + 'c'.repeat(64),
1628
1763
  };
1629
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1764
+ const multisig = createTestMultisig(config);
1630
1765
  await expect(multisig.importProposal(JSON.stringify({
1631
1766
  accountId: multisig.accountId,
1632
1767
  nonce: 1,
@@ -1653,7 +1788,7 @@ describe('Multisig', () => {
1653
1788
  signerCommitments: ['0x' + 'a'.repeat(64), mockSigner.commitment],
1654
1789
  guardianCommitment: '0x' + 'c'.repeat(64),
1655
1790
  };
1656
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1791
+ const multisig = createTestMultisig(config);
1657
1792
  const exported = {
1658
1793
  accountId: multisig.accountId,
1659
1794
  nonce: 1,
@@ -1689,7 +1824,7 @@ describe('Multisig', () => {
1689
1824
  scheme: 'ecdsa',
1690
1825
  publicKey: '0x' + '2'.repeat(66),
1691
1826
  };
1692
- const multisig = new Multisig(mockAccount, config, guardian, ecdsaSigner, mockWebClient);
1827
+ const multisig = createTestMultisig(config, ecdsaSigner);
1693
1828
  const cachedProposalId = '0x' + 'c'.repeat(64);
1694
1829
  const requestedProposalId = '0x' + 'C'.repeat(64);
1695
1830
  const cosignerPubkey = '0x' + '3'.repeat(66);
@@ -1781,7 +1916,7 @@ describe('Multisig', () => {
1781
1916
  signerCommitments: ['0x' + 'a'.repeat(64)],
1782
1917
  guardianCommitment: '0x' + 'c'.repeat(64),
1783
1918
  };
1784
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1919
+ const multisig = createTestMultisig(config);
1785
1920
  const proposalId = '0x' + 'c'.repeat(64);
1786
1921
  const newGuardianPubkey = '0x' + '1'.repeat(64);
1787
1922
  const finalRequest = { kind: 'final-switch-guardian-request' };
@@ -1829,7 +1964,7 @@ describe('Multisig', () => {
1829
1964
  signerCommitments: ['0x' + 'a'.repeat(64)],
1830
1965
  guardianCommitment: '0x' + 'c'.repeat(64),
1831
1966
  };
1832
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1967
+ const multisig = createTestMultisig(config);
1833
1968
  await expect(multisig.createTransactionProposalRequest('0x' + 'nonexistent'.repeat(5))).rejects.toThrow('Proposal not found');
1834
1969
  });
1835
1970
  it('should throw if proposal is still pending', async () => {
@@ -1838,7 +1973,7 @@ describe('Multisig', () => {
1838
1973
  signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
1839
1974
  guardianCommitment: '0x' + 'c'.repeat(64),
1840
1975
  };
1841
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
1976
+ const multisig = createTestMultisig(config);
1842
1977
  const mockProposals = [
1843
1978
  {
1844
1979
  account_id: '0x' + 'a'.repeat(30),
@@ -1881,7 +2016,7 @@ describe('Multisig', () => {
1881
2016
  signerCommitments: ['0x' + 'a'.repeat(64)],
1882
2017
  guardianCommitment: '0x' + 'c'.repeat(64),
1883
2018
  };
1884
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
2019
+ const multisig = createTestMultisig(config);
1885
2020
  const proposalId = '0x' + 'c'.repeat(64);
1886
2021
  vi.mocked(executeForSummary).mockResolvedValueOnce({
1887
2022
  toCommitment: () => ({
@@ -1917,7 +2052,7 @@ describe('Multisig', () => {
1917
2052
  signerCommitments: ['0x' + 'a'.repeat(64)],
1918
2053
  guardianCommitment: '0x' + 'c'.repeat(64),
1919
2054
  };
1920
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
2055
+ const multisig = createTestMultisig(config);
1921
2056
  const proposalId = '0x' + 'c'.repeat(64);
1922
2057
  multisig.proposals.set(proposalId, {
1923
2058
  id: proposalId,
@@ -1952,7 +2087,7 @@ describe('Multisig', () => {
1952
2087
  signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
1953
2088
  guardianCommitment: '0x' + 'c'.repeat(64),
1954
2089
  };
1955
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
2090
+ const multisig = createTestMultisig(config);
1956
2091
  const proposalId = '0x' + 'c'.repeat(64);
1957
2092
  multisig.proposals.set(proposalId, {
1958
2093
  id: proposalId,
@@ -2012,7 +2147,7 @@ describe('Multisig', () => {
2012
2147
  signerCommitments: ['0x' + 'a'.repeat(64)],
2013
2148
  guardianCommitment: '0x' + 'c'.repeat(64),
2014
2149
  };
2015
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
2150
+ const multisig = createTestMultisig(config);
2016
2151
  const proposalId = '0x' + 'c'.repeat(64);
2017
2152
  const finalRequest = { kind: 'fresh-message-word-request' };
2018
2153
  vi.mocked(buildUpdateSignersTransactionRequest)
@@ -2105,7 +2240,7 @@ describe('Multisig', () => {
2105
2240
  signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
2106
2241
  guardianCommitment: '0x' + 'c'.repeat(64),
2107
2242
  };
2108
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
2243
+ const multisig = createTestMultisig(config);
2109
2244
  const proposalId = '0x' + 'c'.repeat(64);
2110
2245
  multisig.proposals.set(proposalId, {
2111
2246
  id: proposalId,
@@ -2142,7 +2277,7 @@ describe('Multisig', () => {
2142
2277
  signerCommitments: ['0x' + 'a'.repeat(64)],
2143
2278
  guardianCommitment: '0x' + 'c'.repeat(64),
2144
2279
  };
2145
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
2280
+ const multisig = createTestMultisig(config);
2146
2281
  await expect(multisig.executeProposal('0x' + 'nonexistent'.repeat(5))).rejects.toThrow('Proposal not found');
2147
2282
  });
2148
2283
  it('should throw if proposal is still pending', async () => {
@@ -2151,7 +2286,7 @@ describe('Multisig', () => {
2151
2286
  signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
2152
2287
  guardianCommitment: '0x' + 'c'.repeat(64),
2153
2288
  };
2154
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
2289
+ const multisig = createTestMultisig(config);
2155
2290
  // Sync with pending proposal (only 1 signature)
2156
2291
  const mockProposals = [
2157
2292
  {
@@ -2196,7 +2331,7 @@ describe('Multisig', () => {
2196
2331
  signerCommitments: ['0x' + 'a'.repeat(64)],
2197
2332
  guardianCommitment: '0x' + 'c'.repeat(64),
2198
2333
  };
2199
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
2334
+ const multisig = createTestMultisig(config);
2200
2335
  const readyDelta = {
2201
2336
  account_id: '0x' + 'a'.repeat(30),
2202
2337
  nonce: 1,
@@ -2258,7 +2393,7 @@ describe('Multisig', () => {
2258
2393
  scheme: 'ecdsa',
2259
2394
  publicKey: '0x' + '2'.repeat(66),
2260
2395
  };
2261
- const multisig = new Multisig(mockAccount, config, guardian, ecdsaSigner, mockWebClient);
2396
+ const multisig = createTestMultisig(config, ecdsaSigner);
2262
2397
  const proposalId = '0x' + 'c'.repeat(64);
2263
2398
  const cosignerPubkey = '0x' + '3'.repeat(66);
2264
2399
  const ackPubkey = '0x' + '4'.repeat(66);
@@ -2344,7 +2479,7 @@ describe('Multisig', () => {
2344
2479
  scheme: 'ecdsa',
2345
2480
  publicKey: '0x' + '2'.repeat(66),
2346
2481
  };
2347
- const multisig = new Multisig(mockAccount, config, guardian, ecdsaSigner, mockWebClient);
2482
+ const multisig = createTestMultisig(config, ecdsaSigner);
2348
2483
  const proposalId = '0x' + 'c'.repeat(64);
2349
2484
  const cosignerPubkey = '0x' + '3'.repeat(66);
2350
2485
  const ackPubkey = '0x' + '4'.repeat(66);
@@ -2417,7 +2552,7 @@ describe('Multisig', () => {
2417
2552
  signerCommitments: ['0x' + 'a'.repeat(64)],
2418
2553
  guardianCommitment: '0x' + 'c'.repeat(64),
2419
2554
  };
2420
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
2555
+ const multisig = createTestMultisig(config);
2421
2556
  const proposalId = '0x' + 'c'.repeat(64);
2422
2557
  const newGuardianPubkey = '0x' + '1'.repeat(64);
2423
2558
  multisig.proposals.set(proposalId, {
@@ -2495,7 +2630,7 @@ describe('Multisig', () => {
2495
2630
  signerCommitments: ['0x' + 'a'.repeat(64)],
2496
2631
  guardianCommitment: '0x' + 'c'.repeat(64),
2497
2632
  };
2498
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
2633
+ const multisig = createTestMultisig(config);
2499
2634
  const proposalId = '0x' + 'c'.repeat(64);
2500
2635
  const newGuardianPubkey = '0x' + '1'.repeat(64);
2501
2636
  multisig.proposals.set(proposalId, {
@@ -2541,7 +2676,7 @@ describe('Multisig', () => {
2541
2676
  signerCommitments: ['0x' + 'a'.repeat(64)],
2542
2677
  guardianCommitment: '0x' + 'c'.repeat(64),
2543
2678
  };
2544
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
2679
+ const multisig = createTestMultisig(config);
2545
2680
  const proposalId = '0x' + 'c'.repeat(64);
2546
2681
  multisig.proposals.set(proposalId, {
2547
2682
  id: proposalId,
@@ -2576,7 +2711,7 @@ describe('Multisig', () => {
2576
2711
  signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
2577
2712
  guardianCommitment: '0x' + 'c'.repeat(64),
2578
2713
  };
2579
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
2714
+ const multisig = createTestMultisig(config);
2580
2715
  const proposalId = '0x' + 'c'.repeat(64);
2581
2716
  multisig.proposals.set(proposalId, {
2582
2717
  id: proposalId,
@@ -2621,7 +2756,7 @@ describe('Multisig', () => {
2621
2756
  signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
2622
2757
  guardianCommitment: '0x' + 'c'.repeat(64),
2623
2758
  };
2624
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
2759
+ const multisig = createTestMultisig(config);
2625
2760
  const proposalId = '0x' + 'c'.repeat(64);
2626
2761
  multisig.proposals.set(proposalId, {
2627
2762
  id: proposalId,
@@ -2687,7 +2822,7 @@ describe('Multisig', () => {
2687
2822
  signerCommitments: ['0x' + 'a'.repeat(64)],
2688
2823
  guardianCommitment: '0x' + 'c'.repeat(64),
2689
2824
  };
2690
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
2825
+ const multisig = createTestMultisig(config);
2691
2826
  const builtinDelta = {
2692
2827
  ...customDelta('change_threshold', [falconSig('0x' + 'a'.repeat(64))]),
2693
2828
  };
@@ -2709,7 +2844,7 @@ describe('Multisig', () => {
2709
2844
  signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
2710
2845
  guardianCommitment: '0x' + 'c'.repeat(64),
2711
2846
  };
2712
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
2847
+ const multisig = createTestMultisig(config);
2713
2848
  mockFetch.mockResolvedValueOnce({
2714
2849
  ok: true,
2715
2850
  json: async () => customDelta('b2agg', [falconSig('0x' + 'a'.repeat(64))]),
@@ -2722,7 +2857,7 @@ describe('Multisig', () => {
2722
2857
  signerCommitments: ['0x' + 'a'.repeat(64)],
2723
2858
  guardianCommitment: '0x' + 'c'.repeat(64),
2724
2859
  };
2725
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
2860
+ const multisig = createTestMultisig(config);
2726
2861
  // Signed commitment comes from TransactionSummary.deserialize -> 'c' * 64.
2727
2862
  // Make the binding request derive a different commitment so the check fails.
2728
2863
  vi.mocked(executeForSummary).mockResolvedValueOnce({
@@ -2743,7 +2878,7 @@ describe('Multisig', () => {
2743
2878
  signerCommitments: ['0x' + 'a'.repeat(64)],
2744
2879
  guardianCommitment: '0x' + 'c'.repeat(64),
2745
2880
  };
2746
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
2881
+ const multisig = createTestMultisig(config);
2747
2882
  const ready = customDelta('b2agg', [falconSig('0x' + 'a'.repeat(64))]);
2748
2883
  // getDeltaProposal
2749
2884
  mockFetch.mockResolvedValueOnce({
@@ -2765,7 +2900,7 @@ describe('Multisig', () => {
2765
2900
  signerCommitments: ['0x' + 'a'.repeat(64)],
2766
2901
  guardianCommitment: '0x' + 'c'.repeat(64),
2767
2902
  };
2768
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
2903
+ const multisig = createTestMultisig(config);
2769
2904
  // Create a proposal with metadata
2770
2905
  const mockDelta = {
2771
2906
  account_id: '0x' + 'a'.repeat(30),
@@ -2818,7 +2953,7 @@ describe('Multisig', () => {
2818
2953
  signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
2819
2954
  guardianCommitment: '0x' + 'c'.repeat(64),
2820
2955
  };
2821
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
2956
+ const multisig = createTestMultisig(config);
2822
2957
  // Sync proposals - no local proposals exist
2823
2958
  const mockProposals = [
2824
2959
  {
@@ -2859,7 +2994,7 @@ describe('Multisig', () => {
2859
2994
  signerCommitments: ['0x' + 'a'.repeat(64)],
2860
2995
  guardianCommitment: '0x' + 'c'.repeat(64),
2861
2996
  };
2862
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
2997
+ const multisig = createTestMultisig(config);
2863
2998
  const mockDelta = {
2864
2999
  account_id: '0x' + 'a'.repeat(30),
2865
3000
  nonce: 1,
@@ -2901,7 +3036,7 @@ describe('Multisig', () => {
2901
3036
  signerCommitments: ['0x' + 'a'.repeat(64)],
2902
3037
  guardianCommitment: '0x' + 'c'.repeat(64),
2903
3038
  };
2904
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
3039
+ const multisig = createTestMultisig(config);
2905
3040
  const mockDelta = {
2906
3041
  account_id: '0x' + 'a'.repeat(30),
2907
3042
  nonce: 1,
@@ -2945,7 +3080,7 @@ describe('Multisig', () => {
2945
3080
  signerCommitments: ['0x' + 'a'.repeat(64)],
2946
3081
  guardianCommitment: '0x' + 'c'.repeat(64),
2947
3082
  };
2948
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
3083
+ const multisig = createTestMultisig(config);
2949
3084
  const mockDelta = {
2950
3085
  account_id: '0x' + 'a'.repeat(30),
2951
3086
  nonce: 1,
@@ -2990,7 +3125,7 @@ describe('Multisig', () => {
2990
3125
  signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
2991
3126
  guardianCommitment: '0x' + 'c'.repeat(64),
2992
3127
  };
2993
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
3128
+ const multisig = createTestMultisig(config);
2994
3129
  // First sync with 1 signature (pending)
2995
3130
  const mockProposalsPending = [
2996
3131
  {
@@ -3074,7 +3209,7 @@ describe('Multisig', () => {
3074
3209
  signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64), '0x' + 'c'.repeat(64)],
3075
3210
  guardianCommitment: '0x' + 'd'.repeat(64),
3076
3211
  };
3077
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
3212
+ const multisig = createTestMultisig(config);
3078
3213
  expect(multisig.threshold).toBe(3);
3079
3214
  });
3080
3215
  it('should expose signerCommitments', () => {
@@ -3084,7 +3219,7 @@ describe('Multisig', () => {
3084
3219
  signerCommitments,
3085
3220
  guardianCommitment: '0x' + 'd'.repeat(64),
3086
3221
  };
3087
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
3222
+ const multisig = createTestMultisig(config);
3088
3223
  expect(multisig.signerCommitments).toEqual(signerCommitments);
3089
3224
  });
3090
3225
  it('should expose guardianCommitment', () => {
@@ -3094,7 +3229,7 @@ describe('Multisig', () => {
3094
3229
  signerCommitments: ['0x' + 'a'.repeat(64)],
3095
3230
  guardianCommitment,
3096
3231
  };
3097
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
3232
+ const multisig = createTestMultisig(config);
3098
3233
  expect(multisig.guardianCommitment).toBe(guardianCommitment);
3099
3234
  });
3100
3235
  it('should expose account when provided', () => {
@@ -3103,7 +3238,7 @@ describe('Multisig', () => {
3103
3238
  signerCommitments: ['0x' + 'a'.repeat(64)],
3104
3239
  guardianCommitment: '0x' + 'd'.repeat(64),
3105
3240
  };
3106
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
3241
+ const multisig = createTestMultisig(config);
3107
3242
  expect(multisig.account).toBe(mockAccount);
3108
3243
  });
3109
3244
  });
@@ -3114,7 +3249,7 @@ describe('Multisig', () => {
3114
3249
  signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
3115
3250
  guardianCommitment: '0x' + 'c'.repeat(64),
3116
3251
  };
3117
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
3252
+ const multisig = createTestMultisig(config);
3118
3253
  // Simulates a GUARDIAN response with canonical snake_case metadata
3119
3254
  const rustProposals = [
3120
3255
  {
@@ -3158,7 +3293,7 @@ describe('Multisig', () => {
3158
3293
  signerCommitments: ['0x' + 'a'.repeat(64)],
3159
3294
  guardianCommitment: '0x' + 'c'.repeat(64),
3160
3295
  };
3161
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
3296
+ const multisig = createTestMultisig(config);
3162
3297
  // P2ID proposal with canonical snake_case fields
3163
3298
  const p2idProposals = [
3164
3299
  {
@@ -3209,7 +3344,7 @@ describe('Multisig', () => {
3209
3344
  signerCommitments: ['0x' + 'a'.repeat(64)],
3210
3345
  guardianCommitment: '0x' + 'c'.repeat(64),
3211
3346
  };
3212
- const multisig = new Multisig(mockAccount, config, guardian, mockSigner, mockWebClient);
3347
+ const multisig = createTestMultisig(config);
3213
3348
  const switchGuardianProposals = [
3214
3349
  {
3215
3350
  account_id: '0x' + 'a'.repeat(30),