@openzeppelin/miden-multisig-client 0.16.0 → 0.16.2

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 (123) hide show
  1. package/README.md +58 -2
  2. package/dist/client.d.ts +12 -0
  3. package/dist/client.d.ts.map +1 -1
  4. package/dist/client.js +12 -2
  5. package/dist/client.js.map +1 -1
  6. package/dist/client.test.js +7 -2
  7. package/dist/client.test.js.map +1 -1
  8. package/dist/index.d.ts +6 -0
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +4 -0
  11. package/dist/index.js.map +1 -1
  12. package/dist/multisig.d.ts +65 -2
  13. package/dist/multisig.d.ts.map +1 -1
  14. package/dist/multisig.js +148 -15
  15. package/dist/multisig.js.map +1 -1
  16. package/dist/multisig.test.js +217 -13
  17. package/dist/multisig.test.js.map +1 -1
  18. package/dist/proposal/factory.d.ts.map +1 -1
  19. package/dist/proposal/factory.js +10 -0
  20. package/dist/proposal/factory.js.map +1 -1
  21. package/dist/proposal/factory.test.d.ts +2 -0
  22. package/dist/proposal/factory.test.d.ts.map +1 -0
  23. package/dist/proposal/factory.test.js +32 -0
  24. package/dist/proposal/factory.test.js.map +1 -0
  25. package/dist/prover/config.d.ts +16 -0
  26. package/dist/prover/config.d.ts.map +1 -0
  27. package/dist/prover/config.js +54 -0
  28. package/dist/prover/config.js.map +1 -0
  29. package/dist/prover/config.test.d.ts +2 -0
  30. package/dist/prover/config.test.d.ts.map +1 -0
  31. package/dist/prover/config.test.js +54 -0
  32. package/dist/prover/config.test.js.map +1 -0
  33. package/dist/prover/errors.d.ts +7 -0
  34. package/dist/prover/errors.d.ts.map +1 -0
  35. package/dist/prover/errors.js +10 -0
  36. package/dist/prover/errors.js.map +1 -0
  37. package/dist/prover/errors.test.d.ts +2 -0
  38. package/dist/prover/errors.test.d.ts.map +1 -0
  39. package/dist/prover/errors.test.js +29 -0
  40. package/dist/prover/errors.test.js.map +1 -0
  41. package/dist/prover/retry.d.ts +5 -0
  42. package/dist/prover/retry.d.ts.map +1 -0
  43. package/dist/prover/retry.js +9 -0
  44. package/dist/prover/retry.js.map +1 -0
  45. package/dist/prover/retry.test.d.ts +2 -0
  46. package/dist/prover/retry.test.d.ts.map +1 -0
  47. package/dist/prover/retry.test.js +20 -0
  48. package/dist/prover/retry.test.js.map +1 -0
  49. package/dist/prover/workflow.d.ts +11 -0
  50. package/dist/prover/workflow.d.ts.map +1 -0
  51. package/dist/prover/workflow.js +18 -0
  52. package/dist/prover/workflow.js.map +1 -0
  53. package/dist/prover/workflow.test.d.ts +2 -0
  54. package/dist/prover/workflow.test.d.ts.map +1 -0
  55. package/dist/prover/workflow.test.js +103 -0
  56. package/dist/prover/workflow.test.js.map +1 -0
  57. package/dist/retry/classify.d.ts +16 -0
  58. package/dist/retry/classify.d.ts.map +1 -0
  59. package/dist/retry/classify.js +187 -0
  60. package/dist/retry/classify.js.map +1 -0
  61. package/dist/retry/runtime.d.ts +13 -0
  62. package/dist/retry/runtime.d.ts.map +1 -0
  63. package/dist/retry/runtime.js +33 -0
  64. package/dist/retry/runtime.js.map +1 -0
  65. package/dist/rpc/config.d.ts +11 -0
  66. package/dist/rpc/config.d.ts.map +1 -0
  67. package/dist/rpc/config.js +17 -0
  68. package/dist/rpc/config.js.map +1 -0
  69. package/dist/rpc/config.test.d.ts +2 -0
  70. package/dist/rpc/config.test.d.ts.map +1 -0
  71. package/dist/rpc/config.test.js +24 -0
  72. package/dist/rpc/config.test.js.map +1 -0
  73. package/dist/rpc/errors.d.ts +2 -0
  74. package/dist/rpc/errors.d.ts.map +1 -0
  75. package/dist/rpc/errors.js +11 -0
  76. package/dist/rpc/errors.js.map +1 -0
  77. package/dist/rpc/errors.test.d.ts +2 -0
  78. package/dist/rpc/errors.test.d.ts.map +1 -0
  79. package/dist/rpc/errors.test.js +34 -0
  80. package/dist/rpc/errors.test.js.map +1 -0
  81. package/dist/rpc/retry.d.ts +4 -0
  82. package/dist/rpc/retry.d.ts.map +1 -0
  83. package/dist/rpc/retry.js +6 -0
  84. package/dist/rpc/retry.js.map +1 -0
  85. package/dist/rpc/retry.test.d.ts +2 -0
  86. package/dist/rpc/retry.test.d.ts.map +1 -0
  87. package/dist/rpc/retry.test.js +98 -0
  88. package/dist/rpc/retry.test.js.map +1 -0
  89. package/dist/transaction/p2id.d.ts +8 -1
  90. package/dist/transaction/p2id.d.ts.map +1 -1
  91. package/dist/transaction/p2id.js +12 -3
  92. package/dist/transaction/p2id.js.map +1 -1
  93. package/dist/transaction.d.ts +1 -1
  94. package/dist/transaction.d.ts.map +1 -1
  95. package/dist/transaction.js +1 -1
  96. package/dist/transaction.js.map +1 -1
  97. package/package.json +2 -2
  98. package/src/client.test.ts +8 -2
  99. package/src/client.ts +28 -2
  100. package/src/index.ts +6 -0
  101. package/src/multisig.test.ts +263 -17
  102. package/src/multisig.ts +201 -14
  103. package/src/proposal/factory.test.ts +40 -0
  104. package/src/proposal/factory.ts +11 -1
  105. package/src/prover/config.test.ts +90 -0
  106. package/src/prover/config.ts +78 -0
  107. package/src/prover/errors.test.ts +61 -0
  108. package/src/prover/errors.ts +10 -0
  109. package/src/prover/retry.test.ts +38 -0
  110. package/src/prover/retry.ts +21 -0
  111. package/src/prover/test-node.d.ts +6 -0
  112. package/src/prover/workflow.test.ts +140 -0
  113. package/src/prover/workflow.ts +19 -0
  114. package/src/retry/classify.ts +220 -0
  115. package/src/retry/runtime.ts +45 -0
  116. package/src/rpc/config.test.ts +45 -0
  117. package/src/rpc/config.ts +30 -0
  118. package/src/rpc/errors.test.ts +69 -0
  119. package/src/rpc/errors.ts +12 -0
  120. package/src/rpc/retry.test.ts +144 -0
  121. package/src/rpc/retry.ts +12 -0
  122. package/src/transaction/p2id.ts +29 -10
  123. package/src/transaction.ts +1 -0
@@ -0,0 +1,144 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { describe, expect, it, vi } from 'vitest';
3
+ import { resolveRpcConfig } from './config.js';
4
+ import { retryRpcRead } from './retry.js';
5
+ import type { RetryRuntime } from '../retry/runtime.js';
6
+ import { retryDelay } from '../retry/runtime.js';
7
+
8
+ interface Fixtures {
9
+ delays: Array<{ retryIndex: number; unitRandom: number; delayMs: number }>;
10
+ }
11
+
12
+ function fixtures(): Fixtures {
13
+ return JSON.parse(
14
+ readFileSync(
15
+ new URL(
16
+ '../../../../fixtures/miden-multisig-client/rpc-policy-fixtures.json',
17
+ import.meta.url,
18
+ ),
19
+ 'utf8',
20
+ ),
21
+ ) as Fixtures;
22
+ }
23
+
24
+ function recordingRuntime(): RetryRuntime & { sleeps: number[] } {
25
+ const sleeps: number[] = [];
26
+ return {
27
+ sleeps,
28
+ sleep: (delayMs) => {
29
+ sleeps.push(delayMs);
30
+ return Promise.resolve();
31
+ },
32
+ unitRandom: () => 0.5,
33
+ };
34
+ }
35
+
36
+ function rateLimitError(): Error {
37
+ return Object.assign(new Error('Too Many Requests!'), {
38
+ code: 'ResourceExhausted',
39
+ });
40
+ }
41
+
42
+ describe('retryRpcRead', () => {
43
+ it('matches every shared delay vector', () => {
44
+ for (const fixture of fixtures().delays) {
45
+ expect(retryDelay(fixture.retryIndex, fixture.unitRandom)).toBe(fixture.delayMs);
46
+ }
47
+ });
48
+
49
+ it('waits out rate limiting and returns the eventual result', async () => {
50
+ const runtime = recordingRuntime();
51
+ const operation = vi
52
+ .fn()
53
+ .mockRejectedValueOnce(rateLimitError())
54
+ .mockRejectedValueOnce(rateLimitError())
55
+ .mockResolvedValueOnce('synced');
56
+
57
+ const result = await retryRpcRead(
58
+ operation,
59
+ resolveRpcConfig({ retry: { maxAttempts: 3 } }),
60
+ runtime,
61
+ );
62
+
63
+ expect(result).toBe('synced');
64
+ expect(operation).toHaveBeenCalledTimes(3);
65
+ expect(runtime.sleeps).toEqual([retryDelay(0, 0.5), retryDelay(1, 0.5)]);
66
+ });
67
+
68
+ it('throws a permanent error immediately without sleeping', async () => {
69
+ const runtime = recordingRuntime();
70
+ const permanent = Object.assign(new Error('malformed account id'), {
71
+ code: 'InvalidArgument',
72
+ });
73
+ const operation = vi.fn().mockRejectedValue(permanent);
74
+
75
+ await expect(
76
+ retryRpcRead(operation, resolveRpcConfig({ retry: { maxAttempts: 5 } }), runtime),
77
+ ).rejects.toBe(permanent);
78
+ expect(operation).toHaveBeenCalledTimes(1);
79
+ expect(runtime.sleeps).toEqual([]);
80
+ });
81
+
82
+ it('rethrows the final upstream error unchanged after budget exhaustion', async () => {
83
+ const runtime = recordingRuntime();
84
+ const final = Object.assign(new Error('still rate limited'), {
85
+ code: 'ResourceExhausted',
86
+ });
87
+ const operation = vi
88
+ .fn()
89
+ .mockRejectedValueOnce(rateLimitError())
90
+ .mockRejectedValueOnce(final);
91
+
92
+ await expect(
93
+ retryRpcRead(operation, resolveRpcConfig({ retry: { maxAttempts: 2 } }), runtime),
94
+ ).rejects.toBe(final);
95
+ expect(operation).toHaveBeenCalledTimes(2);
96
+ expect(runtime.sleeps).toHaveLength(1);
97
+ });
98
+
99
+ it('retries a transient read once under the default config', async () => {
100
+ const runtime = recordingRuntime();
101
+ const operation = vi
102
+ .fn()
103
+ .mockRejectedValueOnce(rateLimitError())
104
+ .mockResolvedValueOnce('synced');
105
+
106
+ await expect(retryRpcRead(operation, resolveRpcConfig(undefined), runtime)).resolves.toBe(
107
+ 'synced',
108
+ );
109
+ expect(operation).toHaveBeenCalledTimes(2);
110
+ expect(runtime.sleeps).toHaveLength(1);
111
+ });
112
+
113
+ it('never retries under an explicit single-attempt config', async () => {
114
+ const runtime = recordingRuntime();
115
+ const operation = vi.fn().mockRejectedValue(rateLimitError());
116
+
117
+ await expect(
118
+ retryRpcRead(operation, resolveRpcConfig({ retry: { maxAttempts: 1 } }), runtime),
119
+ ).rejects.toThrow('Too Many Requests!');
120
+ expect(operation).toHaveBeenCalledTimes(1);
121
+ expect(runtime.sleeps).toEqual([]);
122
+ });
123
+
124
+ it('completes 64 concurrent reads against a rate-limited node', async () => {
125
+ const runtime = recordingRuntime();
126
+ let rejected = 0;
127
+ const operation = () => {
128
+ if (rejected < 40) {
129
+ rejected += 1;
130
+ return Promise.reject(rateLimitError());
131
+ }
132
+ return Promise.resolve('synced');
133
+ };
134
+
135
+ const results = await Promise.all(
136
+ Array.from({ length: 64 }, () =>
137
+ retryRpcRead(operation, resolveRpcConfig({ retry: { maxAttempts: 4 } }), runtime),
138
+ ),
139
+ );
140
+
141
+ expect(results).toEqual(Array.from({ length: 64 }, () => 'synced'));
142
+ expect(rejected).toBe(40);
143
+ });
144
+ });
@@ -0,0 +1,12 @@
1
+ import type { ResolvedRpcConfig } from './config.js';
2
+ import { isTransientRpcError } from './errors.js';
3
+ import type { RetryRuntime } from '../retry/runtime.js';
4
+ import { productionRetryRuntime, retryTransient } from '../retry/runtime.js';
5
+
6
+ export async function retryRpcRead<T>(
7
+ operation: () => Promise<T>,
8
+ config: ResolvedRpcConfig,
9
+ runtime: RetryRuntime = productionRetryRuntime,
10
+ ): Promise<T> {
11
+ return retryTransient(operation, config.maxAttempts, isTransientRpcError, runtime);
12
+ }
@@ -114,28 +114,47 @@ function buildP2idNote(
114
114
  return new Note(noteAssets, noteMetadata, noteRecipient);
115
115
  }
116
116
 
117
- export function buildP2idTransactionRequest(
117
+ /**
118
+ * Rebuilds the P2ID note a proposal creates, from its metadata fields. The
119
+ * note is deterministic in the salt, so the resulting ID matches the note the
120
+ * proposal produces on execution (issue #356). The asset is derived from the
121
+ * account's current vault state, so call this before the transfer executes.
122
+ */
123
+ export function buildP2idNoteFromMetadata(
118
124
  senderId: string,
119
125
  recipientId: string,
120
126
  faucetId: string,
121
127
  amount: bigint,
122
128
  account: Account,
123
- options: P2idTransactionOptions = {},
124
- ): { request: TransactionRequest; salt: Word } {
129
+ noteType: NoteType,
130
+ saltHex: string,
131
+ ): Note {
125
132
  const sender = AccountId.fromHex(senderId);
126
133
  const recipient = AccountId.fromHex(recipientId);
127
134
  const faucet = AccountId.fromHex(faucetId);
128
135
 
129
- const authSaltHex = options.salt ? options.salt.toHex() : randomWord().toHex();
130
-
131
136
  const asset = resolveFungibleAssetFromVault(account, faucet, amount);
132
-
133
137
  const noteAssets = new NoteAssets([asset]);
134
138
 
135
- const note = buildP2idNote(
136
- sender,
137
- recipient,
138
- noteAssets,
139
+ return buildP2idNote(sender, recipient, noteAssets, noteType, saltHex);
140
+ }
141
+
142
+ export function buildP2idTransactionRequest(
143
+ senderId: string,
144
+ recipientId: string,
145
+ faucetId: string,
146
+ amount: bigint,
147
+ account: Account,
148
+ options: P2idTransactionOptions = {},
149
+ ): { request: TransactionRequest; salt: Word } {
150
+ const authSaltHex = options.salt ? options.salt.toHex() : randomWord().toHex();
151
+
152
+ const note = buildP2idNoteFromMetadata(
153
+ senderId,
154
+ recipientId,
155
+ faucetId,
156
+ amount,
157
+ account,
139
158
  options.noteType ?? NoteType.Public,
140
159
  authSaltHex,
141
160
  );
@@ -3,6 +3,7 @@ export {
3
3
  } from './transaction/consumeNotes.js';
4
4
  export { executeForSummary } from './transaction/summary.js';
5
5
  export {
6
+ buildP2idNoteFromMetadata,
6
7
  buildP2idTransactionRequest,
7
8
  parseP2idNoteType,
8
9
  p2idNoteTypeToMetadata,