@cofhe/sdk 0.5.0 → 0.5.2

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.
@@ -10,51 +10,51 @@ import { persist, createJSONStorage } from 'zustand/middleware';
10
10
  import { produce } from 'immer';
11
11
 
12
12
  // core/error.ts
13
- var CofheErrorCode = /* @__PURE__ */ ((CofheErrorCode2) => {
14
- CofheErrorCode2["InternalError"] = "INTERNAL_ERROR";
15
- CofheErrorCode2["UnknownEnvironment"] = "UNKNOWN_ENVIRONMENT";
16
- CofheErrorCode2["InitTfheFailed"] = "INIT_TFHE_FAILED";
17
- CofheErrorCode2["InitViemFailed"] = "INIT_VIEM_FAILED";
18
- CofheErrorCode2["InitEthersFailed"] = "INIT_ETHERS_FAILED";
19
- CofheErrorCode2["NotConnected"] = "NOT_CONNECTED";
20
- CofheErrorCode2["MissingPublicClient"] = "MISSING_PUBLIC_CLIENT";
21
- CofheErrorCode2["MissingWalletClient"] = "MISSING_WALLET_CLIENT";
22
- CofheErrorCode2["MissingProviderParam"] = "MISSING_PROVIDER_PARAM";
23
- CofheErrorCode2["EmptySecurityZonesParam"] = "EMPTY_SECURITY_ZONES_PARAM";
24
- CofheErrorCode2["InvalidPermitData"] = "INVALID_PERMIT_DATA";
25
- CofheErrorCode2["InvalidPermitDomain"] = "INVALID_PERMIT_DOMAIN";
26
- CofheErrorCode2["PermitNotFound"] = "PERMIT_NOT_FOUND";
27
- CofheErrorCode2["CannotRemoveLastPermit"] = "CANNOT_REMOVE_LAST_PERMIT";
28
- CofheErrorCode2["AccountUninitialized"] = "ACCOUNT_UNINITIALIZED";
29
- CofheErrorCode2["ChainIdUninitialized"] = "CHAIN_ID_UNINITIALIZED";
30
- CofheErrorCode2["SealOutputFailed"] = "SEAL_OUTPUT_FAILED";
31
- CofheErrorCode2["SealOutputReturnedNull"] = "SEAL_OUTPUT_RETURNED_NULL";
32
- CofheErrorCode2["InvalidUtype"] = "INVALID_UTYPE";
33
- CofheErrorCode2["DecryptFailed"] = "DECRYPT_FAILED";
34
- CofheErrorCode2["DecryptReturnedNull"] = "DECRYPT_RETURNED_NULL";
35
- CofheErrorCode2["ZkMocksInsertCtHashesFailed"] = "ZK_MOCKS_INSERT_CT_HASHES_FAILED";
36
- CofheErrorCode2["ZkMocksCalcCtHashesFailed"] = "ZK_MOCKS_CALC_CT_HASHES_FAILED";
37
- CofheErrorCode2["ZkMocksVerifySignFailed"] = "ZK_MOCKS_VERIFY_SIGN_FAILED";
38
- CofheErrorCode2["ZkMocksCreateProofSignatureFailed"] = "ZK_MOCKS_CREATE_PROOF_SIGNATURE_FAILED";
39
- CofheErrorCode2["ZkVerifyFailed"] = "ZK_VERIFY_FAILED";
40
- CofheErrorCode2["ZkPackFailed"] = "ZK_PACK_FAILED";
41
- CofheErrorCode2["ZkProveFailed"] = "ZK_PROVE_FAILED";
42
- CofheErrorCode2["EncryptRemainingInItems"] = "ENCRYPT_REMAINING_IN_ITEMS";
43
- CofheErrorCode2["ZkUninitialized"] = "ZK_UNINITIALIZED";
44
- CofheErrorCode2["ZkVerifierUrlUninitialized"] = "ZK_VERIFIER_URL_UNINITIALIZED";
45
- CofheErrorCode2["ThresholdNetworkUrlUninitialized"] = "THRESHOLD_NETWORK_URL_UNINITIALIZED";
46
- CofheErrorCode2["MissingConfig"] = "MISSING_CONFIG";
47
- CofheErrorCode2["UnsupportedChain"] = "UNSUPPORTED_CHAIN";
48
- CofheErrorCode2["MissingZkBuilderAndCrsGenerator"] = "MISSING_ZK_BUILDER_AND_CRS_GENERATOR";
49
- CofheErrorCode2["MissingTfhePublicKeyDeserializer"] = "MISSING_TFHE_PUBLIC_KEY_DESERIALIZER";
50
- CofheErrorCode2["MissingCompactPkeCrsDeserializer"] = "MISSING_COMPACT_PKE_CRS_DESERIALIZER";
51
- CofheErrorCode2["MissingFheKey"] = "MISSING_FHE_KEY";
52
- CofheErrorCode2["MissingCrs"] = "MISSING_CRS";
53
- CofheErrorCode2["FetchKeysFailed"] = "FETCH_KEYS_FAILED";
54
- CofheErrorCode2["PublicWalletGetChainIdFailed"] = "PUBLIC_WALLET_GET_CHAIN_ID_FAILED";
55
- CofheErrorCode2["PublicWalletGetAddressesFailed"] = "PUBLIC_WALLET_GET_ADDRESSES_FAILED";
56
- CofheErrorCode2["RehydrateKeysStoreFailed"] = "REHYDRATE_KEYS_STORE_FAILED";
57
- return CofheErrorCode2;
13
+ var CofheErrorCode = /* @__PURE__ */ ((CofheErrorCode3) => {
14
+ CofheErrorCode3["InternalError"] = "INTERNAL_ERROR";
15
+ CofheErrorCode3["UnknownEnvironment"] = "UNKNOWN_ENVIRONMENT";
16
+ CofheErrorCode3["InitTfheFailed"] = "INIT_TFHE_FAILED";
17
+ CofheErrorCode3["InitViemFailed"] = "INIT_VIEM_FAILED";
18
+ CofheErrorCode3["InitEthersFailed"] = "INIT_ETHERS_FAILED";
19
+ CofheErrorCode3["NotConnected"] = "NOT_CONNECTED";
20
+ CofheErrorCode3["MissingPublicClient"] = "MISSING_PUBLIC_CLIENT";
21
+ CofheErrorCode3["MissingWalletClient"] = "MISSING_WALLET_CLIENT";
22
+ CofheErrorCode3["MissingProviderParam"] = "MISSING_PROVIDER_PARAM";
23
+ CofheErrorCode3["EmptySecurityZonesParam"] = "EMPTY_SECURITY_ZONES_PARAM";
24
+ CofheErrorCode3["InvalidPermitData"] = "INVALID_PERMIT_DATA";
25
+ CofheErrorCode3["InvalidPermitDomain"] = "INVALID_PERMIT_DOMAIN";
26
+ CofheErrorCode3["PermitNotFound"] = "PERMIT_NOT_FOUND";
27
+ CofheErrorCode3["CannotRemoveLastPermit"] = "CANNOT_REMOVE_LAST_PERMIT";
28
+ CofheErrorCode3["AccountUninitialized"] = "ACCOUNT_UNINITIALIZED";
29
+ CofheErrorCode3["ChainIdUninitialized"] = "CHAIN_ID_UNINITIALIZED";
30
+ CofheErrorCode3["SealOutputFailed"] = "SEAL_OUTPUT_FAILED";
31
+ CofheErrorCode3["SealOutputReturnedNull"] = "SEAL_OUTPUT_RETURNED_NULL";
32
+ CofheErrorCode3["InvalidUtype"] = "INVALID_UTYPE";
33
+ CofheErrorCode3["DecryptFailed"] = "DECRYPT_FAILED";
34
+ CofheErrorCode3["DecryptReturnedNull"] = "DECRYPT_RETURNED_NULL";
35
+ CofheErrorCode3["ZkMocksInsertCtHashesFailed"] = "ZK_MOCKS_INSERT_CT_HASHES_FAILED";
36
+ CofheErrorCode3["ZkMocksCalcCtHashesFailed"] = "ZK_MOCKS_CALC_CT_HASHES_FAILED";
37
+ CofheErrorCode3["ZkMocksVerifySignFailed"] = "ZK_MOCKS_VERIFY_SIGN_FAILED";
38
+ CofheErrorCode3["ZkMocksCreateProofSignatureFailed"] = "ZK_MOCKS_CREATE_PROOF_SIGNATURE_FAILED";
39
+ CofheErrorCode3["ZkVerifyFailed"] = "ZK_VERIFY_FAILED";
40
+ CofheErrorCode3["ZkPackFailed"] = "ZK_PACK_FAILED";
41
+ CofheErrorCode3["ZkProveFailed"] = "ZK_PROVE_FAILED";
42
+ CofheErrorCode3["EncryptRemainingInItems"] = "ENCRYPT_REMAINING_IN_ITEMS";
43
+ CofheErrorCode3["ZkUninitialized"] = "ZK_UNINITIALIZED";
44
+ CofheErrorCode3["ZkVerifierUrlUninitialized"] = "ZK_VERIFIER_URL_UNINITIALIZED";
45
+ CofheErrorCode3["ThresholdNetworkUrlUninitialized"] = "THRESHOLD_NETWORK_URL_UNINITIALIZED";
46
+ CofheErrorCode3["MissingConfig"] = "MISSING_CONFIG";
47
+ CofheErrorCode3["UnsupportedChain"] = "UNSUPPORTED_CHAIN";
48
+ CofheErrorCode3["MissingZkBuilderAndCrsGenerator"] = "MISSING_ZK_BUILDER_AND_CRS_GENERATOR";
49
+ CofheErrorCode3["MissingTfhePublicKeyDeserializer"] = "MISSING_TFHE_PUBLIC_KEY_DESERIALIZER";
50
+ CofheErrorCode3["MissingCompactPkeCrsDeserializer"] = "MISSING_COMPACT_PKE_CRS_DESERIALIZER";
51
+ CofheErrorCode3["MissingFheKey"] = "MISSING_FHE_KEY";
52
+ CofheErrorCode3["MissingCrs"] = "MISSING_CRS";
53
+ CofheErrorCode3["FetchKeysFailed"] = "FETCH_KEYS_FAILED";
54
+ CofheErrorCode3["PublicWalletGetChainIdFailed"] = "PUBLIC_WALLET_GET_CHAIN_ID_FAILED";
55
+ CofheErrorCode3["PublicWalletGetAddressesFailed"] = "PUBLIC_WALLET_GET_ADDRESSES_FAILED";
56
+ CofheErrorCode3["RehydrateKeysStoreFailed"] = "REHYDRATE_KEYS_STORE_FAILED";
57
+ return CofheErrorCode3;
58
58
  })(CofheErrorCode || {});
59
59
  var CofheError = class _CofheError extends Error {
60
60
  code;
@@ -1977,6 +1977,96 @@ function computeMinuteRampPollIntervalMs(elapsedMs, params) {
1977
1977
  return Math.min(params.maxIntervalMs, Math.max(params.minIntervalMs, intervalMs));
1978
1978
  }
1979
1979
 
1980
+ // core/decrypt/submitRetry.ts
1981
+ var DEFAULT_404_RETRY_TIMEOUT_MS = 1e4;
1982
+ function isRetryableSubmitStatus(status) {
1983
+ return status === 204 || status === 404;
1984
+ }
1985
+ function normalize404RetryTimeoutMs(params) {
1986
+ const { timeoutMs, operationLabel, errorCode } = params;
1987
+ if (timeoutMs === void 0)
1988
+ return DEFAULT_404_RETRY_TIMEOUT_MS;
1989
+ if (!Number.isFinite(timeoutMs) || timeoutMs < 0) {
1990
+ throw new CofheError({
1991
+ code: errorCode,
1992
+ message: `${operationLabel} submit 404 retry timeout must be a finite number greater than or equal to 0`,
1993
+ context: {
1994
+ timeoutMs
1995
+ }
1996
+ });
1997
+ }
1998
+ return timeoutMs;
1999
+ }
2000
+ async function classifySubmitResponse(params) {
2001
+ const { response, extractErrorMessage } = params;
2002
+ if (isRetryableSubmitStatus(response.status)) {
2003
+ return { kind: "retryable", status: response.status };
2004
+ }
2005
+ if (response.ok) {
2006
+ return { kind: "parse-json" };
2007
+ }
2008
+ let errorMessage = `HTTP ${response.status}`;
2009
+ try {
2010
+ const errorBody = await response.json();
2011
+ const maybeErrorMessage = extractErrorMessage?.(errorBody);
2012
+ if (typeof maybeErrorMessage === "string" && maybeErrorMessage.length > 0) {
2013
+ errorMessage = maybeErrorMessage;
2014
+ } else if (errorBody && typeof errorBody === "object") {
2015
+ const defaultMessage = errorBody.error_message;
2016
+ const fallbackMessage = errorBody.message;
2017
+ if (typeof defaultMessage === "string" && defaultMessage.length > 0) {
2018
+ errorMessage = defaultMessage;
2019
+ } else if (typeof fallbackMessage === "string" && fallbackMessage.length > 0) {
2020
+ errorMessage = fallbackMessage;
2021
+ }
2022
+ }
2023
+ } catch {
2024
+ errorMessage = response.statusText || errorMessage;
2025
+ }
2026
+ return { kind: "fatal-http", errorMessage };
2027
+ }
2028
+ function throwIfSubmitRetryTimedOut(params) {
2029
+ const {
2030
+ operationLabel,
2031
+ errorCode,
2032
+ status,
2033
+ elapsedMs,
2034
+ retry404TimeoutMs,
2035
+ overallTimeoutMs,
2036
+ thresholdNetworkUrl,
2037
+ body,
2038
+ attemptIndex
2039
+ } = params;
2040
+ if (status === 404 && elapsedMs > retry404TimeoutMs) {
2041
+ throw new CofheError({
2042
+ code: errorCode,
2043
+ message: `${operationLabel} submit retried 404 responses without receiving request_id for ${retry404TimeoutMs}ms`,
2044
+ hint: "The ciphertext may not be indexed yet. Increase set404RetryTimeout(...) if the backend is slow to index ciphertexts.",
2045
+ context: {
2046
+ thresholdNetworkUrl,
2047
+ body,
2048
+ attemptIndex,
2049
+ timeoutMs: retry404TimeoutMs,
2050
+ status
2051
+ }
2052
+ });
2053
+ }
2054
+ if (elapsedMs > overallTimeoutMs) {
2055
+ throw new CofheError({
2056
+ code: errorCode,
2057
+ message: `${operationLabel} submit retried without receiving request_id for ${overallTimeoutMs}ms`,
2058
+ hint: "The ciphertext may still be propagating. Try again later.",
2059
+ context: {
2060
+ thresholdNetworkUrl,
2061
+ body,
2062
+ attemptIndex,
2063
+ timeoutMs: overallTimeoutMs,
2064
+ status
2065
+ }
2066
+ });
2067
+ }
2068
+ }
2069
+
1980
2070
  // core/decrypt/tnSealOutputV2.ts
1981
2071
  var POLL_INTERVAL_MS = 1e3;
1982
2072
  var POLL_MAX_INTERVAL_MS = 1e4;
@@ -2038,7 +2128,7 @@ function parseCompletedSealOutputResponse(params) {
2038
2128
  }
2039
2129
  return convertSealedData(sealed);
2040
2130
  }
2041
- async function submitSealOutputRequest(thresholdNetworkUrl, ctHash, chainId, permission, overallStartTime, onPoll) {
2131
+ async function submitSealOutputRequest(thresholdNetworkUrl, ctHash, chainId, permission, overallStartTime, retry404TimeoutMs, onPoll) {
2042
2132
  const body = {
2043
2133
  ct_tempkey: BigInt(ctHash).toString(16).padStart(64, "0"),
2044
2134
  host_chain_id: chainId,
@@ -2068,17 +2158,11 @@ async function submitSealOutputRequest(thresholdNetworkUrl, ctHash, chainId, per
2068
2158
  }
2069
2159
  });
2070
2160
  }
2071
- if (!response.ok) {
2072
- let errorMessage = `HTTP ${response.status}`;
2073
- try {
2074
- const errorBody = await response.json();
2075
- errorMessage = errorBody.error_message || errorBody.message || errorMessage;
2076
- } catch {
2077
- errorMessage = response.statusText || errorMessage;
2078
- }
2161
+ const responseClassification = await classifySubmitResponse({ response });
2162
+ if (responseClassification.kind === "fatal-http") {
2079
2163
  throw new CofheError({
2080
2164
  code: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */,
2081
- message: `sealOutput request failed: ${errorMessage}`,
2165
+ message: `sealOutput request failed: ${responseClassification.errorMessage}`,
2082
2166
  hint: "Check the threshold network URL and request parameters.",
2083
2167
  context: {
2084
2168
  thresholdNetworkUrl,
@@ -2089,8 +2173,8 @@ async function submitSealOutputRequest(thresholdNetworkUrl, ctHash, chainId, per
2089
2173
  }
2090
2174
  });
2091
2175
  }
2092
- let submitResponse;
2093
- if (response.status !== 204) {
2176
+ if (responseClassification.kind === "parse-json") {
2177
+ let submitResponse;
2094
2178
  try {
2095
2179
  submitResponse = await response.json();
2096
2180
  } catch (e) {
@@ -2118,46 +2202,39 @@ async function submitSealOutputRequest(thresholdNetworkUrl, ctHash, chainId, per
2118
2202
  if (submitResponse.request_id) {
2119
2203
  return { kind: "request_id", requestId: submitResponse.request_id };
2120
2204
  }
2121
- }
2122
- if (response.status === 204) {
2123
- const elapsedMs = Date.now() - overallStartTime;
2124
- if (elapsedMs > SEAL_OUTPUT_TIMEOUT_MS) {
2125
- throw new CofheError({
2126
- code: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */,
2127
- message: `sealOutput submit retried without receiving request_id for ${SEAL_OUTPUT_TIMEOUT_MS}ms`,
2128
- hint: "The ciphertext may still be propagating. Try again later.",
2129
- context: {
2130
- thresholdNetworkUrl,
2131
- body,
2132
- attemptIndex,
2133
- timeoutMs: SEAL_OUTPUT_TIMEOUT_MS,
2134
- submitResponse,
2135
- status: response.status
2136
- }
2137
- });
2138
- }
2139
- onPoll?.({
2140
- operation: "sealoutput",
2141
- requestId: "",
2142
- attemptIndex,
2143
- elapsedMs,
2144
- intervalMs: SUBMIT_RETRY_INTERVAL_MS,
2145
- timeoutMs: SEAL_OUTPUT_TIMEOUT_MS
2205
+ throw new CofheError({
2206
+ code: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */,
2207
+ message: `sealOutput submit response missing request_id`,
2208
+ context: {
2209
+ thresholdNetworkUrl,
2210
+ body,
2211
+ submitResponse,
2212
+ attemptIndex
2213
+ }
2146
2214
  });
2147
- await new Promise((resolve) => setTimeout(resolve, SUBMIT_RETRY_INTERVAL_MS));
2148
- attemptIndex += 1;
2149
- continue;
2150
2215
  }
2151
- throw new CofheError({
2152
- code: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */,
2153
- message: `sealOutput submit response missing request_id`,
2154
- context: {
2155
- thresholdNetworkUrl,
2156
- body,
2157
- submitResponse,
2158
- attemptIndex
2159
- }
2216
+ const elapsedMs = Date.now() - overallStartTime;
2217
+ throwIfSubmitRetryTimedOut({
2218
+ operationLabel: "sealOutput",
2219
+ errorCode: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */,
2220
+ status: responseClassification.status,
2221
+ elapsedMs,
2222
+ retry404TimeoutMs,
2223
+ overallTimeoutMs: SEAL_OUTPUT_TIMEOUT_MS,
2224
+ thresholdNetworkUrl,
2225
+ body,
2226
+ attemptIndex
2160
2227
  });
2228
+ onPoll?.({
2229
+ operation: "sealoutput",
2230
+ requestId: "",
2231
+ attemptIndex,
2232
+ elapsedMs,
2233
+ intervalMs: SUBMIT_RETRY_INTERVAL_MS,
2234
+ timeoutMs: SEAL_OUTPUT_TIMEOUT_MS
2235
+ });
2236
+ await new Promise((resolve) => setTimeout(resolve, SUBMIT_RETRY_INTERVAL_MS));
2237
+ attemptIndex += 1;
2161
2238
  }
2162
2239
  }
2163
2240
  async function pollSealOutputStatus(thresholdNetworkUrl, requestId, overallStartTime, onPoll) {
@@ -2272,7 +2349,12 @@ async function pollSealOutputStatus(thresholdNetworkUrl, requestId, overallStart
2272
2349
  });
2273
2350
  }
2274
2351
  async function tnSealOutputV2(params) {
2275
- const { thresholdNetworkUrl, ctHash, chainId, permission, onPoll } = params;
2352
+ const { thresholdNetworkUrl, ctHash, chainId, permission, retry404TimeoutMs, onPoll } = params;
2353
+ const normalized404RetryTimeoutMs = normalize404RetryTimeoutMs({
2354
+ timeoutMs: retry404TimeoutMs,
2355
+ operationLabel: "sealOutput",
2356
+ errorCode: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */
2357
+ });
2276
2358
  const overallStartTime = Date.now();
2277
2359
  const submitResult = await submitSealOutputRequest(
2278
2360
  thresholdNetworkUrl,
@@ -2280,6 +2362,7 @@ async function tnSealOutputV2(params) {
2280
2362
  chainId,
2281
2363
  permission,
2282
2364
  overallStartTime,
2365
+ normalized404RetryTimeoutMs,
2283
2366
  onPoll
2284
2367
  );
2285
2368
  if (submitResult.kind === "completed") {
@@ -2289,12 +2372,14 @@ async function tnSealOutputV2(params) {
2289
2372
  }
2290
2373
 
2291
2374
  // core/decrypt/decryptForViewBuilder.ts
2375
+ var DEFAULT_404_RETRY_TIMEOUT_MS2 = 1e4;
2292
2376
  var DecryptForViewBuilder = class extends BaseBuilder {
2293
2377
  ctHash;
2294
2378
  utype;
2295
2379
  permitHash;
2296
2380
  permit;
2297
2381
  pollCallback;
2382
+ retry404TimeoutMs = DEFAULT_404_RETRY_TIMEOUT_MS2;
2298
2383
  constructor(params) {
2299
2384
  super({
2300
2385
  config: params.config,
@@ -2355,6 +2440,19 @@ var DecryptForViewBuilder = class extends BaseBuilder {
2355
2440
  this.pollCallback = callback;
2356
2441
  return this;
2357
2442
  }
2443
+ set404RetryTimeout(timeoutMs) {
2444
+ if (!Number.isFinite(timeoutMs) || timeoutMs < 0) {
2445
+ throw new CofheError({
2446
+ code: "INTERNAL_ERROR" /* InternalError */,
2447
+ message: "decryptForView: set404RetryTimeout(timeoutMs) expects a finite number greater than or equal to 0.",
2448
+ context: {
2449
+ timeoutMs
2450
+ }
2451
+ });
2452
+ }
2453
+ this.retry404TimeoutMs = timeoutMs;
2454
+ return this;
2455
+ }
2358
2456
  withPermit(permitOrPermitHash) {
2359
2457
  if (typeof permitOrPermitHash === "string") {
2360
2458
  this.permitHash = permitOrPermitHash;
@@ -2483,6 +2581,7 @@ var DecryptForViewBuilder = class extends BaseBuilder {
2483
2581
  chainId: this.chainId,
2484
2582
  permission,
2485
2583
  thresholdNetworkUrl,
2584
+ retry404TimeoutMs: this.retry404TimeoutMs,
2486
2585
  onPoll: this.pollCallback
2487
2586
  });
2488
2587
  return PermitUtils.unseal(permit, sealed);
@@ -2758,7 +2857,7 @@ function assertDecryptStatusResponseV2(value) {
2758
2857
  }
2759
2858
  return value;
2760
2859
  }
2761
- async function submitDecryptRequestV2(thresholdNetworkUrl, ctHash, chainId, permission, overallStartTime, onPoll) {
2860
+ async function submitDecryptRequestV2(thresholdNetworkUrl, ctHash, chainId, permission, overallStartTime, retry404TimeoutMs, onPoll) {
2762
2861
  const body = {
2763
2862
  ct_tempkey: BigInt(ctHash).toString(16).padStart(64, "0"),
2764
2863
  host_chain_id: chainId
@@ -2790,19 +2889,11 @@ async function submitDecryptRequestV2(thresholdNetworkUrl, ctHash, chainId, perm
2790
2889
  }
2791
2890
  });
2792
2891
  }
2793
- if (!response.ok) {
2794
- let errorMessage = `HTTP ${response.status}`;
2795
- try {
2796
- const errorBody = await response.json();
2797
- const maybeMessage = errorBody.error_message || errorBody.message;
2798
- if (typeof maybeMessage === "string" && maybeMessage.length > 0)
2799
- errorMessage = maybeMessage;
2800
- } catch {
2801
- errorMessage = response.statusText || errorMessage;
2802
- }
2892
+ const responseClassification = await classifySubmitResponse({ response });
2893
+ if (responseClassification.kind === "fatal-http") {
2803
2894
  throw new CofheError({
2804
2895
  code: "DECRYPT_FAILED" /* DecryptFailed */,
2805
- message: `decrypt request failed: ${errorMessage}`,
2896
+ message: `decrypt request failed: ${responseClassification.errorMessage}`,
2806
2897
  hint: "Check the threshold network URL and request parameters.",
2807
2898
  context: {
2808
2899
  thresholdNetworkUrl,
@@ -2813,8 +2904,8 @@ async function submitDecryptRequestV2(thresholdNetworkUrl, ctHash, chainId, perm
2813
2904
  }
2814
2905
  });
2815
2906
  }
2816
- let submitResponse;
2817
- if (response.status !== 204) {
2907
+ if (responseClassification.kind === "parse-json") {
2908
+ let submitResponse;
2818
2909
  let rawJson;
2819
2910
  try {
2820
2911
  rawJson = await response.json();
@@ -2844,46 +2935,39 @@ async function submitDecryptRequestV2(thresholdNetworkUrl, ctHash, chainId, perm
2844
2935
  if (submitResponse.request_id) {
2845
2936
  return { kind: "request_id", requestId: submitResponse.request_id };
2846
2937
  }
2847
- }
2848
- if (response.status === 204) {
2849
- const elapsedMs = Date.now() - overallStartTime;
2850
- if (elapsedMs > DECRYPT_TIMEOUT_MS) {
2851
- throw new CofheError({
2852
- code: "DECRYPT_FAILED" /* DecryptFailed */,
2853
- message: `decrypt submit retried without receiving request_id for ${DECRYPT_TIMEOUT_MS}ms`,
2854
- hint: "The ciphertext may still be propagating. Try again later.",
2855
- context: {
2856
- thresholdNetworkUrl,
2857
- body,
2858
- attemptIndex,
2859
- timeoutMs: DECRYPT_TIMEOUT_MS,
2860
- submitResponse,
2861
- status: response.status
2862
- }
2863
- });
2864
- }
2865
- onPoll?.({
2866
- operation: "decrypt",
2867
- requestId: "",
2868
- attemptIndex,
2869
- elapsedMs,
2870
- intervalMs: SUBMIT_RETRY_INTERVAL_MS2,
2871
- timeoutMs: DECRYPT_TIMEOUT_MS
2938
+ throw new CofheError({
2939
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
2940
+ message: `decrypt submit response missing request_id`,
2941
+ context: {
2942
+ thresholdNetworkUrl,
2943
+ body,
2944
+ submitResponse,
2945
+ attemptIndex
2946
+ }
2872
2947
  });
2873
- await new Promise((resolve) => setTimeout(resolve, SUBMIT_RETRY_INTERVAL_MS2));
2874
- attemptIndex += 1;
2875
- continue;
2876
2948
  }
2877
- throw new CofheError({
2878
- code: "DECRYPT_FAILED" /* DecryptFailed */,
2879
- message: `decrypt submit response missing request_id`,
2880
- context: {
2881
- thresholdNetworkUrl,
2882
- body,
2883
- submitResponse,
2884
- attemptIndex
2885
- }
2949
+ const elapsedMs = Date.now() - overallStartTime;
2950
+ throwIfSubmitRetryTimedOut({
2951
+ operationLabel: "decrypt",
2952
+ errorCode: "DECRYPT_FAILED" /* DecryptFailed */,
2953
+ status: responseClassification.status,
2954
+ elapsedMs,
2955
+ retry404TimeoutMs,
2956
+ overallTimeoutMs: DECRYPT_TIMEOUT_MS,
2957
+ thresholdNetworkUrl,
2958
+ body,
2959
+ attemptIndex
2886
2960
  });
2961
+ onPoll?.({
2962
+ operation: "decrypt",
2963
+ requestId: "",
2964
+ attemptIndex,
2965
+ elapsedMs,
2966
+ intervalMs: SUBMIT_RETRY_INTERVAL_MS2,
2967
+ timeoutMs: DECRYPT_TIMEOUT_MS
2968
+ });
2969
+ await new Promise((resolve) => setTimeout(resolve, SUBMIT_RETRY_INTERVAL_MS2));
2970
+ attemptIndex += 1;
2887
2971
  }
2888
2972
  }
2889
2973
  async function pollDecryptStatusV2(thresholdNetworkUrl, requestId, overallStartTime, onPoll) {
@@ -3001,7 +3085,12 @@ async function pollDecryptStatusV2(thresholdNetworkUrl, requestId, overallStartT
3001
3085
  });
3002
3086
  }
3003
3087
  async function tnDecryptV2(params) {
3004
- const { thresholdNetworkUrl, ctHash, chainId, permission, onPoll } = params;
3088
+ const { thresholdNetworkUrl, ctHash, chainId, permission, retry404TimeoutMs, onPoll } = params;
3089
+ const normalized404RetryTimeoutMs = normalize404RetryTimeoutMs({
3090
+ timeoutMs: retry404TimeoutMs,
3091
+ operationLabel: "decrypt",
3092
+ errorCode: "DECRYPT_FAILED" /* DecryptFailed */
3093
+ });
3005
3094
  const overallStartTime = Date.now();
3006
3095
  const submitResult = await submitDecryptRequestV2(
3007
3096
  thresholdNetworkUrl,
@@ -3009,6 +3098,7 @@ async function tnDecryptV2(params) {
3009
3098
  chainId,
3010
3099
  permission,
3011
3100
  overallStartTime,
3101
+ normalized404RetryTimeoutMs,
3012
3102
  onPoll
3013
3103
  );
3014
3104
  if (submitResult.kind === "completed") {
@@ -3018,12 +3108,14 @@ async function tnDecryptV2(params) {
3018
3108
  }
3019
3109
 
3020
3110
  // core/decrypt/decryptForTxBuilder.ts
3111
+ var DEFAULT_404_RETRY_TIMEOUT_MS3 = 1e4;
3021
3112
  var DecryptForTxBuilder = class extends BaseBuilder {
3022
3113
  ctHash;
3023
3114
  permitHash;
3024
3115
  permit;
3025
3116
  permitSelection = "unset";
3026
3117
  pollCallback;
3118
+ retry404TimeoutMs = DEFAULT_404_RETRY_TIMEOUT_MS3;
3027
3119
  constructor(params) {
3028
3120
  super({
3029
3121
  config: params.config,
@@ -3053,6 +3145,19 @@ var DecryptForTxBuilder = class extends BaseBuilder {
3053
3145
  this.pollCallback = callback;
3054
3146
  return this;
3055
3147
  }
3148
+ set404RetryTimeout(timeoutMs) {
3149
+ if (!Number.isFinite(timeoutMs) || timeoutMs < 0) {
3150
+ throw new CofheError({
3151
+ code: "INTERNAL_ERROR" /* InternalError */,
3152
+ message: "decryptForTx: set404RetryTimeout(timeoutMs) expects a finite number greater than or equal to 0.",
3153
+ context: {
3154
+ timeoutMs
3155
+ }
3156
+ });
3157
+ }
3158
+ this.retry404TimeoutMs = timeoutMs;
3159
+ return this;
3160
+ }
3056
3161
  withPermit(permitOrPermitHash) {
3057
3162
  if (this.permitSelection === "with-permit") {
3058
3163
  throw new CofheError({
@@ -3185,6 +3290,7 @@ var DecryptForTxBuilder = class extends BaseBuilder {
3185
3290
  chainId: this.chainId,
3186
3291
  permission,
3187
3292
  thresholdNetworkUrl,
3293
+ retry404TimeoutMs: this.retry404TimeoutMs,
3188
3294
  onPoll: this.pollCallback
3189
3295
  });
3190
3296
  return {
@@ -371,6 +371,7 @@ declare class DecryptForViewBuilder<U extends FheTypes> extends BaseBuilder {
371
371
  private permitHash?;
372
372
  private permit?;
373
373
  private pollCallback?;
374
+ private retry404TimeoutMs;
374
375
  constructor(params: DecryptForViewBuilderParams<U>);
375
376
  /**
376
377
  * @param chainId - Chain to decrypt values from. Used to fetch the threshold network URL and use the correct permit.
@@ -405,6 +406,7 @@ declare class DecryptForViewBuilder<U extends FheTypes> extends BaseBuilder {
405
406
  setAccount(account: string): DecryptForViewBuilder<U>;
406
407
  getAccount(): string | undefined;
407
408
  onPoll(callback: DecryptPollCallbackFunction): DecryptForViewBuilder<U>;
409
+ set404RetryTimeout(timeoutMs: number): DecryptForViewBuilder<U>;
408
410
  /**
409
411
  * Select "use permit" mode (optional).
410
412
  *
@@ -507,6 +509,7 @@ declare class DecryptForTxBuilder extends BaseBuilder {
507
509
  private permit?;
508
510
  private permitSelection;
509
511
  private pollCallback?;
512
+ private retry404TimeoutMs;
510
513
  constructor(params: DecryptForTxBuilderParams);
511
514
  /**
512
515
  * @param chainId - Chain to decrypt values from. Used to fetch the threshold network URL and use the correct permit.
@@ -544,6 +547,8 @@ declare class DecryptForTxBuilder extends BaseBuilder {
544
547
  getAccount(): string | undefined;
545
548
  onPoll(this: DecryptForTxBuilderUnset, callback: DecryptPollCallbackFunction): DecryptForTxBuilderUnset;
546
549
  onPoll(this: DecryptForTxBuilderSelected, callback: DecryptPollCallbackFunction): DecryptForTxBuilderSelected;
550
+ set404RetryTimeout(this: DecryptForTxBuilderUnset, timeoutMs: number): DecryptForTxBuilderUnset;
551
+ set404RetryTimeout(this: DecryptForTxBuilderSelected, timeoutMs: number): DecryptForTxBuilderSelected;
547
552
  /**
548
553
  * Select "use permit" mode.
549
554
  *
@@ -371,6 +371,7 @@ declare class DecryptForViewBuilder<U extends FheTypes> extends BaseBuilder {
371
371
  private permitHash?;
372
372
  private permit?;
373
373
  private pollCallback?;
374
+ private retry404TimeoutMs;
374
375
  constructor(params: DecryptForViewBuilderParams<U>);
375
376
  /**
376
377
  * @param chainId - Chain to decrypt values from. Used to fetch the threshold network URL and use the correct permit.
@@ -405,6 +406,7 @@ declare class DecryptForViewBuilder<U extends FheTypes> extends BaseBuilder {
405
406
  setAccount(account: string): DecryptForViewBuilder<U>;
406
407
  getAccount(): string | undefined;
407
408
  onPoll(callback: DecryptPollCallbackFunction): DecryptForViewBuilder<U>;
409
+ set404RetryTimeout(timeoutMs: number): DecryptForViewBuilder<U>;
408
410
  /**
409
411
  * Select "use permit" mode (optional).
410
412
  *
@@ -507,6 +509,7 @@ declare class DecryptForTxBuilder extends BaseBuilder {
507
509
  private permit?;
508
510
  private permitSelection;
509
511
  private pollCallback?;
512
+ private retry404TimeoutMs;
510
513
  constructor(params: DecryptForTxBuilderParams);
511
514
  /**
512
515
  * @param chainId - Chain to decrypt values from. Used to fetch the threshold network URL and use the correct permit.
@@ -544,6 +547,8 @@ declare class DecryptForTxBuilder extends BaseBuilder {
544
547
  getAccount(): string | undefined;
545
548
  onPoll(this: DecryptForTxBuilderUnset, callback: DecryptPollCallbackFunction): DecryptForTxBuilderUnset;
546
549
  onPoll(this: DecryptForTxBuilderSelected, callback: DecryptPollCallbackFunction): DecryptForTxBuilderSelected;
550
+ set404RetryTimeout(this: DecryptForTxBuilderUnset, timeoutMs: number): DecryptForTxBuilderUnset;
551
+ set404RetryTimeout(this: DecryptForTxBuilderSelected, timeoutMs: number): DecryptForTxBuilderSelected;
547
552
  /**
548
553
  * Select "use permit" mode.
549
554
  *