@cofhe/sdk 0.7.1 → 1.0.0-alpha.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/CHANGELOG.md +35 -0
- package/acps/acp.ts +35 -7
- package/acps/index.ts +28 -12
- package/acps/onchain-utils.ts +188 -49
- package/acps/registry.ts +231 -0
- package/acps/sealing.ts +9 -3
- package/acps/share-metadata/appKey.ts +8 -0
- package/acps/share-metadata/decode.ts +120 -0
- package/acps/share-metadata/describe.ts +198 -0
- package/acps/share-metadata/encode.ts +147 -0
- package/acps/share-metadata/erc7984.ts +26 -0
- package/acps/share-metadata/index.ts +9 -0
- package/acps/share-metadata/layout.ts +69 -0
- package/acps/share-metadata/presets.ts +116 -0
- package/acps/share-metadata/schema.ts +47 -0
- package/acps/share-metadata/test/codec.test.ts +140 -0
- package/acps/share-metadata/test/describe.test.ts +112 -0
- package/acps/share-metadata/test/fixtures.ts +157 -0
- package/acps/share-metadata/test/live.test.ts +56 -0
- package/acps/share-metadata/test/presets.test.ts +119 -0
- package/acps/share-metadata/test/verify.test.ts +144 -0
- package/acps/share-metadata/verify.ts +155 -0
- package/acps/store.ts +48 -18
- package/acps/test/acp.test.ts +102 -7
- package/acps/test/localstorage.test.ts +28 -1
- package/acps/test/onchainAccessStatus.test.ts +116 -0
- package/acps/test/registry.test.ts +144 -0
- package/acps/test/sealing.test.ts +23 -0
- package/acps/test/store.test.ts +71 -0
- package/acps/test/utils.test.ts +63 -0
- package/acps/test/validation.test.ts +0 -4
- package/acps/types.ts +59 -6
- package/acps/utils.ts +15 -2
- package/acps/validation.ts +3 -1
- package/chains/chains/arbitrum.ts +14 -0
- package/chains/chains/ethereum.ts +14 -0
- package/chains/index.ts +5 -1
- package/chains/test/chains.test.ts +24 -3
- package/core/acps.ts +164 -207
- package/core/client.ts +44 -13
- package/core/clientTypes.ts +50 -7
- package/core/config.ts +33 -1
- package/core/consts.ts +14 -2
- package/core/decrypt/apiError.ts +4 -1
- package/core/decrypt/verifyDecryptResult.ts +8 -4
- package/core/encrypt/cofheMocksZkVerifySign.ts +19 -2
- package/core/encrypt/encryptInputsBuilder.ts +6 -0
- package/core/encrypt/zkPackProveVerify.ts +7 -1
- package/core/error.ts +1 -0
- package/core/fetchKeys.ts +2 -2
- package/core/index.ts +9 -1
- package/core/keyStore.ts +23 -10
- package/core/taskManager.ts +48 -0
- package/core/test/acpDefaults.test.ts +108 -19
- package/core/test/acpStoreAccountCase.test.ts +80 -0
- package/core/test/acps.test.ts +128 -3
- package/core/test/apiError.test.ts +8 -0
- package/core/test/appKeyConfig.test.ts +94 -0
- package/core/test/client.test.ts +41 -0
- package/core/test/config.test.ts +24 -0
- package/core/test/decrypt.test.ts +11 -4
- package/core/test/decryptBuilders.test.ts +110 -2
- package/core/test/encryptInputsBuilder.test.ts +1 -1
- package/core/test/fetchKeys.test.ts +23 -2
- package/core/test/incomingShares.test.ts +84 -0
- package/core/test/keyStore.test.ts +18 -6
- package/core/test/mocksEncryptRange.test.ts +90 -0
- package/core/test/taskManager.test.ts +57 -0
- package/core/test/utils.test.ts +128 -0
- package/core/test/verifyDecryptResult.test.ts +51 -0
- package/core/types.ts +8 -0
- package/core/utils.ts +16 -9
- package/dist/acps.cjs +984 -64
- package/dist/acps.d.cts +589 -10
- package/dist/acps.d.ts +589 -10
- package/dist/acps.js +2 -2
- package/dist/chains.cjs +26 -0
- package/dist/chains.d.cts +45 -1
- package/dist/chains.d.ts +45 -1
- package/dist/chains.js +1 -1
- package/dist/chunk-6ZW76ZKB.js +228 -0
- package/dist/chunk-BQWHPIYU.js +1828 -0
- package/dist/chunk-KSOV5B4B.js +50 -0
- package/dist/{chunk-43USWPEH.js → chunk-OL3DZN7A.js} +159 -523
- package/dist/{chunk-N6IDQRRU.js → chunk-UKQAJK4N.js} +25 -1
- package/dist/chunk-XQ32JG3T.js +174 -0
- package/dist/{clientTypes-CYZjFznO.d.cts → clientTypes-C0A9ji5O.d.cts} +132 -29
- package/dist/{clientTypes-BN3nbzYM.d.ts → clientTypes-CcU5WvdV.d.ts} +132 -29
- package/dist/core.cjs +3269 -2626
- package/dist/core.d.cts +32 -8
- package/dist/core.d.ts +32 -8
- package/dist/core.js +5 -4
- package/dist/node.cjs +3074 -2436
- package/dist/node.d.cts +2 -2
- package/dist/node.d.ts +2 -2
- package/dist/node.js +20 -19
- package/dist/{acp-Wi6isVQI.d.cts → onchain-utils-D9Z33IP7.d.cts} +166 -12
- package/dist/{acp-Wi6isVQI.d.ts → onchain-utils-D9Z33IP7.d.ts} +166 -12
- package/dist/web.cjs +3204 -2493
- package/dist/web.d.cts +49 -3
- package/dist/web.d.ts +49 -3
- package/dist/web.js +52 -27
- package/dist/zkProve.worker.cjs +79 -18
- package/dist/zkProve.worker.js +24 -24
- package/node/index.ts +1 -10
- package/node/storage.ts +20 -9
- package/node/test/inherited.test.ts +5 -1
- package/node/test/storage-memory-fallback.test.ts +47 -0
- package/node/test/storage-permissions.test.ts +40 -0
- package/package.json +3 -1
- package/web/index.ts +101 -36
- package/web/test/countingZkProveWorker.ts +31 -0
- package/web/test/inherited.web.test.ts +5 -1
- package/web/test/mainThreadPool.web.test.ts +174 -0
- package/web/test/threadPoolBenchHelper.ts +134 -0
- package/web/test/threadPoolOff.web.test.ts +17 -0
- package/web/test/threadPoolOn.web.test.ts +80 -0
- package/web/test/workerInitRace.web.test.ts +79 -0
- package/web/tfheThreadPool.ts +140 -0
- package/web/workerManager.ts +4 -2
- package/web/zkProve.worker.ts +42 -26
- package/dist/chunk-ESMZCFJY.js +0 -12
- package/dist/chunk-Q7CBWGQX.js +0 -1029
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
1
|
+
import { beforeEach, describe, it, expect, vi } from 'vitest';
|
|
2
2
|
import { acps } from '../acps.js';
|
|
3
|
+
import { clearTaskManagerAddressCache } from '../taskManager.js';
|
|
3
4
|
|
|
4
5
|
type ScopeOpts = {
|
|
5
6
|
revokerData?: number;
|
|
@@ -12,7 +13,10 @@ const opts = (o: ScopeOpts): ScopeOpts => o;
|
|
|
12
13
|
|
|
13
14
|
const CHAIN = 31337;
|
|
14
15
|
const REVOKER = '0x00000000000000000000000000000000000000aa' as `0x${string}`;
|
|
16
|
+
const ACL_REVOKER = '0x00000000000000000000000000000000000000cc' as `0x${string}`;
|
|
15
17
|
const CONTRACT_A = '0x00000000000000000000000000000000000000bb' as `0x${string}`;
|
|
18
|
+
const ACL = '0x00000000000000000000000000000000000000dd' as `0x${string}`;
|
|
19
|
+
const TM = '0x0000000000000000000000000000000000005000' as `0x${string}`;
|
|
16
20
|
|
|
17
21
|
const acpConfig = {
|
|
18
22
|
defaultRevoker: { [CHAIN]: REVOKER },
|
|
@@ -20,33 +24,118 @@ const acpConfig = {
|
|
|
20
24
|
};
|
|
21
25
|
|
|
22
26
|
describe('applyACPDefaults', () => {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
acps.clearAclServedAddresses();
|
|
29
|
+
clearTaskManagerAddressCache();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('uses the exact chain block timestamp for an ACL-served revoker', async () => {
|
|
33
|
+
const latestBlockTimestamp = 1_700_000_000;
|
|
34
|
+
const publicClient = {
|
|
35
|
+
readContract: vi.fn(({ functionName }: { functionName: string }) => {
|
|
36
|
+
if (functionName === 'getTm') return Promise.resolve(TM);
|
|
37
|
+
if (functionName === 'acl') return Promise.resolve(ACL);
|
|
38
|
+
if (functionName === 'defaultRevokerContract') return Promise.resolve(ACL_REVOKER);
|
|
39
|
+
return Promise.reject(new Error(`unexpected ACL read: ${functionName}`));
|
|
40
|
+
}),
|
|
41
|
+
getBlock: vi.fn().mockResolvedValue({ timestamp: BigInt(latestBlockTimestamp) }),
|
|
42
|
+
} as any;
|
|
43
|
+
|
|
44
|
+
const result = await acps.applyACPDefaultsFromChain(opts({}), undefined, publicClient, CHAIN);
|
|
45
|
+
|
|
46
|
+
expect(publicClient.getBlock).toHaveBeenCalledWith();
|
|
47
|
+
expect(result.revokerContract).toBe(ACL_REVOKER);
|
|
48
|
+
expect(result.revokerData).toBe(latestBlockTimestamp);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('prefers configured revoker over ACL but still uses the latest block timestamp', async () => {
|
|
52
|
+
const latestBlockTimestamp = 1_700_000_001;
|
|
53
|
+
const publicClient = {
|
|
54
|
+
readContract: vi.fn(({ functionName }: { functionName: string }) => {
|
|
55
|
+
if (functionName === 'getTm') return Promise.resolve(TM);
|
|
56
|
+
if (functionName === 'acl') return Promise.resolve(ACL);
|
|
57
|
+
if (functionName === 'defaultRevokerContract') return Promise.resolve(ACL_REVOKER);
|
|
58
|
+
return Promise.reject(new Error(`unexpected ACL read: ${functionName}`));
|
|
59
|
+
}),
|
|
60
|
+
getBlock: vi.fn().mockResolvedValue({ timestamp: BigInt(latestBlockTimestamp) }),
|
|
61
|
+
} as any;
|
|
62
|
+
|
|
63
|
+
const result = await acps.applyACPDefaultsFromChain(opts({}), acpConfig, publicClient, CHAIN);
|
|
64
|
+
|
|
65
|
+
expect(publicClient.readContract).not.toHaveBeenCalled();
|
|
66
|
+
expect(publicClient.getBlock).toHaveBeenCalledWith();
|
|
26
67
|
expect(result.revokerContract).toBe(REVOKER);
|
|
27
|
-
expect(result.revokerData).
|
|
28
|
-
|
|
68
|
+
expect(result.revokerData).toBe(latestBlockTimestamp);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('avoids getBlock for explicit revoker options', async () => {
|
|
72
|
+
const publicClient = {
|
|
73
|
+
readContract: vi.fn(),
|
|
74
|
+
getBlock: vi.fn(),
|
|
75
|
+
} as any;
|
|
76
|
+
|
|
77
|
+
const result = await acps.applyACPDefaultsFromChain(
|
|
78
|
+
opts({ revokerData: 42, revokerContract: CONTRACT_A }),
|
|
79
|
+
acpConfig,
|
|
80
|
+
publicClient,
|
|
81
|
+
CHAIN
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
expect(publicClient.readContract).not.toHaveBeenCalled();
|
|
85
|
+
expect(publicClient.getBlock).not.toHaveBeenCalled();
|
|
86
|
+
expect(result).toMatchObject({ revokerData: 42, revokerContract: CONTRACT_A });
|
|
29
87
|
});
|
|
30
88
|
|
|
31
|
-
it('
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
89
|
+
it('avoids getBlock when neither config nor ACL provide a revoker', async () => {
|
|
90
|
+
const publicClient = {
|
|
91
|
+
readContract: vi.fn().mockRejectedValue(new Error('no ACL deployment')),
|
|
92
|
+
getBlock: vi.fn(),
|
|
93
|
+
} as any;
|
|
94
|
+
|
|
95
|
+
const result = await acps.applyACPDefaultsFromChain(opts({}), undefined, publicClient, CHAIN);
|
|
96
|
+
|
|
97
|
+
expect(publicClient.getBlock).not.toHaveBeenCalled();
|
|
98
|
+
expect(result).toEqual({});
|
|
35
99
|
});
|
|
36
100
|
|
|
37
|
-
it('
|
|
38
|
-
const
|
|
101
|
+
it('rejects a block timestamp outside JavaScript safe integer range', async () => {
|
|
102
|
+
const publicClient = {
|
|
103
|
+
readContract: vi.fn(({ functionName }: { functionName: string }) => {
|
|
104
|
+
if (functionName === 'getTm') return Promise.resolve(TM);
|
|
105
|
+
if (functionName === 'acl') return Promise.resolve(ACL);
|
|
106
|
+
if (functionName === 'defaultRevokerContract') return Promise.resolve(ACL_REVOKER);
|
|
107
|
+
return Promise.reject(new Error(`unexpected ACL read: ${functionName}`));
|
|
108
|
+
}),
|
|
109
|
+
getBlock: vi.fn().mockResolvedValue({ timestamp: BigInt(Number.MAX_SAFE_INTEGER) + 1n }),
|
|
110
|
+
} as any;
|
|
111
|
+
|
|
112
|
+
await expect(acps.applyACPDefaultsFromChain(opts({}), undefined, publicClient, CHAIN)).rejects.toThrow(
|
|
113
|
+
/ACP revokerData cannot represent the chain block timestamp .* as a safe integer/
|
|
114
|
+
);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('injects default contract scopes when no scope options given', async () => {
|
|
118
|
+
const result = await acps.applyACPDefaultsFromChain(opts({}), acpConfig, noAclClient(), CHAIN);
|
|
39
119
|
expect(result.contracts).toEqual([CONTRACT_A]);
|
|
40
120
|
});
|
|
41
121
|
|
|
42
|
-
it('does not inject scopes when the caller provides any scope option (incl. explicit scope)', () => {
|
|
43
|
-
|
|
44
|
-
expect(
|
|
45
|
-
expect(
|
|
122
|
+
it('does not inject scopes when the caller provides any scope option (incl. explicit scope)', async () => {
|
|
123
|
+
const apply = (o: ScopeOpts) => acps.applyACPDefaultsFromChain(o, acpConfig, noAclClient(), CHAIN);
|
|
124
|
+
expect((await apply(opts({ scope: 0 }))).contracts).toBeUndefined();
|
|
125
|
+
expect((await apply(opts({ handles: [1n] }))).contracts).toBeUndefined();
|
|
126
|
+
expect((await apply(opts({ contracts: [] }))).contracts).toEqual([]);
|
|
46
127
|
});
|
|
47
128
|
|
|
48
|
-
it('no-op for chains without defaults and for missing config', () => {
|
|
49
|
-
expect(acps.
|
|
50
|
-
expect(acps.
|
|
129
|
+
it('no-op for chains without defaults and for missing config', async () => {
|
|
130
|
+
expect(await acps.applyACPDefaultsFromChain(opts({}), acpConfig, noAclClient(), 999)).toEqual({});
|
|
131
|
+
expect(await acps.applyACPDefaultsFromChain(opts({}), undefined, noAclClient(), CHAIN)).toEqual({});
|
|
51
132
|
});
|
|
52
133
|
});
|
|
134
|
+
|
|
135
|
+
/** A chain with no ACL deployment: the ACL lookup serves no revoker. */
|
|
136
|
+
function noAclClient() {
|
|
137
|
+
return {
|
|
138
|
+
readContract: vi.fn().mockRejectedValue(new Error('no ACL deployment')),
|
|
139
|
+
getBlock: vi.fn().mockResolvedValue({ timestamp: 1_700_000_000n }),
|
|
140
|
+
} as any;
|
|
141
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vitest-environment happy-dom
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
5
|
+
|
|
6
|
+
// Stub the on-chain EIP-712 domain read so the test needs no RPC (same approach as acps.test.ts).
|
|
7
|
+
vi.mock('../../acps/onchain-utils.js', async (importOriginal) => ({
|
|
8
|
+
...(await importOriginal<typeof import('../../acps/onchain-utils.js')>()),
|
|
9
|
+
getAclEIP712Domain: async () => ({
|
|
10
|
+
name: 'ACL',
|
|
11
|
+
version: '2',
|
|
12
|
+
chainId: 421614,
|
|
13
|
+
verifyingContract: '0x1111111111111111111111111111111111111111' as `0x${string}`,
|
|
14
|
+
}),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
import { custom, createWalletClient, type PublicClient, type WalletClient } from 'viem';
|
|
18
|
+
import { arbitrumSepolia } from 'viem/chains';
|
|
19
|
+
import { privateKeyToAccount } from 'viem/accounts';
|
|
20
|
+
import { acpStore } from '@/acps';
|
|
21
|
+
import { acps } from '../acps.js';
|
|
22
|
+
|
|
23
|
+
const chainId = 421614;
|
|
24
|
+
// Offline clients: the wallet signs typed data locally, and any RPC call fails the test.
|
|
25
|
+
const noRpc = custom({
|
|
26
|
+
request: async ({ method }) => {
|
|
27
|
+
throw new Error(`unexpected RPC call: ${method}`);
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
const publicClient = { getChainId: async () => chainId } as unknown as PublicClient;
|
|
31
|
+
const walletClient: WalletClient = createWalletClient({
|
|
32
|
+
chain: arbitrumSepolia,
|
|
33
|
+
transport: noRpc,
|
|
34
|
+
account: privateKeyToAccount('0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'),
|
|
35
|
+
});
|
|
36
|
+
const checksummed = walletClient.account!.address; // 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
|
|
37
|
+
const lowercase = checksummed.toLowerCase();
|
|
38
|
+
|
|
39
|
+
describe('ACP store account keys are case-insensitive', () => {
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
acpStore.store.setState({ acps: {}, activeACPHash: {} });
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('finds the active ACP when the account is passed in lowercase', async () => {
|
|
45
|
+
const created = await acps.createSelf({ issuer: checksummed }, publicClient, walletClient);
|
|
46
|
+
|
|
47
|
+
expect(acps.getActiveACP(chainId, checksummed)?.hash).toBe(created.hash);
|
|
48
|
+
expect(acps.getActiveACP(chainId, lowercase)?.hash).toBe(created.hash);
|
|
49
|
+
expect(acps.getACP(chainId, lowercase, created.hash)?.hash).toBe(created.hash);
|
|
50
|
+
expect(Object.keys(acps.getACPs(chainId, lowercase))).toEqual([created.hash]);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('getOrCreateSelfACP with a lowercase account reuses the stored ACP instead of signing a new one', async () => {
|
|
54
|
+
const signSpy = vi.spyOn(walletClient, 'signTypedData');
|
|
55
|
+
|
|
56
|
+
const first = await acps.getOrCreateSelfACP(publicClient, walletClient, chainId, lowercase);
|
|
57
|
+
const second = await acps.getOrCreateSelfACP(publicClient, walletClient, chainId, lowercase);
|
|
58
|
+
|
|
59
|
+
expect(second.hash).toBe(first.hash);
|
|
60
|
+
expect(signSpy).toHaveBeenCalledTimes(1);
|
|
61
|
+
expect(Object.keys(acps.getACPs(chainId, checksummed))).toHaveLength(1);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('keeps one bucket per account whatever case it is written and read in', async () => {
|
|
65
|
+
const created = await acps.createSelf({ issuer: checksummed }, publicClient, walletClient);
|
|
66
|
+
acpStore.store.setState({ acps: {}, activeACPHash: {} });
|
|
67
|
+
|
|
68
|
+
// Stored under the lowercase spelling (e.g. a wallet client built from a lowercase address)...
|
|
69
|
+
acpStore.setACP(chainId, lowercase, created);
|
|
70
|
+
acpStore.setActiveACPHash(chainId, lowercase, created.hash);
|
|
71
|
+
// ...still found with the checksummed one, and a write in that case reuses the same bucket.
|
|
72
|
+
expect(acpStore.getActiveACP(chainId, checksummed)?.hash).toBe(created.hash);
|
|
73
|
+
acpStore.setACP(chainId, checksummed, created);
|
|
74
|
+
expect(Object.keys(acpStore.store.getState().acps[chainId])).toEqual([lowercase]);
|
|
75
|
+
|
|
76
|
+
acpStore.removeACP(chainId, checksummed, created.hash);
|
|
77
|
+
expect(acpStore.getActiveACPHash(chainId, lowercase)).toBeUndefined();
|
|
78
|
+
expect(acpStore.getACP(chainId, lowercase, created.hash)).toBeUndefined();
|
|
79
|
+
});
|
|
80
|
+
});
|
package/core/test/acps.test.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @vitest-environment happy-dom
|
|
3
3
|
*/
|
|
4
|
-
import { acpStore } from '@/acps';
|
|
4
|
+
import { ACPUtils, acpStore, type IncomingShare, type SharingACP } from '@/acps';
|
|
5
5
|
|
|
6
6
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
7
7
|
|
|
@@ -143,7 +143,8 @@ describe('Core ACPs Tests', () => {
|
|
|
143
143
|
// Import the acp as Alice (recipient)
|
|
144
144
|
const acp = await acps.importShared(acpJson, publicClient, aliceWalletClient);
|
|
145
145
|
|
|
146
|
-
|
|
146
|
+
// the issuer's name is not taken over: the acp is named after its issuer
|
|
147
|
+
expect(acp.name).toBe(`Shared by ${bobAddress.slice(0, 6)}…${bobAddress.slice(-4)}`);
|
|
147
148
|
expect(acp.type).toBe('recipient');
|
|
148
149
|
expect(acp.issuer).toBe(bobAddress);
|
|
149
150
|
expect(acp.recipient).toBe(aliceAddress);
|
|
@@ -152,6 +153,130 @@ describe('Core ACPs Tests', () => {
|
|
|
152
153
|
});
|
|
153
154
|
});
|
|
154
155
|
|
|
156
|
+
describe('checkAccess on the issuer copy of a share', () => {
|
|
157
|
+
it('is valid while unexpired and not revoked, without needing the recipient signature', async () => {
|
|
158
|
+
const sharing = await acps.createSharing(
|
|
159
|
+
{ name: 'To Alice', issuer: bobAddress, recipient: aliceAddress },
|
|
160
|
+
publicClient,
|
|
161
|
+
bobWalletClient
|
|
162
|
+
);
|
|
163
|
+
expect(sharing.recipientSignature).toBe('0x');
|
|
164
|
+
// No revoker configured here, so nothing on chain can disable it: valid without any RPC.
|
|
165
|
+
expect(await acps.checkAccess(sharing, publicClient)).toBe('valid');
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('reports expiry locally', async () => {
|
|
169
|
+
const sharing = await acps.createSharing(
|
|
170
|
+
{ name: 'Old', issuer: bobAddress, recipient: aliceAddress, expiration: 1 },
|
|
171
|
+
publicClient,
|
|
172
|
+
bobWalletClient
|
|
173
|
+
);
|
|
174
|
+
expect(await acps.checkAccess(sharing, publicClient)).toBe('expired');
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe('Import without activation', () => {
|
|
179
|
+
const ZERO_SHARE_ID = `0x${'0'.repeat(64)}` as const;
|
|
180
|
+
|
|
181
|
+
// Alice already decrypts with her own self acp; Bob then shares with her.
|
|
182
|
+
const setUp = async () => {
|
|
183
|
+
const self = await acps.createSelf({ name: 'Alice self', issuer: aliceAddress }, publicClient, aliceWalletClient);
|
|
184
|
+
const sharing = await acps.createSharing(
|
|
185
|
+
{ name: 'Shared with Alice', issuer: bobAddress, recipient: aliceAddress },
|
|
186
|
+
publicClient,
|
|
187
|
+
bobWalletClient
|
|
188
|
+
);
|
|
189
|
+
return { self, sharing };
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const toIncomingShare = (sharing: SharingACP): IncomingShare => {
|
|
193
|
+
const { sealingKey: _sealingKey, recipientSignature: _recipientSignature, ...pub } = ACPUtils.getPublic(sharing);
|
|
194
|
+
return { ...pub, shareId: ZERO_SHARE_ID, metadata: '0x' };
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
it('importShared activates the imported acp by default', async () => {
|
|
198
|
+
const { sharing } = await setUp();
|
|
199
|
+
const imported = await acps.importShared(ACPUtils.export(sharing), publicClient, aliceWalletClient);
|
|
200
|
+
|
|
201
|
+
expect(acps.getActiveACPHash(chainId, aliceAddress)).toBe(imported.hash);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('importShared with activate: false stores the acp and keeps the active one', async () => {
|
|
205
|
+
const { self, sharing } = await setUp();
|
|
206
|
+
const imported = await acps.importShared(ACPUtils.export(sharing), publicClient, aliceWalletClient, {
|
|
207
|
+
activate: false,
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
expect(acps.getACPs(chainId, aliceAddress)?.[imported.hash]).toBeDefined();
|
|
211
|
+
expect(acps.getActiveACPHash(chainId, aliceAddress)).toBe(self.hash);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('importShared with activate: false leaves no active acp when there was none', async () => {
|
|
215
|
+
const sharing = await acps.createSharing(
|
|
216
|
+
{ name: 'Shared with Alice', issuer: bobAddress, recipient: aliceAddress },
|
|
217
|
+
publicClient,
|
|
218
|
+
bobWalletClient
|
|
219
|
+
);
|
|
220
|
+
const imported = await acps.importShared(ACPUtils.export(sharing), publicClient, aliceWalletClient, {
|
|
221
|
+
activate: false,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
expect(acps.getACPs(chainId, aliceAddress)?.[imported.hash]).toBeDefined();
|
|
225
|
+
expect(acps.getActiveACPHash(chainId, aliceAddress)).toBeUndefined();
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('importFromChain activates the imported acp by default', async () => {
|
|
229
|
+
const { sharing } = await setUp();
|
|
230
|
+
const imported = await acps.importFromChain(toIncomingShare(sharing), publicClient, aliceWalletClient);
|
|
231
|
+
|
|
232
|
+
expect(acps.getActiveACPHash(chainId, aliceAddress)).toBe(imported.hash);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('importFromChain keeps the share metadata on the stored recipient acp', async () => {
|
|
236
|
+
const { sharing } = await setUp();
|
|
237
|
+
const imported = await acps.importFromChain(
|
|
238
|
+
{ ...toIncomingShare(sharing), metadata: '0x03aa' },
|
|
239
|
+
publicClient,
|
|
240
|
+
aliceWalletClient
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
expect(imported.metadata).toBe('0x03aa');
|
|
244
|
+
expect(acps.getACPs(chainId, aliceAddress)?.[imported.hash]?.metadata).toBe('0x03aa');
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('importShared and importFromChain take the recipient name as an option', async () => {
|
|
248
|
+
const { sharing } = await setUp();
|
|
249
|
+
const fromJson = await acps.importShared(ACPUtils.export(sharing), publicClient, aliceWalletClient, {
|
|
250
|
+
name: 'From Bob (JSON)',
|
|
251
|
+
});
|
|
252
|
+
const fromChain = await acps.importFromChain(toIncomingShare(sharing), publicClient, aliceWalletClient, {
|
|
253
|
+
name: 'From Bob (chain)',
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
expect(fromJson.name).toBe('From Bob (JSON)');
|
|
257
|
+
expect(fromChain.name).toBe('From Bob (chain)');
|
|
258
|
+
// the same share, so the second import replaced the first in the store
|
|
259
|
+
expect(acps.getACPs(chainId, aliceAddress)?.[fromChain.hash]?.name).toBe('From Bob (chain)');
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('importFromChain of a share without metadata leaves none on the acp', async () => {
|
|
263
|
+
const { sharing } = await setUp();
|
|
264
|
+
const imported = await acps.importFromChain(toIncomingShare(sharing), publicClient, aliceWalletClient);
|
|
265
|
+
|
|
266
|
+
expect(imported.metadata).toBeUndefined();
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('importFromChain with activate: false stores the acp and keeps the active one', async () => {
|
|
270
|
+
const { self, sharing } = await setUp();
|
|
271
|
+
const imported = await acps.importFromChain(toIncomingShare(sharing), publicClient, aliceWalletClient, {
|
|
272
|
+
activate: false,
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
expect(acps.getACPs(chainId, aliceAddress)?.[imported.hash]).toBeDefined();
|
|
276
|
+
expect(acps.getActiveACPHash(chainId, aliceAddress)).toBe(self.hash);
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
155
280
|
describe('ACP Retrieval', () => {
|
|
156
281
|
let createdACP: any;
|
|
157
282
|
let acpHash: string;
|
|
@@ -528,7 +653,7 @@ describe('Core ACPs Tests', () => {
|
|
|
528
653
|
const exported = acps.export(acp);
|
|
529
654
|
const parsed = JSON.parse(exported);
|
|
530
655
|
|
|
531
|
-
expect(parsed
|
|
656
|
+
expect(parsed).not.toHaveProperty('name');
|
|
532
657
|
expect(parsed.type).toBe('sharing');
|
|
533
658
|
expect(parsed.issuer).toBe(bobAddress);
|
|
534
659
|
expect(parsed.recipient).toBe(aliceAddress);
|
|
@@ -47,6 +47,14 @@ describe('mapApiErrorCodeToCofheErrorCode', () => {
|
|
|
47
47
|
CofheErrorCode.SealOutputFailed
|
|
48
48
|
);
|
|
49
49
|
});
|
|
50
|
+
|
|
51
|
+
it('falls back for a code that only exists on Object.prototype', () => {
|
|
52
|
+
for (const inherited of ['toString', 'constructor', 'valueOf', 'hasOwnProperty']) {
|
|
53
|
+
expect(mapApiErrorCodeToCofheErrorCode(inherited, CofheErrorCode.DecryptFailed)).toBe(
|
|
54
|
+
CofheErrorCode.DecryptFailed
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
50
58
|
});
|
|
51
59
|
|
|
52
60
|
describe('parseApiErrorResponseBody', () => {
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
2
|
+
import { type PublicClient, type WalletClient } from 'viem';
|
|
3
|
+
import { createCofheClientBase } from '../client';
|
|
4
|
+
import { type CofheClient } from '../clientTypes';
|
|
5
|
+
import { createCofheConfigBase, type CofheInputConfig } from '../config';
|
|
6
|
+
import { acps } from '../acps';
|
|
7
|
+
import { shareAppKey } from '../../acps/share-metadata';
|
|
8
|
+
|
|
9
|
+
vi.mock('../keyStore', () => ({
|
|
10
|
+
createKeysStore: vi.fn(() => ({
|
|
11
|
+
rehydrateKeysStore: vi.fn().mockResolvedValue(undefined),
|
|
12
|
+
getFheKey: vi.fn(),
|
|
13
|
+
getCrs: vi.fn(),
|
|
14
|
+
setFheKey: vi.fn(),
|
|
15
|
+
setCrs: vi.fn(),
|
|
16
|
+
clearKeysStorage: vi.fn(),
|
|
17
|
+
store: {} as any,
|
|
18
|
+
})),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
const CHAIN_ID = 11155111;
|
|
22
|
+
const ACCOUNT = '0x1234567890123456789012345678901234567890';
|
|
23
|
+
const REGISTRY = '0x00000000000000000000000000000000000000e0';
|
|
24
|
+
const APP = 'explorer.fhenix.io';
|
|
25
|
+
const APP_KEY = shareAppKey(APP);
|
|
26
|
+
|
|
27
|
+
const publicClient = { getChainId: vi.fn().mockResolvedValue(CHAIN_ID) } as unknown as PublicClient;
|
|
28
|
+
const walletClient = {
|
|
29
|
+
getAddresses: vi.fn().mockResolvedValue([ACCOUNT]),
|
|
30
|
+
account: { address: ACCOUNT },
|
|
31
|
+
} as unknown as WalletClient;
|
|
32
|
+
|
|
33
|
+
/** A connected client; `acp.sharingRegistry` is set so the on-chain calls need no ACL lookup. */
|
|
34
|
+
const connectedClient = async (acp: CofheInputConfig['acp']): Promise<CofheClient> => {
|
|
35
|
+
const client = createCofheClientBase({
|
|
36
|
+
config: createCofheConfigBase({ supportedChains: [], acp: { sharingRegistry: { [CHAIN_ID]: REGISTRY }, ...acp } }),
|
|
37
|
+
zkBuilderAndCrsGenerator: {} as any,
|
|
38
|
+
tfhePublicKeyDeserializer: {} as any,
|
|
39
|
+
compactPkeCrsDeserializer: {} as any,
|
|
40
|
+
initTfhe: () => Promise.resolve(false),
|
|
41
|
+
});
|
|
42
|
+
await client.connect(publicClient, walletClient);
|
|
43
|
+
return client;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const sharingOptions = { issuer: ACCOUNT, recipient: '0x00000000000000000000000000000000000000a1' };
|
|
47
|
+
|
|
48
|
+
describe('the app key from config.acp.app', () => {
|
|
49
|
+
const spies = {
|
|
50
|
+
defaults: vi.spyOn(acps, 'applyACPDefaultsFromChain').mockImplementation(async (options) => options),
|
|
51
|
+
createSharing: vi.spyOn(acps, 'createSharing').mockResolvedValue({} as any),
|
|
52
|
+
getOrCreate: vi.spyOn(acps, 'getOrCreateSharingACP').mockResolvedValue({} as any),
|
|
53
|
+
shareOnChain: vi.spyOn(acps, 'shareOnChain').mockResolvedValue({ txHash: '0x', shareId: '0x' }),
|
|
54
|
+
getIncoming: vi.spyOn(acps, 'getIncomingShares').mockResolvedValue([]),
|
|
55
|
+
};
|
|
56
|
+
afterEach(() => Object.values(spies).forEach((spy) => spy.mockClear()));
|
|
57
|
+
|
|
58
|
+
it('goes on every share the client creates', async () => {
|
|
59
|
+
const client = await connectedClient({ app: APP });
|
|
60
|
+
await client.acp.createSharing(sharingOptions);
|
|
61
|
+
expect(spies.createSharing.mock.calls[0][0]).toMatchObject({ ...sharingOptions, appKey: APP_KEY });
|
|
62
|
+
|
|
63
|
+
await client.acp.getOrCreateSharingACP(sharingOptions);
|
|
64
|
+
expect(spies.getOrCreate.mock.calls[0][2]).toMatchObject({ ...sharingOptions, appKey: APP_KEY });
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('goes with labels posted by shareOnChain', async () => {
|
|
68
|
+
const client = await connectedClient({ app: APP });
|
|
69
|
+
await client.acp.shareOnChain({} as any, { labels: [] });
|
|
70
|
+
expect(spies.shareOnChain.mock.calls[0][3]).toEqual({ labels: [], appKey: APP_KEY });
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('filters the inbox', async () => {
|
|
74
|
+
const client = await connectedClient({ app: APP });
|
|
75
|
+
await client.acp.getIncomingShares();
|
|
76
|
+
expect(spies.getIncoming).toHaveBeenCalledWith(publicClient, REGISTRY, ACCOUNT, { appKey: APP_KEY });
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('is the keccak256 of the app name, and an explicit appKey wins over it', async () => {
|
|
80
|
+
const client = await connectedClient({ app: APP });
|
|
81
|
+
const other = shareAppKey('other.app');
|
|
82
|
+
await client.acp.createSharing({ ...sharingOptions, appKey: other });
|
|
83
|
+
expect(spies.createSharing.mock.calls[0][0]).toMatchObject({ appKey: other });
|
|
84
|
+
expect(APP_KEY).toMatch(/^0x[0-9a-f]{64}$/);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('is absent without an app: shares carry no key and every share is listed', async () => {
|
|
88
|
+
const client = await connectedClient({});
|
|
89
|
+
await client.acp.createSharing(sharingOptions);
|
|
90
|
+
expect(spies.createSharing.mock.calls[0][0]).not.toHaveProperty('appKey');
|
|
91
|
+
await client.acp.getIncomingShares();
|
|
92
|
+
expect(spies.getIncoming).toHaveBeenCalledWith(publicClient, REGISTRY, ACCOUNT, {});
|
|
93
|
+
});
|
|
94
|
+
});
|
package/core/test/client.test.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { CofheError, CofheErrorCode } from '../error.js';
|
|
|
6
6
|
import { type PublicClient, type WalletClient } from 'viem';
|
|
7
7
|
import { EncryptInputsBuilder } from '../encrypt/encryptInputsBuilder.js';
|
|
8
8
|
import { Encryptable } from '../types.js';
|
|
9
|
+
import { acps } from '../acps.js';
|
|
9
10
|
|
|
10
11
|
// Mock dependencies
|
|
11
12
|
vi.mock('../keyStore', () => ({
|
|
@@ -428,5 +429,45 @@ describe('createCofheClientBase', () => {
|
|
|
428
429
|
expect(client.acp).toHaveProperty('selectActiveACP');
|
|
429
430
|
expect(client.acp).toHaveProperty('removeActiveACP');
|
|
430
431
|
});
|
|
432
|
+
|
|
433
|
+
describe('import activation', () => {
|
|
434
|
+
const share = { shareId: `0x${'0'.repeat(64)}` } as any;
|
|
435
|
+
|
|
436
|
+
it('importShared forwards activate with the connected clients', async () => {
|
|
437
|
+
const publicClient = createMockPublicClient();
|
|
438
|
+
const walletClient = createMockWalletClient();
|
|
439
|
+
await client.connect(publicClient, walletClient);
|
|
440
|
+
const spy = vi.spyOn(acps, 'importShared').mockResolvedValue({} as any);
|
|
441
|
+
|
|
442
|
+
await client.acp.importShared('{}', { activate: false });
|
|
443
|
+
|
|
444
|
+
expect(spy).toHaveBeenCalledWith('{}', publicClient, walletClient, { activate: false });
|
|
445
|
+
spy.mockRestore();
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
it('importShared still takes explicit clients, activating by default', async () => {
|
|
449
|
+
await client.connect(createMockPublicClient(), createMockWalletClient());
|
|
450
|
+
const publicClient = createMockPublicClient();
|
|
451
|
+
const walletClient = createMockWalletClient();
|
|
452
|
+
const spy = vi.spyOn(acps, 'importShared').mockResolvedValue({} as any);
|
|
453
|
+
|
|
454
|
+
await client.acp.importShared('{}', { publicClient, walletClient });
|
|
455
|
+
|
|
456
|
+
expect(spy).toHaveBeenCalledWith('{}', publicClient, walletClient, { activate: undefined });
|
|
457
|
+
spy.mockRestore();
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
it('importFromChain forwards activate', async () => {
|
|
461
|
+
const publicClient = createMockPublicClient();
|
|
462
|
+
const walletClient = createMockWalletClient();
|
|
463
|
+
await client.connect(publicClient, walletClient);
|
|
464
|
+
const spy = vi.spyOn(acps, 'importFromChain').mockResolvedValue({} as any);
|
|
465
|
+
|
|
466
|
+
await client.acp.importFromChain(share, { activate: false });
|
|
467
|
+
|
|
468
|
+
expect(spy).toHaveBeenCalledWith(share, publicClient, walletClient, { activate: false });
|
|
469
|
+
spy.mockRestore();
|
|
470
|
+
});
|
|
471
|
+
});
|
|
431
472
|
});
|
|
432
473
|
});
|
package/core/test/config.test.ts
CHANGED
|
@@ -62,6 +62,14 @@ describe('createCofheConfigBase', () => {
|
|
|
62
62
|
expectValidConfigItem('environment', 'react', 'react');
|
|
63
63
|
});
|
|
64
64
|
|
|
65
|
+
it('acp.app', () => {
|
|
66
|
+
expectInvalidConfigItem('acp.app', '');
|
|
67
|
+
expectInvalidConfigItem('acp.app', 123);
|
|
68
|
+
|
|
69
|
+
expectValidConfigItem('acp.app', 'explorer.fhenix.io', 'explorer.fhenix.io');
|
|
70
|
+
expectValidConfigItem('acp.app', undefined, undefined);
|
|
71
|
+
});
|
|
72
|
+
|
|
65
73
|
it('supportedChains', () => {
|
|
66
74
|
expectInvalidConfigItem('supportedChains', {});
|
|
67
75
|
expectInvalidConfigItem('supportedChains', 'not-an-array');
|
|
@@ -162,6 +170,22 @@ describe('createCofheConfigBase', () => {
|
|
|
162
170
|
expectValidConfigItem('useWorkers', undefined, true); // defaults to true
|
|
163
171
|
});
|
|
164
172
|
|
|
173
|
+
it('tfheThreads', () => {
|
|
174
|
+
expectInvalidConfigItem('tfheThreads', 'not-a-setting');
|
|
175
|
+
expectInvalidConfigItem('tfheThreads', null);
|
|
176
|
+
expectInvalidConfigItem('tfheThreads', true);
|
|
177
|
+
expectInvalidConfigItem('tfheThreads', 0);
|
|
178
|
+
expectInvalidConfigItem('tfheThreads', -4);
|
|
179
|
+
expectInvalidConfigItem('tfheThreads', 2.5);
|
|
180
|
+
expectInvalidConfigItem('tfheThreads', {});
|
|
181
|
+
|
|
182
|
+
expectValidConfigItem('tfheThreads', 'auto', 'auto');
|
|
183
|
+
expectValidConfigItem('tfheThreads', false, false);
|
|
184
|
+
expectValidConfigItem('tfheThreads', 1, 1);
|
|
185
|
+
expectValidConfigItem('tfheThreads', 4, 4);
|
|
186
|
+
expectValidConfigItem('tfheThreads', undefined, 'auto'); // defaults to 'auto'
|
|
187
|
+
});
|
|
188
|
+
|
|
165
189
|
it('should get config item', () => {
|
|
166
190
|
const config: CofheInputConfig = {
|
|
167
191
|
supportedChains: [sepolia],
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { STAGING_TESTS, stagingViemChain } from './stagingRedirect';
|
|
2
|
-
import { FheTypes, verifyDecryptResult, createCofheConfigBase,
|
|
2
|
+
import { FheTypes, verifyDecryptResult, createCofheConfigBase, getTaskManagerAddress } from '@/core';
|
|
3
3
|
import { getChainById, stagingCofhe } from '@/chains';
|
|
4
4
|
import {
|
|
5
5
|
TEST_PRIVATE_KEY,
|
|
@@ -19,7 +19,12 @@ import { createPublicClient, createWalletClient, http, parseAbi } from 'viem';
|
|
|
19
19
|
import { privateKeyToAccount } from 'viem/accounts';
|
|
20
20
|
import { arbitrumSepolia, baseSepolia, sepolia } from 'viem/chains';
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
// Live-chain suite: needs the funded TEST_PRIVATE_KEY (signs decrypt requests against
|
|
23
|
+
// pre-deployed testnet fixtures). Without it — fork PRs, where secrets are unavailable —
|
|
24
|
+
// the suite skips; CI runs it separately with the key (test-live job).
|
|
25
|
+
const LIVE_TESTS = Boolean(TEST_PRIVATE_KEY);
|
|
26
|
+
const DEFAULT_TEST_PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80';
|
|
27
|
+
const account = privateKeyToAccount(TEST_PRIVATE_KEY || DEFAULT_TEST_PRIVATE_KEY);
|
|
23
28
|
|
|
24
29
|
const TEST_CHAIN_ID = STAGING_TESTS ? stagingViemChain.id : PRIMARY_TEST_CHAIN;
|
|
25
30
|
|
|
@@ -29,7 +34,7 @@ const VIEM_CHAINS: Record<number, Chain> = {
|
|
|
29
34
|
11155111: sepolia,
|
|
30
35
|
};
|
|
31
36
|
|
|
32
|
-
describe('Core – Decrypt Tests', () => {
|
|
37
|
+
describe.skipIf(!LIVE_TESTS)('Core – Decrypt Tests', () => {
|
|
33
38
|
let publicClient: PublicClient;
|
|
34
39
|
let walletClient: WalletClient;
|
|
35
40
|
let config: ReturnType<typeof createCofheConfigBase>;
|
|
@@ -236,6 +241,8 @@ describe('Core – Decrypt Tests', () => {
|
|
|
236
241
|
},
|
|
237
242
|
] as const;
|
|
238
243
|
|
|
244
|
+
const taskManagerAddress = await getTaskManagerAddress(publicClient);
|
|
245
|
+
|
|
239
246
|
for (const sample of samples) {
|
|
240
247
|
const sdkResult = await verifyDecryptResult(sample.handle, sample.cleartext, sample.signature, publicClient);
|
|
241
248
|
|
|
@@ -243,7 +250,7 @@ describe('Core – Decrypt Tests', () => {
|
|
|
243
250
|
'function verifyDecryptResultSafe(uint256 ctHash, uint256 cleartext, bytes signature) view returns (bool)',
|
|
244
251
|
]);
|
|
245
252
|
const tmResult = await publicClient.readContract({
|
|
246
|
-
address:
|
|
253
|
+
address: taskManagerAddress,
|
|
247
254
|
abi: verifyDecryptResultSafeAbi,
|
|
248
255
|
functionName: 'verifyDecryptResultSafe',
|
|
249
256
|
args: [sample.handle, sample.cleartext, sample.signature],
|