@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.
- package/README.md +58 -2
- package/dist/client.d.ts +12 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +12 -2
- package/dist/client.js.map +1 -1
- package/dist/client.test.js +7 -2
- package/dist/client.test.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/multisig.d.ts +65 -2
- package/dist/multisig.d.ts.map +1 -1
- package/dist/multisig.js +148 -15
- package/dist/multisig.js.map +1 -1
- package/dist/multisig.test.js +217 -13
- package/dist/multisig.test.js.map +1 -1
- package/dist/proposal/factory.d.ts.map +1 -1
- package/dist/proposal/factory.js +10 -0
- package/dist/proposal/factory.js.map +1 -1
- package/dist/proposal/factory.test.d.ts +2 -0
- package/dist/proposal/factory.test.d.ts.map +1 -0
- package/dist/proposal/factory.test.js +32 -0
- package/dist/proposal/factory.test.js.map +1 -0
- package/dist/prover/config.d.ts +16 -0
- package/dist/prover/config.d.ts.map +1 -0
- package/dist/prover/config.js +54 -0
- package/dist/prover/config.js.map +1 -0
- package/dist/prover/config.test.d.ts +2 -0
- package/dist/prover/config.test.d.ts.map +1 -0
- package/dist/prover/config.test.js +54 -0
- package/dist/prover/config.test.js.map +1 -0
- package/dist/prover/errors.d.ts +7 -0
- package/dist/prover/errors.d.ts.map +1 -0
- package/dist/prover/errors.js +10 -0
- package/dist/prover/errors.js.map +1 -0
- package/dist/prover/errors.test.d.ts +2 -0
- package/dist/prover/errors.test.d.ts.map +1 -0
- package/dist/prover/errors.test.js +29 -0
- package/dist/prover/errors.test.js.map +1 -0
- package/dist/prover/retry.d.ts +5 -0
- package/dist/prover/retry.d.ts.map +1 -0
- package/dist/prover/retry.js +9 -0
- package/dist/prover/retry.js.map +1 -0
- package/dist/prover/retry.test.d.ts +2 -0
- package/dist/prover/retry.test.d.ts.map +1 -0
- package/dist/prover/retry.test.js +20 -0
- package/dist/prover/retry.test.js.map +1 -0
- package/dist/prover/workflow.d.ts +11 -0
- package/dist/prover/workflow.d.ts.map +1 -0
- package/dist/prover/workflow.js +18 -0
- package/dist/prover/workflow.js.map +1 -0
- package/dist/prover/workflow.test.d.ts +2 -0
- package/dist/prover/workflow.test.d.ts.map +1 -0
- package/dist/prover/workflow.test.js +103 -0
- package/dist/prover/workflow.test.js.map +1 -0
- package/dist/retry/classify.d.ts +16 -0
- package/dist/retry/classify.d.ts.map +1 -0
- package/dist/retry/classify.js +187 -0
- package/dist/retry/classify.js.map +1 -0
- package/dist/retry/runtime.d.ts +13 -0
- package/dist/retry/runtime.d.ts.map +1 -0
- package/dist/retry/runtime.js +33 -0
- package/dist/retry/runtime.js.map +1 -0
- package/dist/rpc/config.d.ts +11 -0
- package/dist/rpc/config.d.ts.map +1 -0
- package/dist/rpc/config.js +17 -0
- package/dist/rpc/config.js.map +1 -0
- package/dist/rpc/config.test.d.ts +2 -0
- package/dist/rpc/config.test.d.ts.map +1 -0
- package/dist/rpc/config.test.js +24 -0
- package/dist/rpc/config.test.js.map +1 -0
- package/dist/rpc/errors.d.ts +2 -0
- package/dist/rpc/errors.d.ts.map +1 -0
- package/dist/rpc/errors.js +11 -0
- package/dist/rpc/errors.js.map +1 -0
- package/dist/rpc/errors.test.d.ts +2 -0
- package/dist/rpc/errors.test.d.ts.map +1 -0
- package/dist/rpc/errors.test.js +34 -0
- package/dist/rpc/errors.test.js.map +1 -0
- package/dist/rpc/retry.d.ts +4 -0
- package/dist/rpc/retry.d.ts.map +1 -0
- package/dist/rpc/retry.js +6 -0
- package/dist/rpc/retry.js.map +1 -0
- package/dist/rpc/retry.test.d.ts +2 -0
- package/dist/rpc/retry.test.d.ts.map +1 -0
- package/dist/rpc/retry.test.js +98 -0
- package/dist/rpc/retry.test.js.map +1 -0
- package/dist/transaction/p2id.d.ts +8 -1
- package/dist/transaction/p2id.d.ts.map +1 -1
- package/dist/transaction/p2id.js +12 -3
- package/dist/transaction/p2id.js.map +1 -1
- package/dist/transaction.d.ts +1 -1
- package/dist/transaction.d.ts.map +1 -1
- package/dist/transaction.js +1 -1
- package/dist/transaction.js.map +1 -1
- package/package.json +2 -2
- package/src/client.test.ts +8 -2
- package/src/client.ts +28 -2
- package/src/index.ts +6 -0
- package/src/multisig.test.ts +263 -17
- package/src/multisig.ts +201 -14
- package/src/proposal/factory.test.ts +40 -0
- package/src/proposal/factory.ts +11 -1
- package/src/prover/config.test.ts +90 -0
- package/src/prover/config.ts +78 -0
- package/src/prover/errors.test.ts +61 -0
- package/src/prover/errors.ts +10 -0
- package/src/prover/retry.test.ts +38 -0
- package/src/prover/retry.ts +21 -0
- package/src/prover/test-node.d.ts +6 -0
- package/src/prover/workflow.test.ts +140 -0
- package/src/prover/workflow.ts +19 -0
- package/src/retry/classify.ts +220 -0
- package/src/retry/runtime.ts +45 -0
- package/src/rpc/config.test.ts +45 -0
- package/src/rpc/config.ts +30 -0
- package/src/rpc/errors.test.ts +69 -0
- package/src/rpc/errors.ts +12 -0
- package/src/rpc/retry.test.ts +144 -0
- package/src/rpc/retry.ts +12 -0
- package/src/transaction/p2id.ts +29 -10
- package/src/transaction.ts +1 -0
package/dist/transaction.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { buildConsumeNotesTransactionRequest, } from './transaction/consumeNotes.js';
|
|
2
2
|
export { executeForSummary } from './transaction/summary.js';
|
|
3
|
-
export { buildP2idTransactionRequest, parseP2idNoteType, p2idNoteTypeToMetadata, type P2idTransactionOptions, } from './transaction/p2id.js';
|
|
3
|
+
export { buildP2idNoteFromMetadata, buildP2idTransactionRequest, parseP2idNoteType, p2idNoteTypeToMetadata, type P2idTransactionOptions, } from './transaction/p2id.js';
|
|
4
4
|
export { buildUpdateGuardianTransactionRequest, } from './transaction/updateGuardian.js';
|
|
5
5
|
export { buildUpdateProcedureThresholdTransactionRequest, } from './transaction/updateProcedureThreshold.js';
|
|
6
6
|
export { buildUpdateSignersTransactionRequest, } from './transaction/updateSigners.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transaction.d.ts","sourceRoot":"","sources":["../src/transaction.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mCAAmC,GACpC,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EACL,2BAA2B,EAC3B,iBAAiB,EACjB,sBAAsB,EACtB,KAAK,sBAAsB,GAC5B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,qCAAqC,GACtC,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,+CAA+C,GAChD,MAAM,2CAA2C,CAAC;AACnD,OAAO,EACL,oCAAoC,GACrC,MAAM,gCAAgC,CAAC"}
|
|
1
|
+
{"version":3,"file":"transaction.d.ts","sourceRoot":"","sources":["../src/transaction.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mCAAmC,GACpC,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EACL,yBAAyB,EACzB,2BAA2B,EAC3B,iBAAiB,EACjB,sBAAsB,EACtB,KAAK,sBAAsB,GAC5B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,qCAAqC,GACtC,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,+CAA+C,GAChD,MAAM,2CAA2C,CAAC;AACnD,OAAO,EACL,oCAAoC,GACrC,MAAM,gCAAgC,CAAC"}
|
package/dist/transaction.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { buildConsumeNotesTransactionRequest, } from './transaction/consumeNotes.js';
|
|
2
2
|
export { executeForSummary } from './transaction/summary.js';
|
|
3
|
-
export { buildP2idTransactionRequest, parseP2idNoteType, p2idNoteTypeToMetadata, } from './transaction/p2id.js';
|
|
3
|
+
export { buildP2idNoteFromMetadata, buildP2idTransactionRequest, parseP2idNoteType, p2idNoteTypeToMetadata, } from './transaction/p2id.js';
|
|
4
4
|
export { buildUpdateGuardianTransactionRequest, } from './transaction/updateGuardian.js';
|
|
5
5
|
export { buildUpdateProcedureThresholdTransactionRequest, } from './transaction/updateProcedureThreshold.js';
|
|
6
6
|
export { buildUpdateSignersTransactionRequest, } from './transaction/updateSigners.js';
|
package/dist/transaction.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transaction.js","sourceRoot":"","sources":["../src/transaction.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mCAAmC,GACpC,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EACL,2BAA2B,EAC3B,iBAAiB,EACjB,sBAAsB,GAEvB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,qCAAqC,GACtC,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,+CAA+C,GAChD,MAAM,2CAA2C,CAAC;AACnD,OAAO,EACL,oCAAoC,GACrC,MAAM,gCAAgC,CAAC"}
|
|
1
|
+
{"version":3,"file":"transaction.js","sourceRoot":"","sources":["../src/transaction.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mCAAmC,GACpC,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EACL,yBAAyB,EACzB,2BAA2B,EAC3B,iBAAiB,EACjB,sBAAsB,GAEvB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,qCAAqC,GACtC,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,+CAA+C,GAChD,MAAM,2CAA2C,CAAC;AACnD,OAAO,EACL,oCAAoC,GACrC,MAAM,gCAAgC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openzeppelin/miden-multisig-client",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.2",
|
|
4
4
|
"description": "TypeScript SDK for Miden multisig accounts with GUARDIAN integration",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"@miden-sdk/miden-sdk": "^0.15.8",
|
|
40
40
|
"@noble/curves": "^1.9.7",
|
|
41
41
|
"@noble/hashes": "^2.0.1",
|
|
42
|
-
"@openzeppelin/guardian-client": "^0.16.
|
|
42
|
+
"@openzeppelin/guardian-client": "^0.16.2"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"typescript": "^5.7.2",
|
package/src/client.test.ts
CHANGED
|
@@ -239,12 +239,18 @@ describe('MultisigClient', () => {
|
|
|
239
239
|
ok: false,
|
|
240
240
|
status: 404,
|
|
241
241
|
statusText: 'Not Found',
|
|
242
|
-
|
|
242
|
+
headers: new Headers(),
|
|
243
|
+
text: async () =>
|
|
244
|
+
JSON.stringify({
|
|
245
|
+
code: 'GUARDIAN_ACCOUNT_NOT_FOUND',
|
|
246
|
+
message: 'Account not found',
|
|
247
|
+
meta: { retryable: false },
|
|
248
|
+
}),
|
|
243
249
|
});
|
|
244
250
|
|
|
245
251
|
await expect(
|
|
246
252
|
client.load('0xnonexistent', mockSigner)
|
|
247
|
-
).rejects.toThrow();
|
|
253
|
+
).rejects.toThrow('Account not found');
|
|
248
254
|
});
|
|
249
255
|
|
|
250
256
|
it('should allow registerOnGuardian after load without explicit initial state', async () => {
|
package/src/client.ts
CHANGED
|
@@ -13,6 +13,16 @@ import { createMultisigAccount } from './account/index.js';
|
|
|
13
13
|
import { AccountInspector } from './inspector.js';
|
|
14
14
|
import { getRawMidenClient, requireConfigValue, requireMidenRpcEndpoint } from './raw-client.js';
|
|
15
15
|
import type { MultisigConfig, Signer } from './types.js';
|
|
16
|
+
import {
|
|
17
|
+
resolveProverConfig,
|
|
18
|
+
type ProverConfig,
|
|
19
|
+
type ResolvedProverConfig,
|
|
20
|
+
} from './prover/config.js';
|
|
21
|
+
import {
|
|
22
|
+
resolveRpcConfig,
|
|
23
|
+
type RpcConfig,
|
|
24
|
+
type ResolvedRpcConfig,
|
|
25
|
+
} from './rpc/config.js';
|
|
16
26
|
|
|
17
27
|
interface AccountKeyBindingSigner {
|
|
18
28
|
bindAccountKey?(midenClient: MidenClient, accountId: string): Promise<void>;
|
|
@@ -41,6 +51,10 @@ export interface MultisigClientConfig {
|
|
|
41
51
|
* the injected `MidenClient`; there is no default.
|
|
42
52
|
*/
|
|
43
53
|
midenRpcEndpoint: string;
|
|
54
|
+
/** Multisig-owned remote prover override and proof retry policy. */
|
|
55
|
+
prover?: ProverConfig;
|
|
56
|
+
/** Retry policy for idempotent Miden node reads; submission is never retried. */
|
|
57
|
+
rpc?: RpcConfig;
|
|
44
58
|
}
|
|
45
59
|
|
|
46
60
|
/**
|
|
@@ -70,6 +84,10 @@ export interface RecoveredAccount {
|
|
|
70
84
|
* const client = new MultisigClient(midenClient, {
|
|
71
85
|
* guardianEndpoint: 'http://localhost:3000',
|
|
72
86
|
* midenRpcEndpoint: 'https://rpc.devnet.miden.io',
|
|
87
|
+
* prover: {
|
|
88
|
+
* url: 'https://prover.example',
|
|
89
|
+
* retry: { maxAttempts: 4 },
|
|
90
|
+
* },
|
|
73
91
|
* });
|
|
74
92
|
*
|
|
75
93
|
* // Get GUARDIAN pubkey for config
|
|
@@ -83,11 +101,15 @@ export interface RecoveredAccount {
|
|
|
83
101
|
export class MultisigClient {
|
|
84
102
|
private readonly midenClient: MidenClient;
|
|
85
103
|
private readonly midenRpcEndpoint: string;
|
|
104
|
+
private readonly proverConfig: ResolvedProverConfig;
|
|
105
|
+
private readonly rpcConfig: ResolvedRpcConfig;
|
|
86
106
|
private _guardianClient: GuardianHttpClient;
|
|
87
107
|
|
|
88
108
|
constructor(midenClient: MidenClient, config: MultisigClientConfig) {
|
|
89
109
|
this.midenClient = midenClient;
|
|
90
110
|
this.midenRpcEndpoint = requireMidenRpcEndpoint(config?.midenRpcEndpoint);
|
|
111
|
+
this.proverConfig = resolveProverConfig(config?.prover, midenClient.defaultProver);
|
|
112
|
+
this.rpcConfig = resolveRpcConfig(config?.rpc);
|
|
91
113
|
this._guardianClient = new GuardianHttpClient(
|
|
92
114
|
requireConfigValue('guardianEndpoint', config?.guardianEndpoint),
|
|
93
115
|
);
|
|
@@ -161,7 +183,9 @@ export class MultisigClient {
|
|
|
161
183
|
signer,
|
|
162
184
|
this.midenClient,
|
|
163
185
|
undefined,
|
|
164
|
-
this.midenRpcEndpoint
|
|
186
|
+
this.midenRpcEndpoint,
|
|
187
|
+
this.proverConfig,
|
|
188
|
+
this.rpcConfig,
|
|
165
189
|
);
|
|
166
190
|
}
|
|
167
191
|
|
|
@@ -213,7 +237,9 @@ export class MultisigClient {
|
|
|
213
237
|
signer,
|
|
214
238
|
this.midenClient,
|
|
215
239
|
accountId,
|
|
216
|
-
this.midenRpcEndpoint
|
|
240
|
+
this.midenRpcEndpoint,
|
|
241
|
+
this.proverConfig,
|
|
242
|
+
this.rpcConfig,
|
|
217
243
|
);
|
|
218
244
|
}
|
|
219
245
|
}
|
package/src/index.ts
CHANGED
|
@@ -25,6 +25,10 @@
|
|
|
25
25
|
* const client = new MultisigClient(midenClient, {
|
|
26
26
|
* guardianEndpoint: 'http://localhost:3000',
|
|
27
27
|
* midenRpcEndpoint: 'https://rpc.devnet.miden.io',
|
|
28
|
+
* prover: {
|
|
29
|
+
* url: 'https://prover.example',
|
|
30
|
+
* retry: { maxAttempts: 4 },
|
|
31
|
+
* },
|
|
28
32
|
* });
|
|
29
33
|
*
|
|
30
34
|
* // Get GUARDIAN pubkey for config
|
|
@@ -45,6 +49,8 @@ export {
|
|
|
45
49
|
type MultisigClientConfig,
|
|
46
50
|
type RecoveredAccount,
|
|
47
51
|
} from './client.js';
|
|
52
|
+
export type { ProverConfig, ProverRetryPolicy } from './prover/config.js';
|
|
53
|
+
export type { RpcConfig, RpcRetryPolicy } from './rpc/config.js';
|
|
48
54
|
export { lookupAuthDigest } from './lookupAuth.js';
|
|
49
55
|
export { Multisig, type AccountState } from './multisig.js';
|
|
50
56
|
export { AccountInspector, type DetectedMultisigConfig, type VaultBalance } from './inspector.js';
|
package/src/multisig.test.ts
CHANGED
|
@@ -8,10 +8,11 @@ import {
|
|
|
8
8
|
executeForSummary,
|
|
9
9
|
} from './transaction.js';
|
|
10
10
|
|
|
11
|
-
const { mockRpcGetAccountDetails, mockAccountDeserialize, mockDetectConfig } = vi.hoisted(() => ({
|
|
11
|
+
const { mockRpcGetAccountDetails, mockAccountDeserialize, mockDetectConfig, mockNoteFileDeserialize } = vi.hoisted(() => ({
|
|
12
12
|
mockRpcGetAccountDetails: vi.fn(),
|
|
13
13
|
mockAccountDeserialize: vi.fn(),
|
|
14
14
|
mockDetectConfig: vi.fn(),
|
|
15
|
+
mockNoteFileDeserialize: vi.fn(),
|
|
15
16
|
}));
|
|
16
17
|
|
|
17
18
|
// Mock the Miden SDK
|
|
@@ -26,6 +27,14 @@ vi.mock('@miden-sdk/miden-sdk', () => ({
|
|
|
26
27
|
Private: 0,
|
|
27
28
|
Public: 1,
|
|
28
29
|
},
|
|
30
|
+
NoteExportFormat: {
|
|
31
|
+
Id: 0,
|
|
32
|
+
Full: 1,
|
|
33
|
+
Details: 2,
|
|
34
|
+
},
|
|
35
|
+
NoteFile: {
|
|
36
|
+
deserialize: mockNoteFileDeserialize,
|
|
37
|
+
},
|
|
29
38
|
TransactionSummary: {
|
|
30
39
|
deserialize: vi.fn().mockReturnValue({
|
|
31
40
|
toCommitment: () => ({
|
|
@@ -91,6 +100,9 @@ vi.mock('./transaction.js', () => ({
|
|
|
91
100
|
request: {},
|
|
92
101
|
salt: { toHex: () => '0x' + 'd'.repeat(64) },
|
|
93
102
|
}),
|
|
103
|
+
buildP2idNoteFromMetadata: vi.fn().mockReturnValue({
|
|
104
|
+
id: () => ({ toString: () => '0x' + 'ab'.repeat(32) }),
|
|
105
|
+
}),
|
|
94
106
|
// Mirrors the real implementations against the mocked NoteType values
|
|
95
107
|
// (Private = 0, Public = 1).
|
|
96
108
|
parseP2idNoteType: vi.fn((value?: string) => {
|
|
@@ -156,7 +168,8 @@ describe('Multisig', () => {
|
|
|
156
168
|
function createTestMultisig(
|
|
157
169
|
config: ConstructorParameters<typeof Multisig>[1],
|
|
158
170
|
signer: Signer = mockSigner,
|
|
159
|
-
accountId?: string
|
|
171
|
+
accountId?: string,
|
|
172
|
+
proverConfig?: ConstructorParameters<typeof Multisig>[7],
|
|
160
173
|
): Multisig {
|
|
161
174
|
return new Multisig(
|
|
162
175
|
mockAccount,
|
|
@@ -165,7 +178,8 @@ describe('Multisig', () => {
|
|
|
165
178
|
signer,
|
|
166
179
|
mockWebClient,
|
|
167
180
|
accountId,
|
|
168
|
-
MIDEN_RPC_ENDPOINT
|
|
181
|
+
MIDEN_RPC_ENDPOINT,
|
|
182
|
+
proverConfig,
|
|
169
183
|
);
|
|
170
184
|
}
|
|
171
185
|
|
|
@@ -226,13 +240,38 @@ describe('Multisig', () => {
|
|
|
226
240
|
submitNewTransaction: vi.fn(),
|
|
227
241
|
submitNewTransactionWithProver: vi.fn(),
|
|
228
242
|
transactions: {
|
|
229
|
-
|
|
243
|
+
executeRequest: vi.fn(),
|
|
230
244
|
},
|
|
231
245
|
getConsumableNotes: vi.fn().mockResolvedValue([]),
|
|
232
246
|
syncState: vi.fn(),
|
|
233
247
|
getAccount: vi.fn().mockResolvedValue(null),
|
|
234
248
|
newAccount: vi.fn(),
|
|
235
249
|
};
|
|
250
|
+
mockWebClient.transactions.executeRequest.mockImplementation(
|
|
251
|
+
async (accountId: unknown, request: unknown) => {
|
|
252
|
+
const result = await mockWebClient.executeTransaction(accountId, request);
|
|
253
|
+
return {
|
|
254
|
+
result,
|
|
255
|
+
prove: async (options?: { prover?: unknown }) => {
|
|
256
|
+
const proof = options?.prover === undefined
|
|
257
|
+
? await mockWebClient.proveTransaction(result)
|
|
258
|
+
: await mockWebClient.proveTransaction(result, options.prover);
|
|
259
|
+
return {
|
|
260
|
+
proof,
|
|
261
|
+
result,
|
|
262
|
+
submit: async () => {
|
|
263
|
+
const blockNumber = await mockWebClient.submitProvenTransaction(proof, result);
|
|
264
|
+
return {
|
|
265
|
+
blockNumber,
|
|
266
|
+
result,
|
|
267
|
+
apply: () => mockWebClient.applyTransaction(result, blockNumber),
|
|
268
|
+
};
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
},
|
|
274
|
+
);
|
|
236
275
|
});
|
|
237
276
|
|
|
238
277
|
describe('constructor', () => {
|
|
@@ -1398,6 +1437,154 @@ describe('Multisig', () => {
|
|
|
1398
1437
|
});
|
|
1399
1438
|
});
|
|
1400
1439
|
|
|
1440
|
+
describe('exportNoteToBytes / importNoteFromBytes (issue #356)', () => {
|
|
1441
|
+
const config = {
|
|
1442
|
+
threshold: 1,
|
|
1443
|
+
signerCommitments: ['0x' + '1'.repeat(64)],
|
|
1444
|
+
guardianCommitment: '0x' + '3'.repeat(64),
|
|
1445
|
+
};
|
|
1446
|
+
|
|
1447
|
+
it('exports the full note with proof when the inclusion proof is known', async () => {
|
|
1448
|
+
const noteFile = { serialize: () => new Uint8Array([9, 9, 9]) };
|
|
1449
|
+
mockWebClient.getOutputNote = vi.fn().mockResolvedValue({
|
|
1450
|
+
inclusionProof: () => ({}),
|
|
1451
|
+
});
|
|
1452
|
+
mockWebClient.exportNoteFile = vi.fn().mockResolvedValue(noteFile);
|
|
1453
|
+
|
|
1454
|
+
const multisig = createTestMultisig(config);
|
|
1455
|
+
const bytes = await multisig.exportNoteToBytes('0x' + 'ab'.repeat(32));
|
|
1456
|
+
|
|
1457
|
+
expect(bytes).toEqual(new Uint8Array([9, 9, 9]));
|
|
1458
|
+
// NoteExportFormat.Full = 1 in the SDK mock
|
|
1459
|
+
expect(mockWebClient.exportNoteFile).toHaveBeenCalledWith('0x' + 'ab'.repeat(32), 1);
|
|
1460
|
+
});
|
|
1461
|
+
|
|
1462
|
+
it('falls back to a details-only export before the note commits on chain', async () => {
|
|
1463
|
+
const noteFile = { serialize: () => new Uint8Array([7]) };
|
|
1464
|
+
mockWebClient.getOutputNote = vi.fn().mockResolvedValue({
|
|
1465
|
+
inclusionProof: () => undefined,
|
|
1466
|
+
});
|
|
1467
|
+
mockWebClient.exportNoteFile = vi.fn().mockResolvedValue(noteFile);
|
|
1468
|
+
|
|
1469
|
+
const multisig = createTestMultisig(config);
|
|
1470
|
+
await multisig.exportNoteToBytes(' 0x' + 'ab'.repeat(32) + ' ');
|
|
1471
|
+
|
|
1472
|
+
// NoteExportFormat.Details = 2 in the SDK mock; the id is trimmed
|
|
1473
|
+
expect(mockWebClient.exportNoteFile).toHaveBeenCalledWith('0x' + 'ab'.repeat(32), 2);
|
|
1474
|
+
});
|
|
1475
|
+
|
|
1476
|
+
it('rejects exporting a note the local store does not know', async () => {
|
|
1477
|
+
mockWebClient.getOutputNote = vi.fn().mockRejectedValue(new Error('no such note'));
|
|
1478
|
+
mockWebClient.exportNoteFile = vi.fn();
|
|
1479
|
+
|
|
1480
|
+
const multisig = createTestMultisig(config);
|
|
1481
|
+
await expect(multisig.exportNoteToBytes('0x' + 'ab'.repeat(32))).rejects.toThrow(
|
|
1482
|
+
/not found in the local store/,
|
|
1483
|
+
);
|
|
1484
|
+
expect(mockWebClient.exportNoteFile).not.toHaveBeenCalled();
|
|
1485
|
+
});
|
|
1486
|
+
|
|
1487
|
+
it('rejects exporting when the store resolves no record', async () => {
|
|
1488
|
+
mockWebClient.getOutputNote = vi.fn().mockResolvedValue(undefined);
|
|
1489
|
+
mockWebClient.exportNoteFile = vi.fn();
|
|
1490
|
+
|
|
1491
|
+
const multisig = createTestMultisig(config);
|
|
1492
|
+
await expect(multisig.exportNoteToBytes('0x' + 'ab'.repeat(32))).rejects.toThrow(
|
|
1493
|
+
/not found in the local store/,
|
|
1494
|
+
);
|
|
1495
|
+
expect(mockWebClient.exportNoteFile).not.toHaveBeenCalled();
|
|
1496
|
+
});
|
|
1497
|
+
|
|
1498
|
+
it('imports note file bytes and returns the resolved identifier', async () => {
|
|
1499
|
+
const decoded = { marker: 'note-file' };
|
|
1500
|
+
mockNoteFileDeserialize.mockReturnValue(decoded);
|
|
1501
|
+
mockWebClient.importNoteFile = vi.fn().mockResolvedValue('0x' + 'cd'.repeat(32));
|
|
1502
|
+
|
|
1503
|
+
const multisig = createTestMultisig(config);
|
|
1504
|
+
const noteId = await multisig.importNoteFromBytes(new Uint8Array([1, 2, 3]));
|
|
1505
|
+
|
|
1506
|
+
expect(mockNoteFileDeserialize).toHaveBeenCalledWith(new Uint8Array([1, 2, 3]));
|
|
1507
|
+
expect(mockWebClient.importNoteFile).toHaveBeenCalledWith(decoded);
|
|
1508
|
+
expect(noteId).toBe('0x' + 'cd'.repeat(32));
|
|
1509
|
+
});
|
|
1510
|
+
|
|
1511
|
+
it('rejects bytes that do not decode as a note file', async () => {
|
|
1512
|
+
mockNoteFileDeserialize.mockImplementation(() => {
|
|
1513
|
+
throw new Error('bad bytes');
|
|
1514
|
+
});
|
|
1515
|
+
mockWebClient.importNoteFile = vi.fn();
|
|
1516
|
+
|
|
1517
|
+
const multisig = createTestMultisig(config);
|
|
1518
|
+
await expect(multisig.importNoteFromBytes(new Uint8Array([0]))).rejects.toThrow(
|
|
1519
|
+
/failed to decode note file: bad bytes/,
|
|
1520
|
+
);
|
|
1521
|
+
expect(mockWebClient.importNoteFile).not.toHaveBeenCalled();
|
|
1522
|
+
});
|
|
1523
|
+
});
|
|
1524
|
+
|
|
1525
|
+
describe('exportNoteToFile / importNoteFromFile (issue #356)', () => {
|
|
1526
|
+
const config = {
|
|
1527
|
+
threshold: 1,
|
|
1528
|
+
signerCommitments: ['0x' + '1'.repeat(64)],
|
|
1529
|
+
guardianCommitment: '0x' + '3'.repeat(64),
|
|
1530
|
+
};
|
|
1531
|
+
|
|
1532
|
+
it('rejects exportNoteToFile outside a browser environment', async () => {
|
|
1533
|
+
const multisig = createTestMultisig(config);
|
|
1534
|
+
await expect(multisig.exportNoteToFile('0x' + 'ab'.repeat(32))).rejects.toThrow(
|
|
1535
|
+
/requires a browser environment/,
|
|
1536
|
+
);
|
|
1537
|
+
});
|
|
1538
|
+
|
|
1539
|
+
it('imports from a File/Blob by delegating to importNoteFromBytes', async () => {
|
|
1540
|
+
const decoded = { marker: 'note-file' };
|
|
1541
|
+
mockNoteFileDeserialize.mockReturnValue(decoded);
|
|
1542
|
+
mockWebClient.importNoteFile = vi.fn().mockResolvedValue('0x' + 'cd'.repeat(32));
|
|
1543
|
+
|
|
1544
|
+
const multisig = createTestMultisig(config);
|
|
1545
|
+
const noteId = await multisig.importNoteFromFile(new Blob([new Uint8Array([1, 2, 3])]));
|
|
1546
|
+
|
|
1547
|
+
expect(mockNoteFileDeserialize).toHaveBeenCalledWith(new Uint8Array([1, 2, 3]));
|
|
1548
|
+
expect(noteId).toBe('0x' + 'cd'.repeat(32));
|
|
1549
|
+
});
|
|
1550
|
+
});
|
|
1551
|
+
|
|
1552
|
+
describe('getP2idNoteId (issue #356)', () => {
|
|
1553
|
+
const config = {
|
|
1554
|
+
threshold: 1,
|
|
1555
|
+
signerCommitments: ['0x' + '1'.repeat(64)],
|
|
1556
|
+
guardianCommitment: '0x' + '3'.repeat(64),
|
|
1557
|
+
};
|
|
1558
|
+
|
|
1559
|
+
it('computes the deterministic note ID from p2id proposal metadata', async () => {
|
|
1560
|
+
const multisig = createTestMultisig(config);
|
|
1561
|
+
const proposal = {
|
|
1562
|
+
metadata: {
|
|
1563
|
+
proposalType: 'p2id',
|
|
1564
|
+
recipientId: '0x' + 'b'.repeat(30),
|
|
1565
|
+
faucetId: '0x' + 'c'.repeat(30),
|
|
1566
|
+
amount: '100',
|
|
1567
|
+
saltHex: '0x' + 'd'.repeat(64),
|
|
1568
|
+
noteType: 'private',
|
|
1569
|
+
},
|
|
1570
|
+
} as any;
|
|
1571
|
+
|
|
1572
|
+
const noteId = await multisig.getP2idNoteId(proposal);
|
|
1573
|
+
expect(noteId).toBe('0x' + 'ab'.repeat(32));
|
|
1574
|
+
});
|
|
1575
|
+
|
|
1576
|
+
it('rejects non-p2id proposals', async () => {
|
|
1577
|
+
const multisig = createTestMultisig(config);
|
|
1578
|
+
const proposal = {
|
|
1579
|
+
metadata: { proposalType: 'consume_notes' },
|
|
1580
|
+
} as any;
|
|
1581
|
+
|
|
1582
|
+
await expect(multisig.getP2idNoteId(proposal)).rejects.toThrow(
|
|
1583
|
+
/requires a P2ID proposal/,
|
|
1584
|
+
);
|
|
1585
|
+
});
|
|
1586
|
+
});
|
|
1587
|
+
|
|
1401
1588
|
describe('createChangeThresholdProposal', () => {
|
|
1402
1589
|
it('passes the signer scheme to update-signers requests', async () => {
|
|
1403
1590
|
vi.mocked(executeForSummary).mockResolvedValue({
|
|
@@ -2106,6 +2293,7 @@ describe('Multisig', () => {
|
|
|
2106
2293
|
ok: false,
|
|
2107
2294
|
status: 404,
|
|
2108
2295
|
statusText: 'Not Found',
|
|
2296
|
+
headers: new Headers(),
|
|
2109
2297
|
// Feature 009: only a conforming { code, message, meta } envelope is
|
|
2110
2298
|
// folded into the error message; raw text bodies are dropped.
|
|
2111
2299
|
text: async () =>
|
|
@@ -2971,7 +3159,11 @@ describe('Multisig', () => {
|
|
|
2971
3159
|
publicKey: '0x' + '2'.repeat(66),
|
|
2972
3160
|
};
|
|
2973
3161
|
|
|
2974
|
-
const multisig = createTestMultisig(config, ecdsaSigner
|
|
3162
|
+
const multisig = createTestMultisig(config, ecdsaSigner, undefined, {
|
|
3163
|
+
kind: 'remote',
|
|
3164
|
+
maxAttempts: 2,
|
|
3165
|
+
createProver: () => ({} as never),
|
|
3166
|
+
});
|
|
2975
3167
|
const proposalId = '0x' + 'c'.repeat(64);
|
|
2976
3168
|
const cosignerPubkey = '0x' + '3'.repeat(66);
|
|
2977
3169
|
const ackPubkey = '0x' + '4'.repeat(66);
|
|
@@ -3036,10 +3228,21 @@ describe('Multisig', () => {
|
|
|
3036
3228
|
ack_scheme: 'ecdsa',
|
|
3037
3229
|
}),
|
|
3038
3230
|
});
|
|
3039
|
-
mockWebClient.
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3231
|
+
mockWebClient.proveTransaction
|
|
3232
|
+
.mockRejectedValueOnce(Object.assign(new Error('unavailable'), { code: 'Unavailable' }))
|
|
3233
|
+
.mockResolvedValueOnce({});
|
|
3234
|
+
vi.useFakeTimers();
|
|
3235
|
+
try {
|
|
3236
|
+
const execution = multisig.executeProposal(proposalId);
|
|
3237
|
+
await vi.runAllTimersAsync();
|
|
3238
|
+
await expect(execution).resolves.toBeUndefined();
|
|
3239
|
+
} finally {
|
|
3240
|
+
vi.useRealTimers();
|
|
3241
|
+
}
|
|
3242
|
+
expect(mockWebClient.executeTransaction).toHaveBeenCalledTimes(1);
|
|
3243
|
+
expect(mockWebClient.proveTransaction).toHaveBeenCalledTimes(2);
|
|
3244
|
+
expect(mockWebClient.submitProvenTransaction).toHaveBeenCalledTimes(1);
|
|
3245
|
+
expect(mockWebClient.applyTransaction).toHaveBeenCalledTimes(1);
|
|
3043
3246
|
|
|
3044
3247
|
expect(vi.mocked(signatureHexToBytes)).toHaveBeenNthCalledWith(
|
|
3045
3248
|
1,
|
|
@@ -3151,9 +3354,11 @@ describe('Multisig', () => {
|
|
|
3151
3354
|
ack_scheme: 'ecdsa',
|
|
3152
3355
|
}),
|
|
3153
3356
|
});
|
|
3154
|
-
mockWebClient.transactions.submit.mockResolvedValueOnce({});
|
|
3155
|
-
|
|
3156
3357
|
await expect(multisig.executeProposal(proposalId)).resolves.toBeUndefined();
|
|
3358
|
+
expect(mockWebClient.executeTransaction).toHaveBeenCalledTimes(1);
|
|
3359
|
+
expect(mockWebClient.proveTransaction).toHaveBeenCalledTimes(1);
|
|
3360
|
+
expect(mockWebClient.submitProvenTransaction).toHaveBeenCalledTimes(1);
|
|
3361
|
+
expect(mockWebClient.applyTransaction).toHaveBeenCalledTimes(1);
|
|
3157
3362
|
|
|
3158
3363
|
expect(vi.mocked(signatureHexToBytes)).toHaveBeenNthCalledWith(
|
|
3159
3364
|
1,
|
|
@@ -3260,10 +3465,11 @@ describe('Multisig', () => {
|
|
|
3260
3465
|
ok: true,
|
|
3261
3466
|
json: async () => ({ success: true, message: 'ok', ack_pubkey: '0x' + 'f'.repeat(64) }),
|
|
3262
3467
|
});
|
|
3263
|
-
mockWebClient.transactions.submit.mockResolvedValueOnce({});
|
|
3264
|
-
|
|
3265
3468
|
await expect(multisig.executeProposal(proposalId)).resolves.toBeUndefined();
|
|
3266
|
-
expect(mockWebClient.
|
|
3469
|
+
expect(mockWebClient.executeTransaction).toHaveBeenCalledTimes(1);
|
|
3470
|
+
expect(mockWebClient.proveTransaction).toHaveBeenCalledTimes(1);
|
|
3471
|
+
expect(mockWebClient.submitProvenTransaction).toHaveBeenCalledTimes(1);
|
|
3472
|
+
expect(mockWebClient.applyTransaction).toHaveBeenCalledTimes(1);
|
|
3267
3473
|
});
|
|
3268
3474
|
|
|
3269
3475
|
it('should still switch GUARDIAN when the pre-switch canonicalization push fails', async () => {
|
|
@@ -3311,10 +3517,11 @@ describe('Multisig', () => {
|
|
|
3311
3517
|
ok: true,
|
|
3312
3518
|
json: async () => ({ success: true, message: 'ok', ack_pubkey: '0x' + 'f'.repeat(64) }),
|
|
3313
3519
|
});
|
|
3314
|
-
mockWebClient.transactions.submit.mockResolvedValueOnce({});
|
|
3315
|
-
|
|
3316
3520
|
await expect(multisig.executeProposal(proposalId)).resolves.toBeUndefined();
|
|
3317
|
-
expect(mockWebClient.
|
|
3521
|
+
expect(mockWebClient.executeTransaction).toHaveBeenCalledTimes(1);
|
|
3522
|
+
expect(mockWebClient.proveTransaction).toHaveBeenCalledTimes(1);
|
|
3523
|
+
expect(mockWebClient.submitProvenTransaction).toHaveBeenCalledTimes(1);
|
|
3524
|
+
expect(mockWebClient.applyTransaction).toHaveBeenCalledTimes(1);
|
|
3318
3525
|
});
|
|
3319
3526
|
|
|
3320
3527
|
it('should reject switch_guardian execution when endpoint commitment mismatches', async () => {
|
|
@@ -3453,6 +3660,45 @@ describe('Multisig', () => {
|
|
|
3453
3660
|
});
|
|
3454
3661
|
});
|
|
3455
3662
|
|
|
3663
|
+
describe('submitTransaction', () => {
|
|
3664
|
+
it('uses the configured total proof-attempt budget without repeating other stages', async () => {
|
|
3665
|
+
const multisig = createTestMultisig(
|
|
3666
|
+
{
|
|
3667
|
+
threshold: 1,
|
|
3668
|
+
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
3669
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
3670
|
+
},
|
|
3671
|
+
mockSigner,
|
|
3672
|
+
undefined,
|
|
3673
|
+
{
|
|
3674
|
+
kind: 'remote',
|
|
3675
|
+
maxAttempts: 4,
|
|
3676
|
+
createProver: () => ({} as never),
|
|
3677
|
+
},
|
|
3678
|
+
);
|
|
3679
|
+
const transient = Object.assign(new Error('unavailable'), { code: 'Unavailable' });
|
|
3680
|
+
mockWebClient.proveTransaction
|
|
3681
|
+
.mockRejectedValueOnce(transient)
|
|
3682
|
+
.mockRejectedValueOnce(transient)
|
|
3683
|
+
.mockRejectedValueOnce(transient)
|
|
3684
|
+
.mockResolvedValueOnce({});
|
|
3685
|
+
|
|
3686
|
+
vi.useFakeTimers();
|
|
3687
|
+
try {
|
|
3688
|
+
const submission = multisig.submitTransaction({} as never);
|
|
3689
|
+
await vi.runAllTimersAsync();
|
|
3690
|
+
await submission;
|
|
3691
|
+
} finally {
|
|
3692
|
+
vi.useRealTimers();
|
|
3693
|
+
}
|
|
3694
|
+
|
|
3695
|
+
expect(mockWebClient.executeTransaction).toHaveBeenCalledTimes(1);
|
|
3696
|
+
expect(mockWebClient.proveTransaction).toHaveBeenCalledTimes(4);
|
|
3697
|
+
expect(mockWebClient.submitProvenTransaction).toHaveBeenCalledTimes(1);
|
|
3698
|
+
expect(mockWebClient.applyTransaction).toHaveBeenCalledTimes(1);
|
|
3699
|
+
});
|
|
3700
|
+
});
|
|
3701
|
+
|
|
3456
3702
|
describe('prepareCustomExecution', () => {
|
|
3457
3703
|
const requestBytes = new Uint8Array([9, 8, 7]);
|
|
3458
3704
|
|