@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.
- package/README.md +172 -47
- package/dist/account/builder.d.ts +2 -2
- package/dist/account/builder.d.ts.map +1 -1
- package/dist/account/builder.js +49 -24
- package/dist/account/builder.js.map +1 -1
- package/dist/connectivity.d.ts +2 -0
- package/dist/connectivity.d.ts.map +1 -1
- package/dist/connectivity.js +7 -4
- package/dist/connectivity.js.map +1 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/multisig/authArgErrors.d.ts +59 -0
- package/dist/multisig/authArgErrors.d.ts.map +1 -0
- package/dist/multisig/authArgErrors.js +115 -0
- package/dist/multisig/authArgErrors.js.map +1 -0
- package/dist/multisig/helpers.d.ts +5 -0
- package/dist/multisig/helpers.d.ts.map +1 -1
- package/dist/multisig/helpers.js +7 -2
- package/dist/multisig/helpers.js.map +1 -1
- package/dist/multisig.d.ts +140 -0
- package/dist/multisig.d.ts.map +1 -1
- package/dist/multisig.js +419 -12
- package/dist/multisig.js.map +1 -1
- package/dist/procedures.d.ts +1 -1
- package/dist/procedures.js +1 -1
- package/dist/recovery/proposalNoteImport.d.ts +173 -0
- package/dist/recovery/proposalNoteImport.d.ts.map +1 -0
- package/dist/recovery/proposalNoteImport.js +411 -0
- package/dist/recovery/proposalNoteImport.js.map +1 -0
- package/dist/recovery/publicNoteBackfill.d.ts +168 -0
- package/dist/recovery/publicNoteBackfill.d.ts.map +1 -0
- package/dist/recovery/publicNoteBackfill.js +368 -0
- package/dist/recovery/publicNoteBackfill.js.map +1 -0
- package/dist/recovery/recoverNotes.d.ts +147 -0
- package/dist/recovery/recoverNotes.d.ts.map +1 -0
- package/dist/recovery/recoverNotes.js +163 -0
- package/dist/recovery/recoverNotes.js.map +1 -0
- package/dist/recovery/transportDrain.d.ts +124 -0
- package/dist/recovery/transportDrain.d.ts.map +1 -0
- package/dist/recovery/transportDrain.js +262 -0
- package/dist/recovery/transportDrain.js.map +1 -0
- package/dist/transaction/consumeNotes.d.ts.map +1 -1
- package/dist/transaction/consumeNotes.js +4 -1
- package/dist/transaction/consumeNotes.js.map +1 -1
- package/dist/transaction/p2id.d.ts.map +1 -1
- package/dist/transaction/p2id.js +4 -1
- package/dist/transaction/p2id.js.map +1 -1
- package/dist/transaction/summary.d.ts +10 -4
- package/dist/transaction/summary.d.ts.map +1 -1
- package/dist/transaction/summary.js +13 -7
- package/dist/transaction/summary.js.map +1 -1
- package/dist/transaction/updateGuardian.js +4 -1
- package/dist/transaction/updateGuardian.js.map +1 -1
- package/dist/transaction/updateProcedureThreshold.js +4 -1
- package/dist/transaction/updateProcedureThreshold.js.map +1 -1
- package/dist/transaction/updateSigners.js +4 -1
- package/dist/transaction/updateSigners.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 +14 -10
- package/src/account/builder.ts +87 -35
- package/src/connectivity.ts +9 -4
- package/src/index.ts +34 -1
- package/src/multisig/authArgErrors.ts +138 -0
- package/src/multisig/helpers.ts +7 -2
- package/src/multisig.ts +513 -20
- package/src/procedures.ts +1 -1
- package/src/recovery/proposalNoteImport.ts +548 -0
- package/src/recovery/publicNoteBackfill.ts +502 -0
- package/src/recovery/recoverNotes.ts +288 -0
- package/src/recovery/transportDrain.ts +330 -0
- package/src/transaction/consumeNotes.ts +4 -1
- package/src/transaction/p2id.ts +4 -1
- package/src/transaction/summary.ts +13 -7
- package/src/transaction/updateGuardian.ts +4 -1
- package/src/transaction/updateProcedureThreshold.ts +4 -1
- package/src/transaction/updateSigners.ts +4 -1
- package/src/transaction.ts +1 -1
- package/dist/account/builder.test.d.ts +0 -2
- package/dist/account/builder.test.d.ts.map +0 -1
- package/dist/account/builder.test.js +0 -166
- package/dist/account/builder.test.js.map +0 -1
- package/dist/account/masm/account-components/auth.d.ts +0 -2
- package/dist/account/masm/account-components/auth.d.ts.map +0 -1
- package/dist/account/masm/account-components/auth.js +0 -46
- package/dist/account/masm/account-components/auth.js.map +0 -1
- package/dist/account/masm/index.d.ts +0 -2
- package/dist/account/masm/index.d.ts.map +0 -1
- package/dist/account/masm/index.js +0 -4
- package/dist/account/masm/index.js.map +0 -1
- package/dist/account/masm.d.ts +0 -2
- package/dist/account/masm.d.ts.map +0 -1
- package/dist/account/masm.js +0 -4
- package/dist/account/masm.js.map +0 -1
- package/dist/account/storage.test.d.ts +0 -2
- package/dist/account/storage.test.d.ts.map +0 -1
- package/dist/account/storage.test.js +0 -73
- package/dist/account/storage.test.js.map +0 -1
- package/dist/client.test.d.ts +0 -2
- package/dist/client.test.d.ts.map +0 -1
- package/dist/client.test.js +0 -380
- package/dist/client.test.js.map +0 -1
- package/dist/connectivity.test.d.ts +0 -2
- package/dist/connectivity.test.d.ts.map +0 -1
- package/dist/connectivity.test.js +0 -61
- package/dist/connectivity.test.js.map +0 -1
- package/dist/inspector.test.d.ts +0 -2
- package/dist/inspector.test.d.ts.map +0 -1
- package/dist/inspector.test.js +0 -425
- package/dist/inspector.test.js.map +0 -1
- package/dist/lookupAuth.test.d.ts +0 -2
- package/dist/lookupAuth.test.d.ts.map +0 -1
- package/dist/lookupAuth.test.js +0 -138
- package/dist/lookupAuth.test.js.map +0 -1
- package/dist/multisig/consumeNotesErrors.test.d.ts +0 -2
- package/dist/multisig/consumeNotesErrors.test.d.ts.map +0 -1
- package/dist/multisig/consumeNotesErrors.test.js +0 -28
- package/dist/multisig/consumeNotesErrors.test.js.map +0 -1
- package/dist/multisig/helpers.test.d.ts +0 -2
- package/dist/multisig/helpers.test.d.ts.map +0 -1
- package/dist/multisig/helpers.test.js +0 -94
- package/dist/multisig/helpers.test.js.map +0 -1
- package/dist/multisig.test.d.ts +0 -2
- package/dist/multisig.test.d.ts.map +0 -1
- package/dist/multisig.test.js +0 -4112
- package/dist/multisig.test.js.map +0 -1
- package/dist/proposal/factory.test.d.ts +0 -2
- package/dist/proposal/factory.test.d.ts.map +0 -1
- package/dist/proposal/factory.test.js +0 -32
- package/dist/proposal/factory.test.js.map +0 -1
- package/dist/proposal/metadata.test.d.ts +0 -2
- package/dist/proposal/metadata.test.d.ts.map +0 -1
- package/dist/proposal/metadata.test.js +0 -193
- package/dist/proposal/metadata.test.js.map +0 -1
- package/dist/prover/config.test.d.ts +0 -2
- package/dist/prover/config.test.d.ts.map +0 -1
- package/dist/prover/config.test.js +0 -54
- package/dist/prover/config.test.js.map +0 -1
- package/dist/prover/errors.test.d.ts +0 -2
- package/dist/prover/errors.test.d.ts.map +0 -1
- package/dist/prover/errors.test.js +0 -29
- package/dist/prover/errors.test.js.map +0 -1
- package/dist/prover/retry.test.d.ts +0 -2
- package/dist/prover/retry.test.d.ts.map +0 -1
- package/dist/prover/retry.test.js +0 -20
- package/dist/prover/retry.test.js.map +0 -1
- package/dist/prover/workflow.test.d.ts +0 -2
- package/dist/prover/workflow.test.d.ts.map +0 -1
- package/dist/prover/workflow.test.js +0 -105
- package/dist/prover/workflow.test.js.map +0 -1
- package/dist/raw-client.test.d.ts +0 -2
- package/dist/raw-client.test.d.ts.map +0 -1
- package/dist/raw-client.test.js +0 -111
- package/dist/raw-client.test.js.map +0 -1
- package/dist/rpc/config.test.d.ts +0 -2
- package/dist/rpc/config.test.d.ts.map +0 -1
- package/dist/rpc/config.test.js +0 -24
- package/dist/rpc/config.test.js.map +0 -1
- package/dist/rpc/errors.test.d.ts +0 -2
- package/dist/rpc/errors.test.d.ts.map +0 -1
- package/dist/rpc/errors.test.js +0 -34
- package/dist/rpc/errors.test.js.map +0 -1
- package/dist/rpc/retry.test.d.ts +0 -2
- package/dist/rpc/retry.test.d.ts.map +0 -1
- package/dist/rpc/retry.test.js +0 -98
- package/dist/rpc/retry.test.js.map +0 -1
- package/dist/signers/ecdsa.test.d.ts +0 -2
- package/dist/signers/ecdsa.test.d.ts.map +0 -1
- package/dist/signers/ecdsa.test.js +0 -88
- package/dist/signers/ecdsa.test.js.map +0 -1
- package/dist/signers/falcon.test.d.ts +0 -2
- package/dist/signers/falcon.test.d.ts.map +0 -1
- package/dist/signers/falcon.test.js +0 -161
- package/dist/signers/falcon.test.js.map +0 -1
- package/dist/signers/miden-wallet.ecdsa-recovery.test.d.ts +0 -2
- package/dist/signers/miden-wallet.ecdsa-recovery.test.d.ts.map +0 -1
- package/dist/signers/miden-wallet.ecdsa-recovery.test.js +0 -148
- package/dist/signers/miden-wallet.ecdsa-recovery.test.js.map +0 -1
- package/dist/signers/miden-wallet.test.d.ts +0 -2
- package/dist/signers/miden-wallet.test.d.ts.map +0 -1
- package/dist/signers/miden-wallet.test.js +0 -164
- package/dist/signers/miden-wallet.test.js.map +0 -1
- package/dist/signers/para.test.d.ts +0 -2
- package/dist/signers/para.test.d.ts.map +0 -1
- package/dist/signers/para.test.js +0 -146
- package/dist/signers/para.test.js.map +0 -1
- package/dist/transaction/index.d.ts +0 -8
- package/dist/transaction/index.d.ts.map +0 -1
- package/dist/transaction/index.js +0 -7
- package/dist/transaction/index.js.map +0 -1
- package/dist/transaction/p2id.test.d.ts +0 -2
- package/dist/transaction/p2id.test.d.ts.map +0 -1
- package/dist/transaction/p2id.test.js +0 -246
- package/dist/transaction/p2id.test.js.map +0 -1
- package/dist/transaction/rpoRandomCoin.test.d.ts +0 -2
- package/dist/transaction/rpoRandomCoin.test.d.ts.map +0 -1
- package/dist/transaction/rpoRandomCoin.test.js +0 -52
- package/dist/transaction/rpoRandomCoin.test.js.map +0 -1
- package/dist/transaction/summary.test.d.ts +0 -2
- package/dist/transaction/summary.test.d.ts.map +0 -1
- package/dist/transaction/summary.test.js +0 -26
- package/dist/transaction/summary.test.js.map +0 -1
- package/dist/transaction.test.d.ts +0 -2
- package/dist/transaction.test.d.ts.map +0 -1
- package/dist/transaction.test.js +0 -122
- package/dist/transaction.test.js.map +0 -1
- package/dist/types/proposal.test.d.ts +0 -2
- package/dist/types/proposal.test.d.ts.map +0 -1
- package/dist/types/proposal.test.js +0 -56
- package/dist/types/proposal.test.js.map +0 -1
- package/dist/utils/digest.test.d.ts +0 -2
- package/dist/utils/digest.test.d.ts.map +0 -1
- package/dist/utils/digest.test.js +0 -48
- package/dist/utils/digest.test.js.map +0 -1
- package/dist/utils/ecdsa.test.d.ts +0 -2
- package/dist/utils/ecdsa.test.d.ts.map +0 -1
- package/dist/utils/ecdsa.test.js +0 -131
- package/dist/utils/ecdsa.test.js.map +0 -1
- package/dist/utils/encoding.test.d.ts +0 -2
- package/dist/utils/encoding.test.d.ts.map +0 -1
- package/dist/utils/encoding.test.js +0 -185
- package/dist/utils/encoding.test.js.map +0 -1
- package/dist/utils/key.test.d.ts +0 -2
- package/dist/utils/key.test.d.ts.map +0 -1
- package/dist/utils/key.test.js +0 -82
- package/dist/utils/key.test.js.map +0 -1
- package/dist/utils/signature.test.d.ts +0 -2
- package/dist/utils/signature.test.d.ts.map +0 -1
- package/dist/utils/signature.test.js +0 -164
- package/dist/utils/signature.test.js.map +0 -1
- package/dist/utils/word.test.d.ts +0 -2
- package/dist/utils/word.test.d.ts.map +0 -1
- package/dist/utils/word.test.js +0 -54
- package/dist/utils/word.test.js.map +0 -1
- package/masm/account_components/auth/guarded_multisig.masm +0 -42
- package/src/account/builder.test.ts +0 -238
- package/src/account/masm/account-components/auth.ts +0 -46
- package/src/account/masm/index.ts +0 -4
- package/src/account/masm.ts +0 -4
- package/src/account/storage.test.ts +0 -90
- package/src/client.test.ts +0 -473
- package/src/connectivity.test.ts +0 -67
- package/src/inspector.test.ts +0 -542
- package/src/lookupAuth.test.ts +0 -185
- package/src/multisig/consumeNotesErrors.test.ts +0 -43
- package/src/multisig/helpers.test.ts +0 -114
- package/src/multisig.test.ts +0 -5008
- package/src/proposal/factory.test.ts +0 -40
- package/src/proposal/metadata.test.ts +0 -235
- package/src/prover/config.test.ts +0 -90
- package/src/prover/errors.test.ts +0 -61
- package/src/prover/retry.test.ts +0 -38
- package/src/prover/workflow.test.ts +0 -145
- package/src/raw-client.test.ts +0 -171
- package/src/rpc/config.test.ts +0 -45
- package/src/rpc/errors.test.ts +0 -69
- package/src/rpc/retry.test.ts +0 -144
- package/src/signers/ecdsa.test.ts +0 -111
- package/src/signers/falcon.test.ts +0 -197
- package/src/signers/miden-wallet.ecdsa-recovery.test.ts +0 -201
- package/src/signers/miden-wallet.test.ts +0 -206
- package/src/signers/para.test.ts +0 -186
- package/src/transaction/index.ts +0 -13
- package/src/transaction/p2id.test.ts +0 -371
- package/src/transaction/rpoRandomCoin.test.ts +0 -64
- package/src/transaction/summary.test.ts +0 -32
- package/src/transaction.test.ts +0 -142
- package/src/types/proposal.test.ts +0 -68
- package/src/utils/digest.test.ts +0 -55
- package/src/utils/ecdsa.test.ts +0 -164
- package/src/utils/encoding.test.ts +0 -233
- package/src/utils/key.test.ts +0 -91
- package/src/utils/signature.test.ts +0 -210
- 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
|
-
`;
|
package/src/account/masm.ts
DELETED
|
@@ -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
|
-
});
|
package/src/client.test.ts
DELETED
|
@@ -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
|
-
});
|
package/src/connectivity.test.ts
DELETED
|
@@ -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
|
-
});
|