@cofhe/sdk 0.4.0 → 0.5.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.
Files changed (95) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/adapters/{ethers5.test.ts → test/ethers5.test.ts} +2 -2
  3. package/adapters/{ethers6.test.ts → test/ethers6.test.ts} +2 -2
  4. package/adapters/{hardhat.hh2.test.ts → test/hardhat.hh2.test.ts} +2 -2
  5. package/adapters/{index.test.ts → test/index.test.ts} +1 -1
  6. package/adapters/{wagmi.test.ts → test/wagmi.test.ts} +1 -1
  7. package/chains/{chains.test.ts → test/chains.test.ts} +1 -1
  8. package/core/client.ts +11 -1
  9. package/core/clientTypes.ts +3 -1
  10. package/core/consts.ts +9 -0
  11. package/core/decrypt/cofheMocksDecryptForTx.ts +14 -3
  12. package/core/decrypt/decryptForTxBuilder.ts +16 -2
  13. package/core/decrypt/decryptForViewBuilder.ts +14 -7
  14. package/core/decrypt/polling.ts +14 -0
  15. package/core/decrypt/tnDecryptV2.ts +250 -110
  16. package/core/decrypt/tnSealOutputV2.ts +245 -104
  17. package/core/decrypt/verifyDecryptResult.ts +65 -0
  18. package/core/encrypt/cofheMocksZkVerifySign.ts +6 -6
  19. package/core/encrypt/zkPackProveVerify.ts +10 -19
  20. package/core/fetchKeys.ts +0 -2
  21. package/core/index.ts +9 -1
  22. package/core/keyStore.ts +5 -2
  23. package/core/permits.ts +5 -0
  24. package/core/{client.test.ts → test/client.test.ts} +7 -7
  25. package/core/{config.test.ts → test/config.test.ts} +1 -1
  26. package/core/test/decrypt.test.ts +252 -0
  27. package/core/test/decryptBuilders.test.ts +390 -0
  28. package/core/{encrypt → test}/encryptInputsBuilder.test.ts +61 -6
  29. package/core/{fetchKeys.test.ts → test/fetchKeys.test.ts} +3 -3
  30. package/core/{keyStore.test.ts → test/keyStore.test.ts} +5 -3
  31. package/core/{permits.test.ts → test/permits.test.ts} +42 -1
  32. package/core/test/pollCallbacks.test.ts +563 -0
  33. package/core/types.ts +13 -0
  34. package/dist/chains.d.cts +2 -2
  35. package/dist/chains.d.ts +2 -2
  36. package/dist/chunk-4FP4V35O.js +13 -0
  37. package/dist/{chunk-NWDKXBIP.js → chunk-MRCKUMOS.js} +62 -22
  38. package/dist/{chunk-MXND5SVN.js → chunk-S7OKGLFD.js} +485 -207
  39. package/dist/{clientTypes-kkrRdawm.d.ts → clientTypes-BSbwairE.d.cts} +23 -6
  40. package/dist/{clientTypes-ACVWbrXL.d.cts → clientTypes-DDmcgZ0a.d.ts} +23 -6
  41. package/dist/core.cjs +561 -244
  42. package/dist/core.d.cts +24 -6
  43. package/dist/core.d.ts +24 -6
  44. package/dist/core.js +3 -2
  45. package/dist/node.cjs +566 -246
  46. package/dist/node.d.cts +3 -3
  47. package/dist/node.d.ts +3 -3
  48. package/dist/node.js +14 -7
  49. package/dist/{permit-MZ502UBl.d.cts → permit-DnVMDT5h.d.cts} +34 -4
  50. package/dist/{permit-MZ502UBl.d.ts → permit-DnVMDT5h.d.ts} +34 -4
  51. package/dist/permits.cjs +66 -29
  52. package/dist/permits.d.cts +18 -13
  53. package/dist/permits.d.ts +18 -13
  54. package/dist/permits.js +2 -1
  55. package/dist/web.cjs +588 -251
  56. package/dist/web.d.cts +8 -4
  57. package/dist/web.d.ts +8 -4
  58. package/dist/web.js +34 -11
  59. package/dist/zkProve.worker.cjs +6 -3
  60. package/dist/zkProve.worker.js +5 -3
  61. package/node/index.ts +13 -4
  62. package/node/test/client.test.ts +25 -0
  63. package/node/test/config.test.ts +16 -0
  64. package/node/test/inherited.test.ts +244 -0
  65. package/node/test/tfheinit.test.ts +56 -0
  66. package/package.json +24 -22
  67. package/permits/permit.ts +31 -5
  68. package/permits/sealing.ts +1 -1
  69. package/permits/{localstorage.test.ts → test/localstorage.test.ts} +2 -2
  70. package/permits/{permit.test.ts → test/permit.test.ts} +35 -1
  71. package/permits/{sealing.test.ts → test/sealing.test.ts} +1 -1
  72. package/permits/{store.test.ts → test/store.test.ts} +2 -2
  73. package/permits/{validation.test.ts → test/validation.test.ts} +82 -6
  74. package/permits/types.ts +1 -1
  75. package/permits/validation.ts +42 -2
  76. package/web/const.ts +2 -0
  77. package/web/index.ts +20 -6
  78. package/web/storage.ts +18 -3
  79. package/web/{client.web.test.ts → test/client.web.test.ts} +13 -1
  80. package/web/test/config.web.test.ts +16 -0
  81. package/web/test/inherited.web.test.ts +245 -0
  82. package/web/test/tfheinit.web.test.ts +62 -0
  83. package/web/{worker.config.web.test.ts → test/worker.config.web.test.ts} +1 -1
  84. package/web/{worker.output.web.test.ts → test/worker.output.web.test.ts} +1 -1
  85. package/web/{workerManager.test.ts → test/workerManager.test.ts} +1 -1
  86. package/web/{workerManager.web.test.ts → test/workerManager.web.test.ts} +1 -1
  87. package/web/zkProve.worker.ts +4 -3
  88. package/node/client.test.ts +0 -147
  89. package/node/config.test.ts +0 -68
  90. package/node/encryptInputs.test.ts +0 -155
  91. package/web/config.web.test.ts +0 -69
  92. package/web/encryptInputs.web.test.ts +0 -172
  93. package/web/worker.builder.web.test.ts +0 -148
  94. /package/dist/{types-YiAC4gig.d.cts → types-C07FK-cL.d.cts} +0 -0
  95. /package/dist/{types-YiAC4gig.d.ts → types-C07FK-cL.d.ts} +0 -0
package/core/permits.ts CHANGED
@@ -75,6 +75,10 @@ const getHash = (permit: PermitHashFields) => {
75
75
  return PermitUtils.getHash(permit);
76
76
  };
77
77
 
78
+ const exportShared = (permit: Permit) => {
79
+ return PermitUtils.export(permit);
80
+ };
81
+
78
82
  const serialize = (permit: Permit) => {
79
83
  return PermitUtils.serialize(permit);
80
84
  };
@@ -188,6 +192,7 @@ export const permits = {
188
192
  getOrCreateSharingPermit,
189
193
 
190
194
  getHash,
195
+ export: exportShared,
191
196
  serialize,
192
197
  deserialize,
193
198
 
@@ -1,14 +1,14 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- import { createCofheClientBase } from './client.js';
3
- import { type CofheClient, type CofheClientConnectionState } from './clientTypes.js';
4
- import { createCofheConfigBase, type CofheEnvironment } from './config.js';
5
- import { CofheError, CofheErrorCode } from './error.js';
2
+ import { createCofheClientBase } from '../client.js';
3
+ import { type CofheClient, type CofheClientConnectionState } from '../clientTypes.js';
4
+ import { createCofheConfigBase, type CofheEnvironment } from '../config.js';
5
+ import { CofheError, CofheErrorCode } from '../error.js';
6
6
  import { type PublicClient, type WalletClient } from 'viem';
7
- import { EncryptInputsBuilder } from './encrypt/encryptInputsBuilder.js';
8
- import { Encryptable } from './types.js';
7
+ import { EncryptInputsBuilder } from '../encrypt/encryptInputsBuilder.js';
8
+ import { Encryptable } from '../types.js';
9
9
 
10
10
  // Mock dependencies
11
- vi.mock('./keyStore', () => ({
11
+ vi.mock('../keyStore', () => ({
12
12
  createKeysStore: vi.fn(() => ({
13
13
  rehydrateKeysStore: vi.fn().mockResolvedValue(undefined),
14
14
  getFheKey: vi.fn(),
@@ -9,7 +9,7 @@ import {
9
9
  getCoFheUrlOrThrow,
10
10
  getZkVerifierUrlOrThrow,
11
11
  getThresholdNetworkUrlOrThrow,
12
- } from './config.js';
12
+ } from '../config.js';
13
13
 
14
14
  describe('createCofheConfigBase', () => {
15
15
  const validBaseConfig: CofheInputConfig = {
@@ -0,0 +1,252 @@
1
+ import { FheTypes, verifyDecryptResult, createCofheConfigBase, TASK_MANAGER_ADDRESS } from '@/core';
2
+ import { getChainById } from '@/chains';
3
+ import {
4
+ TEST_PRIVATE_KEY,
5
+ PRIMARY_TEST_CHAIN,
6
+ primaryTestChainRegistry,
7
+ isPrimaryTestChainReady,
8
+ } from '@cofhe/test-setup';
9
+
10
+ import { permits } from '../permits.js';
11
+ import { DecryptForTxBuilder } from '../decrypt/decryptForTxBuilder.js';
12
+ import { DecryptForViewBuilder } from '../decrypt/decryptForViewBuilder.js';
13
+
14
+ import { describe, it, expect, beforeAll } from 'vitest';
15
+ import type { Chain, PublicClient, WalletClient } from 'viem';
16
+ import { createPublicClient, createWalletClient, http, parseAbi } from 'viem';
17
+ import { privateKeyToAccount } from 'viem/accounts';
18
+ import { arbitrumSepolia, baseSepolia, sepolia } from 'viem/chains';
19
+
20
+ const account = privateKeyToAccount(TEST_PRIVATE_KEY);
21
+
22
+ const VIEM_CHAINS: Record<number, Chain> = {
23
+ 421614: arbitrumSepolia,
24
+ 84532: baseSepolia,
25
+ 11155111: sepolia,
26
+ };
27
+
28
+ describe('Core – Decrypt Tests', () => {
29
+ let publicClient: PublicClient;
30
+ let walletClient: WalletClient;
31
+ let config: ReturnType<typeof createCofheConfigBase>;
32
+
33
+ let privateCtHash: `0x${string}`;
34
+ let privateValue: bigint;
35
+
36
+ let publicCtHash: `0x${string}`;
37
+ let publicValue: bigint;
38
+
39
+ beforeAll(() => {
40
+ if (!isPrimaryTestChainReady(primaryTestChainRegistry)) {
41
+ throw new Error('Primary test chain registry is not initialized. Run `pnpm test:setup` first.');
42
+ }
43
+
44
+ const reg = primaryTestChainRegistry;
45
+ const chainId = reg.chainId;
46
+
47
+ const viemChain = VIEM_CHAINS[chainId];
48
+ if (!viemChain) throw new Error(`No viem chain mapping for chain ${chainId}`);
49
+
50
+ const cofheChain = getChainById(chainId);
51
+ if (!cofheChain) throw new Error(`No cofhe chain config for chain ${chainId}`);
52
+
53
+ config = createCofheConfigBase({ supportedChains: [cofheChain] });
54
+
55
+ privateCtHash = reg.privateValue.ctHash as `0x${string}`;
56
+ privateValue = BigInt(reg.privateValue.value);
57
+
58
+ publicCtHash = reg.publicValue.ctHash as `0x${string}`;
59
+ publicValue = BigInt(reg.publicValue.value);
60
+
61
+ publicClient = createPublicClient({ chain: viemChain, transport: http() });
62
+ walletClient = createWalletClient({ chain: viemChain, transport: http(), account });
63
+ });
64
+
65
+ function txBuilder(ctHash: `0x${string}`) {
66
+ return new DecryptForTxBuilder({
67
+ config,
68
+ publicClient,
69
+ walletClient,
70
+ chainId: PRIMARY_TEST_CHAIN,
71
+ account: account.address,
72
+ ctHash,
73
+ requireConnected: undefined,
74
+ });
75
+ }
76
+
77
+ function viewBuilder(ctHash: `0x${string}`, utype: FheTypes) {
78
+ return new DecryptForViewBuilder({
79
+ config,
80
+ publicClient,
81
+ walletClient,
82
+ chainId: PRIMARY_TEST_CHAIN,
83
+ account: account.address,
84
+ ctHash,
85
+ utype,
86
+ requireConnected: undefined,
87
+ });
88
+ }
89
+
90
+ async function createPermit() {
91
+ return permits.createSelf({ issuer: account.address, name: 'Decrypt Test Permit' }, publicClient, walletClient);
92
+ }
93
+
94
+ // ---------------------------------------------------------------------------
95
+ // decryptForTx – withoutPermit (global allowance)
96
+ // ---------------------------------------------------------------------------
97
+
98
+ describe('decryptForTx – withoutPermit (global allowance)', () => {
99
+ it('should decrypt a publicly allowed ciphertext', async () => {
100
+ const result = await txBuilder(publicCtHash).withoutPermit().execute();
101
+
102
+ expect(BigInt(result.ctHash)).toBe(BigInt(publicCtHash));
103
+ expect(result.decryptedValue).toBe(publicValue);
104
+ expect(result.signature).toMatch(/^0x[0-9a-fA-F]+$/);
105
+ }, 180000);
106
+ });
107
+
108
+ // ---------------------------------------------------------------------------
109
+ // decryptForTx – withPermit
110
+ // ---------------------------------------------------------------------------
111
+
112
+ describe('decryptForTx – withPermit', () => {
113
+ it('should decrypt with a self permit', async () => {
114
+ const permit = await createPermit();
115
+
116
+ const result = await txBuilder(privateCtHash).withPermit(permit).execute();
117
+
118
+ expect(BigInt(result.ctHash)).toBe(BigInt(privateCtHash));
119
+ expect(result.decryptedValue).toBe(privateValue);
120
+ expect(result.signature).toBeDefined();
121
+ }, 180000);
122
+
123
+ it('should auto-resolve active permit', async () => {
124
+ const permit = await createPermit();
125
+ permits.selectActivePermit(PRIMARY_TEST_CHAIN, account.address, permit.hash);
126
+
127
+ const result = await txBuilder(privateCtHash).withPermit().execute();
128
+
129
+ expect(BigInt(result.ctHash)).toBe(BigInt(privateCtHash));
130
+ expect(result.decryptedValue).toBe(privateValue);
131
+ }, 180000);
132
+ });
133
+
134
+ // ---------------------------------------------------------------------------
135
+ // verifyDecryptResult
136
+ // ---------------------------------------------------------------------------
137
+
138
+ describe('verifyDecryptResult', () => {
139
+ it('should verify a valid decrypt result', async () => {
140
+ const permit = await createPermit();
141
+
142
+ const decryptResult = await txBuilder(privateCtHash).withPermit(permit).execute();
143
+
144
+ const isValid = await verifyDecryptResult(
145
+ decryptResult.ctHash,
146
+ privateValue,
147
+ decryptResult.signature,
148
+ publicClient
149
+ );
150
+ expect(isValid).toBe(true);
151
+ }, 180000);
152
+
153
+ it('should return false for invalid inputs', async () => {
154
+ const permit = await createPermit();
155
+
156
+ const decryptResult = await txBuilder(privateCtHash).withPermit(permit).execute();
157
+
158
+ expect(
159
+ await verifyDecryptResult(decryptResult.ctHash, privateValue + 1n, decryptResult.signature, publicClient)
160
+ ).toBe(false);
161
+ }, 180000);
162
+ });
163
+
164
+ // ---------------------------------------------------------------------------
165
+ // decryptForView
166
+ // ---------------------------------------------------------------------------
167
+
168
+ describe('decryptForView', () => {
169
+ it('should return the plaintext value', async () => {
170
+ await createPermit();
171
+
172
+ const result = await viewBuilder(privateCtHash, FheTypes.Uint32).execute();
173
+ expect(result).toBe(privateValue);
174
+ }, 180000);
175
+
176
+ it('should agree with decryptForTx on the same handle', async () => {
177
+ const permit = await createPermit();
178
+
179
+ const viewResult = await viewBuilder(privateCtHash, FheTypes.Uint32).execute();
180
+ const txResult = await txBuilder(privateCtHash).withPermit(permit).execute();
181
+
182
+ expect(viewResult).toBe(privateValue);
183
+ expect(BigInt(txResult.ctHash)).toBe(BigInt(privateCtHash));
184
+ expect(txResult.decryptedValue).toBe(privateValue);
185
+ }, 180000);
186
+ });
187
+
188
+ describe('verifyDecryptResult', () => {
189
+ it('should correctly verify a valid decrypt result', async () => {
190
+ const permit = await createPermit();
191
+
192
+ const decryptResult = await txBuilder(privateCtHash).withPermit(permit).execute();
193
+
194
+ const isValid = await verifyDecryptResult(
195
+ decryptResult.ctHash,
196
+ privateValue,
197
+ decryptResult.signature,
198
+ publicClient
199
+ );
200
+ expect(isValid).toBe(true);
201
+ }, 180000);
202
+
203
+ it('should verify identically to the on-chain TaskManager contract', async () => {
204
+ const permit = await createPermit();
205
+ const decryptResult = await txBuilder(privateCtHash).withPermit(permit).execute();
206
+
207
+ const samples = [
208
+ {
209
+ shouldBe: true,
210
+ handle: BigInt(decryptResult.ctHash),
211
+ cleartext: decryptResult.decryptedValue,
212
+ signature: decryptResult.signature,
213
+ },
214
+ {
215
+ shouldBe: false,
216
+ handle: BigInt(decryptResult.ctHash),
217
+ cleartext: decryptResult.decryptedValue + 1n,
218
+ signature: decryptResult.signature,
219
+ },
220
+ {
221
+ shouldBe: false,
222
+ handle: BigInt(decryptResult.ctHash) + 1n,
223
+ cleartext: decryptResult.decryptedValue,
224
+ signature: decryptResult.signature,
225
+ },
226
+ {
227
+ shouldBe: false,
228
+ handle: BigInt(decryptResult.ctHash),
229
+ cleartext: decryptResult.decryptedValue,
230
+ signature: `${decryptResult.signature}00`,
231
+ },
232
+ ] as const;
233
+
234
+ for (const sample of samples) {
235
+ const sdkResult = await verifyDecryptResult(sample.handle, sample.cleartext, sample.signature, publicClient);
236
+
237
+ const verifyDecryptResultSafeAbi = parseAbi([
238
+ 'function verifyDecryptResultSafe(uint256 ctHash, uint256 cleartext, bytes signature) view returns (bool)',
239
+ ]);
240
+ const tmResult = await publicClient.readContract({
241
+ address: TASK_MANAGER_ADDRESS,
242
+ abi: verifyDecryptResultSafeAbi,
243
+ functionName: 'verifyDecryptResultSafe',
244
+ args: [sample.handle, sample.cleartext, sample.signature],
245
+ });
246
+
247
+ expect(sdkResult).to.equal(tmResult);
248
+ expect(sdkResult).to.equal(sample.shouldBe);
249
+ }
250
+ });
251
+ });
252
+ });
@@ -0,0 +1,390 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { type PublicClient, type WalletClient, createPublicClient, createWalletClient, http } from 'viem';
3
+ import { privateKeyToAccount } from 'viem/accounts';
4
+ import { arbitrumSepolia } from 'viem/chains';
5
+ import { DecryptForTxBuilder } from '../decrypt/decryptForTxBuilder.js';
6
+ import { DecryptForViewBuilder } from '../decrypt/decryptForViewBuilder.js';
7
+ import { createCofheConfigBase, type CofheConfig } from '../config.js';
8
+ import { CofheErrorCode } from '../error.js';
9
+ import { FheTypes } from '../types.js';
10
+
11
+ const TEST_PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80';
12
+ const account = privateKeyToAccount(TEST_PRIVATE_KEY);
13
+ const TEST_CHAIN_ID = 421614;
14
+ const TEST_CT_HASH = '0xabcdef1234567890';
15
+
16
+ const MockCoFheUrl = 'http://localhost:3001';
17
+
18
+ const publicClient: PublicClient = createPublicClient({
19
+ chain: arbitrumSepolia,
20
+ transport: http(),
21
+ });
22
+
23
+ const walletClient: WalletClient = createWalletClient({
24
+ chain: arbitrumSepolia,
25
+ transport: http(),
26
+ account,
27
+ });
28
+
29
+ const mockConfig: CofheConfig = createCofheConfigBase({
30
+ supportedChains: [
31
+ {
32
+ id: TEST_CHAIN_ID,
33
+ name: 'Mock Chain',
34
+ network: 'Mock Network',
35
+ coFheUrl: MockCoFheUrl,
36
+ thresholdNetworkUrl: MockCoFheUrl,
37
+ environment: 'TESTNET',
38
+ verifierUrl: MockCoFheUrl,
39
+ },
40
+ ],
41
+ });
42
+
43
+ function createTxBuilder(overrides?: Partial<{ chainId: number; account: string; ctHash: string | bigint }>) {
44
+ return new DecryptForTxBuilder({
45
+ config: mockConfig,
46
+ publicClient,
47
+ walletClient,
48
+ chainId: TEST_CHAIN_ID,
49
+ account: account.address,
50
+ ctHash: TEST_CT_HASH,
51
+ requireConnected: undefined,
52
+ ...overrides,
53
+ });
54
+ }
55
+
56
+ function createViewBuilder<U extends FheTypes>(
57
+ utype: U,
58
+ overrides?: Partial<{ chainId: number; account: string; ctHash: string | bigint }>
59
+ ) {
60
+ return new DecryptForViewBuilder<U>({
61
+ config: mockConfig,
62
+ publicClient,
63
+ walletClient,
64
+ chainId: TEST_CHAIN_ID,
65
+ account: account.address,
66
+ ctHash: TEST_CT_HASH,
67
+ utype,
68
+ requireConnected: undefined,
69
+ ...overrides,
70
+ });
71
+ }
72
+
73
+ // ---------------------------------------------------------------------------
74
+ // DecryptForTxBuilder
75
+ // ---------------------------------------------------------------------------
76
+
77
+ describe('DecryptForTxBuilder', () => {
78
+ // --- setChainId / getChainId ---
79
+
80
+ describe('setChainId / getChainId', () => {
81
+ it('should store and return the chainId', () => {
82
+ const builder = createTxBuilder({ chainId: undefined });
83
+ expect(builder.getChainId()).toBeUndefined();
84
+
85
+ builder.setChainId(11155111);
86
+ expect(builder.getChainId()).toBe(11155111);
87
+ });
88
+
89
+ it('should allow overriding', () => {
90
+ const builder = createTxBuilder({ chainId: 1 });
91
+ builder.setChainId(42);
92
+ expect(builder.getChainId()).toBe(42);
93
+ });
94
+ });
95
+
96
+ // --- setAccount / getAccount ---
97
+
98
+ describe('setAccount / getAccount', () => {
99
+ it('should store and return the account', () => {
100
+ const builder = createTxBuilder({ account: undefined });
101
+ expect(builder.getAccount()).toBeUndefined();
102
+
103
+ builder.setAccount('0xdeadbeef');
104
+ expect(builder.getAccount()).toBe('0xdeadbeef');
105
+ });
106
+
107
+ it('should allow overriding', () => {
108
+ const builder = createTxBuilder();
109
+ builder.setAccount('0xnewaccount');
110
+ expect(builder.getAccount()).toBe('0xnewaccount');
111
+ });
112
+ });
113
+
114
+ // --- withPermit / withoutPermit selection ---
115
+
116
+ describe('withPermit / withoutPermit selection', () => {
117
+ it('withPermit() should set permit selection', () => {
118
+ const builder = createTxBuilder();
119
+ const selected = builder.withPermit();
120
+ expect(selected).toBeDefined();
121
+ expect(selected.getPermit()).toBeUndefined();
122
+ expect(selected.getPermitHash()).toBeUndefined();
123
+ });
124
+
125
+ it('withPermit(hash) should store the permit hash', () => {
126
+ const builder = createTxBuilder();
127
+ const selected = builder.withPermit('0xmypermithash');
128
+ expect(selected.getPermitHash()).toBe('0xmypermithash');
129
+ expect(selected.getPermit()).toBeUndefined();
130
+ });
131
+
132
+ it('withoutPermit() should set permit selection', () => {
133
+ const builder = createTxBuilder();
134
+ const selected = builder.withoutPermit();
135
+ expect(selected).toBeDefined();
136
+ expect(selected.getPermit()).toBeUndefined();
137
+ expect(selected.getPermitHash()).toBeUndefined();
138
+ });
139
+
140
+ it('should throw when withPermit() is called twice', () => {
141
+ const builder = createTxBuilder();
142
+ builder.withPermit();
143
+
144
+ expect(() => (builder as any).withPermit()).toThrow('withPermit() can only be selected once');
145
+ });
146
+
147
+ it('should throw when withoutPermit() is called twice', () => {
148
+ const builder = createTxBuilder();
149
+ builder.withoutPermit();
150
+
151
+ expect(() => (builder as any).withoutPermit()).toThrow('withoutPermit() can only be selected once');
152
+ });
153
+
154
+ it('should throw when withPermit() is called after withoutPermit()', () => {
155
+ const builder = createTxBuilder();
156
+ builder.withoutPermit();
157
+
158
+ expect(() => (builder as any).withPermit()).toThrow('cannot call withPermit() after withoutPermit()');
159
+ });
160
+
161
+ it('should throw when withoutPermit() is called after withPermit()', () => {
162
+ const builder = createTxBuilder();
163
+ builder.withPermit();
164
+
165
+ expect(() => (builder as any).withoutPermit()).toThrow('cannot call withoutPermit() after withPermit()');
166
+ });
167
+ });
168
+
169
+ // --- chaining ---
170
+
171
+ describe('chaining', () => {
172
+ it('should return the builder from each setter for fluent chaining', () => {
173
+ const builder = createTxBuilder({ chainId: undefined, account: undefined });
174
+ const result = builder.setChainId(TEST_CHAIN_ID).setAccount(account.address).withPermit();
175
+
176
+ expect(result).toBeDefined();
177
+ expect(result.getChainId()).toBe(TEST_CHAIN_ID);
178
+ expect(result.getAccount()).toBe(account.address);
179
+ });
180
+
181
+ it('should allow setChainId and setAccount after withPermit', () => {
182
+ const builder = createTxBuilder({ chainId: undefined, account: undefined });
183
+ const selected = builder.withPermit();
184
+ selected.setChainId(99);
185
+ selected.setAccount('0xabc');
186
+
187
+ expect(selected.getChainId()).toBe(99);
188
+ expect(selected.getAccount()).toBe('0xabc');
189
+ });
190
+
191
+ it('should allow setChainId and setAccount after withoutPermit', () => {
192
+ const builder = createTxBuilder({ chainId: undefined, account: undefined });
193
+ const selected = builder.withoutPermit();
194
+ selected.setChainId(99);
195
+ selected.setAccount('0xabc');
196
+
197
+ expect(selected.getChainId()).toBe(99);
198
+ expect(selected.getAccount()).toBe('0xabc');
199
+ });
200
+ });
201
+
202
+ // --- execute error paths ---
203
+
204
+ describe('execute – error paths', () => {
205
+ it('should throw when execute() is called without permit selection', async () => {
206
+ const builder = createTxBuilder();
207
+
208
+ try {
209
+ await builder.execute();
210
+ expect.fail('Expected error');
211
+ } catch (error) {
212
+ expect((error as any).code).toBe(CofheErrorCode.InternalError);
213
+ expect((error as Error).message).toContain('missing permit selection');
214
+ }
215
+ });
216
+
217
+ it('should throw when withPermit() has no active permit', async () => {
218
+ const builder = createTxBuilder();
219
+
220
+ try {
221
+ await builder.withPermit().execute();
222
+ expect.fail('Expected PermitNotFound error');
223
+ } catch (error) {
224
+ expect((error as any).code).toBe(CofheErrorCode.PermitNotFound);
225
+ expect((error as Error).message).toContain('Active permit not found');
226
+ }
227
+ });
228
+
229
+ it('should throw when withPermit(hash) cannot find permit', async () => {
230
+ const builder = createTxBuilder();
231
+
232
+ try {
233
+ await builder.withPermit('0xnonexistent').execute();
234
+ expect.fail('Expected PermitNotFound error');
235
+ } catch (error) {
236
+ expect((error as any).code).toBe(CofheErrorCode.PermitNotFound);
237
+ expect((error as Error).message).toContain('Permit with hash');
238
+ }
239
+ });
240
+ });
241
+
242
+ // --- constructor error paths ---
243
+
244
+ describe('constructor – error paths', () => {
245
+ it('should throw when config is undefined', () => {
246
+ expect(
247
+ () =>
248
+ new DecryptForTxBuilder({
249
+ config: undefined,
250
+ publicClient,
251
+ walletClient,
252
+ chainId: TEST_CHAIN_ID,
253
+ account: account.address,
254
+ ctHash: TEST_CT_HASH,
255
+ requireConnected: undefined,
256
+ })
257
+ ).toThrow();
258
+ });
259
+ });
260
+ });
261
+
262
+ // ---------------------------------------------------------------------------
263
+ // DecryptForViewBuilder
264
+ // ---------------------------------------------------------------------------
265
+
266
+ describe('DecryptForViewBuilder', () => {
267
+ // --- setChainId / getChainId ---
268
+
269
+ describe('setChainId / getChainId', () => {
270
+ it('should store and return the chainId', () => {
271
+ const builder = createViewBuilder(FheTypes.Uint32, { chainId: undefined });
272
+ expect(builder.getChainId()).toBeUndefined();
273
+
274
+ builder.setChainId(11155111);
275
+ expect(builder.getChainId()).toBe(11155111);
276
+ });
277
+ });
278
+
279
+ // --- setAccount / getAccount ---
280
+
281
+ describe('setAccount / getAccount', () => {
282
+ it('should store and return the account', () => {
283
+ const builder = createViewBuilder(FheTypes.Uint32, { account: undefined });
284
+ expect(builder.getAccount()).toBeUndefined();
285
+
286
+ builder.setAccount('0xdeadbeef');
287
+ expect(builder.getAccount()).toBe('0xdeadbeef');
288
+ });
289
+ });
290
+
291
+ // --- withPermit ---
292
+
293
+ describe('withPermit', () => {
294
+ it('withPermit() should clear permit and hash', () => {
295
+ const builder = createViewBuilder(FheTypes.Uint32);
296
+ builder.withPermit();
297
+ expect(builder.getPermit()).toBeUndefined();
298
+ expect(builder.getPermitHash()).toBeUndefined();
299
+ });
300
+
301
+ it('withPermit(hash) should store the permit hash', () => {
302
+ const builder = createViewBuilder(FheTypes.Uint32);
303
+ builder.withPermit('0xmypermithash');
304
+ expect(builder.getPermitHash()).toBe('0xmypermithash');
305
+ expect(builder.getPermit()).toBeUndefined();
306
+ });
307
+
308
+ it('should allow overriding permit selection', () => {
309
+ const builder = createViewBuilder(FheTypes.Uint32);
310
+ builder.withPermit('0xfirst');
311
+ expect(builder.getPermitHash()).toBe('0xfirst');
312
+
313
+ builder.withPermit('0xsecond');
314
+ expect(builder.getPermitHash()).toBe('0xsecond');
315
+ });
316
+ });
317
+
318
+ // --- chaining ---
319
+
320
+ describe('chaining', () => {
321
+ it('should return the builder from each setter for fluent chaining', () => {
322
+ const builder = createViewBuilder(FheTypes.Uint32, { chainId: undefined, account: undefined });
323
+ const result = builder.setChainId(TEST_CHAIN_ID).setAccount(account.address).withPermit();
324
+
325
+ expect(result).toBeDefined();
326
+ expect(result.getChainId()).toBe(TEST_CHAIN_ID);
327
+ expect(result.getAccount()).toBe(account.address);
328
+ });
329
+ });
330
+
331
+ // --- execute error paths ---
332
+
333
+ describe('execute – error paths', () => {
334
+ it('should throw when active permit is not found', async () => {
335
+ const builder = createViewBuilder(FheTypes.Uint32);
336
+
337
+ try {
338
+ await builder.execute();
339
+ expect.fail('Expected PermitNotFound error');
340
+ } catch (error) {
341
+ expect((error as any).code).toBe(CofheErrorCode.PermitNotFound);
342
+ expect((error as Error).message).toContain('Active permit not found');
343
+ }
344
+ });
345
+
346
+ it('should throw when withPermit(hash) cannot find permit', async () => {
347
+ const builder = createViewBuilder(FheTypes.Uint32);
348
+ builder.withPermit('0xnonexistent');
349
+
350
+ try {
351
+ await builder.execute();
352
+ expect.fail('Expected PermitNotFound error');
353
+ } catch (error) {
354
+ expect((error as any).code).toBe(CofheErrorCode.PermitNotFound);
355
+ expect((error as Error).message).toContain('Permit with hash');
356
+ }
357
+ });
358
+
359
+ it('should throw for invalid utype', async () => {
360
+ const builder = createViewBuilder(999 as FheTypes);
361
+
362
+ try {
363
+ await builder.execute();
364
+ expect.fail('Expected InvalidUtype error');
365
+ } catch (error) {
366
+ expect((error as any).code).toBe(CofheErrorCode.InvalidUtype);
367
+ }
368
+ });
369
+ });
370
+
371
+ // --- constructor error paths ---
372
+
373
+ describe('constructor – error paths', () => {
374
+ it('should throw when config is undefined', () => {
375
+ expect(
376
+ () =>
377
+ new DecryptForViewBuilder({
378
+ config: undefined,
379
+ publicClient,
380
+ walletClient,
381
+ chainId: TEST_CHAIN_ID,
382
+ account: account.address,
383
+ ctHash: TEST_CT_HASH,
384
+ utype: FheTypes.Uint32,
385
+ requireConnected: undefined,
386
+ })
387
+ ).toThrow();
388
+ });
389
+ });
390
+ });