@cofhe/sdk 1.1.0-alpha.0 → 1.1.0-alpha.1

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.
@@ -11,6 +11,7 @@ import { cofheMocksDecryptForTx } from './cofheMocksDecryptForTx';
11
11
  import { getPublicClientChainID, sleep } from '../utils';
12
12
  import { type DecryptPollCallbackFunction } from '../types';
13
13
  import { tnDecryptV2 } from './tnDecryptV2';
14
+ import { assertDecryptTimeoutMs } from './submitRetry';
14
15
 
15
16
  const DEFAULT_404_RETRY_TIMEOUT_MS = 10_000;
16
17
 
@@ -65,6 +66,7 @@ export class DecryptForTxBuilder extends BaseBuilder {
65
66
  private acpSelection: DecryptForTxACPSelection = 'unset';
66
67
  private pollCallback?: DecryptPollCallbackFunction;
67
68
  private retry404TimeoutMs = DEFAULT_404_RETRY_TIMEOUT_MS;
69
+ private timeoutMs?: number;
68
70
 
69
71
  constructor(params: DecryptForTxBuilderParams) {
70
72
  super({
@@ -153,6 +155,19 @@ export class DecryptForTxBuilder extends BaseBuilder {
153
155
  return this;
154
156
  }
155
157
 
158
+ /**
159
+ * The whole budget of this decrypt in milliseconds, submit retries and status polling together
160
+ * (5 minutes by default). Past it, `execute()` rejects with `DecryptFailed`; a request still
161
+ * processing on the threshold network is not cancelled.
162
+ */
163
+ setTimeout(this: DecryptForTxBuilderUnset, timeoutMs: number): DecryptForTxBuilderUnset;
164
+ setTimeout(this: DecryptForTxBuilderSelected, timeoutMs: number): DecryptForTxBuilderSelected;
165
+ setTimeout(timeoutMs: number): DecryptForTxBuilder {
166
+ assertDecryptTimeoutMs(timeoutMs, 'decryptForTx');
167
+ this.timeoutMs = timeoutMs;
168
+ return this;
169
+ }
170
+
156
171
  /**
157
172
  * Select "use acp" mode.
158
173
  *
@@ -326,6 +341,7 @@ export class DecryptForTxBuilder extends BaseBuilder {
326
341
  acp: wireAcp,
327
342
  thresholdNetworkUrl,
328
343
  retry404TimeoutMs: this.retry404TimeoutMs,
344
+ timeoutMs: this.timeoutMs,
329
345
  onPoll: this.pollCallback,
330
346
  });
331
347
 
@@ -13,6 +13,7 @@ import { cofheMocksDecryptForView } from './cofheMocksDecryptForView.js';
13
13
  import { tnSealOutputV2 } from './tnSealOutputV2.js';
14
14
  import { sleep } from '../utils.js';
15
15
  import { type DecryptPollCallbackFunction } from '../types.js';
16
+ import { assertDecryptTimeoutMs } from './submitRetry.js';
16
17
 
17
18
  const DEFAULT_404_RETRY_TIMEOUT_MS = 10_000;
18
19
 
@@ -51,6 +52,7 @@ export class DecryptForViewBuilder<U extends FheTypes> extends BaseBuilder {
51
52
  private acp?: ACP;
52
53
  private pollCallback?: DecryptPollCallbackFunction;
53
54
  private retry404TimeoutMs = DEFAULT_404_RETRY_TIMEOUT_MS;
55
+ private timeoutMs?: number;
54
56
 
55
57
  constructor(params: DecryptForViewBuilderParams<U>) {
56
58
  super({
@@ -134,6 +136,17 @@ export class DecryptForViewBuilder<U extends FheTypes> extends BaseBuilder {
134
136
  return this;
135
137
  }
136
138
 
139
+ /**
140
+ * The whole budget of this decrypt in milliseconds, submit retries and status polling together
141
+ * (5 minutes by default). Past it, `execute()` rejects with `SealOutputFailed`; a request still
142
+ * processing on the threshold network is not cancelled.
143
+ */
144
+ setTimeout(timeoutMs: number): DecryptForViewBuilder<U> {
145
+ assertDecryptTimeoutMs(timeoutMs, 'decryptForView');
146
+ this.timeoutMs = timeoutMs;
147
+ return this;
148
+ }
149
+
137
150
  /**
138
151
  * Select "use acp" mode (optional).
139
152
  *
@@ -295,6 +308,7 @@ export class DecryptForViewBuilder<U extends FheTypes> extends BaseBuilder {
295
308
  acp: wireAcp,
296
309
  thresholdNetworkUrl,
297
310
  retry404TimeoutMs: this.retry404TimeoutMs,
311
+ timeoutMs: this.timeoutMs,
298
312
  onPoll: this.pollCallback,
299
313
  });
300
314
  return ACPUtils.unseal(acp, sealed);
@@ -2,6 +2,22 @@ import { CofheError, CofheErrorCode } from '../error.js';
2
2
  import { mapApiErrorCodeToCofheErrorCode, parseApiErrorResponseBody } from './apiError.js';
3
3
 
4
4
  export const DEFAULT_404_RETRY_TIMEOUT_MS = 10_000;
5
+ /** The default budget of one decrypt, submit retries and status polling together. */
6
+ export const DEFAULT_DECRYPT_TIMEOUT_MS = 5 * 60 * 1000;
7
+
8
+ /**
9
+ * Validates a builder's `setTimeout(timeoutMs)` argument: the overall budget must be a positive,
10
+ * finite number of milliseconds (0 would time out before the first attempt).
11
+ */
12
+ export function assertDecryptTimeoutMs(timeoutMs: number, operationLabel: string): void {
13
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
14
+ throw new CofheError({
15
+ code: CofheErrorCode.InternalError,
16
+ message: `${operationLabel}: setTimeout(timeoutMs) expects a finite number greater than 0.`,
17
+ context: { timeoutMs },
18
+ });
19
+ }
20
+ }
5
21
 
6
22
  export type SubmitRetryableStatus = 204 | 404;
7
23
 
@@ -6,12 +6,16 @@ import { type DecryptPollCallbackFunction } from '../types';
6
6
  import { normalizeTnSignature, parseDecryptedBytesToBigInt } from './tnDecryptUtils';
7
7
  import { computeMinuteRampPollIntervalMs } from './polling.js';
8
8
  import { mapApiErrorCodeToCofheErrorCode, parseApiErrorResponseBody } from './apiError.js';
9
- import { classifySubmitResponse, normalize404RetryTimeoutMs, throwIfSubmitRetryTimedOut } from './submitRetry.js';
9
+ import {
10
+ DEFAULT_DECRYPT_TIMEOUT_MS,
11
+ classifySubmitResponse,
12
+ normalize404RetryTimeoutMs,
13
+ throwIfSubmitRetryTimedOut,
14
+ } from './submitRetry.js';
10
15
 
11
16
  // Polling configuration
12
17
  const POLL_INTERVAL_MS = 1000; // 1 second
13
18
  const POLL_MAX_INTERVAL_MS = 10_000; // 10 seconds
14
- const DECRYPT_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes total across submit + poll
15
19
  const SUBMIT_RETRY_INTERVAL_MS = 1000; // 1 second
16
20
 
17
21
  type DecryptSubmitResponseV2 = {
@@ -182,6 +186,7 @@ async function submitDecryptRequestV2(
182
186
  acp: ACPPublic | null,
183
187
  overallStartTime: number,
184
188
  retry404TimeoutMs: number,
189
+ timeoutMs: number,
185
190
  onPoll?: DecryptPollCallbackFunction
186
191
  ): Promise<DecryptSubmitResultV2> {
187
192
  const body: {
@@ -306,7 +311,7 @@ async function submitDecryptRequestV2(
306
311
  status: responseClassification.status,
307
312
  elapsedMs,
308
313
  retry404TimeoutMs,
309
- overallTimeoutMs: DECRYPT_TIMEOUT_MS,
314
+ overallTimeoutMs: timeoutMs,
310
315
  thresholdNetworkUrl,
311
316
  body,
312
317
  attemptIndex,
@@ -319,7 +324,7 @@ async function submitDecryptRequestV2(
319
324
  attemptIndex,
320
325
  elapsedMs,
321
326
  intervalMs: SUBMIT_RETRY_INTERVAL_MS,
322
- timeoutMs: DECRYPT_TIMEOUT_MS,
327
+ timeoutMs,
323
328
  });
324
329
 
325
330
  await new Promise((resolve) => setTimeout(resolve, SUBMIT_RETRY_INTERVAL_MS));
@@ -331,6 +336,7 @@ async function pollDecryptStatusV2(
331
336
  thresholdNetworkUrl: string,
332
337
  requestId: string,
333
338
  overallStartTime: number,
339
+ timeoutMs: number,
334
340
  onPoll?: DecryptPollCallbackFunction
335
341
  ): Promise<{ decryptedValue: bigint; signature: `0x${string}` }> {
336
342
  let attemptIndex = 0;
@@ -348,18 +354,18 @@ async function pollDecryptStatusV2(
348
354
  attemptIndex,
349
355
  elapsedMs,
350
356
  intervalMs,
351
- timeoutMs: DECRYPT_TIMEOUT_MS,
357
+ timeoutMs,
352
358
  });
353
359
 
354
- if (elapsedMs > DECRYPT_TIMEOUT_MS) {
360
+ if (elapsedMs > timeoutMs) {
355
361
  throw new CofheError({
356
362
  code: CofheErrorCode.DecryptFailed,
357
- message: `decrypt polling timed out after ${DECRYPT_TIMEOUT_MS}ms`,
363
+ message: `decrypt polling timed out after ${timeoutMs}ms`,
358
364
  hint: 'The request may still be processing. Try again later.',
359
365
  context: {
360
366
  thresholdNetworkUrl,
361
367
  requestId,
362
- timeoutMs: DECRYPT_TIMEOUT_MS,
368
+ timeoutMs,
363
369
  },
364
370
  });
365
371
  }
@@ -462,9 +468,12 @@ export async function tnDecryptV2(params: {
462
468
  acp: ACPPublic | null;
463
469
  thresholdNetworkUrl: string;
464
470
  retry404TimeoutMs?: number;
471
+ /** The whole budget, submit retries and status polling together; `DEFAULT_DECRYPT_TIMEOUT_MS` by default. */
472
+ timeoutMs?: number;
465
473
  onPoll?: DecryptPollCallbackFunction;
466
474
  }): Promise<{ decryptedValue: bigint; signature: `0x${string}` }> {
467
475
  const { thresholdNetworkUrl, ctHash, chainId, acp, retry404TimeoutMs, onPoll } = params;
476
+ const timeoutMs = params.timeoutMs ?? DEFAULT_DECRYPT_TIMEOUT_MS;
468
477
  const normalized404RetryTimeoutMs = normalize404RetryTimeoutMs({
469
478
  timeoutMs: retry404TimeoutMs,
470
479
  operationLabel: 'decrypt',
@@ -478,6 +487,7 @@ export async function tnDecryptV2(params: {
478
487
  acp,
479
488
  overallStartTime,
480
489
  normalized404RetryTimeoutMs,
490
+ timeoutMs,
481
491
  onPoll
482
492
  );
483
493
 
@@ -485,5 +495,5 @@ export async function tnDecryptV2(params: {
485
495
  return submitResult;
486
496
  }
487
497
 
488
- return await pollDecryptStatusV2(thresholdNetworkUrl, submitResult.requestId, overallStartTime, onPoll);
498
+ return await pollDecryptStatusV2(thresholdNetworkUrl, submitResult.requestId, overallStartTime, timeoutMs, onPoll);
489
499
  }
@@ -5,13 +5,17 @@ import { CofheError, CofheErrorCode } from '../error.js';
5
5
  import { type DecryptPollCallbackFunction } from '../types.js';
6
6
  import { computeMinuteRampPollIntervalMs } from './polling.js';
7
7
  import { mapApiErrorCodeToCofheErrorCode, parseApiErrorResponseBody } from './apiError.js';
8
- import { classifySubmitResponse, normalize404RetryTimeoutMs, throwIfSubmitRetryTimedOut } from './submitRetry.js';
8
+ import {
9
+ DEFAULT_DECRYPT_TIMEOUT_MS,
10
+ classifySubmitResponse,
11
+ normalize404RetryTimeoutMs,
12
+ throwIfSubmitRetryTimedOut,
13
+ } from './submitRetry.js';
9
14
  import {} from './tnDecryptUtils.js';
10
15
 
11
16
  // Polling configuration
12
17
  const POLL_INTERVAL_MS = 1000; // 1 second
13
18
  const POLL_MAX_INTERVAL_MS = 10_000; // 10 seconds
14
- const SEAL_OUTPUT_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes total across submit + poll
15
19
  const SUBMIT_RETRY_INTERVAL_MS = 1000; // 1 second
16
20
 
17
21
  // V2 API response types
@@ -141,6 +145,7 @@ async function submitSealOutputRequest(
141
145
  acp: ACPPublic,
142
146
  overallStartTime: number,
143
147
  retry404TimeoutMs: number,
148
+ timeoutMs: number,
144
149
  onPoll?: DecryptPollCallbackFunction
145
150
  ): Promise<SealOutputSubmitResult> {
146
151
  const body = {
@@ -254,7 +259,7 @@ async function submitSealOutputRequest(
254
259
  status: responseClassification.status,
255
260
  elapsedMs,
256
261
  retry404TimeoutMs,
257
- overallTimeoutMs: SEAL_OUTPUT_TIMEOUT_MS,
262
+ overallTimeoutMs: timeoutMs,
258
263
  thresholdNetworkUrl,
259
264
  body,
260
265
  attemptIndex,
@@ -267,7 +272,7 @@ async function submitSealOutputRequest(
267
272
  attemptIndex,
268
273
  elapsedMs,
269
274
  intervalMs: SUBMIT_RETRY_INTERVAL_MS,
270
- timeoutMs: SEAL_OUTPUT_TIMEOUT_MS,
275
+ timeoutMs,
271
276
  });
272
277
 
273
278
  await new Promise((resolve) => setTimeout(resolve, SUBMIT_RETRY_INTERVAL_MS));
@@ -282,6 +287,7 @@ async function pollSealOutputStatus(
282
287
  thresholdNetworkUrl: string,
283
288
  requestId: string,
284
289
  overallStartTime: number,
290
+ timeoutMs: number,
285
291
  onPoll?: DecryptPollCallbackFunction
286
292
  ): Promise<EthEncryptedData> {
287
293
  let attemptIndex = 0;
@@ -299,19 +305,19 @@ async function pollSealOutputStatus(
299
305
  attemptIndex,
300
306
  elapsedMs,
301
307
  intervalMs,
302
- timeoutMs: SEAL_OUTPUT_TIMEOUT_MS,
308
+ timeoutMs,
303
309
  });
304
310
 
305
311
  // Check timeout
306
- if (elapsedMs > SEAL_OUTPUT_TIMEOUT_MS) {
312
+ if (elapsedMs > timeoutMs) {
307
313
  throw new CofheError({
308
314
  code: CofheErrorCode.SealOutputFailed,
309
- message: `sealOutput polling timed out after ${SEAL_OUTPUT_TIMEOUT_MS}ms`,
315
+ message: `sealOutput polling timed out after ${timeoutMs}ms`,
310
316
  hint: 'The request may still be processing. Try again later.',
311
317
  context: {
312
318
  thresholdNetworkUrl,
313
319
  requestId,
314
- timeoutMs: SEAL_OUTPUT_TIMEOUT_MS,
320
+ timeoutMs,
315
321
  },
316
322
  });
317
323
  }
@@ -416,9 +422,12 @@ export async function tnSealOutputV2(params: {
416
422
  acp: ACPPublic;
417
423
  thresholdNetworkUrl: string;
418
424
  retry404TimeoutMs?: number;
425
+ /** The whole budget, submit retries and status polling together; `DEFAULT_DECRYPT_TIMEOUT_MS` by default. */
426
+ timeoutMs?: number;
419
427
  onPoll?: DecryptPollCallbackFunction;
420
428
  }): Promise<EthEncryptedData> {
421
429
  const { thresholdNetworkUrl, ctHash, chainId, acp, retry404TimeoutMs, onPoll } = params;
430
+ const timeoutMs = params.timeoutMs ?? DEFAULT_DECRYPT_TIMEOUT_MS;
422
431
  const normalized404RetryTimeoutMs = normalize404RetryTimeoutMs({
423
432
  timeoutMs: retry404TimeoutMs,
424
433
  operationLabel: 'sealOutput',
@@ -434,6 +443,7 @@ export async function tnSealOutputV2(params: {
434
443
  acp,
435
444
  overallStartTime,
436
445
  normalized404RetryTimeoutMs,
446
+ timeoutMs,
437
447
  onPoll
438
448
  );
439
449
 
@@ -442,5 +452,5 @@ export async function tnSealOutputV2(params: {
442
452
  }
443
453
 
444
454
  // Step 2: Poll for status until completed
445
- return await pollSealOutputStatus(thresholdNetworkUrl, submitResult.requestId, overallStartTime, onPoll);
455
+ return await pollSealOutputStatus(thresholdNetworkUrl, submitResult.requestId, overallStartTime, timeoutMs, onPoll);
446
456
  }
@@ -224,6 +224,21 @@ describe('DecryptForTxBuilder', () => {
224
224
 
225
225
  expect(() => builder.set404RetryTimeout(-1)).toThrow('set404RetryTimeout(timeoutMs) expects');
226
226
  });
227
+
228
+ it('should allow configuring the overall timeout', () => {
229
+ const builder = createTxBuilder();
230
+ const result = builder.setTimeout(20_000);
231
+
232
+ expect(result).toBe(builder);
233
+ expect((builder as any).timeoutMs).toBe(20_000);
234
+ });
235
+
236
+ it('should throw for an overall timeout that is not a positive number', () => {
237
+ const builder = createTxBuilder();
238
+
239
+ expect(() => builder.setTimeout(0)).toThrow('setTimeout(timeoutMs) expects');
240
+ expect(() => builder.setTimeout(Number.NaN)).toThrow('setTimeout(timeoutMs) expects');
241
+ });
227
242
  });
228
243
 
229
244
  // --- execute error paths ---
@@ -367,6 +382,21 @@ describe('DecryptForViewBuilder', () => {
367
382
 
368
383
  expect(() => builder.set404RetryTimeout(-1)).toThrow('set404RetryTimeout(timeoutMs) expects');
369
384
  });
385
+
386
+ it('should allow configuring the overall timeout', () => {
387
+ const builder = createViewBuilder(FheTypes.Uint32);
388
+ const result = builder.setTimeout(20_000);
389
+
390
+ expect(result).toBe(builder);
391
+ expect((builder as any).timeoutMs).toBe(20_000);
392
+ });
393
+
394
+ it('should throw for an overall timeout that is not a positive number', () => {
395
+ const builder = createViewBuilder(FheTypes.Uint32);
396
+
397
+ expect(() => builder.setTimeout(0)).toThrow('setTimeout(timeoutMs) expects');
398
+ expect(() => builder.setTimeout(-5)).toThrow('setTimeout(timeoutMs) expects');
399
+ });
370
400
  });
371
401
 
372
402
  // --- execute error paths ---
@@ -503,6 +533,16 @@ describe('execute – expired acp (regression, issue #272)', () => {
503
533
  expect(result.decryptedValue).toBe(7n);
504
534
  expect(tnDecryptV2).toHaveBeenCalledOnce();
505
535
  });
536
+
537
+ it('passes the overall timeout to the threshold network call', async () => {
538
+ vi.mocked(tnDecryptV2).mockResolvedValue({ decryptedValue: 7n, signature: '0x1234' });
539
+ const acp = createSignedSelfACP(now() + 3600);
540
+ seedACP(acp);
541
+
542
+ await selectTxACP('active acp', acp).setTimeout(20_000).execute();
543
+
544
+ expect(tnDecryptV2).toHaveBeenCalledWith(expect.objectContaining({ timeoutMs: 20_000 }));
545
+ });
506
546
  });
507
547
 
508
548
  describe('decryptForView', () => {
@@ -522,5 +562,16 @@ describe('execute – expired acp (regression, issue #272)', () => {
522
562
  await expect(selectViewACP('active acp', acp).execute()).rejects.toThrow('stubbed threshold network');
523
563
  expect(tnSealOutputV2).toHaveBeenCalledOnce();
524
564
  });
565
+
566
+ it('passes the overall timeout to the threshold network call', async () => {
567
+ vi.mocked(tnSealOutputV2).mockRejectedValue(new Error('stubbed threshold network'));
568
+ const acp = createSignedSelfACP(now() + 3600);
569
+ seedACP(acp);
570
+
571
+ await expect(selectViewACP('active acp', acp).setTimeout(20_000).execute()).rejects.toThrow(
572
+ 'stubbed threshold network'
573
+ );
574
+ expect(tnSealOutputV2).toHaveBeenCalledWith(expect.objectContaining({ timeoutMs: 20_000 }));
575
+ });
525
576
  });
526
577
  });
@@ -1929,6 +1929,16 @@ function mapApiErrorCodeToCofheErrorCode(apiErrorCode, fallback) {
1929
1929
 
1930
1930
  // core/decrypt/submitRetry.ts
1931
1931
  var DEFAULT_404_RETRY_TIMEOUT_MS = 1e4;
1932
+ var DEFAULT_DECRYPT_TIMEOUT_MS = 5 * 60 * 1e3;
1933
+ function assertDecryptTimeoutMs(timeoutMs, operationLabel) {
1934
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
1935
+ throw new CofheError({
1936
+ code: "INTERNAL_ERROR" /* InternalError */,
1937
+ message: `${operationLabel}: setTimeout(timeoutMs) expects a finite number greater than 0.`,
1938
+ context: { timeoutMs }
1939
+ });
1940
+ }
1941
+ }
1932
1942
  function normalize404RetryTimeoutMs(params) {
1933
1943
  const { timeoutMs, operationLabel, errorCode } = params;
1934
1944
  if (timeoutMs === void 0)
@@ -2067,7 +2077,6 @@ function parseDecryptedBytesToBigInt(decrypted) {
2067
2077
  // core/decrypt/tnSealOutputV2.ts
2068
2078
  var POLL_INTERVAL_MS = 1e3;
2069
2079
  var POLL_MAX_INTERVAL_MS = 1e4;
2070
- var SEAL_OUTPUT_TIMEOUT_MS = 5 * 60 * 1e3;
2071
2080
  var SUBMIT_RETRY_INTERVAL_MS = 1e3;
2072
2081
  function numberArrayToUint8Array(arr) {
2073
2082
  return new Uint8Array(arr);
@@ -2125,7 +2134,7 @@ function parseCompletedSealOutputResponse(params) {
2125
2134
  }
2126
2135
  return convertSealedData(sealed);
2127
2136
  }
2128
- async function submitSealOutputRequest(thresholdNetworkUrl, ctHash, chainId, acp, overallStartTime, retry404TimeoutMs, onPoll) {
2137
+ async function submitSealOutputRequest(thresholdNetworkUrl, ctHash, chainId, acp, overallStartTime, retry404TimeoutMs, timeoutMs, onPoll) {
2129
2138
  const body = {
2130
2139
  ct_tempkey: BigInt(ctHash).toString(16).padStart(64, "0"),
2131
2140
  host_chain_id: chainId,
@@ -2228,7 +2237,7 @@ async function submitSealOutputRequest(thresholdNetworkUrl, ctHash, chainId, acp
2228
2237
  status: responseClassification.status,
2229
2238
  elapsedMs,
2230
2239
  retry404TimeoutMs,
2231
- overallTimeoutMs: SEAL_OUTPUT_TIMEOUT_MS,
2240
+ overallTimeoutMs: timeoutMs,
2232
2241
  thresholdNetworkUrl,
2233
2242
  body,
2234
2243
  attemptIndex,
@@ -2240,13 +2249,13 @@ async function submitSealOutputRequest(thresholdNetworkUrl, ctHash, chainId, acp
2240
2249
  attemptIndex,
2241
2250
  elapsedMs,
2242
2251
  intervalMs: SUBMIT_RETRY_INTERVAL_MS,
2243
- timeoutMs: SEAL_OUTPUT_TIMEOUT_MS
2252
+ timeoutMs
2244
2253
  });
2245
2254
  await new Promise((resolve) => setTimeout(resolve, SUBMIT_RETRY_INTERVAL_MS));
2246
2255
  attemptIndex += 1;
2247
2256
  }
2248
2257
  }
2249
- async function pollSealOutputStatus(thresholdNetworkUrl, requestId, overallStartTime, onPoll) {
2258
+ async function pollSealOutputStatus(thresholdNetworkUrl, requestId, overallStartTime, timeoutMs, onPoll) {
2250
2259
  let attemptIndex = 0;
2251
2260
  while (true) {
2252
2261
  const elapsedMs = Date.now() - overallStartTime;
@@ -2260,17 +2269,17 @@ async function pollSealOutputStatus(thresholdNetworkUrl, requestId, overallStart
2260
2269
  attemptIndex,
2261
2270
  elapsedMs,
2262
2271
  intervalMs,
2263
- timeoutMs: SEAL_OUTPUT_TIMEOUT_MS
2272
+ timeoutMs
2264
2273
  });
2265
- if (elapsedMs > SEAL_OUTPUT_TIMEOUT_MS) {
2274
+ if (elapsedMs > timeoutMs) {
2266
2275
  throw new CofheError({
2267
2276
  code: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */,
2268
- message: `sealOutput polling timed out after ${SEAL_OUTPUT_TIMEOUT_MS}ms`,
2277
+ message: `sealOutput polling timed out after ${timeoutMs}ms`,
2269
2278
  hint: "The request may still be processing. Try again later.",
2270
2279
  context: {
2271
2280
  thresholdNetworkUrl,
2272
2281
  requestId,
2273
- timeoutMs: SEAL_OUTPUT_TIMEOUT_MS
2282
+ timeoutMs
2274
2283
  }
2275
2284
  });
2276
2285
  }
@@ -2358,6 +2367,7 @@ async function pollSealOutputStatus(thresholdNetworkUrl, requestId, overallStart
2358
2367
  }
2359
2368
  async function tnSealOutputV2(params) {
2360
2369
  const { thresholdNetworkUrl, ctHash, chainId, acp, retry404TimeoutMs, onPoll } = params;
2370
+ const timeoutMs = params.timeoutMs ?? DEFAULT_DECRYPT_TIMEOUT_MS;
2361
2371
  const normalized404RetryTimeoutMs = normalize404RetryTimeoutMs({
2362
2372
  timeoutMs: retry404TimeoutMs,
2363
2373
  operationLabel: "sealOutput",
@@ -2371,12 +2381,13 @@ async function tnSealOutputV2(params) {
2371
2381
  acp,
2372
2382
  overallStartTime,
2373
2383
  normalized404RetryTimeoutMs,
2384
+ timeoutMs,
2374
2385
  onPoll
2375
2386
  );
2376
2387
  if (submitResult.kind === "completed") {
2377
2388
  return submitResult.sealed;
2378
2389
  }
2379
- return await pollSealOutputStatus(thresholdNetworkUrl, submitResult.requestId, overallStartTime, onPoll);
2390
+ return await pollSealOutputStatus(thresholdNetworkUrl, submitResult.requestId, overallStartTime, timeoutMs, onPoll);
2380
2391
  }
2381
2392
 
2382
2393
  // core/decrypt/decryptForViewBuilder.ts
@@ -2388,6 +2399,7 @@ var DecryptForViewBuilder = class extends BaseBuilder {
2388
2399
  acp;
2389
2400
  pollCallback;
2390
2401
  retry404TimeoutMs = DEFAULT_404_RETRY_TIMEOUT_MS2;
2402
+ timeoutMs;
2391
2403
  constructor(params) {
2392
2404
  super({
2393
2405
  config: params.config,
@@ -2461,6 +2473,16 @@ var DecryptForViewBuilder = class extends BaseBuilder {
2461
2473
  this.retry404TimeoutMs = timeoutMs;
2462
2474
  return this;
2463
2475
  }
2476
+ /**
2477
+ * The whole budget of this decrypt in milliseconds, submit retries and status polling together
2478
+ * (5 minutes by default). Past it, `execute()` rejects with `SealOutputFailed`; a request still
2479
+ * processing on the threshold network is not cancelled.
2480
+ */
2481
+ setTimeout(timeoutMs) {
2482
+ assertDecryptTimeoutMs(timeoutMs, "decryptForView");
2483
+ this.timeoutMs = timeoutMs;
2484
+ return this;
2485
+ }
2464
2486
  withACP(acpOrACPHash) {
2465
2487
  if (typeof acpOrACPHash === "string") {
2466
2488
  this.acpHash = acpOrACPHash;
@@ -2590,6 +2612,7 @@ var DecryptForViewBuilder = class extends BaseBuilder {
2590
2612
  acp: wireAcp,
2591
2613
  thresholdNetworkUrl,
2592
2614
  retry404TimeoutMs: this.retry404TimeoutMs,
2615
+ timeoutMs: this.timeoutMs,
2593
2616
  onPoll: this.pollCallback
2594
2617
  });
2595
2618
  return ACPUtils.unseal(acp, sealed);
@@ -2692,7 +2715,6 @@ async function cofheMocksDecryptForTx(ctHash, utype, acp, publicClient) {
2692
2715
  // core/decrypt/tnDecryptV2.ts
2693
2716
  var POLL_INTERVAL_MS2 = 1e3;
2694
2717
  var POLL_MAX_INTERVAL_MS2 = 1e4;
2695
- var DECRYPT_TIMEOUT_MS = 5 * 60 * 1e3;
2696
2718
  var SUBMIT_RETRY_INTERVAL_MS2 = 1e3;
2697
2719
  function assertDecryptSubmitResponseV2(value) {
2698
2720
  if (value == null || typeof value !== "object") {
@@ -2809,7 +2831,7 @@ function assertDecryptStatusResponseV2(value) {
2809
2831
  }
2810
2832
  return value;
2811
2833
  }
2812
- async function submitDecryptRequestV2(thresholdNetworkUrl, ctHash, chainId, acp, overallStartTime, retry404TimeoutMs, onPoll) {
2834
+ async function submitDecryptRequestV2(thresholdNetworkUrl, ctHash, chainId, acp, overallStartTime, retry404TimeoutMs, timeoutMs, onPoll) {
2813
2835
  const body = {
2814
2836
  ct_tempkey: BigInt(ctHash).toString(16).padStart(64, "0"),
2815
2837
  host_chain_id: chainId
@@ -2916,7 +2938,7 @@ async function submitDecryptRequestV2(thresholdNetworkUrl, ctHash, chainId, acp,
2916
2938
  status: responseClassification.status,
2917
2939
  elapsedMs,
2918
2940
  retry404TimeoutMs,
2919
- overallTimeoutMs: DECRYPT_TIMEOUT_MS,
2941
+ overallTimeoutMs: timeoutMs,
2920
2942
  thresholdNetworkUrl,
2921
2943
  body,
2922
2944
  attemptIndex,
@@ -2928,13 +2950,13 @@ async function submitDecryptRequestV2(thresholdNetworkUrl, ctHash, chainId, acp,
2928
2950
  attemptIndex,
2929
2951
  elapsedMs,
2930
2952
  intervalMs: SUBMIT_RETRY_INTERVAL_MS2,
2931
- timeoutMs: DECRYPT_TIMEOUT_MS
2953
+ timeoutMs
2932
2954
  });
2933
2955
  await new Promise((resolve) => setTimeout(resolve, SUBMIT_RETRY_INTERVAL_MS2));
2934
2956
  attemptIndex += 1;
2935
2957
  }
2936
2958
  }
2937
- async function pollDecryptStatusV2(thresholdNetworkUrl, requestId, overallStartTime, onPoll) {
2959
+ async function pollDecryptStatusV2(thresholdNetworkUrl, requestId, overallStartTime, timeoutMs, onPoll) {
2938
2960
  let attemptIndex = 0;
2939
2961
  while (true) {
2940
2962
  const elapsedMs = Date.now() - overallStartTime;
@@ -2948,17 +2970,17 @@ async function pollDecryptStatusV2(thresholdNetworkUrl, requestId, overallStartT
2948
2970
  attemptIndex,
2949
2971
  elapsedMs,
2950
2972
  intervalMs,
2951
- timeoutMs: DECRYPT_TIMEOUT_MS
2973
+ timeoutMs
2952
2974
  });
2953
- if (elapsedMs > DECRYPT_TIMEOUT_MS) {
2975
+ if (elapsedMs > timeoutMs) {
2954
2976
  throw new CofheError({
2955
2977
  code: "DECRYPT_FAILED" /* DecryptFailed */,
2956
- message: `decrypt polling timed out after ${DECRYPT_TIMEOUT_MS}ms`,
2978
+ message: `decrypt polling timed out after ${timeoutMs}ms`,
2957
2979
  hint: "The request may still be processing. Try again later.",
2958
2980
  context: {
2959
2981
  thresholdNetworkUrl,
2960
2982
  requestId,
2961
- timeoutMs: DECRYPT_TIMEOUT_MS
2983
+ timeoutMs
2962
2984
  }
2963
2985
  });
2964
2986
  }
@@ -3047,6 +3069,7 @@ async function pollDecryptStatusV2(thresholdNetworkUrl, requestId, overallStartT
3047
3069
  }
3048
3070
  async function tnDecryptV2(params) {
3049
3071
  const { thresholdNetworkUrl, ctHash, chainId, acp, retry404TimeoutMs, onPoll } = params;
3072
+ const timeoutMs = params.timeoutMs ?? DEFAULT_DECRYPT_TIMEOUT_MS;
3050
3073
  const normalized404RetryTimeoutMs = normalize404RetryTimeoutMs({
3051
3074
  timeoutMs: retry404TimeoutMs,
3052
3075
  operationLabel: "decrypt",
@@ -3060,12 +3083,13 @@ async function tnDecryptV2(params) {
3060
3083
  acp,
3061
3084
  overallStartTime,
3062
3085
  normalized404RetryTimeoutMs,
3086
+ timeoutMs,
3063
3087
  onPoll
3064
3088
  );
3065
3089
  if (submitResult.kind === "completed") {
3066
3090
  return submitResult;
3067
3091
  }
3068
- return await pollDecryptStatusV2(thresholdNetworkUrl, submitResult.requestId, overallStartTime, onPoll);
3092
+ return await pollDecryptStatusV2(thresholdNetworkUrl, submitResult.requestId, overallStartTime, timeoutMs, onPoll);
3069
3093
  }
3070
3094
 
3071
3095
  // core/decrypt/decryptForTxBuilder.ts
@@ -3077,6 +3101,7 @@ var DecryptForTxBuilder = class extends BaseBuilder {
3077
3101
  acpSelection = "unset";
3078
3102
  pollCallback;
3079
3103
  retry404TimeoutMs = DEFAULT_404_RETRY_TIMEOUT_MS3;
3104
+ timeoutMs;
3080
3105
  constructor(params) {
3081
3106
  super({
3082
3107
  config: params.config,
@@ -3119,6 +3144,11 @@ var DecryptForTxBuilder = class extends BaseBuilder {
3119
3144
  this.retry404TimeoutMs = timeoutMs;
3120
3145
  return this;
3121
3146
  }
3147
+ setTimeout(timeoutMs) {
3148
+ assertDecryptTimeoutMs(timeoutMs, "decryptForTx");
3149
+ this.timeoutMs = timeoutMs;
3150
+ return this;
3151
+ }
3122
3152
  withACP(acpOrACPHash) {
3123
3153
  if (this.acpSelection === "with-acp") {
3124
3154
  throw new CofheError({
@@ -3252,6 +3282,7 @@ var DecryptForTxBuilder = class extends BaseBuilder {
3252
3282
  acp: wireAcp,
3253
3283
  thresholdNetworkUrl,
3254
3284
  retry404TimeoutMs: this.retry404TimeoutMs,
3285
+ timeoutMs: this.timeoutMs,
3255
3286
  onPoll: this.pollCallback
3256
3287
  });
3257
3288
  return {
@@ -465,6 +465,7 @@ declare class DecryptForViewBuilder<U extends FheTypes> extends BaseBuilder {
465
465
  private acp?;
466
466
  private pollCallback?;
467
467
  private retry404TimeoutMs;
468
+ private timeoutMs?;
468
469
  constructor(params: DecryptForViewBuilderParams<U>);
469
470
  /**
470
471
  * @param chainId - Chain to decrypt values from. Used to fetch the threshold network URL and use the correct acp.
@@ -500,6 +501,12 @@ declare class DecryptForViewBuilder<U extends FheTypes> extends BaseBuilder {
500
501
  getAccount(): string | undefined;
501
502
  onPoll(callback: DecryptPollCallbackFunction): DecryptForViewBuilder<U>;
502
503
  set404RetryTimeout(timeoutMs: number): DecryptForViewBuilder<U>;
504
+ /**
505
+ * The whole budget of this decrypt in milliseconds, submit retries and status polling together
506
+ * (5 minutes by default). Past it, `execute()` rejects with `SealOutputFailed`; a request still
507
+ * processing on the threshold network is not cancelled.
508
+ */
509
+ setTimeout(timeoutMs: number): DecryptForViewBuilder<U>;
503
510
  /**
504
511
  * Select "use acp" mode (optional).
505
512
  *
@@ -603,6 +610,7 @@ declare class DecryptForTxBuilder extends BaseBuilder {
603
610
  private acpSelection;
604
611
  private pollCallback?;
605
612
  private retry404TimeoutMs;
613
+ private timeoutMs?;
606
614
  constructor(params: DecryptForTxBuilderParams);
607
615
  /**
608
616
  * @param chainId - Chain to decrypt values from. Used to fetch the threshold network URL and use the correct acp.
@@ -642,6 +650,13 @@ declare class DecryptForTxBuilder extends BaseBuilder {
642
650
  onPoll(this: DecryptForTxBuilderSelected, callback: DecryptPollCallbackFunction): DecryptForTxBuilderSelected;
643
651
  set404RetryTimeout(this: DecryptForTxBuilderUnset, timeoutMs: number): DecryptForTxBuilderUnset;
644
652
  set404RetryTimeout(this: DecryptForTxBuilderSelected, timeoutMs: number): DecryptForTxBuilderSelected;
653
+ /**
654
+ * The whole budget of this decrypt in milliseconds, submit retries and status polling together
655
+ * (5 minutes by default). Past it, `execute()` rejects with `DecryptFailed`; a request still
656
+ * processing on the threshold network is not cancelled.
657
+ */
658
+ setTimeout(this: DecryptForTxBuilderUnset, timeoutMs: number): DecryptForTxBuilderUnset;
659
+ setTimeout(this: DecryptForTxBuilderSelected, timeoutMs: number): DecryptForTxBuilderSelected;
645
660
  /**
646
661
  * Select "use acp" mode.
647
662
  *