@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
@@ -0,0 +1,359 @@
1
+ /* eslint-disable no-dupe-class-members */
2
+ import { hardhat } from '@/chains';
3
+ import { type Permit, type Permission, PermitUtils } from '@/permits';
4
+
5
+ import { FheTypes } from '../types';
6
+ import { getThresholdNetworkUrlOrThrow } from '../config';
7
+ import { CofheError, CofheErrorCode } from '../error';
8
+ import { permits } from '../permits';
9
+ import { BaseBuilder, type BaseBuilderParams } from '../baseBuilder';
10
+ import { cofheMocksDecryptForTx } from './cofheMocksDecryptForTx';
11
+ import { getPublicClientChainID, sleep } from '../utils';
12
+ import { type DecryptPollCallbackFunction } from '../types';
13
+ import { tnDecryptV2 } from './tnDecryptV2';
14
+
15
+ /**
16
+ * API
17
+ *
18
+ * await client.decryptForTx(ctHash)
19
+ * .setChainId(chainId)
20
+ * .setAccount(account)
21
+ * .withPermit(permit | permitHash | undefined)
22
+ * // or .withoutPermit()
23
+ * .execute()
24
+ *
25
+ * If chainId not set, uses client's chainId
26
+ * If account not set, uses client's account
27
+ * You MUST choose one permit mode before calling execute():
28
+ * - withPermit(...) to decrypt using a permit
29
+ * - withoutPermit() to decrypt via global allowance (no permit)
30
+ *
31
+ * withPermit() (no args / undefined) uses the active permit for chainId + account.
32
+ * withoutPermit() uses global allowance (no permit required).
33
+ *
34
+ * Returns the decrypted value + proof ready for tx.
35
+ */
36
+
37
+ type DecryptForTxPermitSelection = 'unset' | 'with-permit' | 'without-permit';
38
+
39
+ type DecryptForTxBuilderParams = BaseBuilderParams & {
40
+ ctHash: bigint | string;
41
+ };
42
+
43
+ export type DecryptForTxResult = {
44
+ ctHash: bigint | string;
45
+ decryptedValue: bigint;
46
+ signature: `0x${string}`; // Threshold network signature for publishDecryptResult
47
+ };
48
+
49
+ /**
50
+ * Type-level gating:
51
+ * - The initial builder returned from `client.decryptForTx(...)` intentionally does not expose `execute()`.
52
+ * - Calling `withPermit(...)` or `withoutPermit()` returns a builder that *does* expose `execute()`, but no longer
53
+ * exposes `withPermit/withoutPermit` (so you can't select twice, or switch modes).
54
+ */
55
+ export type DecryptForTxBuilderUnset = Omit<DecryptForTxBuilder, 'execute'>;
56
+
57
+ export type DecryptForTxBuilderSelected = Omit<DecryptForTxBuilder, 'withPermit' | 'withoutPermit'>;
58
+
59
+ export class DecryptForTxBuilder extends BaseBuilder {
60
+ private ctHash: bigint | string;
61
+ private permitHash?: string;
62
+ private permit?: Permit;
63
+ private permitSelection: DecryptForTxPermitSelection = 'unset';
64
+ private pollCallback?: DecryptPollCallbackFunction;
65
+
66
+ constructor(params: DecryptForTxBuilderParams) {
67
+ super({
68
+ config: params.config,
69
+ publicClient: params.publicClient,
70
+ walletClient: params.walletClient,
71
+ chainId: params.chainId,
72
+ account: params.account,
73
+ requireConnected: params.requireConnected,
74
+ });
75
+
76
+ this.ctHash = params.ctHash;
77
+ }
78
+
79
+ /**
80
+ * @param chainId - Chain to decrypt values from. Used to fetch the threshold network URL and use the correct permit.
81
+ *
82
+ * If not provided, the chainId will be fetched from the connected publicClient.
83
+ *
84
+ * Example:
85
+ * ```typescript
86
+ * const result = await decryptForTx(ctHash)
87
+ * .setChainId(11155111)
88
+ * .execute();
89
+ * ```
90
+ *
91
+ * @returns The chainable DecryptForTxBuilder instance.
92
+ */
93
+ setChainId(this: DecryptForTxBuilderUnset, chainId: number): DecryptForTxBuilderUnset;
94
+ setChainId(this: DecryptForTxBuilderSelected, chainId: number): DecryptForTxBuilderSelected;
95
+ setChainId(chainId: number): DecryptForTxBuilder {
96
+ this.chainId = chainId;
97
+ return this;
98
+ }
99
+
100
+ getChainId(): number | undefined {
101
+ return this.chainId;
102
+ }
103
+
104
+ /**
105
+ * @param account - Account to decrypt values from. Used to fetch the correct permit.
106
+ *
107
+ * If not provided, the account will be fetched from the connected walletClient.
108
+ *
109
+ * Example:
110
+ * ```typescript
111
+ * const result = await decryptForTx(ctHash)
112
+ * .setAccount('0x1234567890123456789012345678901234567890')
113
+ * .execute();
114
+ * ```
115
+ *
116
+ * @returns The chainable DecryptForTxBuilder instance.
117
+ */
118
+ setAccount(this: DecryptForTxBuilderUnset, account: string): DecryptForTxBuilderUnset;
119
+ setAccount(this: DecryptForTxBuilderSelected, account: string): DecryptForTxBuilderSelected;
120
+ setAccount(account: string): DecryptForTxBuilder {
121
+ this.account = account;
122
+ return this;
123
+ }
124
+
125
+ getAccount(): string | undefined {
126
+ return this.account;
127
+ }
128
+
129
+ onPoll(this: DecryptForTxBuilderUnset, callback: DecryptPollCallbackFunction): DecryptForTxBuilderUnset;
130
+ onPoll(this: DecryptForTxBuilderSelected, callback: DecryptPollCallbackFunction): DecryptForTxBuilderSelected;
131
+ onPoll(callback: DecryptPollCallbackFunction): DecryptForTxBuilder {
132
+ this.pollCallback = callback;
133
+ return this;
134
+ }
135
+
136
+ /**
137
+ * Select "use permit" mode.
138
+ *
139
+ * - `withPermit(permit)` uses the provided permit.
140
+ * - `withPermit(permitHash)` fetches that permit.
141
+ * - `withPermit()` uses the active permit for the resolved `chainId + account`.
142
+ *
143
+ * Note: "global allowance" (no permit) is ONLY available via `withoutPermit()`.
144
+ */
145
+ withPermit(): DecryptForTxBuilderSelected;
146
+ withPermit(permitHash: string): DecryptForTxBuilderSelected;
147
+ withPermit(permit: Permit): DecryptForTxBuilderSelected;
148
+ withPermit(permitOrPermitHash?: Permit | string): DecryptForTxBuilderSelected {
149
+ if (this.permitSelection === 'with-permit') {
150
+ throw new CofheError({
151
+ code: CofheErrorCode.InternalError,
152
+ message: 'decryptForTx: withPermit() can only be selected once.',
153
+ hint: 'Choose the permit mode once. If you need a different permit, start a new decryptForTx() builder chain.',
154
+ });
155
+ }
156
+
157
+ if (this.permitSelection === 'without-permit') {
158
+ throw new CofheError({
159
+ code: CofheErrorCode.InternalError,
160
+ message: 'decryptForTx: cannot call withPermit() after withoutPermit() has been selected.',
161
+ hint: 'Choose exactly one permit mode: either call .withPermit(...) or .withoutPermit(), but not both.',
162
+ });
163
+ }
164
+
165
+ this.permitSelection = 'with-permit';
166
+
167
+ if (typeof permitOrPermitHash === 'string') {
168
+ this.permitHash = permitOrPermitHash;
169
+ this.permit = undefined;
170
+ } else if (permitOrPermitHash === undefined) {
171
+ // Explicitly choose "active permit" resolution at execute()
172
+ this.permitHash = undefined;
173
+ this.permit = undefined;
174
+ } else {
175
+ // Permit object
176
+ this.permit = permitOrPermitHash;
177
+ this.permitHash = undefined;
178
+ }
179
+
180
+ return this as unknown as DecryptForTxBuilderSelected;
181
+ }
182
+
183
+ /**
184
+ * Select "no permit" mode.
185
+ *
186
+ * This uses global allowance (no permit required) and sends an empty permission payload to `/decrypt`.
187
+ */
188
+ withoutPermit(): DecryptForTxBuilderSelected {
189
+ if (this.permitSelection === 'without-permit') {
190
+ throw new CofheError({
191
+ code: CofheErrorCode.InternalError,
192
+ message: 'decryptForTx: withoutPermit() can only be selected once.',
193
+ hint: 'Choose the permit mode once. If you need a different mode, start a new decryptForTx() builder chain.',
194
+ });
195
+ }
196
+
197
+ if (this.permitSelection === 'with-permit') {
198
+ throw new CofheError({
199
+ code: CofheErrorCode.InternalError,
200
+ message: 'decryptForTx: cannot call withoutPermit() after withPermit() has been selected.',
201
+ hint: 'Choose exactly one permit mode: either call .withPermit(...) or .withoutPermit(), but not both.',
202
+ });
203
+ }
204
+
205
+ this.permitSelection = 'without-permit';
206
+ this.permitHash = undefined;
207
+ this.permit = undefined;
208
+ return this as unknown as DecryptForTxBuilderSelected;
209
+ }
210
+
211
+ getPermit(): Permit | undefined {
212
+ return this.permit;
213
+ }
214
+
215
+ getPermitHash(): string | undefined {
216
+ return this.permitHash;
217
+ }
218
+
219
+ private async getThresholdNetworkUrl(): Promise<string> {
220
+ this.assertChainId();
221
+ return getThresholdNetworkUrlOrThrow(this.config, this.chainId);
222
+ }
223
+
224
+ private async getResolvedPermit(): Promise<Permit | null> {
225
+ if (this.permitSelection === 'unset') {
226
+ throw new CofheError({
227
+ code: CofheErrorCode.InternalError,
228
+ message: 'decryptForTx: missing permit selection; call withPermit(...) or withoutPermit() before execute().',
229
+ hint: 'Call .withPermit() to use the active permit, or .withoutPermit() for global allowance.',
230
+ });
231
+ }
232
+
233
+ if (this.permitSelection === 'without-permit') {
234
+ return null;
235
+ }
236
+
237
+ // with-permit mode
238
+ if (this.permit) return this.permit;
239
+
240
+ this.assertChainId();
241
+ this.assertAccount();
242
+
243
+ // Fetch with permit hash
244
+ if (this.permitHash) {
245
+ const permit = await permits.getPermit(this.chainId, this.account, this.permitHash);
246
+ if (!permit) {
247
+ throw new CofheError({
248
+ code: CofheErrorCode.PermitNotFound,
249
+ message: `Permit with hash <${this.permitHash}> not found for account <${this.account}> and chainId <${this.chainId}>`,
250
+ hint: 'Ensure the permit exists and is valid.',
251
+ context: {
252
+ chainId: this.chainId,
253
+ account: this.account,
254
+ permitHash: this.permitHash,
255
+ },
256
+ });
257
+ }
258
+ return permit;
259
+ }
260
+
261
+ // Fetch active permit (default for withPermit() with no args)
262
+ const permit = await permits.getActivePermit(this.chainId, this.account);
263
+ if (!permit) {
264
+ throw new CofheError({
265
+ code: CofheErrorCode.PermitNotFound,
266
+ message: `Active permit not found for chainId <${this.chainId}> and account <${this.account}>`,
267
+ hint: 'Create a permit (e.g. client.permits.createSelf(...)) and/or set it active (client.permits.selectActivePermit(hash)).',
268
+ context: {
269
+ chainId: this.chainId,
270
+ account: this.account,
271
+ },
272
+ });
273
+ }
274
+ return permit;
275
+ }
276
+
277
+ /**
278
+ * On hardhat, interact with MockThresholdNetwork contract
279
+ */
280
+ private async mocksDecryptForTx(permit: Permit | null): Promise<DecryptForTxResult> {
281
+ this.assertPublicClient();
282
+
283
+ // Configurable delay before decrypting to simulate the CoFHE decrypt processing time
284
+ // Recommended 1000ms on web
285
+ // Recommended 0ms on hardhat (will be called during tests no need for fake delay)
286
+ const delay = this.config.mocks.decryptDelay;
287
+ if (delay > 0) await sleep(delay);
288
+
289
+ const result = await cofheMocksDecryptForTx(this.ctHash, 0 as FheTypes, permit, this.publicClient);
290
+ return result;
291
+ }
292
+
293
+ /**
294
+ * In the production context, perform a true decryption with the CoFHE coprocessor.
295
+ */
296
+ private async productionDecryptForTx(permit: Permit | null): Promise<DecryptForTxResult> {
297
+ this.assertChainId();
298
+ this.assertPublicClient();
299
+
300
+ const thresholdNetworkUrl = await this.getThresholdNetworkUrl();
301
+
302
+ const permission = permit ? PermitUtils.getPermission(permit, true) : null;
303
+ const { decryptedValue, signature } = await tnDecryptV2({
304
+ ctHash: this.ctHash,
305
+ chainId: this.chainId,
306
+ permission,
307
+ thresholdNetworkUrl,
308
+ onPoll: this.pollCallback,
309
+ });
310
+
311
+ return {
312
+ ctHash: this.ctHash,
313
+ decryptedValue,
314
+ signature,
315
+ };
316
+ }
317
+
318
+ /**
319
+ * Final step of the decryptForTx process. MUST BE CALLED LAST IN THE CHAIN.
320
+ *
321
+ * You must explicitly choose one permit mode before calling `execute()`:
322
+ * - `withPermit(permit)` / `withPermit(permitHash)` / `withPermit()` (active permit)
323
+ * - `withoutPermit()` (global allowance)
324
+ */
325
+ async execute(): Promise<DecryptForTxResult> {
326
+ // Resolve permit (can be Permit object or null for global allowance)
327
+ const permit = await this.getResolvedPermit();
328
+
329
+ // If permit is provided, validate it
330
+ if (permit !== null) {
331
+ // Ensure permit validity
332
+ PermitUtils.validate(permit);
333
+
334
+ // Extract chainId from signed permit
335
+ const chainId = permit._signedDomain!.chainId;
336
+
337
+ if (chainId === hardhat.id) {
338
+ return await this.mocksDecryptForTx(permit);
339
+ } else {
340
+ return await this.productionDecryptForTx(permit);
341
+ }
342
+ } else {
343
+ // Global allowance - no permit
344
+ // If chainId not set, try to get it from publicClient
345
+ if (!this.chainId) {
346
+ this.assertPublicClient();
347
+ this.chainId = await getPublicClientChainID(this.publicClient);
348
+ }
349
+
350
+ this.assertChainId();
351
+
352
+ if (this.chainId === hardhat.id) {
353
+ return await this.mocksDecryptForTx(null);
354
+ } else {
355
+ return await this.productionDecryptForTx(null);
356
+ }
357
+ }
358
+ }
359
+ }
@@ -0,0 +1,332 @@
1
+ /* eslint-disable no-dupe-class-members */
2
+ import { hardhat } from '@/chains';
3
+ import { type Permit, PermitUtils } from '@/permits';
4
+
5
+ import { FheTypes, type UnsealedItem } from '../types.js';
6
+ import { getThresholdNetworkUrlOrThrow } from '../config.js';
7
+ import { CofheError, CofheErrorCode } from '../error.js';
8
+ import { permits } from '../permits.js';
9
+ import { isValidUtype, convertViaUtype } from './decryptUtils.js';
10
+ import { BaseBuilder, type BaseBuilderParams } from '../baseBuilder.js';
11
+ import { cofheMocksDecryptForView } from './cofheMocksDecryptForView.js';
12
+ // import { tnSealOutputV1 } from './tnSealOutputV1.js';
13
+ import { tnSealOutputV2 } from './tnSealOutputV2.js';
14
+ import { sleep } from '../utils.js';
15
+ import { type DecryptPollCallbackFunction } from '../types.js';
16
+
17
+ /**
18
+ * API
19
+ *
20
+ * await client.decryptForView(ctHash, utype)
21
+ * .setChainId(chainId)
22
+ * .setAccount(account)
23
+ * .withPermit() // optional (active permit)
24
+ * // or .withPermit(permitHash) / .withPermit(permit)
25
+ * .execute()
26
+ *
27
+ * If chainId not set, uses client's chainId
28
+ * If account not set, uses client's account
29
+ * withPermit() uses chainId + account to get the active permit.
30
+ * withPermit(permitHash) fetches that permit using chainId + account.
31
+ * withPermit(permit) uses the provided permit regardless of chainId/account.
32
+ *
33
+ * Note: decryptForView always requires a permit (no global-allowance mode).
34
+ *
35
+ * Returns the unsealed item.
36
+ */
37
+
38
+ type DecryptForViewBuilderParams<U extends FheTypes> = BaseBuilderParams & {
39
+ ctHash: bigint | string;
40
+ utype: U;
41
+ permitHash?: string;
42
+ permit?: Permit;
43
+ };
44
+
45
+ export class DecryptForViewBuilder<U extends FheTypes> extends BaseBuilder {
46
+ private ctHash: bigint | string;
47
+ private utype: U;
48
+ private permitHash?: string;
49
+ private permit?: Permit;
50
+ private pollCallback?: DecryptPollCallbackFunction;
51
+
52
+ constructor(params: DecryptForViewBuilderParams<U>) {
53
+ super({
54
+ config: params.config,
55
+ publicClient: params.publicClient,
56
+ walletClient: params.walletClient,
57
+ chainId: params.chainId,
58
+ account: params.account,
59
+ requireConnected: params.requireConnected,
60
+ });
61
+
62
+ this.ctHash = params.ctHash;
63
+ this.utype = params.utype;
64
+ this.permitHash = params.permitHash;
65
+ this.permit = params.permit;
66
+ }
67
+
68
+ /**
69
+ * @param chainId - Chain to decrypt values from. Used to fetch the threshold network URL and use the correct permit.
70
+ *
71
+ * If not provided, the chainId will be fetched from the connected publicClient.
72
+ *
73
+ * Example:
74
+ * ```typescript
75
+ * const unsealed = await client.decryptForView(ctHash, utype)
76
+ * .setChainId(11155111)
77
+ * .execute();
78
+ * ```
79
+ *
80
+ * @returns The chainable DecryptForViewBuilder instance.
81
+ */
82
+ setChainId(chainId: number): DecryptForViewBuilder<U> {
83
+ this.chainId = chainId;
84
+ return this;
85
+ }
86
+
87
+ getChainId(): number | undefined {
88
+ return this.chainId;
89
+ }
90
+
91
+ /**
92
+ * @param account - Account to decrypt values from. Used to fetch the correct permit.
93
+ *
94
+ * If not provided, the account will be fetched from the connected walletClient.
95
+ *
96
+ * Example:
97
+ * ```typescript
98
+ * const unsealed = await client.decryptForView(ctHash, utype)
99
+ * .setAccount('0x1234567890123456789012345678901234567890')
100
+ * .execute();
101
+ * ```
102
+ *
103
+ * @returns The chainable DecryptForViewBuilder instance.
104
+ */
105
+ setAccount(account: string): DecryptForViewBuilder<U> {
106
+ this.account = account;
107
+ return this;
108
+ }
109
+
110
+ getAccount(): string | undefined {
111
+ return this.account;
112
+ }
113
+
114
+ onPoll(callback: DecryptPollCallbackFunction): DecryptForViewBuilder<U> {
115
+ this.pollCallback = callback;
116
+ return this;
117
+ }
118
+
119
+ /**
120
+ * Select "use permit" mode (optional).
121
+ *
122
+ * - `withPermit(permit)` uses the provided permit.
123
+ * - `withPermit(permitHash)` fetches that permit.
124
+ * - `withPermit()` uses the active permit for the resolved `chainId + account`.
125
+ */
126
+ withPermit(): DecryptForViewBuilder<U>;
127
+ withPermit(permitHash: string): DecryptForViewBuilder<U>;
128
+ withPermit(permit: Permit): DecryptForViewBuilder<U>;
129
+ withPermit(permitOrPermitHash?: Permit | string): DecryptForViewBuilder<U> {
130
+ if (typeof permitOrPermitHash === 'string') {
131
+ this.permitHash = permitOrPermitHash;
132
+ this.permit = undefined;
133
+ } else if (permitOrPermitHash === undefined) {
134
+ // Explicitly choose "active permit" resolution at execute()
135
+ this.permitHash = undefined;
136
+ this.permit = undefined;
137
+ } else {
138
+ // Permit object
139
+ this.permit = permitOrPermitHash;
140
+ this.permitHash = undefined;
141
+ }
142
+
143
+ return this;
144
+ }
145
+
146
+ /**
147
+ * @param permitHash - Permit hash to decrypt values from. Used to fetch the correct permit.
148
+ *
149
+ * If not provided, the active permit for the chainId and account will be used.
150
+ * If `setPermit()` is called, it will be used regardless of chainId, account, or permitHash.
151
+ *
152
+ * Example:
153
+ * ```typescript
154
+ * const unsealed = await client.decryptForView(ctHash, utype)
155
+ * .setPermitHash('0x1234567890123456789012345678901234567890')
156
+ * .execute();
157
+ * ```
158
+ *
159
+ * @returns The chainable DecryptForViewBuilder instance.
160
+ */
161
+ /** @deprecated Use `withPermit(permitHash)` instead. */
162
+ setPermitHash(permitHash: string): DecryptForViewBuilder<U> {
163
+ return this.withPermit(permitHash);
164
+ }
165
+
166
+ getPermitHash(): string | undefined {
167
+ return this.permitHash;
168
+ }
169
+
170
+ /**
171
+ * @param permit - Permit to decrypt values with. If provided, it will be used regardless of chainId, account, or permitHash.
172
+ *
173
+ * If not provided, the permit will be determined by chainId, account, and permitHash.
174
+ *
175
+ * Example:
176
+ * ```typescript
177
+ * const unsealed = await client.decryptForView(ctHash, utype)
178
+ * .setPermit(permit)
179
+ * .execute();
180
+ * ```
181
+ *
182
+ * @returns The chainable DecryptForViewBuilder instance.
183
+ */
184
+ /** @deprecated Use `withPermit(permit)` instead. */
185
+ setPermit(permit: Permit): DecryptForViewBuilder<U> {
186
+ return this.withPermit(permit);
187
+ }
188
+
189
+ getPermit(): Permit | undefined {
190
+ return this.permit;
191
+ }
192
+
193
+ private async getThresholdNetworkUrl(): Promise<string> {
194
+ this.assertChainId();
195
+ return getThresholdNetworkUrlOrThrow(this.config, this.chainId);
196
+ }
197
+
198
+ private validateUtypeOrThrow(): void {
199
+ if (!isValidUtype(this.utype))
200
+ throw new CofheError({
201
+ code: CofheErrorCode.InvalidUtype,
202
+ message: `Invalid utype to decrypt to`,
203
+ context: {
204
+ utype: this.utype,
205
+ },
206
+ });
207
+ }
208
+
209
+ private async getResolvedPermit(): Promise<Permit> {
210
+ if (this.permit) return this.permit;
211
+
212
+ this.assertChainId();
213
+ this.assertAccount();
214
+
215
+ // Fetch with permit hash
216
+ if (this.permitHash) {
217
+ const permit = await permits.getPermit(this.chainId, this.account, this.permitHash);
218
+ if (!permit) {
219
+ throw new CofheError({
220
+ code: CofheErrorCode.PermitNotFound,
221
+ message: `Permit with hash <${this.permitHash}> not found for account <${this.account}> and chainId <${this.chainId}>`,
222
+ hint: 'Ensure the permit exists and is valid.',
223
+ context: {
224
+ chainId: this.chainId,
225
+ account: this.account,
226
+ permitHash: this.permitHash,
227
+ },
228
+ });
229
+ }
230
+ return permit;
231
+ }
232
+
233
+ // Fetch with active permit
234
+ const permit = await permits.getActivePermit(this.chainId, this.account);
235
+ if (!permit) {
236
+ throw new CofheError({
237
+ code: CofheErrorCode.PermitNotFound,
238
+ message: `Active permit not found for chainId <${this.chainId}> and account <${this.account}>`,
239
+ hint: 'Ensure a permit exists for this account on this chain.',
240
+ context: {
241
+ chainId: this.chainId,
242
+ account: this.account,
243
+ },
244
+ });
245
+ }
246
+ return permit;
247
+ }
248
+
249
+ /**
250
+ * On hardhat, interact with MockZkVerifier contract instead of CoFHE
251
+ */
252
+ private async mocksSealOutput(permit: Permit): Promise<bigint> {
253
+ this.assertPublicClient();
254
+
255
+ // Configurable delay before decrypting the output to simulate the CoFHE decrypt processing time
256
+ // Recommended 1000ms on web
257
+ // Recommended 0ms on hardhat (will be called during tests no need for fake delay)
258
+ const mocksDecryptDelay = this.config.mocks.decryptDelay;
259
+ if (mocksDecryptDelay > 0) await sleep(mocksDecryptDelay);
260
+
261
+ return cofheMocksDecryptForView(this.ctHash, this.utype, permit, this.publicClient);
262
+ }
263
+
264
+ /**
265
+ * In the production context, perform a true decryption with the CoFHE coprocessor.
266
+ */
267
+ private async productionSealOutput(permit: Permit): Promise<bigint> {
268
+ this.assertChainId();
269
+ this.assertPublicClient();
270
+
271
+ const thresholdNetworkUrl = await this.getThresholdNetworkUrl();
272
+ const permission = PermitUtils.getPermission(permit, true);
273
+ // const sealed = await tnSealOutputV1(this.ctHash, this.chainId, permission, thresholdNetworkUrl);
274
+ const sealed = await tnSealOutputV2({
275
+ ctHash: this.ctHash,
276
+ chainId: this.chainId,
277
+ permission,
278
+ thresholdNetworkUrl,
279
+ onPoll: this.pollCallback,
280
+ });
281
+ return PermitUtils.unseal(permit, sealed);
282
+ }
283
+
284
+ /**
285
+ * Final step of the decryption process. MUST BE CALLED LAST IN THE CHAIN.
286
+ *
287
+ * This will:
288
+ * - Use a permit based on provided permit OR chainId + account + permitHash
289
+ * - Check permit validity
290
+ * - Call CoFHE `/sealoutput` with the permit, which returns a sealed (encrypted) item
291
+ * - Unseal the sealed item with the permit
292
+ * - Return the unsealed item
293
+ *
294
+ * Example:
295
+ * ```typescript
296
+ * const unsealed = await client.decryptForView(ctHash, utype)
297
+ * .setChainId(11155111) // optional
298
+ * .setAccount('0x123...890') // optional
299
+ * .withPermit() // optional
300
+ * .execute(); // execute
301
+ * ```
302
+ *
303
+ * @returns The unsealed item.
304
+ */
305
+ async execute(): Promise<UnsealedItem<U>> {
306
+ // Ensure utype is valid
307
+ this.validateUtypeOrThrow();
308
+
309
+ // Resolve permit
310
+ const permit = await this.getResolvedPermit();
311
+
312
+ // Ensure permit validity
313
+ PermitUtils.validate(permit);
314
+
315
+ // Extract chainId from signed permit
316
+ // Use this chainId to fetch the threshold network URL since this.chainId may be undefined
317
+ const chainId = permit._signedDomain!.chainId;
318
+
319
+ // Check permit validity on-chain
320
+ // TODO: PermitUtils.validateOnChain(permit, this.publicClient);
321
+
322
+ let unsealed: bigint;
323
+
324
+ if (chainId === hardhat.id) {
325
+ unsealed = await this.mocksSealOutput(permit);
326
+ } else {
327
+ unsealed = await this.productionSealOutput(permit);
328
+ }
329
+
330
+ return convertViaUtype(this.utype, unsealed);
331
+ }
332
+ }