@cofhe/sdk 0.0.0-alpha-20260409113701

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 (132) hide show
  1. package/CHANGELOG.md +146 -0
  2. package/adapters/ethers5.test.ts +174 -0
  3. package/adapters/ethers5.ts +36 -0
  4. package/adapters/ethers6.test.ts +169 -0
  5. package/adapters/ethers6.ts +36 -0
  6. package/adapters/hardhat-node.ts +167 -0
  7. package/adapters/hardhat.hh2.test.ts +159 -0
  8. package/adapters/hardhat.ts +36 -0
  9. package/adapters/index.test.ts +20 -0
  10. package/adapters/index.ts +5 -0
  11. package/adapters/smartWallet.ts +99 -0
  12. package/adapters/test-utils.ts +53 -0
  13. package/adapters/types.ts +6 -0
  14. package/adapters/wagmi.test.ts +156 -0
  15. package/adapters/wagmi.ts +17 -0
  16. package/chains/chains/arbSepolia.ts +14 -0
  17. package/chains/chains/baseSepolia.ts +14 -0
  18. package/chains/chains/hardhat.ts +15 -0
  19. package/chains/chains/localcofhe.ts +14 -0
  20. package/chains/chains/sepolia.ts +14 -0
  21. package/chains/chains.test.ts +50 -0
  22. package/chains/defineChain.ts +18 -0
  23. package/chains/index.ts +35 -0
  24. package/chains/types.ts +32 -0
  25. package/core/baseBuilder.ts +119 -0
  26. package/core/client.test.ts +429 -0
  27. package/core/client.ts +341 -0
  28. package/core/clientTypes.ts +119 -0
  29. package/core/config.test.ts +242 -0
  30. package/core/config.ts +225 -0
  31. package/core/consts.ts +22 -0
  32. package/core/decrypt/MockThresholdNetworkAbi.ts +179 -0
  33. package/core/decrypt/cofheMocksDecryptForTx.ts +84 -0
  34. package/core/decrypt/cofheMocksDecryptForView.ts +48 -0
  35. package/core/decrypt/decryptForTxBuilder.ts +359 -0
  36. package/core/decrypt/decryptForViewBuilder.ts +332 -0
  37. package/core/decrypt/decryptUtils.ts +28 -0
  38. package/core/decrypt/pollCallbacks.test.ts +194 -0
  39. package/core/decrypt/polling.ts +14 -0
  40. package/core/decrypt/tnDecryptUtils.ts +65 -0
  41. package/core/decrypt/tnDecryptV1.ts +171 -0
  42. package/core/decrypt/tnDecryptV2.ts +365 -0
  43. package/core/decrypt/tnSealOutputV1.ts +59 -0
  44. package/core/decrypt/tnSealOutputV2.ts +324 -0
  45. package/core/decrypt/verifyDecryptResult.ts +52 -0
  46. package/core/encrypt/MockZkVerifierAbi.ts +106 -0
  47. package/core/encrypt/cofheMocksZkVerifySign.ts +281 -0
  48. package/core/encrypt/encryptInputsBuilder.test.ts +747 -0
  49. package/core/encrypt/encryptInputsBuilder.ts +583 -0
  50. package/core/encrypt/encryptUtils.ts +67 -0
  51. package/core/encrypt/zkPackProveVerify.ts +335 -0
  52. package/core/error.ts +168 -0
  53. package/core/fetchKeys.test.ts +195 -0
  54. package/core/fetchKeys.ts +144 -0
  55. package/core/index.ts +106 -0
  56. package/core/keyStore.test.ts +226 -0
  57. package/core/keyStore.ts +154 -0
  58. package/core/permits.test.ts +493 -0
  59. package/core/permits.ts +201 -0
  60. package/core/types.ts +419 -0
  61. package/core/utils.ts +130 -0
  62. package/dist/adapters.cjs +88 -0
  63. package/dist/adapters.d.cts +14576 -0
  64. package/dist/adapters.d.ts +14576 -0
  65. package/dist/adapters.js +83 -0
  66. package/dist/chains.cjs +111 -0
  67. package/dist/chains.d.cts +121 -0
  68. package/dist/chains.d.ts +121 -0
  69. package/dist/chains.js +1 -0
  70. package/dist/chunk-36FBWLUS.js +3310 -0
  71. package/dist/chunk-7HLGHV67.js +990 -0
  72. package/dist/chunk-TBLR7NNE.js +102 -0
  73. package/dist/clientTypes-AVSCBet7.d.cts +998 -0
  74. package/dist/clientTypes-flH1ju82.d.ts +998 -0
  75. package/dist/core.cjs +4362 -0
  76. package/dist/core.d.cts +138 -0
  77. package/dist/core.d.ts +138 -0
  78. package/dist/core.js +3 -0
  79. package/dist/node.cjs +4225 -0
  80. package/dist/node.d.cts +22 -0
  81. package/dist/node.d.ts +22 -0
  82. package/dist/node.js +91 -0
  83. package/dist/permit-jRirYqFt.d.cts +376 -0
  84. package/dist/permit-jRirYqFt.d.ts +376 -0
  85. package/dist/permits.cjs +1025 -0
  86. package/dist/permits.d.cts +353 -0
  87. package/dist/permits.d.ts +353 -0
  88. package/dist/permits.js +1 -0
  89. package/dist/types-YiAC4gig.d.cts +33 -0
  90. package/dist/types-YiAC4gig.d.ts +33 -0
  91. package/dist/web.cjs +4434 -0
  92. package/dist/web.d.cts +42 -0
  93. package/dist/web.d.ts +42 -0
  94. package/dist/web.js +256 -0
  95. package/dist/zkProve.worker.cjs +93 -0
  96. package/dist/zkProve.worker.d.cts +2 -0
  97. package/dist/zkProve.worker.d.ts +2 -0
  98. package/dist/zkProve.worker.js +91 -0
  99. package/node/client.test.ts +159 -0
  100. package/node/config.test.ts +68 -0
  101. package/node/encryptInputs.test.ts +155 -0
  102. package/node/index.ts +97 -0
  103. package/node/storage.ts +51 -0
  104. package/package.json +121 -0
  105. package/permits/index.ts +68 -0
  106. package/permits/localstorage.test.ts +113 -0
  107. package/permits/onchain-utils.ts +221 -0
  108. package/permits/permit.test.ts +534 -0
  109. package/permits/permit.ts +386 -0
  110. package/permits/sealing.test.ts +84 -0
  111. package/permits/sealing.ts +131 -0
  112. package/permits/signature.ts +79 -0
  113. package/permits/store.test.ts +88 -0
  114. package/permits/store.ts +156 -0
  115. package/permits/test-utils.ts +28 -0
  116. package/permits/types.ts +204 -0
  117. package/permits/utils.ts +58 -0
  118. package/permits/validation.test.ts +361 -0
  119. package/permits/validation.ts +327 -0
  120. package/web/client.web.test.ts +159 -0
  121. package/web/config.web.test.ts +69 -0
  122. package/web/const.ts +2 -0
  123. package/web/encryptInputs.web.test.ts +172 -0
  124. package/web/index.ts +166 -0
  125. package/web/storage.ts +49 -0
  126. package/web/worker.builder.web.test.ts +148 -0
  127. package/web/worker.config.web.test.ts +329 -0
  128. package/web/worker.output.web.test.ts +84 -0
  129. package/web/workerManager.test.ts +80 -0
  130. package/web/workerManager.ts +214 -0
  131. package/web/workerManager.web.test.ts +114 -0
  132. package/web/zkProve.worker.ts +133 -0
package/core/client.ts ADDED
@@ -0,0 +1,341 @@
1
+ import type { CreateSelfPermitOptions, CreateSharingPermitOptions, ImportSharedPermitOptions } from '@/permits';
2
+
3
+ import { createStore } from 'zustand/vanilla';
4
+ import { type Hex, type PublicClient, type WalletClient } from 'viem';
5
+ import { CofheError, CofheErrorCode } from './error.js';
6
+ import { EncryptInputsBuilder } from './encrypt/encryptInputsBuilder.js';
7
+ import { createKeysStore } from './keyStore.js';
8
+ import { permits } from './permits.js';
9
+ import { DecryptForViewBuilder } from './decrypt/decryptForViewBuilder.js';
10
+ import { DecryptForTxBuilder, type DecryptForTxBuilderUnset } from './decrypt/decryptForTxBuilder.js';
11
+ import { verifyDecryptResult as verifyDecryptResultStandalone } from './decrypt/verifyDecryptResult.js';
12
+ import { getPublicClientChainID, getWalletClientAccount } from './utils.js';
13
+ import type { CofheClientConnectionState, CofheClientParams, CofheClient, CofheClientPermits } from './clientTypes.js';
14
+ import type { EncryptableItem, FheTypes } from './types.js';
15
+ import type { CofheConfig } from './config.js';
16
+
17
+ export const InitialConnectStore: CofheClientConnectionState = {
18
+ connected: false,
19
+ connecting: false,
20
+ connectError: undefined,
21
+ chainId: undefined,
22
+ account: undefined,
23
+ publicClient: undefined,
24
+ walletClient: undefined,
25
+ };
26
+
27
+ /**
28
+ * Creates a CoFHE client instance (base implementation)
29
+ * @param {CofheClientParams} opts - Initialization options including config and platform-specific serializers
30
+ * @returns {CofheClient} - The CoFHE client instance
31
+ */
32
+ export function createCofheClientBase<TConfig extends CofheConfig>(
33
+ opts: CofheClientParams<TConfig>
34
+ ): CofheClient<TConfig> {
35
+ // Create keysStorage instance using configured storage
36
+ const keysStorage = createKeysStore(opts.config.fheKeyStorage);
37
+
38
+ // Zustand store for reactive state management
39
+
40
+ const connectStore = createStore<CofheClientConnectionState>(() => InitialConnectStore);
41
+
42
+ // Minimal cancellation mechanism: incremented on each connect/disconnect.
43
+ // If a connect finishes after a disconnect, it must not overwrite the disconnected state.
44
+ let connectAttemptId = 0;
45
+
46
+ // Helper to update state
47
+ const updateConnectState = (partial: Partial<CofheClientConnectionState>) => {
48
+ connectStore.setState((state) => ({ ...state, ...partial }));
49
+ };
50
+
51
+ // Called before any operation, throws of connection not yet established
52
+ const _requireConnected = () => {
53
+ const state = connectStore.getState();
54
+ const notConnected =
55
+ !state.connected || !state.account || !state.chainId || !state.publicClient || !state.walletClient;
56
+ if (notConnected) {
57
+ throw new CofheError({
58
+ code: CofheErrorCode.NotConnected,
59
+ message: 'Client must be connected, account and chainId must be initialized',
60
+ hint: 'Ensure client.connect() has been called and awaited.',
61
+ context: {
62
+ connected: state.connected,
63
+ account: state.account,
64
+ chainId: state.chainId,
65
+ publicClient: state.publicClient,
66
+ walletClient: state.walletClient,
67
+ },
68
+ });
69
+ }
70
+ };
71
+
72
+ // LIFECYCLE
73
+
74
+ async function connect(publicClient: PublicClient, walletClient: WalletClient) {
75
+ const state = connectStore.getState();
76
+
77
+ // Exit if already connected and clients are the same
78
+ if (state.connected && state.publicClient === publicClient && state.walletClient === walletClient) return;
79
+
80
+ connectAttemptId += 1;
81
+ const localAttemptId = connectAttemptId;
82
+
83
+ // Set connecting state
84
+ updateConnectState({
85
+ ...InitialConnectStore,
86
+ connecting: true,
87
+ });
88
+
89
+ // Fetch chainId and account
90
+ try {
91
+ const chainId = await getPublicClientChainID(publicClient);
92
+ const account = await getWalletClientAccount(walletClient);
93
+
94
+ // If a disconnect (or a newer connect) happened while awaiting, ignore this completion.
95
+ if (localAttemptId !== connectAttemptId) return;
96
+
97
+ updateConnectState({
98
+ connected: true,
99
+ connecting: false,
100
+ connectError: undefined,
101
+ chainId,
102
+ account,
103
+ publicClient,
104
+ walletClient,
105
+ });
106
+ } catch (e) {
107
+ // Ignore stale errors too.
108
+ if (localAttemptId !== connectAttemptId) return;
109
+
110
+ updateConnectState({
111
+ ...InitialConnectStore,
112
+ connectError: e,
113
+ });
114
+ throw e;
115
+ }
116
+ }
117
+
118
+ function disconnect() {
119
+ connectAttemptId += 1;
120
+ updateConnectState({ ...InitialConnectStore });
121
+ }
122
+
123
+ // CLIENT OPERATIONS
124
+
125
+ function encryptInputs<T extends EncryptableItem[]>(inputs: [...T]): EncryptInputsBuilder<[...T]> {
126
+ const state = connectStore.getState();
127
+
128
+ return new EncryptInputsBuilder({
129
+ inputs,
130
+ account: state.account ?? undefined,
131
+ chainId: state.chainId ?? undefined,
132
+
133
+ config: opts.config,
134
+ publicClient: state.publicClient ?? undefined,
135
+ walletClient: state.walletClient ?? undefined,
136
+ zkvWalletClient: opts.config._internal?.zkvWalletClient,
137
+
138
+ tfhePublicKeyDeserializer: opts.tfhePublicKeyDeserializer,
139
+ compactPkeCrsDeserializer: opts.compactPkeCrsDeserializer,
140
+ zkBuilderAndCrsGenerator: opts.zkBuilderAndCrsGenerator,
141
+ initTfhe: opts.initTfhe,
142
+ zkProveWorkerFn: opts.zkProveWorkerFn,
143
+
144
+ keysStorage,
145
+
146
+ requireConnected: _requireConnected,
147
+ });
148
+ }
149
+
150
+ function decryptForView<U extends FheTypes>(ctHash: bigint | string, utype: U): DecryptForViewBuilder<U> {
151
+ const state = connectStore.getState();
152
+
153
+ return new DecryptForViewBuilder({
154
+ ctHash,
155
+ utype,
156
+ chainId: state.chainId,
157
+ account: state.account,
158
+
159
+ config: opts.config,
160
+ publicClient: state.publicClient,
161
+ walletClient: state.walletClient,
162
+
163
+ requireConnected: _requireConnected,
164
+ });
165
+ }
166
+
167
+ function decryptForTx(ctHash: bigint | string): DecryptForTxBuilderUnset {
168
+ const state = connectStore.getState();
169
+
170
+ return new DecryptForTxBuilder({
171
+ ctHash,
172
+ chainId: state.chainId,
173
+ account: state.account,
174
+
175
+ config: opts.config,
176
+ publicClient: state.publicClient,
177
+ walletClient: state.walletClient,
178
+
179
+ requireConnected: _requireConnected,
180
+ });
181
+ }
182
+
183
+ // VERIFY DECRYPT RESULT
184
+ function verifyDecryptResult(handle: bigint | string, cleartext: bigint, signature: Hex): Promise<boolean> {
185
+ _requireConnected();
186
+ const { publicClient } = connectStore.getState();
187
+ return verifyDecryptResultStandalone(handle, cleartext, signature, publicClient!);
188
+ }
189
+
190
+ // PERMITS - Context-aware wrapper
191
+
192
+ const _getChainIdAndAccount = (chainId?: number, account?: string) => {
193
+ const state = connectStore.getState();
194
+ const _chainId = chainId ?? state.chainId;
195
+ const _account = account ?? state.account;
196
+
197
+ if (_chainId == null || _account == null) {
198
+ throw new CofheError({
199
+ code: CofheErrorCode.NotConnected,
200
+ message: 'ChainId or account not available.',
201
+ hint: 'Ensure client.connect() has been called, or provide chainId and account explicitly.',
202
+ context: {
203
+ chainId: _chainId,
204
+ account: _account,
205
+ },
206
+ });
207
+ }
208
+
209
+ return { chainId: _chainId, account: _account };
210
+ };
211
+
212
+ const clientPermits: CofheClientPermits = {
213
+ // Pass through store access
214
+ getSnapshot: permits.getSnapshot,
215
+ subscribe: permits.subscribe,
216
+
217
+ // Creation methods (require connection)
218
+ createSelf: async (
219
+ options: CreateSelfPermitOptions,
220
+ clients?: { publicClient: PublicClient; walletClient: WalletClient }
221
+ ) => {
222
+ _requireConnected();
223
+ const { publicClient, walletClient } = clients ?? connectStore.getState();
224
+ return permits.createSelf(options, publicClient!, walletClient!);
225
+ },
226
+
227
+ createSharing: async (
228
+ options: CreateSharingPermitOptions,
229
+ clients?: { publicClient: PublicClient; walletClient: WalletClient }
230
+ ) => {
231
+ _requireConnected();
232
+ const { publicClient, walletClient } = clients ?? connectStore.getState();
233
+ return permits.createSharing(options, publicClient!, walletClient!);
234
+ },
235
+
236
+ importShared: async (
237
+ options: ImportSharedPermitOptions | string,
238
+ clients?: { publicClient: PublicClient; walletClient: WalletClient }
239
+ ) => {
240
+ _requireConnected();
241
+ const { publicClient, walletClient } = clients ?? connectStore.getState();
242
+ return permits.importShared(options, publicClient!, walletClient!);
243
+ },
244
+
245
+ // Get or create methods (require connection)
246
+ getOrCreateSelfPermit: async (chainId?: number, account?: string, options?: CreateSelfPermitOptions) => {
247
+ _requireConnected();
248
+ const { chainId: _chainId, account: _account } = _getChainIdAndAccount(chainId, account);
249
+ const { publicClient, walletClient } = connectStore.getState();
250
+ return permits.getOrCreateSelfPermit(publicClient!, walletClient!, _chainId, _account, options);
251
+ },
252
+
253
+ getOrCreateSharingPermit: async (options: CreateSharingPermitOptions, chainId?: number, account?: string) => {
254
+ _requireConnected();
255
+ const { chainId: _chainId, account: _account } = _getChainIdAndAccount(chainId, account);
256
+ const { publicClient, walletClient } = connectStore.getState();
257
+ return permits.getOrCreateSharingPermit(publicClient!, walletClient!, options, _chainId, _account);
258
+ },
259
+
260
+ // Retrieval methods (auto-fill chainId/account)
261
+ getPermit: (hash: string, chainId?: number, account?: string) => {
262
+ const { chainId: _chainId, account: _account } = _getChainIdAndAccount(chainId, account);
263
+ return permits.getPermit(_chainId, _account, hash);
264
+ },
265
+
266
+ getPermits: (chainId?: number, account?: string) => {
267
+ const { chainId: _chainId, account: _account } = _getChainIdAndAccount(chainId, account);
268
+ return permits.getPermits(_chainId, _account);
269
+ },
270
+
271
+ getActivePermit: (chainId?: number, account?: string) => {
272
+ const { chainId: _chainId, account: _account } = _getChainIdAndAccount(chainId, account);
273
+ return permits.getActivePermit(_chainId, _account);
274
+ },
275
+
276
+ getActivePermitHash: (chainId?: number, account?: string) => {
277
+ const { chainId: _chainId, account: _account } = _getChainIdAndAccount(chainId, account);
278
+ return permits.getActivePermitHash(_chainId, _account);
279
+ },
280
+
281
+ // Mutation methods (auto-fill chainId/account)
282
+ selectActivePermit: (hash: string, chainId?: number, account?: string) => {
283
+ const { chainId: _chainId, account: _account } = _getChainIdAndAccount(chainId, account);
284
+ return permits.selectActivePermit(_chainId, _account, hash);
285
+ },
286
+
287
+ removePermit: async (hash: string, chainId?: number, account?: string) => {
288
+ const { chainId: _chainId, account: _account } = _getChainIdAndAccount(chainId, account);
289
+ return permits.removePermit(_chainId, _account, hash);
290
+ },
291
+
292
+ removeActivePermit: async (chainId?: number, account?: string) => {
293
+ const { chainId: _chainId, account: _account } = _getChainIdAndAccount(chainId, account);
294
+ return permits.removeActivePermit(_chainId, _account);
295
+ },
296
+
297
+ // Utils (no context needed)
298
+ getHash: permits.getHash,
299
+ serialize: permits.serialize,
300
+ deserialize: permits.deserialize,
301
+ };
302
+
303
+ return {
304
+ // Zustand reactive accessors (don't export store directly to prevent mutation)
305
+ getSnapshot: connectStore.getState,
306
+ subscribe: connectStore.subscribe,
307
+
308
+ // flags (read-only: reflect snapshot)
309
+ get connection() {
310
+ return connectStore.getState();
311
+ },
312
+ get connected() {
313
+ return connectStore.getState().connected;
314
+ },
315
+ get connecting() {
316
+ return connectStore.getState().connecting;
317
+ },
318
+
319
+ // config & platform-specific (read-only)
320
+ config: opts.config,
321
+
322
+ connect,
323
+ disconnect,
324
+ encryptInputs,
325
+ decryptForView,
326
+ /**
327
+ * @deprecated Use `decryptForView` instead. Kept for backward compatibility.
328
+ */
329
+ decryptHandle: decryptForView,
330
+ decryptForTx,
331
+ verifyDecryptResult,
332
+ permits: clientPermits,
333
+
334
+ // Add SDK-specific methods below that require connection
335
+ // Example:
336
+ // async encryptData(data: unknown) {
337
+ // requireConnected();
338
+ // // Use state.publicClient and state.walletClient for implementation
339
+ // },
340
+ };
341
+ }
@@ -0,0 +1,119 @@
1
+ // TODO: Extract client types to its own file, keep this one as primitives
2
+ import { type Hex, type PublicClient, type WalletClient } from 'viem';
3
+ import { type CofheConfig } from './config.js';
4
+ import { type DecryptForViewBuilder } from './decrypt/decryptForViewBuilder.js';
5
+ import { type DecryptForTxBuilderUnset } from './decrypt/decryptForTxBuilder.js';
6
+ import { type EncryptInputsBuilder } from './encrypt/encryptInputsBuilder.js';
7
+ import { type ZkBuilderAndCrsGenerator, type ZkProveWorkerFunction } from './encrypt/zkPackProveVerify.js';
8
+ import { type FheKeyDeserializer } from './fetchKeys.js';
9
+ import { permits } from './permits.js';
10
+ import type { EncryptableItem, FheTypes, TfheInitializer } from './types.js';
11
+ import type { PermitUtils } from 'permits/permit.js';
12
+ import type {
13
+ CreateSelfPermitOptions,
14
+ Permit,
15
+ CreateSharingPermitOptions,
16
+ ImportSharedPermitOptions,
17
+ SharingPermit,
18
+ RecipientPermit,
19
+ SelfPermit,
20
+ } from 'permits/types.js';
21
+
22
+ // CLIENT
23
+
24
+ export type CofheClient<TConfig extends CofheConfig = CofheConfig> = {
25
+ // --- state access ---
26
+ getSnapshot(): CofheClientConnectionState;
27
+ subscribe(listener: Listener): () => void;
28
+
29
+ // --- convenience flags (read-only) ---
30
+ readonly connection: CofheClientConnectionState;
31
+ readonly connected: boolean;
32
+ readonly connecting: boolean;
33
+
34
+ // --- config & platform-specific ---
35
+ readonly config: TConfig;
36
+
37
+ connect(publicClient: PublicClient, walletClient: WalletClient): Promise<void>;
38
+ /**
39
+ * Clears the current connection state (account/chainId/clients) and marks the client as disconnected.
40
+ *
41
+ * This does not delete persisted permits or stored FHE keys; it only resets the in-memory connection.
42
+ */
43
+ disconnect(): void;
44
+ /**
45
+ * Types docstring
46
+ */
47
+ encryptInputs<T extends EncryptableItem[]>(inputs: [...T]): EncryptInputsBuilder<[...T]>;
48
+ /**
49
+ * @deprecated Use `decryptForView` instead. Kept for backward compatibility.
50
+ */
51
+ decryptHandle<U extends FheTypes>(ctHash: bigint | string, utype: U): DecryptForViewBuilder<U>;
52
+ decryptForView<U extends FheTypes>(ctHash: bigint | string, utype: U): DecryptForViewBuilder<U>;
53
+ decryptForTx(ctHash: bigint | string): DecryptForTxBuilderUnset;
54
+ verifyDecryptResult(handle: bigint | string, cleartext: bigint, signature: Hex): Promise<boolean>;
55
+ permits: CofheClientPermits;
56
+ };
57
+
58
+ export type CofheClientConnectionState = {
59
+ connected: boolean;
60
+ connecting: boolean;
61
+ connectError: unknown | undefined;
62
+ chainId: number | undefined;
63
+ account: `0x${string}` | undefined;
64
+ publicClient: PublicClient | undefined;
65
+ walletClient: WalletClient | undefined;
66
+ };
67
+
68
+ type Listener = (snapshot: CofheClientConnectionState) => void;
69
+
70
+ export type CofheClientPermitsClients = {
71
+ publicClient: PublicClient;
72
+ walletClient: WalletClient;
73
+ };
74
+
75
+ export type CofheClientPermits = {
76
+ getSnapshot: typeof permits.getSnapshot;
77
+ subscribe: typeof permits.subscribe;
78
+
79
+ // Creation methods (require connection, no params)
80
+ createSelf: (options: CreateSelfPermitOptions, clients?: CofheClientPermitsClients) => Promise<SelfPermit>;
81
+ createSharing: (options: CreateSharingPermitOptions, clients?: CofheClientPermitsClients) => Promise<SharingPermit>;
82
+ importShared: (
83
+ options: ImportSharedPermitOptions | string,
84
+ clients?: CofheClientPermitsClients
85
+ ) => Promise<RecipientPermit>;
86
+
87
+ // Retrieval methods (chainId/account optional)
88
+ getPermit: (hash: string, chainId?: number, account?: string) => Permit | undefined;
89
+ getPermits: (chainId?: number, account?: string) => Record<string, Permit>;
90
+ getActivePermit: (chainId?: number, account?: string) => Permit | undefined;
91
+ getActivePermitHash: (chainId?: number, account?: string) => string | undefined;
92
+
93
+ // Get or create methods (get active or create new, chainId/account optional)
94
+ getOrCreateSelfPermit: (chainId?: number, account?: string, options?: CreateSelfPermitOptions) => Promise<Permit>;
95
+ getOrCreateSharingPermit: (
96
+ options: CreateSharingPermitOptions,
97
+ chainId?: number,
98
+ account?: string
99
+ ) => Promise<Permit>;
100
+
101
+ // Mutation methods (chainId/account optional)
102
+ selectActivePermit: (hash: string, chainId?: number, account?: string) => void;
103
+ removePermit: (hash: string, chainId?: number, account?: string) => void;
104
+ removeActivePermit: (chainId?: number, account?: string) => void;
105
+
106
+ // Utils
107
+ getHash: typeof PermitUtils.getHash;
108
+ serialize: typeof PermitUtils.serialize;
109
+ deserialize: typeof PermitUtils.deserialize;
110
+ };
111
+
112
+ export type CofheClientParams<TConfig extends CofheConfig> = {
113
+ config: TConfig;
114
+ zkBuilderAndCrsGenerator: ZkBuilderAndCrsGenerator;
115
+ tfhePublicKeyDeserializer: FheKeyDeserializer;
116
+ compactPkeCrsDeserializer: FheKeyDeserializer;
117
+ initTfhe: TfheInitializer;
118
+ zkProveWorkerFn?: ZkProveWorkerFunction;
119
+ };
@@ -0,0 +1,242 @@
1
+ import { sepolia, hardhat } from '@/chains';
2
+
3
+ import { describe, it, expect, vi } from 'vitest';
4
+ import {
5
+ createCofheConfigBase,
6
+ getCofheConfigItem,
7
+ type CofheInputConfig,
8
+ getSupportedChainOrThrow,
9
+ getCoFheUrlOrThrow,
10
+ getZkVerifierUrlOrThrow,
11
+ getThresholdNetworkUrlOrThrow,
12
+ } from './config.js';
13
+
14
+ describe('createCofheConfigBase', () => {
15
+ const validBaseConfig: CofheInputConfig = {
16
+ supportedChains: [],
17
+ };
18
+
19
+ const setNestedValue = (obj: any, path: string, value: any): void => {
20
+ const keys = path.split('.');
21
+ const lastKey = keys.pop()!;
22
+ const target = keys.reduce((acc, key) => {
23
+ if (!acc[key]) acc[key] = {};
24
+ return acc[key];
25
+ }, obj);
26
+ target[lastKey] = value;
27
+ };
28
+
29
+ const getNestedValue = (obj: any, path: string): any => {
30
+ return path.split('.').reduce((acc, key) => acc?.[key], obj);
31
+ };
32
+
33
+ const expectInvalidConfigItem = (path: string, value: any, log = false): void => {
34
+ const config = { ...validBaseConfig };
35
+ setNestedValue(config, path, value);
36
+ if (log) {
37
+ console.log('expect config invalid', path, value, config);
38
+ try {
39
+ createCofheConfigBase(config as CofheInputConfig);
40
+ } catch (e) {
41
+ console.log('expect config invalid', path, value, config, e);
42
+ }
43
+ }
44
+ expect(() => createCofheConfigBase(config as CofheInputConfig)).toThrow('Invalid cofhe configuration:');
45
+ };
46
+
47
+ const expectValidConfigItem = (path: string, value: any, expectedValue: any): void => {
48
+ const config = { ...validBaseConfig };
49
+ setNestedValue(config, path, value);
50
+ const result = createCofheConfigBase(config);
51
+ expect(getNestedValue(result, path)).toEqual(expectedValue);
52
+ };
53
+
54
+ it('environment', () => {
55
+ expectInvalidConfigItem('environment', 'not-a-valid-environment');
56
+ expectInvalidConfigItem('environment', 123);
57
+ expectInvalidConfigItem('environment', {});
58
+
59
+ expectValidConfigItem('environment', 'node', 'node');
60
+ expectValidConfigItem('environment', 'hardhat', 'hardhat');
61
+ expectValidConfigItem('environment', 'web', 'web');
62
+ expectValidConfigItem('environment', 'react', 'react');
63
+ });
64
+
65
+ it('supportedChains', () => {
66
+ expectInvalidConfigItem('supportedChains', {});
67
+ expectInvalidConfigItem('supportedChains', 'not-an-array');
68
+ expectInvalidConfigItem('supportedChains', null);
69
+ expectInvalidConfigItem('supportedChains', undefined);
70
+
71
+ expectValidConfigItem('supportedChains', [sepolia], [sepolia]);
72
+ expectValidConfigItem('supportedChains', [sepolia, hardhat], [sepolia, hardhat]);
73
+ });
74
+
75
+ it('defaultPermitExpiration', () => {
76
+ expectInvalidConfigItem('defaultPermitExpiration', 'not-a-number');
77
+ expectInvalidConfigItem('defaultPermitExpiration', null);
78
+
79
+ expectValidConfigItem('defaultPermitExpiration', 5, 5);
80
+ expectValidConfigItem('defaultPermitExpiration', undefined, 60 * 60 * 24 * 30);
81
+ });
82
+
83
+ it('fheKeyStorage', async () => {
84
+ expectInvalidConfigItem('fheKeyStorage', 'not-an-object');
85
+
86
+ expectValidConfigItem('fheKeyStorage', undefined, null);
87
+ expectValidConfigItem('fheKeyStorage', null, null);
88
+
89
+ let getItemCalled = false;
90
+ let setItemCalled = false;
91
+ let removeItemCalled = false;
92
+
93
+ const fakeStorage = {
94
+ getItem: (name: string) => {
95
+ getItemCalled = true;
96
+ return Promise.resolve(null);
97
+ },
98
+ setItem: (name: string, value: any) => {
99
+ setItemCalled = true;
100
+ return Promise.resolve();
101
+ },
102
+ removeItem: (name: string) => {
103
+ removeItemCalled = true;
104
+ return Promise.resolve();
105
+ },
106
+ };
107
+
108
+ const config = { ...validBaseConfig, fheKeyStorage: fakeStorage };
109
+ const result = createCofheConfigBase(config);
110
+
111
+ expect(result.fheKeyStorage).not.toBeNull();
112
+ await result.fheKeyStorage!.getItem('test');
113
+ await result.fheKeyStorage!.setItem('test', 'test');
114
+ await result.fheKeyStorage!.removeItem('test');
115
+
116
+ expect(getItemCalled).toBe(true);
117
+ expect(setItemCalled).toBe(true);
118
+ expect(removeItemCalled).toBe(true);
119
+
120
+ const invalidStorageNotAFunction = {
121
+ getItem: 'not-a-function',
122
+ setItem: 'not-a-function',
123
+ removeItem: 'not-a-function',
124
+ };
125
+
126
+ expectInvalidConfigItem('fheKeyStorage', invalidStorageNotAFunction);
127
+ });
128
+
129
+ it('mocks', () => {
130
+ expectInvalidConfigItem('mocks', 'not-an-object');
131
+ expectInvalidConfigItem('mocks', null);
132
+ });
133
+
134
+ it('mocks.decryptDelay', () => {
135
+ expectInvalidConfigItem('mocks.decryptDelay', 'not-a-number');
136
+ expectInvalidConfigItem('mocks.decryptDelay', null);
137
+
138
+ expectValidConfigItem('mocks.decryptDelay', undefined, 0);
139
+ expectValidConfigItem('mocks.decryptDelay', 1000, 1000);
140
+ });
141
+
142
+ it('mocks.encryptDelay', () => {
143
+ expectInvalidConfigItem('mocks.encryptDelay', 'not-a-number');
144
+ expectInvalidConfigItem('mocks.encryptDelay', null);
145
+ expectInvalidConfigItem('mocks.encryptDelay', [100, 100, 100]); // wrong tuple length
146
+ expectInvalidConfigItem('mocks.encryptDelay', ['a', 'b', 'c', 'd', 'e']); // non-number elements
147
+
148
+ expectValidConfigItem('mocks.encryptDelay', undefined, [100, 100, 100, 500, 500]);
149
+ expectValidConfigItem('mocks.encryptDelay', 200, 200);
150
+ expectValidConfigItem('mocks.encryptDelay', 0, 0);
151
+ expectValidConfigItem('mocks.encryptDelay', [10, 20, 30, 40, 50], [10, 20, 30, 40, 50]);
152
+ });
153
+
154
+ it('useWorkers', () => {
155
+ expectInvalidConfigItem('useWorkers', 'not-a-boolean');
156
+ expectInvalidConfigItem('useWorkers', null);
157
+ expectInvalidConfigItem('useWorkers', 123);
158
+ expectInvalidConfigItem('useWorkers', {});
159
+
160
+ expectValidConfigItem('useWorkers', true, true);
161
+ expectValidConfigItem('useWorkers', false, false);
162
+ expectValidConfigItem('useWorkers', undefined, true); // defaults to true
163
+ });
164
+
165
+ it('should get config item', () => {
166
+ const config: CofheInputConfig = {
167
+ supportedChains: [sepolia],
168
+ };
169
+
170
+ const result = createCofheConfigBase(config);
171
+
172
+ const supportedChains = getCofheConfigItem(result, 'supportedChains');
173
+ expect(supportedChains).toEqual(config.supportedChains);
174
+ });
175
+ });
176
+
177
+ describe('Config helper functions', () => {
178
+ const config = createCofheConfigBase({
179
+ supportedChains: [sepolia, hardhat],
180
+ });
181
+
182
+ describe('getSupportedChainOrThrow', () => {
183
+ it('should return chain when found', () => {
184
+ expect(getSupportedChainOrThrow(config, sepolia.id)).toEqual(sepolia);
185
+ });
186
+
187
+ it('should throw UnsupportedChain error when not found', () => {
188
+ expect(() => getSupportedChainOrThrow(config, 999999)).toThrow();
189
+ });
190
+ });
191
+
192
+ describe('getCoFheUrlOrThrow', () => {
193
+ it('should return coFheUrl', () => {
194
+ expect(getCoFheUrlOrThrow(config, sepolia.id)).toBe(sepolia.coFheUrl);
195
+ });
196
+
197
+ it('should throw when chain not found', () => {
198
+ expect(() => getCoFheUrlOrThrow(config, 999999)).toThrow();
199
+ });
200
+
201
+ it('should throw MissingConfig when url not set', () => {
202
+ const configWithoutUrl = createCofheConfigBase({
203
+ supportedChains: [{ ...sepolia, coFheUrl: undefined } as any],
204
+ });
205
+ expect(() => getCoFheUrlOrThrow(configWithoutUrl, sepolia.id)).toThrow();
206
+ });
207
+ });
208
+
209
+ describe('getZkVerifierUrlOrThrow', () => {
210
+ it('should return verifierUrl', () => {
211
+ expect(getZkVerifierUrlOrThrow(config, sepolia.id)).toBe(sepolia.verifierUrl);
212
+ });
213
+
214
+ it('should throw when chain not found', () => {
215
+ expect(() => getZkVerifierUrlOrThrow(config, 999999)).toThrow();
216
+ });
217
+
218
+ it('should throw ZkVerifierUrlUninitialized when url not set', () => {
219
+ const configWithoutUrl = createCofheConfigBase({
220
+ supportedChains: [{ ...sepolia, verifierUrl: undefined } as any],
221
+ });
222
+ expect(() => getZkVerifierUrlOrThrow(configWithoutUrl, sepolia.id)).toThrow();
223
+ });
224
+ });
225
+
226
+ describe('getThresholdNetworkUrlOrThrow', () => {
227
+ it('should return thresholdNetworkUrl', () => {
228
+ expect(getThresholdNetworkUrlOrThrow(config, sepolia.id)).toBe(sepolia.thresholdNetworkUrl);
229
+ });
230
+
231
+ it('should throw when chain not found', () => {
232
+ expect(() => getThresholdNetworkUrlOrThrow(config, 999999)).toThrow();
233
+ });
234
+
235
+ it('should throw ThresholdNetworkUrlUninitialized when url not set', () => {
236
+ const configWithoutUrl = createCofheConfigBase({
237
+ supportedChains: [{ ...sepolia, thresholdNetworkUrl: undefined } as any],
238
+ });
239
+ expect(() => getThresholdNetworkUrlOrThrow(configWithoutUrl, sepolia.id)).toThrow();
240
+ });
241
+ });
242
+ });