@openzeppelin/miden-multisig-client 0.12.6

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 (215) hide show
  1. package/README.md +189 -0
  2. package/dist/account/builder.d.ts +23 -0
  3. package/dist/account/builder.d.ts.map +1 -0
  4. package/dist/account/builder.js +85 -0
  5. package/dist/account/builder.js.map +1 -0
  6. package/dist/account/index.d.ts +6 -0
  7. package/dist/account/index.d.ts.map +1 -0
  8. package/dist/account/index.js +6 -0
  9. package/dist/account/index.js.map +1 -0
  10. package/dist/account/masm.d.ts +3 -0
  11. package/dist/account/masm.d.ts.map +1 -0
  12. package/dist/account/masm.js +605 -0
  13. package/dist/account/masm.js.map +1 -0
  14. package/dist/account/storage.d.ts +10 -0
  15. package/dist/account/storage.d.ts.map +1 -0
  16. package/dist/account/storage.js +56 -0
  17. package/dist/account/storage.js.map +1 -0
  18. package/dist/account.d.ts +104 -0
  19. package/dist/account.d.ts.map +1 -0
  20. package/dist/account.js +136 -0
  21. package/dist/account.js.map +1 -0
  22. package/dist/builder.d.ts +54 -0
  23. package/dist/builder.d.ts.map +1 -0
  24. package/dist/builder.js +78 -0
  25. package/dist/builder.js.map +1 -0
  26. package/dist/client.d.ts +73 -0
  27. package/dist/client.d.ts.map +1 -0
  28. package/dist/client.js +104 -0
  29. package/dist/client.js.map +1 -0
  30. package/dist/client.test.d.ts +2 -0
  31. package/dist/client.test.d.ts.map +1 -0
  32. package/dist/client.test.js +150 -0
  33. package/dist/client.test.js.map +1 -0
  34. package/dist/http.d.ts +64 -0
  35. package/dist/http.d.ts.map +1 -0
  36. package/dist/http.js +162 -0
  37. package/dist/http.js.map +1 -0
  38. package/dist/index.d.ts +53 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +52 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/inspector.d.ts +49 -0
  43. package/dist/inspector.d.ts.map +1 -0
  44. package/dist/inspector.js +120 -0
  45. package/dist/inspector.js.map +1 -0
  46. package/dist/inspector.test.d.ts +2 -0
  47. package/dist/inspector.test.d.ts.map +1 -0
  48. package/dist/inspector.test.js +205 -0
  49. package/dist/inspector.test.js.map +1 -0
  50. package/dist/keystore.d.ts +65 -0
  51. package/dist/keystore.d.ts.map +1 -0
  52. package/dist/keystore.js +121 -0
  53. package/dist/keystore.js.map +1 -0
  54. package/dist/miden.d.ts +9 -0
  55. package/dist/miden.d.ts.map +1 -0
  56. package/dist/miden.js +26 -0
  57. package/dist/miden.js.map +1 -0
  58. package/dist/multisig/helpers.d.ts +4 -0
  59. package/dist/multisig/helpers.d.ts.map +1 -0
  60. package/dist/multisig/helpers.js +21 -0
  61. package/dist/multisig/helpers.js.map +1 -0
  62. package/dist/multisig/helpers.test.d.ts +2 -0
  63. package/dist/multisig/helpers.test.d.ts.map +1 -0
  64. package/dist/multisig/helpers.test.js +94 -0
  65. package/dist/multisig/helpers.test.js.map +1 -0
  66. package/dist/multisig/metadata.d.ts +18 -0
  67. package/dist/multisig/metadata.d.ts.map +1 -0
  68. package/dist/multisig/metadata.js +100 -0
  69. package/dist/multisig/metadata.js.map +1 -0
  70. package/dist/multisig/metadata.test.d.ts +2 -0
  71. package/dist/multisig/metadata.test.d.ts.map +1 -0
  72. package/dist/multisig/metadata.test.js +323 -0
  73. package/dist/multisig/metadata.test.js.map +1 -0
  74. package/dist/multisig.d.ts +199 -0
  75. package/dist/multisig.d.ts.map +1 -0
  76. package/dist/multisig.js +821 -0
  77. package/dist/multisig.js.map +1 -0
  78. package/dist/multisig.test.d.ts +2 -0
  79. package/dist/multisig.test.d.ts.map +1 -0
  80. package/dist/multisig.test.js +1121 -0
  81. package/dist/multisig.test.js.map +1 -0
  82. package/dist/procedures.d.ts +57 -0
  83. package/dist/procedures.d.ts.map +1 -0
  84. package/dist/procedures.js +62 -0
  85. package/dist/procedures.js.map +1 -0
  86. package/dist/signer.d.ts +36 -0
  87. package/dist/signer.d.ts.map +1 -0
  88. package/dist/signer.js +68 -0
  89. package/dist/signer.js.map +1 -0
  90. package/dist/signer.test.d.ts +2 -0
  91. package/dist/signer.test.d.ts.map +1 -0
  92. package/dist/signer.test.js +117 -0
  93. package/dist/signer.test.js.map +1 -0
  94. package/dist/transaction/consumeNotes.d.ts +7 -0
  95. package/dist/transaction/consumeNotes.d.ts.map +1 -0
  96. package/dist/transaction/consumeNotes.js +28 -0
  97. package/dist/transaction/consumeNotes.js.map +1 -0
  98. package/dist/transaction/index.d.ts +7 -0
  99. package/dist/transaction/index.d.ts.map +1 -0
  100. package/dist/transaction/index.js +6 -0
  101. package/dist/transaction/index.js.map +1 -0
  102. package/dist/transaction/options.d.ts +6 -0
  103. package/dist/transaction/options.d.ts.map +1 -0
  104. package/dist/transaction/options.js +2 -0
  105. package/dist/transaction/options.js.map +1 -0
  106. package/dist/transaction/p2id.d.ts +7 -0
  107. package/dist/transaction/p2id.d.ts.map +1 -0
  108. package/dist/transaction/p2id.js +43 -0
  109. package/dist/transaction/p2id.js.map +1 -0
  110. package/dist/transaction/summary.d.ts +3 -0
  111. package/dist/transaction/summary.d.ts.map +1 -0
  112. package/dist/transaction/summary.js +6 -0
  113. package/dist/transaction/summary.js.map +1 -0
  114. package/dist/transaction/updatePsm.d.ts +7 -0
  115. package/dist/transaction/updatePsm.d.ts.map +1 -0
  116. package/dist/transaction/updatePsm.js +43 -0
  117. package/dist/transaction/updatePsm.js.map +1 -0
  118. package/dist/transaction/updateSigners.d.ts +8 -0
  119. package/dist/transaction/updateSigners.d.ts.map +1 -0
  120. package/dist/transaction/updateSigners.js +60 -0
  121. package/dist/transaction/updateSigners.js.map +1 -0
  122. package/dist/transaction.d.ts +6 -0
  123. package/dist/transaction.d.ts.map +1 -0
  124. package/dist/transaction.js +8 -0
  125. package/dist/transaction.js.map +1 -0
  126. package/dist/transaction.test.d.ts +2 -0
  127. package/dist/transaction.test.d.ts.map +1 -0
  128. package/dist/transaction.test.js +117 -0
  129. package/dist/transaction.test.js.map +1 -0
  130. package/dist/transport/http.d.ts +64 -0
  131. package/dist/transport/http.d.ts.map +1 -0
  132. package/dist/transport/http.js +162 -0
  133. package/dist/transport/http.js.map +1 -0
  134. package/dist/transport/index.d.ts +5 -0
  135. package/dist/transport/index.d.ts.map +1 -0
  136. package/dist/transport/index.js +5 -0
  137. package/dist/transport/index.js.map +1 -0
  138. package/dist/types/proposal.d.ts +74 -0
  139. package/dist/types/proposal.d.ts.map +1 -0
  140. package/dist/types/proposal.js +2 -0
  141. package/dist/types/proposal.js.map +1 -0
  142. package/dist/types.d.ts +60 -0
  143. package/dist/types.d.ts.map +1 -0
  144. package/dist/types.js +2 -0
  145. package/dist/types.js.map +1 -0
  146. package/dist/utils/encoding.d.ts +7 -0
  147. package/dist/utils/encoding.d.ts.map +1 -0
  148. package/dist/utils/encoding.js +54 -0
  149. package/dist/utils/encoding.js.map +1 -0
  150. package/dist/utils/encoding.test.d.ts +2 -0
  151. package/dist/utils/encoding.test.d.ts.map +1 -0
  152. package/dist/utils/encoding.test.js +185 -0
  153. package/dist/utils/encoding.test.js.map +1 -0
  154. package/dist/utils/index.d.ts +5 -0
  155. package/dist/utils/index.d.ts.map +1 -0
  156. package/dist/utils/index.js +5 -0
  157. package/dist/utils/index.js.map +1 -0
  158. package/dist/utils/random.d.ts +3 -0
  159. package/dist/utils/random.d.ts.map +1 -0
  160. package/dist/utils/random.js +14 -0
  161. package/dist/utils/random.js.map +1 -0
  162. package/dist/utils/signature.d.ts +12 -0
  163. package/dist/utils/signature.d.ts.map +1 -0
  164. package/dist/utils/signature.js +28 -0
  165. package/dist/utils/signature.js.map +1 -0
  166. package/dist/utils/signature.test.d.ts +2 -0
  167. package/dist/utils/signature.test.d.ts.map +1 -0
  168. package/dist/utils/signature.test.js +149 -0
  169. package/dist/utils/signature.test.js.map +1 -0
  170. package/dist/utils/word.d.ts +4 -0
  171. package/dist/utils/word.d.ts.map +1 -0
  172. package/dist/utils/word.js +8 -0
  173. package/dist/utils/word.js.map +1 -0
  174. package/dist/utils/word.test.d.ts +2 -0
  175. package/dist/utils/word.test.d.ts.map +1 -0
  176. package/dist/utils/word.test.js +54 -0
  177. package/dist/utils/word.test.js.map +1 -0
  178. package/masm/multisig.masm +423 -0
  179. package/masm/psm.masm +178 -0
  180. package/package.json +42 -0
  181. package/src/account/builder.ts +112 -0
  182. package/src/account/index.ts +15 -0
  183. package/src/account/masm.ts +606 -0
  184. package/src/account/storage.ts +70 -0
  185. package/src/client.test.ts +177 -0
  186. package/src/client.ts +129 -0
  187. package/src/index.ts +118 -0
  188. package/src/inspector.test.ts +237 -0
  189. package/src/inspector.ts +142 -0
  190. package/src/multisig/helpers.test.ts +114 -0
  191. package/src/multisig/helpers.ts +23 -0
  192. package/src/multisig.test.ts +1294 -0
  193. package/src/multisig.ts +1097 -0
  194. package/src/procedures.ts +72 -0
  195. package/src/signer.test.ts +135 -0
  196. package/src/signer.ts +75 -0
  197. package/src/transaction/consumeNotes.ts +41 -0
  198. package/src/transaction/index.ts +7 -0
  199. package/src/transaction/options.ts +7 -0
  200. package/src/transaction/p2id.ts +105 -0
  201. package/src/transaction/summary.ts +12 -0
  202. package/src/transaction/updatePsm.ts +69 -0
  203. package/src/transaction/updateSigners.ts +96 -0
  204. package/src/transaction.test.ts +136 -0
  205. package/src/transaction.ts +16 -0
  206. package/src/types/proposal.ts +80 -0
  207. package/src/types.ts +81 -0
  208. package/src/utils/encoding.test.ts +233 -0
  209. package/src/utils/encoding.ts +60 -0
  210. package/src/utils/index.ts +5 -0
  211. package/src/utils/random.ts +15 -0
  212. package/src/utils/signature.test.ts +187 -0
  213. package/src/utils/signature.ts +36 -0
  214. package/src/utils/word.test.ts +64 -0
  215. package/src/utils/word.ts +11 -0
@@ -0,0 +1,177 @@
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('@demox-labs/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
+ storage: vi.fn(),
18
+ vault: vi.fn(),
19
+ })),
20
+ },
21
+ }));
22
+
23
+ // Mock the AccountInspector
24
+ vi.mock('./inspector.js', () => ({
25
+ AccountInspector: {
26
+ fromAccount: vi.fn(() => ({
27
+ threshold: 2,
28
+ numSigners: 2,
29
+ signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
30
+ psmEnabled: true,
31
+ psmCommitment: '0x' + 'c'.repeat(64),
32
+ vaultBalances: [],
33
+ procedureThresholds: new Map(),
34
+ })),
35
+ },
36
+ }));
37
+
38
+ // Mock the account creation module
39
+ vi.mock('./account/index.js', () => ({
40
+ createMultisigAccount: vi.fn().mockResolvedValue({
41
+ account: {
42
+ id: () => ({
43
+ toString: () => '0x' + 'a'.repeat(30),
44
+ prefix: () => ({ asInt: () => BigInt(1) }),
45
+ suffix: () => ({ asInt: () => BigInt(2) }),
46
+ }),
47
+ serialize: () => new Uint8Array([1, 2, 3]),
48
+ },
49
+ seed: new Uint8Array([4, 5, 6]),
50
+ }),
51
+ }));
52
+
53
+ // Mock fetch for PSM client
54
+ const mockFetch = vi.fn();
55
+ vi.stubGlobal('fetch', mockFetch);
56
+
57
+ describe('MultisigClient', () => {
58
+ let webClient: any;
59
+ let mockSigner: Signer;
60
+
61
+ beforeEach(() => {
62
+ mockFetch.mockReset();
63
+
64
+ webClient = {
65
+ createScriptBuilder: vi.fn(),
66
+ executeTransaction: vi.fn(),
67
+ proveTransaction: vi.fn(),
68
+ submitProvenTransaction: vi.fn(),
69
+ applyTransaction: vi.fn(),
70
+ syncState: vi.fn(),
71
+ newAccount: vi.fn(),
72
+ getAccount: vi.fn().mockResolvedValue(null),
73
+ };
74
+
75
+ mockSigner = {
76
+ commitment: '0x' + '1'.repeat(64),
77
+ publicKey: '0x' + '2'.repeat(64),
78
+ signAccountIdWithTimestamp: vi.fn().mockReturnValue('0x' + 'a'.repeat(128)),
79
+ signCommitment: vi.fn().mockReturnValue('0x' + 'b'.repeat(128)),
80
+ };
81
+ });
82
+
83
+ describe('constructor', () => {
84
+ it('should create client with default PSM endpoint', () => {
85
+ const client = new MultisigClient(webClient);
86
+ expect(client).toBeInstanceOf(MultisigClient);
87
+ });
88
+
89
+ it('should create client with custom PSM endpoint', () => {
90
+ const client = new MultisigClient(webClient, { psmEndpoint: 'http://custom:8080' });
91
+ expect(client).toBeInstanceOf(MultisigClient);
92
+ });
93
+ });
94
+
95
+ describe('psmClient getter', () => {
96
+ it('should expose PSM client for getting pubkey', () => {
97
+ const client = new MultisigClient(webClient);
98
+ expect(client.psmClient).toBeDefined();
99
+ });
100
+ });
101
+
102
+ describe('create', () => {
103
+ it('should create multisig and return Multisig instance', async () => {
104
+ const client = new MultisigClient(webClient);
105
+
106
+ const config = {
107
+ threshold: 2,
108
+ signerCommitments: ['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)],
109
+ psmCommitment: '0x' + 'c'.repeat(64),
110
+ };
111
+
112
+ const multisig = await client.create(config, mockSigner);
113
+
114
+ expect(multisig).toBeDefined();
115
+ expect(multisig.threshold).toBe(2);
116
+ expect(multisig.signerCommitments).toEqual(config.signerCommitments);
117
+ expect(multisig.psmCommitment).toBe(config.psmCommitment);
118
+ });
119
+
120
+ it('should set signer on PSM client', async () => {
121
+ const client = new MultisigClient(webClient);
122
+
123
+ const config = {
124
+ threshold: 1,
125
+ signerCommitments: ['0x' + 'a'.repeat(64)],
126
+ psmCommitment: '0x' + 'c'.repeat(64),
127
+ };
128
+
129
+ const multisig = await client.create(config, mockSigner);
130
+ expect(multisig.signerCommitment).toBe(mockSigner.commitment);
131
+ });
132
+ });
133
+
134
+ describe('load', () => {
135
+ it('should load existing multisig account and detect config', async () => {
136
+ const client = new MultisigClient(webClient);
137
+
138
+ // Mock getState response
139
+ mockFetch.mockResolvedValueOnce({
140
+ ok: true,
141
+ json: async () => ({
142
+ account_id: '0x' + 'd'.repeat(30),
143
+ commitment: '0x' + 'e'.repeat(64),
144
+ state_json: { data: 'base64state' },
145
+ created_at: '2024-01-01T00:00:00Z',
146
+ updated_at: '2024-01-02T00:00:00Z',
147
+ }),
148
+ });
149
+
150
+ const accountId = '0x' + 'd'.repeat(30);
151
+ const multisig = await client.load(accountId, mockSigner);
152
+
153
+ expect(multisig).toBeDefined();
154
+ expect(multisig.accountId).toBe(accountId);
155
+ // Config is detected from account storage via AccountInspector
156
+ expect(multisig.threshold).toBe(2);
157
+ expect(multisig.signerCommitments).toEqual(['0x' + 'a'.repeat(64), '0x' + 'b'.repeat(64)]);
158
+ expect(multisig.psmCommitment).toBe('0x' + 'c'.repeat(64));
159
+ expect(multisig.account).toBeNull(); // Loaded accounts don't have the SDK Account
160
+ });
161
+
162
+ it('should throw if account not found on PSM', async () => {
163
+ const client = new MultisigClient(webClient);
164
+
165
+ mockFetch.mockResolvedValueOnce({
166
+ ok: false,
167
+ status: 404,
168
+ statusText: 'Not Found',
169
+ text: async () => 'Account not found',
170
+ });
171
+
172
+ await expect(
173
+ client.load('0xnonexistent', mockSigner)
174
+ ).rejects.toThrow();
175
+ });
176
+ });
177
+ });
package/src/client.ts ADDED
@@ -0,0 +1,129 @@
1
+ /**
2
+ * MultisigClient - Factory for creating and loading multisig accounts.
3
+ *
4
+ * This is the main entry point for the multisig SDK. It provides methods
5
+ * to create new multisig accounts and load existing ones.
6
+ */
7
+
8
+ import { type WebClient, Account, AccountId } from '@demox-labs/miden-sdk';
9
+ import { PsmHttpClient } from '@openzeppelin/psm-client';
10
+ import { Multisig } from './multisig.js';
11
+ import { createMultisigAccount } from './account/index.js';
12
+ import { AccountInspector } from './inspector.js';
13
+ import type { MultisigConfig, Signer } from './types.js';
14
+
15
+ /**
16
+ * Configuration for MultisigClient.
17
+ */
18
+ export interface MultisigClientConfig {
19
+ /** PSM server endpoint */
20
+ psmEndpoint?: string;
21
+ }
22
+
23
+ /**
24
+ * Client for creating and loading multisig accounts.
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * import { MultisigClient, FalconSigner } from '@openzeppelin/miden-multisig-client';
29
+ * import { WebClient, SecretKey } from '@demox-labs/miden-sdk';
30
+ *
31
+ * // Initialize
32
+ * const webClient = await WebClient.createClient('https://rpc.testnet.miden.io:443');
33
+ * const secretKey = SecretKey.rpoFalconWithRNG(seed);
34
+ * const signer = new FalconSigner(secretKey);
35
+ *
36
+ * // Create client
37
+ * const client = new MultisigClient(webClient, { psmEndpoint: 'http://localhost:3000' });
38
+ *
39
+ * // Get PSM pubkey for config
40
+ * const psmCommitment = await client.psmClient.getPubkey();
41
+ *
42
+ * // Create multisig
43
+ * const config = { threshold: 2, signerCommitments: [...], psmCommitment };
44
+ * const multisig = await client.create(config, signer);
45
+ * ```
46
+ */
47
+ export class MultisigClient {
48
+ private readonly webClient: WebClient;
49
+ private _psmClient: PsmHttpClient;
50
+
51
+ constructor(webClient: WebClient, config: MultisigClientConfig = {}) {
52
+ this.webClient = webClient;
53
+ this._psmClient = new PsmHttpClient(config.psmEndpoint ?? 'http://localhost:3000');
54
+ }
55
+
56
+ /**
57
+ * Change the PSM endpoint.
58
+ *
59
+ * @param endpoint - The new PSM server endpoint URL
60
+ */
61
+ setPsmEndpoint(endpoint: string): void {
62
+ this._psmClient = new PsmHttpClient(endpoint);
63
+ }
64
+
65
+ /**
66
+ * Access the internal PSM client.
67
+ */
68
+ get psmClient(): PsmHttpClient {
69
+ return this._psmClient;
70
+ }
71
+
72
+ /**
73
+ * Create a new multisig account.
74
+ *
75
+ * @param config - Multisig configuration (threshold, signers, PSM commitment)
76
+ * @param signer - The signer for this client (one of the cosigners)
77
+ * @returns A Multisig instance wrapping the created account
78
+ */
79
+ async create(config: MultisigConfig, signer: Signer): Promise<Multisig> {
80
+ this._psmClient.setSigner(signer);
81
+
82
+ const { account } = await createMultisigAccount(this.webClient, config);
83
+
84
+ return new Multisig(account, config, this._psmClient, signer, this.webClient);
85
+ }
86
+
87
+ /**
88
+ * Load an existing multisig account from PSM.
89
+ *
90
+ * @param accountId - The account ID to load
91
+ * @param signer - The signer for this client
92
+ * @returns A Multisig instance for the loaded account
93
+ */
94
+ async load(accountId: string, signer: Signer): Promise<Multisig> {
95
+ this._psmClient.setSigner(signer);
96
+
97
+ const stateResponse = await this._psmClient.getState(accountId);
98
+
99
+ const accountBase64 = stateResponse.stateJson.data;
100
+ if (!accountBase64) {
101
+ throw new Error('No account data found in PSM state');
102
+ }
103
+
104
+ const binaryString = atob(accountBase64);
105
+ const accountBytes = new Uint8Array(binaryString.length);
106
+ for (let i = 0; i < binaryString.length; i++) {
107
+ accountBytes[i] = binaryString.charCodeAt(i);
108
+ }
109
+ const account = Account.deserialize(accountBytes);
110
+
111
+ const detected = AccountInspector.fromAccount(account);
112
+ const config: MultisigConfig = {
113
+ threshold: detected.threshold,
114
+ signerCommitments: detected.signerCommitments,
115
+ psmCommitment: detected.psmCommitment ?? '',
116
+ psmEnabled: detected.psmEnabled,
117
+ procedureThresholds: Array.from(detected.procedureThresholds.entries()).map(
118
+ ([procedure, threshold]) => ({ procedure, threshold })
119
+ ),
120
+ };
121
+
122
+ const existingAccount = await this.webClient.getAccount(AccountId.fromHex(accountId));
123
+ if (!existingAccount) {
124
+ await this.webClient.newAccount(account, true);
125
+ }
126
+
127
+ return new Multisig(null, config, this._psmClient, signer, this.webClient, accountId);
128
+ }
129
+ }
package/src/index.ts ADDED
@@ -0,0 +1,118 @@
1
+ /**
2
+ * @openzeppelin/miden-multisig-client
3
+ *
4
+ * TypeScript SDK for Miden multisig accounts with PSM (Private State Manager) integration.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import {
9
+ * MultisigClient,
10
+ * FalconSigner,
11
+ * } from '@openzeppelin/miden-multisig-client';
12
+ * import { WebClient, SecretKey } from '@demox-labs/miden-sdk';
13
+ *
14
+ * // Initialize WebClient
15
+ * const webClient = await WebClient.createClient('https://rpc.testnet.miden.io:443');
16
+ * await webClient.syncState();
17
+ *
18
+ * // Generate a key dynamically
19
+ * const seed = new Uint8Array(32);
20
+ * crypto.getRandomValues(seed);
21
+ * const secretKey = SecretKey.rpoFalconWithRNG(seed);
22
+ *
23
+ * // Store in miden-sdk's keystore
24
+ * await webClient.addAccountSecretKeyToWebStore(secretKey);
25
+ *
26
+ * // Create a signer
27
+ * const signer = new FalconSigner(secretKey);
28
+ *
29
+ * // Create multisig client
30
+ * const client = new MultisigClient(webClient, { psmEndpoint: 'http://localhost:3000' });
31
+ *
32
+ * // Get PSM pubkey for config
33
+ * const psmCommitment = await client.psmClient.getPubkey();
34
+ *
35
+ * // Create multisig account
36
+ * const config = { threshold: 2, signerCommitments: [signer.commitment, ...], psmCommitment };
37
+ * const multisig = await client.create(config, signer);
38
+ *
39
+ * // Register on PSM and work with proposals
40
+ * await multisig.registerOnPsm();
41
+ * await multisig.syncProposals();
42
+ * ```
43
+ */
44
+
45
+ export { MultisigClient, type MultisigClientConfig } from './client.js';
46
+ export { Multisig, type AccountState } from './multisig.js';
47
+ export { AccountInspector, type DetectedMultisigConfig, type VaultBalance } from './inspector.js';
48
+ export {
49
+ executeForSummary,
50
+ buildUpdateSignersTransactionRequest,
51
+ buildUpdatePsmTransactionRequest,
52
+ buildConsumeNotesTransactionRequest,
53
+ buildP2idTransactionRequest,
54
+ } from './transaction.js';
55
+
56
+ export { PsmHttpClient, PsmHttpError } from '@openzeppelin/psm-client';
57
+
58
+ export { FalconSigner } from './signer.js';
59
+
60
+ export {
61
+ createMultisigAccount,
62
+ validateMultisigConfig,
63
+ buildMultisigStorageSlots,
64
+ buildPsmStorageSlots,
65
+ storageLayoutBuilder,
66
+ StorageLayoutBuilder,
67
+ } from './account/index.js';
68
+
69
+ export {
70
+ PROCEDURE_ROOTS,
71
+ getProcedureRoot,
72
+ isProcedureName,
73
+ getProcedureNames,
74
+ type ProcedureName,
75
+ } from './procedures.js';
76
+
77
+ export type {
78
+ // Account types
79
+ MultisigAccountState,
80
+ MultisigConfig,
81
+ CreateAccountResult,
82
+ ProcedureThreshold,
83
+
84
+ // Proposal types
85
+ Proposal,
86
+ ProposalStatus,
87
+ ProposalSignatureEntry,
88
+ ProposalMetadata,
89
+ ProposalType,
90
+ ExportedProposal,
91
+
92
+ // Transaction types
93
+ TransactionType,
94
+
95
+ // Note types
96
+ ConsumableNote,
97
+ NoteAsset,
98
+
99
+ // Signature types
100
+ FalconSignature,
101
+ Signer,
102
+
103
+ // PSM API types
104
+ AuthConfig,
105
+ DeltaObject,
106
+ DeltaStatus,
107
+ StateObject,
108
+ CosignerSignature,
109
+
110
+ // Request/Response types
111
+ ConfigureRequest,
112
+ ConfigureResponse,
113
+ DeltaProposalRequest,
114
+ DeltaProposalResponse,
115
+ ProposalsResponse,
116
+ PubkeyResponse,
117
+ SignProposalRequest,
118
+ } from './types.js';
@@ -0,0 +1,237 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { AccountInspector } from './inspector.js';
3
+
4
+ // Mock the Miden SDK
5
+ vi.mock('@demox-labs/miden-sdk', () => {
6
+ const createMockWord = (values: bigint[]) => ({
7
+ toU64s: () => values,
8
+ toHex: () => '0x' + values.map(v => v.toString(16).padStart(16, '0')).join(''),
9
+ });
10
+
11
+ const createMockStorage = (slots: Map<number, any>, maps: Map<number, Map<string, any>>) => ({
12
+ getItem: (index: number) => slots.get(index) ?? createMockWord([0n, 0n, 0n, 0n]),
13
+ getMapItem: (slotIndex: number, key: any) => {
14
+ const map = maps.get(slotIndex);
15
+ if (!map) throw new Error('Map not found');
16
+ const keyStr = key.toU64s?.()[0]?.toString() ?? '0';
17
+ const value = map.get(keyStr);
18
+ if (!value) throw new Error('Key not found');
19
+ return value;
20
+ },
21
+ });
22
+
23
+ const createMockVault = (assets: Array<{ faucetId: string; amount: bigint }>) => ({
24
+ fungibleAssets: () => assets.map(a => ({
25
+ faucetId: () => ({ toString: () => a.faucetId }),
26
+ amount: () => a.amount,
27
+ })),
28
+ });
29
+
30
+ return {
31
+ Account: {
32
+ deserialize: vi.fn((bytes: Uint8Array) => {
33
+ // Return different mocked accounts based on test scenario
34
+ // Default: 2-of-3 multisig with PSM enabled
35
+ const slot0 = createMockWord([2n, 3n, 0n, 0n]); // threshold=2, numSigners=3
36
+ const slot4 = createMockWord([1n, 0n, 0n, 0n]); // PSM enabled
37
+
38
+ const signerMap = new Map<string, any>();
39
+ signerMap.set('0', createMockWord([BigInt('0x1111111111111111'), BigInt('0x2222222222222222'), BigInt('0x3333333333333333'), BigInt('0x4444444444444444')]));
40
+ signerMap.set('1', createMockWord([BigInt('0x5555555555555555'), BigInt('0x6666666666666666'), BigInt('0x7777777777777777'), BigInt('0x8888888888888888')]));
41
+ signerMap.set('2', createMockWord([BigInt('0xaaaaaaaaaaaaaaaa'), BigInt('0xbbbbbbbbbbbbbbbb'), BigInt('0xcccccccccccccccc'), BigInt('0xdddddddddddddddd')]));
42
+
43
+ const psmMap = new Map<string, any>();
44
+ psmMap.set('0', createMockWord([BigInt('0xeeeeeeeeeeeeeeee'), BigInt('0xffffffffffffffff'), BigInt('0x0000000000000001'), BigInt('0x0000000000000002')]));
45
+
46
+ const slots = new Map<number, any>();
47
+ slots.set(0, slot0);
48
+ slots.set(4, slot4);
49
+
50
+ const maps = new Map<number, Map<string, any>>();
51
+ maps.set(1, signerMap);
52
+ maps.set(5, psmMap);
53
+
54
+ return {
55
+ storage: () => createMockStorage(slots, maps),
56
+ vault: () => createMockVault([
57
+ { faucetId: '0xfaucet1', amount: 1000n },
58
+ { faucetId: '0xfaucet2', amount: 500n },
59
+ ]),
60
+ };
61
+ }),
62
+ },
63
+ Word: vi.fn().mockImplementation((arr: BigUint64Array) => ({
64
+ toU64s: () => Array.from(arr),
65
+ toHex: () => '0x' + Array.from(arr).map(v => v.toString(16).padStart(16, '0')).join(''),
66
+ })),
67
+ };
68
+ });
69
+
70
+ describe('AccountInspector', () => {
71
+ beforeEach(() => {
72
+ vi.clearAllMocks();
73
+ });
74
+
75
+ describe('fromBase64', () => {
76
+ it('deserializes account from base64 and extracts config', () => {
77
+ const base64 = btoa(String.fromCharCode(...new Uint8Array([1, 2, 3])));
78
+ const config = AccountInspector.fromBase64(base64);
79
+
80
+ expect(config.threshold).toBe(2);
81
+ expect(config.numSigners).toBe(3);
82
+ });
83
+
84
+ it('extracts PSM status', () => {
85
+ const base64 = btoa(String.fromCharCode(...new Uint8Array([1, 2, 3])));
86
+ const config = AccountInspector.fromBase64(base64);
87
+
88
+ expect(config.psmEnabled).toBe(true);
89
+ expect(config.psmCommitment).toMatch(/^0x[a-f0-9]+$/);
90
+ });
91
+
92
+ it('extracts signer commitments', () => {
93
+ const base64 = btoa(String.fromCharCode(...new Uint8Array([1, 2, 3])));
94
+ const config = AccountInspector.fromBase64(base64);
95
+
96
+ expect(config.signerCommitments).toHaveLength(3);
97
+ config.signerCommitments.forEach(commitment => {
98
+ expect(commitment).toMatch(/^0x[a-f0-9]+$/);
99
+ });
100
+ });
101
+
102
+ it('extracts vault balances', () => {
103
+ const base64 = btoa(String.fromCharCode(...new Uint8Array([1, 2, 3])));
104
+ const config = AccountInspector.fromBase64(base64);
105
+
106
+ expect(config.vaultBalances).toHaveLength(2);
107
+ expect(config.vaultBalances[0]).toEqual({ faucetId: '0xfaucet1', amount: 1000n });
108
+ expect(config.vaultBalances[1]).toEqual({ faucetId: '0xfaucet2', amount: 500n });
109
+ });
110
+ });
111
+
112
+ describe('fromAccount', () => {
113
+ it('extracts threshold from slot 0', async () => {
114
+ const { Account } = await import('@demox-labs/miden-sdk');
115
+ const account = Account.deserialize(new Uint8Array([1, 2, 3]));
116
+ const config = AccountInspector.fromAccount(account);
117
+
118
+ expect(config.threshold).toBe(2);
119
+ });
120
+
121
+ it('extracts numSigners from slot 0', async () => {
122
+ const { Account } = await import('@demox-labs/miden-sdk');
123
+ const account = Account.deserialize(new Uint8Array([1, 2, 3]));
124
+ const config = AccountInspector.fromAccount(account);
125
+
126
+ expect(config.numSigners).toBe(3);
127
+ });
128
+ });
129
+ });
130
+
131
+ describe('AccountInspector edge cases', () => {
132
+ beforeEach(() => {
133
+ vi.clearAllMocks();
134
+ });
135
+
136
+ it('handles account with PSM disabled', async () => {
137
+ const { Account } = await import('@demox-labs/miden-sdk');
138
+
139
+ // Override mock for this test
140
+ vi.mocked(Account.deserialize).mockReturnValueOnce({
141
+ storage: () => ({
142
+ getItem: (index: number) => {
143
+ if (index === 0) return { toU64s: () => [1n, 1n, 0n, 0n] };
144
+ if (index === 4) return { toU64s: () => [0n, 0n, 0n, 0n] }; // PSM disabled
145
+ return { toU64s: () => [0n, 0n, 0n, 0n] };
146
+ },
147
+ getMapItem: (slotIndex: number, key: any) => {
148
+ if (slotIndex === 1 && key.toU64s?.()[0] === 0n) {
149
+ return {
150
+ toHex: () => '0x' + 'a'.repeat(64),
151
+ toU64s: () => [1n, 2n, 3n, 4n],
152
+ };
153
+ }
154
+ throw new Error('Not found');
155
+ },
156
+ }),
157
+ vault: () => ({
158
+ fungibleAssets: () => [],
159
+ }),
160
+ } as any);
161
+
162
+ const account = Account.deserialize(new Uint8Array([1, 2, 3]));
163
+ const config = AccountInspector.fromAccount(account);
164
+
165
+ expect(config.psmEnabled).toBe(false);
166
+ expect(config.psmCommitment).toBeNull();
167
+ });
168
+
169
+ it('handles account with empty vault', async () => {
170
+ const { Account } = await import('@demox-labs/miden-sdk');
171
+
172
+ vi.mocked(Account.deserialize).mockReturnValueOnce({
173
+ storage: () => ({
174
+ getItem: () => ({ toU64s: () => [1n, 1n, 0n, 0n] }),
175
+ getMapItem: () => {
176
+ throw new Error('Not found');
177
+ },
178
+ }),
179
+ vault: () => ({
180
+ fungibleAssets: () => [],
181
+ }),
182
+ } as any);
183
+
184
+ const account = Account.deserialize(new Uint8Array([1, 2, 3]));
185
+ const config = AccountInspector.fromAccount(account);
186
+
187
+ expect(config.vaultBalances).toEqual([]);
188
+ });
189
+
190
+ it('handles missing signer map entries gracefully', async () => {
191
+ const { Account } = await import('@demox-labs/miden-sdk');
192
+
193
+ vi.mocked(Account.deserialize).mockReturnValueOnce({
194
+ storage: () => ({
195
+ getItem: (index: number) => {
196
+ if (index === 0) return { toU64s: () => [2n, 5n, 0n, 0n] }; // threshold=2, numSigners=5
197
+ if (index === 4) return { toU64s: () => [0n, 0n, 0n, 0n] };
198
+ return { toU64s: () => [0n, 0n, 0n, 0n] };
199
+ },
200
+ getMapItem: () => {
201
+ throw new Error('Map entry not found');
202
+ },
203
+ }),
204
+ vault: () => ({
205
+ fungibleAssets: () => [],
206
+ }),
207
+ } as any);
208
+
209
+ const account = Account.deserialize(new Uint8Array([1, 2, 3]));
210
+ const config = AccountInspector.fromAccount(account);
211
+
212
+ // Should gracefully handle missing entries
213
+ expect(config.numSigners).toBe(5);
214
+ expect(config.signerCommitments).toEqual([]); // All entries missing
215
+ });
216
+
217
+ it('handles vault access error gracefully', async () => {
218
+ const { Account } = await import('@demox-labs/miden-sdk');
219
+
220
+ vi.mocked(Account.deserialize).mockReturnValueOnce({
221
+ storage: () => ({
222
+ getItem: () => ({ toU64s: () => [1n, 1n, 0n, 0n] }),
223
+ getMapItem: () => {
224
+ throw new Error('Not found');
225
+ },
226
+ }),
227
+ vault: () => {
228
+ throw new Error('Vault access failed');
229
+ },
230
+ } as any);
231
+
232
+ const account = Account.deserialize(new Uint8Array([1, 2, 3]));
233
+ const config = AccountInspector.fromAccount(account);
234
+
235
+ expect(config.vaultBalances).toEqual([]);
236
+ });
237
+ });