@openzeppelin/miden-multisig-client 0.17.0-rc.2 → 0.17.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 (278) hide show
  1. package/README.md +172 -47
  2. package/dist/account/builder.d.ts +2 -2
  3. package/dist/account/builder.d.ts.map +1 -1
  4. package/dist/account/builder.js +49 -24
  5. package/dist/account/builder.js.map +1 -1
  6. package/dist/connectivity.d.ts +2 -0
  7. package/dist/connectivity.d.ts.map +1 -1
  8. package/dist/connectivity.js +7 -4
  9. package/dist/connectivity.js.map +1 -1
  10. package/dist/index.d.ts +8 -1
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +7 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/multisig/authArgErrors.d.ts +59 -0
  15. package/dist/multisig/authArgErrors.d.ts.map +1 -0
  16. package/dist/multisig/authArgErrors.js +115 -0
  17. package/dist/multisig/authArgErrors.js.map +1 -0
  18. package/dist/multisig/helpers.d.ts +5 -0
  19. package/dist/multisig/helpers.d.ts.map +1 -1
  20. package/dist/multisig/helpers.js +7 -2
  21. package/dist/multisig/helpers.js.map +1 -1
  22. package/dist/multisig.d.ts +140 -0
  23. package/dist/multisig.d.ts.map +1 -1
  24. package/dist/multisig.js +419 -12
  25. package/dist/multisig.js.map +1 -1
  26. package/dist/procedures.d.ts +1 -1
  27. package/dist/procedures.js +1 -1
  28. package/dist/recovery/proposalNoteImport.d.ts +173 -0
  29. package/dist/recovery/proposalNoteImport.d.ts.map +1 -0
  30. package/dist/recovery/proposalNoteImport.js +411 -0
  31. package/dist/recovery/proposalNoteImport.js.map +1 -0
  32. package/dist/recovery/publicNoteBackfill.d.ts +168 -0
  33. package/dist/recovery/publicNoteBackfill.d.ts.map +1 -0
  34. package/dist/recovery/publicNoteBackfill.js +368 -0
  35. package/dist/recovery/publicNoteBackfill.js.map +1 -0
  36. package/dist/recovery/recoverNotes.d.ts +147 -0
  37. package/dist/recovery/recoverNotes.d.ts.map +1 -0
  38. package/dist/recovery/recoverNotes.js +163 -0
  39. package/dist/recovery/recoverNotes.js.map +1 -0
  40. package/dist/recovery/transportDrain.d.ts +124 -0
  41. package/dist/recovery/transportDrain.d.ts.map +1 -0
  42. package/dist/recovery/transportDrain.js +262 -0
  43. package/dist/recovery/transportDrain.js.map +1 -0
  44. package/dist/transaction/consumeNotes.d.ts.map +1 -1
  45. package/dist/transaction/consumeNotes.js +4 -1
  46. package/dist/transaction/consumeNotes.js.map +1 -1
  47. package/dist/transaction/p2id.d.ts.map +1 -1
  48. package/dist/transaction/p2id.js +4 -1
  49. package/dist/transaction/p2id.js.map +1 -1
  50. package/dist/transaction/summary.d.ts +10 -4
  51. package/dist/transaction/summary.d.ts.map +1 -1
  52. package/dist/transaction/summary.js +13 -7
  53. package/dist/transaction/summary.js.map +1 -1
  54. package/dist/transaction/updateGuardian.js +4 -1
  55. package/dist/transaction/updateGuardian.js.map +1 -1
  56. package/dist/transaction/updateProcedureThreshold.js +4 -1
  57. package/dist/transaction/updateProcedureThreshold.js.map +1 -1
  58. package/dist/transaction/updateSigners.js +4 -1
  59. package/dist/transaction/updateSigners.js.map +1 -1
  60. package/dist/transaction.d.ts +1 -1
  61. package/dist/transaction.d.ts.map +1 -1
  62. package/dist/transaction.js +1 -1
  63. package/dist/transaction.js.map +1 -1
  64. package/package.json +14 -10
  65. package/src/account/builder.ts +87 -35
  66. package/src/connectivity.ts +9 -4
  67. package/src/index.ts +34 -1
  68. package/src/multisig/authArgErrors.ts +138 -0
  69. package/src/multisig/helpers.ts +7 -2
  70. package/src/multisig.ts +513 -20
  71. package/src/procedures.ts +1 -1
  72. package/src/recovery/proposalNoteImport.ts +548 -0
  73. package/src/recovery/publicNoteBackfill.ts +502 -0
  74. package/src/recovery/recoverNotes.ts +288 -0
  75. package/src/recovery/transportDrain.ts +330 -0
  76. package/src/transaction/consumeNotes.ts +4 -1
  77. package/src/transaction/p2id.ts +4 -1
  78. package/src/transaction/summary.ts +13 -7
  79. package/src/transaction/updateGuardian.ts +4 -1
  80. package/src/transaction/updateProcedureThreshold.ts +4 -1
  81. package/src/transaction/updateSigners.ts +4 -1
  82. package/src/transaction.ts +1 -1
  83. package/dist/account/builder.test.d.ts +0 -2
  84. package/dist/account/builder.test.d.ts.map +0 -1
  85. package/dist/account/builder.test.js +0 -166
  86. package/dist/account/builder.test.js.map +0 -1
  87. package/dist/account/masm/account-components/auth.d.ts +0 -2
  88. package/dist/account/masm/account-components/auth.d.ts.map +0 -1
  89. package/dist/account/masm/account-components/auth.js +0 -46
  90. package/dist/account/masm/account-components/auth.js.map +0 -1
  91. package/dist/account/masm/index.d.ts +0 -2
  92. package/dist/account/masm/index.d.ts.map +0 -1
  93. package/dist/account/masm/index.js +0 -4
  94. package/dist/account/masm/index.js.map +0 -1
  95. package/dist/account/masm.d.ts +0 -2
  96. package/dist/account/masm.d.ts.map +0 -1
  97. package/dist/account/masm.js +0 -4
  98. package/dist/account/masm.js.map +0 -1
  99. package/dist/account/storage.test.d.ts +0 -2
  100. package/dist/account/storage.test.d.ts.map +0 -1
  101. package/dist/account/storage.test.js +0 -73
  102. package/dist/account/storage.test.js.map +0 -1
  103. package/dist/client.test.d.ts +0 -2
  104. package/dist/client.test.d.ts.map +0 -1
  105. package/dist/client.test.js +0 -380
  106. package/dist/client.test.js.map +0 -1
  107. package/dist/connectivity.test.d.ts +0 -2
  108. package/dist/connectivity.test.d.ts.map +0 -1
  109. package/dist/connectivity.test.js +0 -61
  110. package/dist/connectivity.test.js.map +0 -1
  111. package/dist/inspector.test.d.ts +0 -2
  112. package/dist/inspector.test.d.ts.map +0 -1
  113. package/dist/inspector.test.js +0 -425
  114. package/dist/inspector.test.js.map +0 -1
  115. package/dist/lookupAuth.test.d.ts +0 -2
  116. package/dist/lookupAuth.test.d.ts.map +0 -1
  117. package/dist/lookupAuth.test.js +0 -138
  118. package/dist/lookupAuth.test.js.map +0 -1
  119. package/dist/multisig/consumeNotesErrors.test.d.ts +0 -2
  120. package/dist/multisig/consumeNotesErrors.test.d.ts.map +0 -1
  121. package/dist/multisig/consumeNotesErrors.test.js +0 -28
  122. package/dist/multisig/consumeNotesErrors.test.js.map +0 -1
  123. package/dist/multisig/helpers.test.d.ts +0 -2
  124. package/dist/multisig/helpers.test.d.ts.map +0 -1
  125. package/dist/multisig/helpers.test.js +0 -94
  126. package/dist/multisig/helpers.test.js.map +0 -1
  127. package/dist/multisig.test.d.ts +0 -2
  128. package/dist/multisig.test.d.ts.map +0 -1
  129. package/dist/multisig.test.js +0 -4112
  130. package/dist/multisig.test.js.map +0 -1
  131. package/dist/proposal/factory.test.d.ts +0 -2
  132. package/dist/proposal/factory.test.d.ts.map +0 -1
  133. package/dist/proposal/factory.test.js +0 -32
  134. package/dist/proposal/factory.test.js.map +0 -1
  135. package/dist/proposal/metadata.test.d.ts +0 -2
  136. package/dist/proposal/metadata.test.d.ts.map +0 -1
  137. package/dist/proposal/metadata.test.js +0 -193
  138. package/dist/proposal/metadata.test.js.map +0 -1
  139. package/dist/prover/config.test.d.ts +0 -2
  140. package/dist/prover/config.test.d.ts.map +0 -1
  141. package/dist/prover/config.test.js +0 -54
  142. package/dist/prover/config.test.js.map +0 -1
  143. package/dist/prover/errors.test.d.ts +0 -2
  144. package/dist/prover/errors.test.d.ts.map +0 -1
  145. package/dist/prover/errors.test.js +0 -29
  146. package/dist/prover/errors.test.js.map +0 -1
  147. package/dist/prover/retry.test.d.ts +0 -2
  148. package/dist/prover/retry.test.d.ts.map +0 -1
  149. package/dist/prover/retry.test.js +0 -20
  150. package/dist/prover/retry.test.js.map +0 -1
  151. package/dist/prover/workflow.test.d.ts +0 -2
  152. package/dist/prover/workflow.test.d.ts.map +0 -1
  153. package/dist/prover/workflow.test.js +0 -105
  154. package/dist/prover/workflow.test.js.map +0 -1
  155. package/dist/raw-client.test.d.ts +0 -2
  156. package/dist/raw-client.test.d.ts.map +0 -1
  157. package/dist/raw-client.test.js +0 -111
  158. package/dist/raw-client.test.js.map +0 -1
  159. package/dist/rpc/config.test.d.ts +0 -2
  160. package/dist/rpc/config.test.d.ts.map +0 -1
  161. package/dist/rpc/config.test.js +0 -24
  162. package/dist/rpc/config.test.js.map +0 -1
  163. package/dist/rpc/errors.test.d.ts +0 -2
  164. package/dist/rpc/errors.test.d.ts.map +0 -1
  165. package/dist/rpc/errors.test.js +0 -34
  166. package/dist/rpc/errors.test.js.map +0 -1
  167. package/dist/rpc/retry.test.d.ts +0 -2
  168. package/dist/rpc/retry.test.d.ts.map +0 -1
  169. package/dist/rpc/retry.test.js +0 -98
  170. package/dist/rpc/retry.test.js.map +0 -1
  171. package/dist/signers/ecdsa.test.d.ts +0 -2
  172. package/dist/signers/ecdsa.test.d.ts.map +0 -1
  173. package/dist/signers/ecdsa.test.js +0 -88
  174. package/dist/signers/ecdsa.test.js.map +0 -1
  175. package/dist/signers/falcon.test.d.ts +0 -2
  176. package/dist/signers/falcon.test.d.ts.map +0 -1
  177. package/dist/signers/falcon.test.js +0 -161
  178. package/dist/signers/falcon.test.js.map +0 -1
  179. package/dist/signers/miden-wallet.ecdsa-recovery.test.d.ts +0 -2
  180. package/dist/signers/miden-wallet.ecdsa-recovery.test.d.ts.map +0 -1
  181. package/dist/signers/miden-wallet.ecdsa-recovery.test.js +0 -148
  182. package/dist/signers/miden-wallet.ecdsa-recovery.test.js.map +0 -1
  183. package/dist/signers/miden-wallet.test.d.ts +0 -2
  184. package/dist/signers/miden-wallet.test.d.ts.map +0 -1
  185. package/dist/signers/miden-wallet.test.js +0 -164
  186. package/dist/signers/miden-wallet.test.js.map +0 -1
  187. package/dist/signers/para.test.d.ts +0 -2
  188. package/dist/signers/para.test.d.ts.map +0 -1
  189. package/dist/signers/para.test.js +0 -146
  190. package/dist/signers/para.test.js.map +0 -1
  191. package/dist/transaction/index.d.ts +0 -8
  192. package/dist/transaction/index.d.ts.map +0 -1
  193. package/dist/transaction/index.js +0 -7
  194. package/dist/transaction/index.js.map +0 -1
  195. package/dist/transaction/p2id.test.d.ts +0 -2
  196. package/dist/transaction/p2id.test.d.ts.map +0 -1
  197. package/dist/transaction/p2id.test.js +0 -246
  198. package/dist/transaction/p2id.test.js.map +0 -1
  199. package/dist/transaction/rpoRandomCoin.test.d.ts +0 -2
  200. package/dist/transaction/rpoRandomCoin.test.d.ts.map +0 -1
  201. package/dist/transaction/rpoRandomCoin.test.js +0 -52
  202. package/dist/transaction/rpoRandomCoin.test.js.map +0 -1
  203. package/dist/transaction/summary.test.d.ts +0 -2
  204. package/dist/transaction/summary.test.d.ts.map +0 -1
  205. package/dist/transaction/summary.test.js +0 -26
  206. package/dist/transaction/summary.test.js.map +0 -1
  207. package/dist/transaction.test.d.ts +0 -2
  208. package/dist/transaction.test.d.ts.map +0 -1
  209. package/dist/transaction.test.js +0 -122
  210. package/dist/transaction.test.js.map +0 -1
  211. package/dist/types/proposal.test.d.ts +0 -2
  212. package/dist/types/proposal.test.d.ts.map +0 -1
  213. package/dist/types/proposal.test.js +0 -56
  214. package/dist/types/proposal.test.js.map +0 -1
  215. package/dist/utils/digest.test.d.ts +0 -2
  216. package/dist/utils/digest.test.d.ts.map +0 -1
  217. package/dist/utils/digest.test.js +0 -48
  218. package/dist/utils/digest.test.js.map +0 -1
  219. package/dist/utils/ecdsa.test.d.ts +0 -2
  220. package/dist/utils/ecdsa.test.d.ts.map +0 -1
  221. package/dist/utils/ecdsa.test.js +0 -131
  222. package/dist/utils/ecdsa.test.js.map +0 -1
  223. package/dist/utils/encoding.test.d.ts +0 -2
  224. package/dist/utils/encoding.test.d.ts.map +0 -1
  225. package/dist/utils/encoding.test.js +0 -185
  226. package/dist/utils/encoding.test.js.map +0 -1
  227. package/dist/utils/key.test.d.ts +0 -2
  228. package/dist/utils/key.test.d.ts.map +0 -1
  229. package/dist/utils/key.test.js +0 -82
  230. package/dist/utils/key.test.js.map +0 -1
  231. package/dist/utils/signature.test.d.ts +0 -2
  232. package/dist/utils/signature.test.d.ts.map +0 -1
  233. package/dist/utils/signature.test.js +0 -164
  234. package/dist/utils/signature.test.js.map +0 -1
  235. package/dist/utils/word.test.d.ts +0 -2
  236. package/dist/utils/word.test.d.ts.map +0 -1
  237. package/dist/utils/word.test.js +0 -54
  238. package/dist/utils/word.test.js.map +0 -1
  239. package/masm/account_components/auth/guarded_multisig.masm +0 -42
  240. package/src/account/builder.test.ts +0 -238
  241. package/src/account/masm/account-components/auth.ts +0 -46
  242. package/src/account/masm/index.ts +0 -4
  243. package/src/account/masm.ts +0 -4
  244. package/src/account/storage.test.ts +0 -90
  245. package/src/client.test.ts +0 -473
  246. package/src/connectivity.test.ts +0 -67
  247. package/src/inspector.test.ts +0 -542
  248. package/src/lookupAuth.test.ts +0 -185
  249. package/src/multisig/consumeNotesErrors.test.ts +0 -43
  250. package/src/multisig/helpers.test.ts +0 -114
  251. package/src/multisig.test.ts +0 -5008
  252. package/src/proposal/factory.test.ts +0 -40
  253. package/src/proposal/metadata.test.ts +0 -235
  254. package/src/prover/config.test.ts +0 -90
  255. package/src/prover/errors.test.ts +0 -61
  256. package/src/prover/retry.test.ts +0 -38
  257. package/src/prover/workflow.test.ts +0 -145
  258. package/src/raw-client.test.ts +0 -171
  259. package/src/rpc/config.test.ts +0 -45
  260. package/src/rpc/errors.test.ts +0 -69
  261. package/src/rpc/retry.test.ts +0 -144
  262. package/src/signers/ecdsa.test.ts +0 -111
  263. package/src/signers/falcon.test.ts +0 -197
  264. package/src/signers/miden-wallet.ecdsa-recovery.test.ts +0 -201
  265. package/src/signers/miden-wallet.test.ts +0 -206
  266. package/src/signers/para.test.ts +0 -186
  267. package/src/transaction/index.ts +0 -13
  268. package/src/transaction/p2id.test.ts +0 -371
  269. package/src/transaction/rpoRandomCoin.test.ts +0 -64
  270. package/src/transaction/summary.test.ts +0 -32
  271. package/src/transaction.test.ts +0 -142
  272. package/src/types/proposal.test.ts +0 -68
  273. package/src/utils/digest.test.ts +0 -55
  274. package/src/utils/ecdsa.test.ts +0 -164
  275. package/src/utils/encoding.test.ts +0 -233
  276. package/src/utils/key.test.ts +0 -91
  277. package/src/utils/signature.test.ts +0 -210
  278. package/src/utils/word.test.ts +0 -64
@@ -1,46 +0,0 @@
1
- // This file is generated by scripts/generate-masm.mjs from the MASM contract sources.
2
- // Do not edit it by hand.
3
-
4
- export const GUARDED_MULTISIG_ACCOUNT_COMPONENT_MASM = `# The MASM code of the Multi-Signature Authentication component integrated with a state guardian.
5
- #
6
- # See the \`AuthGuardedMultisig\` Rust type's documentation for more details.
7
-
8
- use miden::standards::auth::multisig
9
- use miden::standards::auth::guardian
10
-
11
- pub use {update_signers_and_threshold} from miden::standards::auth::multisig
12
- pub use {get_threshold_and_num_approvers} from miden::standards::auth::multisig
13
- pub use {set_procedure_threshold} from miden::standards::auth::multisig
14
- pub use {get_signer_at} from miden::standards::auth::multisig
15
- pub use {is_signer} from miden::standards::auth::multisig
16
-
17
- pub use {update_guardian_public_key} from miden::standards::auth::guardian
18
-
19
- #! Authenticate a transaction with multi-signature support and optional guardian verification.
20
- #!
21
- #! Inputs:
22
- #! Operand stack: [SALT]
23
- #! Outputs:
24
- #! Operand stack: []
25
- #!
26
- #! Invocation: call
27
- @auth_script
28
- pub proc auth_tx_guarded_multisig(salt: word)
29
- # zero the leading user params (not exposed through this component's interface); the SALT
30
- # occupies the trailing four
31
- push.0.0.0
32
- # => [0, 0, 0, SALT]
33
-
34
- exec.multisig::auth_tx
35
- # => [TX_SUMMARY_COMMITMENT]
36
-
37
- dupw
38
- # => [TX_SUMMARY_COMMITMENT, TX_SUMMARY_COMMITMENT]
39
-
40
- exec.guardian::verify_signature
41
- # => [TX_SUMMARY_COMMITMENT]
42
-
43
- exec.multisig::record_and_assert_new_tx
44
- # => []
45
- end
46
- `;
@@ -1,4 +0,0 @@
1
- // This file is generated by scripts/generate-masm.mjs from the MASM contract sources.
2
- // Do not edit it by hand.
3
-
4
- export * from './account-components/auth.js';
@@ -1,4 +0,0 @@
1
- // This file is generated by scripts/generate-masm.mjs from the MASM contract sources.
2
- // Do not edit it by hand.
3
-
4
- export * from './masm/index.js';
@@ -1,90 +0,0 @@
1
- import { describe, expect, it, vi, beforeEach } from 'vitest';
2
-
3
- const { storageMaps, storageSlotMap } = vi.hoisted(() => {
4
- const storageMaps: Array<{ entries: Array<{ key: unknown; value: unknown }> }> = [];
5
- const storageSlotMap = vi.fn((name, map) => ({ kind: 'map', name, map }));
6
-
7
- return { storageMaps, storageSlotMap };
8
- });
9
-
10
- vi.mock('@miden-sdk/miden-sdk', () => {
11
- class MockWord {
12
- data: BigUint64Array | string;
13
-
14
- constructor(data: BigUint64Array | string) {
15
- this.data = data;
16
- }
17
-
18
- static fromHex(hex: string) {
19
- return new MockWord(hex);
20
- }
21
- }
22
-
23
- class MockStorageMap {
24
- entries: Array<{ key: unknown; value: unknown }> = [];
25
-
26
- constructor() {
27
- storageMaps.push(this);
28
- }
29
-
30
- insert(key: unknown, value: unknown) {
31
- this.entries.push({ key, value });
32
- }
33
- }
34
-
35
- return {
36
- Word: MockWord,
37
- StorageMap: MockStorageMap,
38
- StorageSlot: {
39
- fromValue: vi.fn((name, value) => ({ kind: 'value', name, value })),
40
- map: storageSlotMap,
41
- },
42
- };
43
- });
44
-
45
- describe('buildMultisigStorageSlots', () => {
46
- beforeEach(() => {
47
- storageMaps.length = 0;
48
- storageSlotMap.mockClear();
49
- });
50
-
51
- it('keeps single-signer public-key and scheme maps to the configured signer count', async () => {
52
- const { buildMultisigStorageSlots } = await import('./storage.js');
53
-
54
- buildMultisigStorageSlots({
55
- threshold: 1,
56
- signerCommitments: ['0x' + '1'.repeat(64)],
57
- guardianCommitment: '0x' + '2'.repeat(64),
58
- signatureScheme: 'falcon',
59
- });
60
-
61
- const signersMap = storageMaps[0];
62
- const signerSchemesMap = storageMaps[1];
63
-
64
- expect(signersMap.entries).toHaveLength(1);
65
- expect(signerSchemesMap.entries).toHaveLength(1);
66
- expect(signersMap.entries[0].key).toMatchObject({
67
- data: new BigUint64Array([0n, 0n, 0n, 0n]),
68
- });
69
- expect(signerSchemesMap.entries[0].key).toMatchObject({
70
- data: new BigUint64Array([0n, 0n, 0n, 0n]),
71
- });
72
- });
73
-
74
- it('does not pad signer maps when there are already multiple signers', async () => {
75
- const { buildMultisigStorageSlots } = await import('./storage.js');
76
-
77
- buildMultisigStorageSlots({
78
- threshold: 1,
79
- signerCommitments: ['0x' + '1'.repeat(64), '0x' + '3'.repeat(64)],
80
- guardianCommitment: '0x' + '2'.repeat(64),
81
- signatureScheme: 'falcon',
82
- });
83
-
84
- const signersMap = storageMaps[0];
85
- const signerSchemesMap = storageMaps[1];
86
-
87
- expect(signersMap.entries).toHaveLength(2);
88
- expect(signerSchemesMap.entries).toHaveLength(2);
89
- });
90
- });
@@ -1,473 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- import { MultisigClient } from './client.js';
3
- import type { Signer } from './types.js';
4
-
5
- // Mock the Miden SDK
6
- vi.mock('@miden-sdk/miden-sdk', () => ({
7
- AccountId: {
8
- fromHex: vi.fn((hex: string) => ({ toString: () => hex })),
9
- },
10
- Account: {
11
- deserialize: vi.fn(() => ({
12
- id: () => ({
13
- toString: () => '0x' + 'd'.repeat(30),
14
- prefix: () => ({ asInt: () => BigInt(1) }),
15
- suffix: () => ({ asInt: () => BigInt(2) }),
16
- }),
17
- serialize: () => new Uint8Array([1, 2, 3]),
18
- storage: vi.fn(),
19
- vault: vi.fn(),
20
- })),
21
- },
22
- Word: vi.fn(),
23
- }));
24
-
25
- // Mock the AccountInspector, keeping the real assertCompleteDetectedConfig
26
- // so load()'s fail-closed validation is exercised.
27
- vi.mock('./inspector.js', async (importOriginal) => {
28
- const actual = await importOriginal<typeof import('./inspector.js')>();
29
- return {
30
- ...actual,
31
- AccountInspector: {
32
- fromAccount: vi.fn(() => ({
33
- threshold: 2,
34
- numSigners: 2,
35
- signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
36
- guardianCommitment: '0x' + 'c'.repeat(64),
37
- vaultBalances: [],
38
- procedureThresholds: new Map(),
39
- })),
40
- },
41
- };
42
- });
43
-
44
- // Mock the account creation module
45
- vi.mock('./account/index.js', () => ({
46
- createMultisigAccount: vi.fn().mockResolvedValue({
47
- account: {
48
- id: () => ({
49
- toString: () => '0x' + 'a'.repeat(30),
50
- prefix: () => ({ asInt: () => BigInt(1) }),
51
- suffix: () => ({ asInt: () => BigInt(2) }),
52
- }),
53
- serialize: () => new Uint8Array([1, 2, 3]),
54
- },
55
- seed: new Uint8Array([4, 5, 6]),
56
- }),
57
- }));
58
-
59
- // Mock fetch for GUARDIAN client
60
- const mockFetch = vi.fn();
61
- vi.stubGlobal('fetch', mockFetch);
62
-
63
- const GUARDIAN_URL = 'http://localhost:3000';
64
- const MIDEN_RPC = 'http://localhost:57291';
65
- const CLIENT_CONFIG = { guardianEndpoint: GUARDIAN_URL, midenRpcEndpoint: MIDEN_RPC };
66
-
67
- describe('MultisigClient', () => {
68
- let webClient: any;
69
- let mockSigner: Signer;
70
-
71
- beforeEach(() => {
72
- mockFetch.mockReset();
73
-
74
- webClient = {
75
- accounts: {
76
- get: vi.fn().mockResolvedValue(null),
77
- insert: vi.fn().mockResolvedValue(undefined),
78
- },
79
- keystore: {
80
- insert: vi.fn().mockResolvedValue(undefined),
81
- },
82
- };
83
-
84
- mockSigner = {
85
- commitment: '0x' + '1'.repeat(64),
86
- publicKey: '0x' + '2'.repeat(64),
87
- scheme: 'falcon',
88
- signAccountIdWithTimestamp: vi.fn().mockResolvedValue('0x' + 'a'.repeat(128)),
89
- signRequest: vi.fn().mockReturnValue('0x' + 'a'.repeat(128)),
90
- signCommitment: vi.fn().mockReturnValue('0x' + 'b'.repeat(128)),
91
- };
92
- });
93
-
94
- describe('constructor', () => {
95
- it('should create client when both endpoints are supplied', () => {
96
- const client = new MultisigClient(webClient, CLIENT_CONFIG);
97
- expect(client).toBeInstanceOf(MultisigClient);
98
- });
99
-
100
- it('should create client with custom GUARDIAN endpoint', () => {
101
- const client = new MultisigClient(webClient, {
102
- guardianEndpoint: 'http://custom:8080',
103
- midenRpcEndpoint: MIDEN_RPC,
104
- });
105
- expect(client).toBeInstanceOf(MultisigClient);
106
- });
107
-
108
- it('throws when the config object is omitted', () => {
109
- expect(() => new (MultisigClient as any)(webClient)).toThrow(
110
- 'missing required configuration: midenRpcEndpoint',
111
- );
112
- });
113
-
114
- it.each([undefined, null, 42, '', ' '])(
115
- 'throws before any network or store access when midenRpcEndpoint is %j',
116
- (endpoint) => {
117
- expect(
118
- () =>
119
- new MultisigClient(webClient, {
120
- guardianEndpoint: GUARDIAN_URL,
121
- midenRpcEndpoint: endpoint as any,
122
- }),
123
- ).toThrow('missing required configuration: midenRpcEndpoint');
124
- expect(mockFetch).not.toHaveBeenCalled();
125
- expect(webClient.accounts.get).not.toHaveBeenCalled();
126
- expect(webClient.accounts.insert).not.toHaveBeenCalled();
127
- },
128
- );
129
-
130
- it.each([undefined, null, 42, '', ' '])(
131
- 'throws before any network or store access when guardianEndpoint is %j',
132
- (endpoint) => {
133
- expect(
134
- () =>
135
- new MultisigClient(webClient, {
136
- guardianEndpoint: endpoint as any,
137
- midenRpcEndpoint: MIDEN_RPC,
138
- }),
139
- ).toThrow('missing required configuration: guardianEndpoint');
140
- expect(mockFetch).not.toHaveBeenCalled();
141
- expect(webClient.accounts.get).not.toHaveBeenCalled();
142
- expect(webClient.accounts.insert).not.toHaveBeenCalled();
143
- },
144
- );
145
-
146
- it('rejects a blank endpoint passed to setGuardianEndpoint', () => {
147
- const client = new MultisigClient(webClient, CLIENT_CONFIG);
148
- expect(() => client.setGuardianEndpoint(' ')).toThrow(
149
- 'missing required configuration: guardianEndpoint',
150
- );
151
- });
152
- });
153
-
154
- describe('guardianClient getter', () => {
155
- it('should expose GUARDIAN client for getting pubkey', () => {
156
- const client = new MultisigClient(webClient, CLIENT_CONFIG);
157
- expect(client.guardianClient).toBeDefined();
158
- });
159
- });
160
-
161
- describe('create', () => {
162
- it('should create multisig and return Multisig instance', async () => {
163
- const client = new MultisigClient(webClient, CLIENT_CONFIG);
164
-
165
- const config = {
166
- threshold: 2,
167
- signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
168
- guardianCommitment: '0x' + 'c'.repeat(64),
169
- };
170
-
171
- const multisig = await client.create(config, mockSigner);
172
-
173
- expect(multisig).toBeDefined();
174
- expect(multisig.threshold).toBe(2);
175
- expect(multisig.signerCommitments).toEqual(config.signerCommitments);
176
- expect(multisig.guardianCommitment).toBe(config.guardianCommitment);
177
- });
178
-
179
- it('should set signer on GUARDIAN client', async () => {
180
- const client = new MultisigClient(webClient, CLIENT_CONFIG);
181
-
182
- const config = {
183
- threshold: 1,
184
- signerCommitments: ['0x' + 'a'.repeat(64)],
185
- guardianCommitment: '0x' + 'c'.repeat(64),
186
- };
187
-
188
- const multisig = await client.create(config, mockSigner);
189
- expect(multisig.signerCommitment).toBe(mockSigner.commitment);
190
- });
191
-
192
- it('binds the signer auth key to the created account when supported', async () => {
193
- const client = new MultisigClient(webClient, CLIENT_CONFIG);
194
- const bindAccountKey = vi.fn().mockResolvedValue(undefined);
195
- const bindingSigner = {
196
- ...mockSigner,
197
- bindAccountKey,
198
- };
199
-
200
- await client.create({
201
- threshold: 1,
202
- signerCommitments: ['0x' + 'a'.repeat(64)],
203
- guardianCommitment: '0x' + 'c'.repeat(64),
204
- }, bindingSigner);
205
-
206
- expect(bindAccountKey).toHaveBeenCalledWith(webClient, '0x' + 'a'.repeat(30));
207
- });
208
- });
209
-
210
- describe('load', () => {
211
- it('should load existing multisig account and detect config', async () => {
212
- const client = new MultisigClient(webClient, CLIENT_CONFIG);
213
-
214
- // Mock getState response
215
- mockFetch.mockResolvedValueOnce({
216
- ok: true,
217
- json: async () => ({
218
- account_id: '0x' + 'd'.repeat(30),
219
- commitment: '0x' + 'e'.repeat(64),
220
- state_json: { data: 'base64state' },
221
- created_at: '2024-01-01T00:00:00Z',
222
- updated_at: '2024-01-02T00:00:00Z',
223
- }),
224
- });
225
-
226
- const accountId = '0x' + 'd'.repeat(30);
227
- const multisig = await client.load(accountId, mockSigner);
228
-
229
- expect(multisig).toBeDefined();
230
- expect(multisig.accountId).toBe(accountId);
231
- // Config is detected from account storage via AccountInspector
232
- expect(multisig.threshold).toBe(2);
233
- expect(multisig.signerCommitments).toEqual(['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)]);
234
- expect(multisig.guardianCommitment).toBe('0x' + 'c'.repeat(64));
235
- expect(multisig.account).not.toBeNull();
236
- expect(webClient.accounts.get).toHaveBeenCalledTimes(1);
237
- expect(webClient.accounts.insert).toHaveBeenCalledTimes(1);
238
- });
239
-
240
- it('fails closed when the detected signer set is incomplete (issue #306 review)', async () => {
241
- const client = new MultisigClient(webClient, CLIENT_CONFIG);
242
-
243
- const { AccountInspector } = await import('./inspector.js');
244
- // Storage reports 3 signers but only 2 entries were readable — adopting
245
- // this config would let membership proposals drop the missing key.
246
- vi.mocked(AccountInspector.fromAccount).mockReturnValueOnce({
247
- threshold: 2,
248
- numSigners: 3,
249
- signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
250
- guardianCommitment: '0x' + 'c'.repeat(64),
251
- vaultBalances: [],
252
- procedureThresholds: new Map(),
253
- });
254
-
255
- mockFetch.mockResolvedValueOnce({
256
- ok: true,
257
- json: async () => ({
258
- account_id: '0x' + 'd'.repeat(30),
259
- commitment: '0x' + 'e'.repeat(64),
260
- state_json: { data: 'base64state' },
261
- created_at: '2024-01-01T00:00:00Z',
262
- updated_at: '2024-01-02T00:00:00Z',
263
- }),
264
- });
265
-
266
- await expect(client.load('0x' + 'd'.repeat(30), mockSigner)).rejects.toThrow(
267
- /incomplete signer set: storage reports 3 signers, read 2/,
268
- );
269
- });
270
-
271
- it('fails closed when the guardian commitment is missing', async () => {
272
- const client = new MultisigClient(webClient, CLIENT_CONFIG);
273
-
274
- const { AccountInspector } = await import('./inspector.js');
275
- vi.mocked(AccountInspector.fromAccount).mockReturnValueOnce({
276
- threshold: 1,
277
- numSigners: 1,
278
- signerCommitments: ['0x' + 'a'.repeat(64)],
279
- guardianCommitment: null,
280
- vaultBalances: [],
281
- procedureThresholds: new Map(),
282
- });
283
-
284
- mockFetch.mockResolvedValueOnce({
285
- ok: true,
286
- json: async () => ({
287
- account_id: '0x' + 'd'.repeat(30),
288
- commitment: '0x' + 'e'.repeat(64),
289
- state_json: { data: 'base64state' },
290
- created_at: '2024-01-01T00:00:00Z',
291
- updated_at: '2024-01-02T00:00:00Z',
292
- }),
293
- });
294
-
295
- await expect(client.load('0x' + 'd'.repeat(30), mockSigner)).rejects.toThrow(
296
- /missing guardian commitment/,
297
- );
298
- });
299
-
300
- it('should throw if account not found on GUARDIAN', async () => {
301
- const client = new MultisigClient(webClient, CLIENT_CONFIG);
302
-
303
- mockFetch.mockResolvedValueOnce({
304
- ok: false,
305
- status: 404,
306
- statusText: 'Not Found',
307
- headers: new Headers(),
308
- text: async () =>
309
- JSON.stringify({
310
- code: 'account_not_found',
311
- message: 'Account not found',
312
- meta: { retryable: false },
313
- }),
314
- });
315
-
316
- await expect(
317
- client.load('0xnonexistent', mockSigner)
318
- ).rejects.toThrow('Account not found');
319
- });
320
-
321
- it('should allow registerOnGuardian after load without explicit initial state', async () => {
322
- const client = new MultisigClient(webClient, CLIENT_CONFIG);
323
-
324
- mockFetch.mockResolvedValueOnce({
325
- ok: true,
326
- json: async () => ({
327
- account_id: '0x' + 'd'.repeat(30),
328
- commitment: '0x' + 'e'.repeat(64),
329
- state_json: { data: 'base64state' },
330
- created_at: '2024-01-01T00:00:00Z',
331
- updated_at: '2024-01-02T00:00:00Z',
332
- }),
333
- });
334
-
335
- mockFetch.mockResolvedValueOnce({
336
- ok: true,
337
- json: async () => ({
338
- success: true,
339
- message: 'Account configured',
340
- ack_pubkey: '0x' + 'f'.repeat(64),
341
- }),
342
- });
343
-
344
- const accountId = '0x' + 'd'.repeat(30);
345
- const multisig = await client.load(accountId, mockSigner);
346
-
347
- await expect(multisig.registerOnGuardian()).resolves.toBeUndefined();
348
- expect(webClient.accounts.get).toHaveBeenCalledTimes(1);
349
- expect(webClient.accounts.insert).toHaveBeenCalledTimes(1);
350
- });
351
-
352
- it('binds the signer auth key after loading an account when supported', async () => {
353
- const client = new MultisigClient(webClient, CLIENT_CONFIG);
354
- const bindAccountKey = vi.fn().mockResolvedValue(undefined);
355
- const bindingSigner = {
356
- ...mockSigner,
357
- bindAccountKey,
358
- };
359
-
360
- mockFetch.mockResolvedValueOnce({
361
- ok: true,
362
- json: async () => ({
363
- account_id: '0x' + 'd'.repeat(30),
364
- commitment: '0x' + 'e'.repeat(64),
365
- state_json: { data: 'base64state' },
366
- created_at: '2024-01-01T00:00:00Z',
367
- updated_at: '2024-01-02T00:00:00Z',
368
- }),
369
- });
370
-
371
- await client.load('0x' + 'd'.repeat(30), bindingSigner);
372
-
373
- expect(bindAccountKey).toHaveBeenCalledWith(webClient, '0x' + 'd'.repeat(30));
374
- });
375
- });
376
-
377
- // --- recoverByKey -------------------
378
-
379
- describe('recoverByKey', () => {
380
- function makeLookupCapableSigner() {
381
- return {
382
- commitment: '0x' + 'a'.repeat(64),
383
- publicKey: '0x' + 'p'.repeat(897),
384
- scheme: 'falcon' as const,
385
- signAccountIdWithTimestamp: vi.fn().mockResolvedValue('0x' + 'a'.repeat(128)),
386
- signRequest: vi.fn().mockReturnValue('0x' + 'a'.repeat(128)),
387
- signCommitment: vi.fn().mockReturnValue('0x' + 'b'.repeat(128)),
388
- signLookupMessage: vi.fn().mockResolvedValue('0x' + 'c'.repeat(762)),
389
- };
390
- }
391
-
392
- function mockServerLookupResponse(accountIds: string[]) {
393
- mockFetch.mockResolvedValueOnce({
394
- ok: true,
395
- json: async () => ({
396
- accounts: accountIds.map((id) => ({ account_id: id })),
397
- }),
398
- });
399
- }
400
-
401
- function mockServerStateResponse(accountId: string) {
402
- mockFetch.mockResolvedValueOnce({
403
- ok: true,
404
- json: async () => ({
405
- account_id: accountId,
406
- commitment: '0x' + 'f'.repeat(64),
407
- state_json: { data: 'base64data' },
408
- created_at: '2024-01-01T00:00:00Z',
409
- updated_at: '2024-01-01T00:00:00Z',
410
- }),
411
- });
412
- }
413
-
414
- it('returns one (accountId, state) pair when lookup matches a single account', async () => {
415
- const client = new MultisigClient(webClient, CLIENT_CONFIG);
416
- const signer = makeLookupCapableSigner();
417
- const accountId = '0x7bfb0f38b0fafa103f86a805594170';
418
-
419
- mockServerLookupResponse([accountId]);
420
- mockServerStateResponse(accountId);
421
-
422
- const recovered = await client.recoverByKey(signer);
423
-
424
- expect(recovered).toHaveLength(1);
425
- expect(recovered[0].accountId).toBe(accountId);
426
- expect(recovered[0].state.commitment).toBe('0x' + 'f'.repeat(64));
427
- expect(signer.signLookupMessage).toHaveBeenCalledTimes(1);
428
- expect(signer.signLookupMessage).toHaveBeenCalledWith(
429
- signer.commitment,
430
- expect.any(Number)
431
- );
432
- // Lookup + getState = exactly two HTTP requests.
433
- expect(mockFetch).toHaveBeenCalledTimes(2);
434
- });
435
-
436
- it('returns multiple (accountId, state) pairs when one commitment authorizes several accounts', async () => {
437
- const client = new MultisigClient(webClient, CLIENT_CONFIG);
438
- const signer = makeLookupCapableSigner();
439
- const accountA = '0xaaa1';
440
- const accountB = '0xbbb2';
441
-
442
- mockServerLookupResponse([accountA, accountB]);
443
- mockServerStateResponse(accountA);
444
- mockServerStateResponse(accountB);
445
-
446
- const recovered = await client.recoverByKey(signer);
447
-
448
- expect(recovered.map((r) => r.accountId)).toEqual([accountA, accountB]);
449
- // 1 lookup + 2 state fetches.
450
- expect(mockFetch).toHaveBeenCalledTimes(3);
451
- });
452
-
453
- it('returns empty array when no account authorizes the commitment', async () => {
454
- const client = new MultisigClient(webClient, CLIENT_CONFIG);
455
- const signer = makeLookupCapableSigner();
456
-
457
- mockServerLookupResponse([]);
458
-
459
- const recovered = await client.recoverByKey(signer);
460
-
461
- expect(recovered).toEqual([]);
462
- // Only the lookup HTTP call — no per-account state fetches.
463
- expect(mockFetch).toHaveBeenCalledTimes(1);
464
- });
465
-
466
- it('throws a clear error when the signer does not implement signLookupMessage', async () => {
467
- const client = new MultisigClient(webClient, CLIENT_CONFIG);
468
- // mockSigner from the outer beforeEach lacks signLookupMessage.
469
- await expect(client.recoverByKey(mockSigner)).rejects.toThrow(/signLookupMessage/);
470
- expect(mockFetch).not.toHaveBeenCalled();
471
- });
472
- });
473
- });
@@ -1,67 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { GuardianHttpError } from '@openzeppelin/guardian-client';
3
- import { isLikelyNetworkError, toUserFacingError } from './connectivity.js';
4
-
5
- describe('isLikelyNetworkError', () => {
6
- it('flags codeless transport failures', () => {
7
- for (const m of [
8
- 'Failed to fetch',
9
- 'NetworkError when attempting to fetch resource',
10
- 'Load failed',
11
- 'The operation was aborted',
12
- 'request timed out',
13
- 'connection refused',
14
- 'getaddrinfo ENOTFOUND guardian.example',
15
- ]) {
16
- expect(isLikelyNetworkError(new TypeError(m))).toBe(true);
17
- }
18
- });
19
-
20
- it('does not flag semantic errors', () => {
21
- expect(isLikelyNetworkError(new Error('account is paused'))).toBe(false);
22
- expect(isLikelyNetworkError(new Error('insufficient signatures'))).toBe(false);
23
- });
24
- });
25
-
26
- describe('toUserFacingError', () => {
27
- it('uses the server code + user-safe message when Guardian was reached', () => {
28
- const body = JSON.stringify({
29
- code: 'account_paused',
30
- message: "This account is paused and can't approve transactions right now.",
31
- meta: { retryable: false },
32
- });
33
- const result = toUserFacingError(new GuardianHttpError(409, 'Conflict', body));
34
- expect(result.code).toBe('account_paused');
35
- expect(result.userMessage).toContain('paused');
36
- expect(result.category).toBeUndefined();
37
- });
38
-
39
- it('classifies a codeless transport failure as connectivity', () => {
40
- const result = toUserFacingError(new TypeError('Failed to fetch'));
41
- expect(result.code).toBeUndefined();
42
- expect(result.category).toBe('unreachable');
43
- expect(result.userMessage).toContain("Can't reach Guardian");
44
- // The raw transport text is never the primary message.
45
- expect(result.userMessage).not.toContain('Failed to fetch');
46
- });
47
-
48
- it('classifies timeouts and aborts as the timeout category', () => {
49
- for (const m of ['request timed out', 'The operation was aborted']) {
50
- const result = toUserFacingError(new Error(m));
51
- expect(result.category).toBe('timeout');
52
- expect(result.userMessage).toContain("Can't reach Guardian");
53
- }
54
- });
55
-
56
- it('treats a reachable proxy 5xx with no Guardian body as connectivity', () => {
57
- const result = toUserFacingError(new GuardianHttpError(502, 'Bad Gateway', '<html>nope</html>'));
58
- expect(result.category).toBe('unreachable');
59
- expect(result.userMessage).toContain("Can't reach Guardian");
60
- });
61
-
62
- it('falls back to a generic message for unknown non-Guardian errors', () => {
63
- const result = toUserFacingError(new Error('totally unexpected'));
64
- expect(result.userMessage).toBe('Something went wrong. Please try again.');
65
- expect(result.userMessage).not.toContain('totally unexpected');
66
- });
67
- });