@cofhe/sdk 0.3.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/adapters/{ethers5.test.ts → test/ethers5.test.ts} +2 -2
  3. package/adapters/{ethers6.test.ts → test/ethers6.test.ts} +2 -2
  4. package/adapters/{hardhat.hh2.test.ts → test/hardhat.hh2.test.ts} +2 -2
  5. package/adapters/{index.test.ts → test/index.test.ts} +1 -1
  6. package/adapters/{wagmi.test.ts → test/wagmi.test.ts} +1 -1
  7. package/chains/{chains.test.ts → test/chains.test.ts} +1 -1
  8. package/core/client.ts +15 -5
  9. package/core/clientTypes.ts +7 -5
  10. package/core/consts.ts +9 -0
  11. package/core/decrypt/cofheMocksDecryptForTx.ts +14 -3
  12. package/core/decrypt/decryptForTxBuilder.ts +24 -10
  13. package/core/decrypt/decryptForViewBuilder.ts +14 -7
  14. package/core/decrypt/polling.ts +14 -0
  15. package/core/decrypt/tnDecryptUtils.ts +65 -0
  16. package/core/decrypt/{tnDecrypt.ts → tnDecryptV1.ts} +7 -70
  17. package/core/decrypt/tnDecryptV2.ts +483 -0
  18. package/core/decrypt/tnSealOutputV2.ts +245 -104
  19. package/core/decrypt/verifyDecryptResult.ts +65 -0
  20. package/core/encrypt/cofheMocksZkVerifySign.ts +6 -6
  21. package/core/encrypt/zkPackProveVerify.ts +10 -19
  22. package/core/fetchKeys.ts +0 -2
  23. package/core/index.ts +9 -1
  24. package/core/keyStore.ts +5 -2
  25. package/core/permits.ts +8 -3
  26. package/core/{client.test.ts → test/client.test.ts} +7 -7
  27. package/core/{config.test.ts → test/config.test.ts} +1 -1
  28. package/core/test/decrypt.test.ts +252 -0
  29. package/core/test/decryptBuilders.test.ts +390 -0
  30. package/core/{encrypt → test}/encryptInputsBuilder.test.ts +61 -6
  31. package/core/{fetchKeys.test.ts → test/fetchKeys.test.ts} +3 -3
  32. package/core/{keyStore.test.ts → test/keyStore.test.ts} +5 -3
  33. package/core/{permits.test.ts → test/permits.test.ts} +42 -1
  34. package/core/test/pollCallbacks.test.ts +563 -0
  35. package/core/types.ts +21 -0
  36. package/dist/chains.d.cts +2 -2
  37. package/dist/chains.d.ts +2 -2
  38. package/dist/chunk-4FP4V35O.js +13 -0
  39. package/dist/{chunk-NWDKXBIP.js → chunk-MRCKUMOS.js} +62 -22
  40. package/dist/{chunk-LWMRB6SD.js → chunk-S7OKGLFD.js} +615 -198
  41. package/dist/{clientTypes-Y43CKbOz.d.cts → clientTypes-BSbwairE.d.cts} +38 -13
  42. package/dist/{clientTypes-PQha8zes.d.ts → clientTypes-DDmcgZ0a.d.ts} +38 -13
  43. package/dist/core.cjs +691 -235
  44. package/dist/core.d.cts +24 -6
  45. package/dist/core.d.ts +24 -6
  46. package/dist/core.js +3 -2
  47. package/dist/node.cjs +696 -237
  48. package/dist/node.d.cts +3 -3
  49. package/dist/node.d.ts +3 -3
  50. package/dist/node.js +14 -7
  51. package/dist/{permit-MZ502UBl.d.ts → permit-DnVMDT5h.d.cts} +34 -4
  52. package/dist/{permit-MZ502UBl.d.cts → permit-DnVMDT5h.d.ts} +34 -4
  53. package/dist/permits.cjs +66 -29
  54. package/dist/permits.d.cts +18 -13
  55. package/dist/permits.d.ts +18 -13
  56. package/dist/permits.js +2 -1
  57. package/dist/web.cjs +718 -242
  58. package/dist/web.d.cts +8 -4
  59. package/dist/web.d.ts +8 -4
  60. package/dist/web.js +34 -11
  61. package/dist/zkProve.worker.cjs +6 -3
  62. package/dist/zkProve.worker.js +5 -3
  63. package/node/index.ts +13 -4
  64. package/node/test/client.test.ts +25 -0
  65. package/node/test/config.test.ts +16 -0
  66. package/node/test/inherited.test.ts +244 -0
  67. package/node/test/tfheinit.test.ts +56 -0
  68. package/package.json +24 -22
  69. package/permits/permit.ts +31 -5
  70. package/permits/sealing.ts +1 -1
  71. package/permits/{localstorage.test.ts → test/localstorage.test.ts} +2 -2
  72. package/permits/{permit.test.ts → test/permit.test.ts} +35 -1
  73. package/permits/{sealing.test.ts → test/sealing.test.ts} +1 -1
  74. package/permits/{store.test.ts → test/store.test.ts} +2 -2
  75. package/permits/{validation.test.ts → test/validation.test.ts} +82 -6
  76. package/permits/types.ts +1 -1
  77. package/permits/validation.ts +42 -2
  78. package/web/const.ts +2 -0
  79. package/web/index.ts +20 -6
  80. package/web/storage.ts +18 -3
  81. package/web/{client.web.test.ts → test/client.web.test.ts} +13 -1
  82. package/web/test/config.web.test.ts +16 -0
  83. package/web/test/inherited.web.test.ts +245 -0
  84. package/web/test/tfheinit.web.test.ts +62 -0
  85. package/web/{worker.config.web.test.ts → test/worker.config.web.test.ts} +1 -1
  86. package/web/{worker.output.web.test.ts → test/worker.output.web.test.ts} +1 -1
  87. package/web/{workerManager.test.ts → test/workerManager.test.ts} +1 -1
  88. package/web/{workerManager.web.test.ts → test/workerManager.web.test.ts} +1 -1
  89. package/web/zkProve.worker.ts +4 -3
  90. package/node/client.test.ts +0 -147
  91. package/node/config.test.ts +0 -68
  92. package/node/encryptInputs.test.ts +0 -155
  93. package/web/config.web.test.ts +0 -69
  94. package/web/encryptInputs.web.test.ts +0 -172
  95. package/web/worker.builder.web.test.ts +0 -148
  96. /package/dist/{types-YiAC4gig.d.cts → types-C07FK-cL.d.cts} +0 -0
  97. /package/dist/{types-YiAC4gig.d.ts → types-C07FK-cL.d.ts} +0 -0
@@ -1,7 +1,8 @@
1
1
  import { hardhat as hardhat$1 } from './chunk-TBLR7NNE.js';
2
- import { permitStore, PermitUtils, MOCKS_THRESHOLD_NETWORK_ADDRESS, MOCKS_DECRYPT_RESULT_SIGNER_PRIVATE_KEY, MOCKS_ZK_VERIFIER_SIGNER_PRIVATE_KEY, MOCKS_ZK_VERIFIER_ADDRESS } from './chunk-NWDKXBIP.js';
2
+ import { permitStore, PermitUtils } from './chunk-MRCKUMOS.js';
3
+ import { TFHE_RS_KEY_VERSION, TASK_MANAGER_ADDRESS, TFHE_RS_ZK_MAX_BITS, TFHE_RS_SAFE_SERIALIZATION_SIZE_LIMIT, MOCKS_THRESHOLD_NETWORK_ADDRESS, MOCKS_DECRYPT_RESULT_SIGNER_PRIVATE_KEY, MOCKS_ZK_VERIFIER_SIGNER_PRIVATE_KEY, MOCKS_ZK_VERIFIER_ADDRESS } from './chunk-4FP4V35O.js';
3
4
  import { createStore } from 'zustand/vanilla';
4
- import { encodePacked, keccak256, createWalletClient, http, getAddress, parseSignature, serializeSignature } from 'viem';
5
+ import { parseAbi, isAddressEqual, zeroAddress, recoverAddress, encodePacked, keccak256, createWalletClient, http, getAddress, parseSignature, serializeSignature } from 'viem';
5
6
  import { hardhat } from 'viem/chains';
6
7
  import { sign, privateKeyToAccount } from 'viem/accounts';
7
8
  import { z } from 'zod';
@@ -372,7 +373,6 @@ var MAX_UINT32 = 4294967295n;
372
373
  var MAX_UINT64 = 18446744073709551615n;
373
374
  var MAX_UINT128 = 340282366920938463463374607431768211455n;
374
375
  var MAX_UINT160 = 1461501637330902918203684832716283019655932542975n;
375
- var MAX_ENCRYPTABLE_BITS = 2048;
376
376
  var zkPack = (items, builder) => {
377
377
  let totalBits = 0;
378
378
  for (const item of items) {
@@ -436,14 +436,14 @@ var zkPack = (items, builder) => {
436
436
  }
437
437
  }
438
438
  }
439
- if (totalBits > MAX_ENCRYPTABLE_BITS) {
439
+ if (totalBits > TFHE_RS_ZK_MAX_BITS) {
440
440
  throw new CofheError({
441
441
  code: "ZK_PACK_FAILED" /* ZkPackFailed */,
442
- message: `Total bits ${totalBits} exceeds ${MAX_ENCRYPTABLE_BITS}`,
443
- hint: `Ensure that the total bits of the items to encrypt does not exceed ${MAX_ENCRYPTABLE_BITS}`,
442
+ message: `Total bits ${totalBits} exceeds ${TFHE_RS_ZK_MAX_BITS}`,
443
+ hint: `Ensure that the total bits of the items to encrypt does not exceed ${TFHE_RS_ZK_MAX_BITS}`,
444
444
  context: {
445
445
  totalBits,
446
- maxBits: MAX_ENCRYPTABLE_BITS,
446
+ maxBits: TFHE_RS_ZK_MAX_BITS,
447
447
  items
448
448
  }
449
449
  });
@@ -462,7 +462,7 @@ var zkProve = async (builder, crs, metadata) => {
462
462
  1
463
463
  // ZkComputeLoad.Verify
464
464
  );
465
- resolve(compactList.serialize());
465
+ resolve(compactList.safe_serialize(TFHE_RS_SAFE_SERIALIZATION_SIZE_LIMIT));
466
466
  }, 0);
467
467
  });
468
468
  };
@@ -679,14 +679,14 @@ async function cofheMocksCheckEncryptableBits(items) {
679
679
  }
680
680
  }
681
681
  }
682
- if (totalBits > MAX_ENCRYPTABLE_BITS) {
682
+ if (totalBits > TFHE_RS_ZK_MAX_BITS) {
683
683
  throw new CofheError({
684
684
  code: "ZK_PACK_FAILED" /* ZkPackFailed */,
685
- message: `Total bits ${totalBits} exceeds ${MAX_ENCRYPTABLE_BITS}`,
686
- hint: `Ensure that the total bits of the items to encrypt does not exceed ${MAX_ENCRYPTABLE_BITS}`,
685
+ message: `Total bits ${totalBits} exceeds ${TFHE_RS_ZK_MAX_BITS}`,
686
+ hint: `Ensure that the total bits of the items to encrypt does not exceed ${TFHE_RS_ZK_MAX_BITS}`,
687
687
  context: {
688
688
  totalBits,
689
- maxBits: MAX_ENCRYPTABLE_BITS,
689
+ maxBits: TFHE_RS_ZK_MAX_BITS,
690
690
  items
691
691
  }
692
692
  });
@@ -924,6 +924,7 @@ function getThresholdNetworkUrlOrThrow(config, chainId) {
924
924
  }
925
925
  return url;
926
926
  }
927
+ var KEYSTORE_NAME = `cofhesdk-keys-v${TFHE_RS_KEY_VERSION}`;
927
928
  function isValidPersistedState(state) {
928
929
  if (state && typeof state === "object") {
929
930
  if ("fhe" in state && "crs" in state) {
@@ -978,7 +979,7 @@ function createKeysStore(storage) {
978
979
  };
979
980
  const clearKeysStorage = async () => {
980
981
  if (storage) {
981
- await storage.removeItem("cofhesdk-keys");
982
+ await storage.removeItem(KEYSTORE_NAME);
982
983
  }
983
984
  };
984
985
  const rehydrateKeysStore = async () => {
@@ -1008,7 +1009,7 @@ function createStoreWithPersit(storage) {
1008
1009
  if (_error)
1009
1010
  throw new Error(`onRehydrateStorage: Error rehydrating keys store: ${_error}`);
1010
1011
  },
1011
- name: "cofhesdk-keys",
1012
+ name: KEYSTORE_NAME,
1012
1013
  storage: createJSONStorage(() => storage),
1013
1014
  merge: (persistedState, currentState) => {
1014
1015
  const persisted = isValidPersistedState(persistedState) ? persistedState : DEFAULT_KEYS_STORE;
@@ -1672,19 +1673,22 @@ var importShared = async (options, publicClient, walletClient) => {
1672
1673
  var getHash = (permit) => {
1673
1674
  return PermitUtils.getHash(permit);
1674
1675
  };
1676
+ var exportShared = (permit) => {
1677
+ return PermitUtils.export(permit);
1678
+ };
1675
1679
  var serialize = (permit) => {
1676
1680
  return PermitUtils.serialize(permit);
1677
1681
  };
1678
1682
  var deserialize = (serialized) => {
1679
1683
  return PermitUtils.deserialize(serialized);
1680
1684
  };
1681
- var getPermit = async (chainId, account, hash) => {
1685
+ var getPermit = (chainId, account, hash) => {
1682
1686
  return permitStore.getPermit(chainId, account, hash);
1683
1687
  };
1684
- var getPermits = async (chainId, account) => {
1688
+ var getPermits = (chainId, account) => {
1685
1689
  return permitStore.getPermits(chainId, account);
1686
1690
  };
1687
- var getActivePermit = async (chainId, account) => {
1691
+ var getActivePermit = (chainId, account) => {
1688
1692
  return permitStore.getActivePermit(chainId, account);
1689
1693
  };
1690
1694
  var getActivePermitHash = (chainId, account) => {
@@ -1722,6 +1726,7 @@ var permits = {
1722
1726
  getOrCreateSelfPermit,
1723
1727
  getOrCreateSharingPermit,
1724
1728
  getHash,
1729
+ export: exportShared,
1725
1730
  serialize,
1726
1731
  deserialize,
1727
1732
  getPermit,
@@ -1964,9 +1969,19 @@ async function cofheMocksDecryptForView(ctHash, utype, permit, publicClient) {
1964
1969
  return unsealed;
1965
1970
  }
1966
1971
 
1972
+ // core/decrypt/polling.ts
1973
+ function computeMinuteRampPollIntervalMs(elapsedMs, params) {
1974
+ const elapsedSeconds = Math.floor(elapsedMs / 1e3);
1975
+ const intervalSeconds = 1 + Math.floor(elapsedSeconds / 60);
1976
+ const intervalMs = intervalSeconds * 1e3;
1977
+ return Math.min(params.maxIntervalMs, Math.max(params.minIntervalMs, intervalMs));
1978
+ }
1979
+
1967
1980
  // core/decrypt/tnSealOutputV2.ts
1968
1981
  var POLL_INTERVAL_MS = 1e3;
1969
- var POLL_TIMEOUT_MS = 5 * 60 * 1e3;
1982
+ var POLL_MAX_INTERVAL_MS = 1e4;
1983
+ var SEAL_OUTPUT_TIMEOUT_MS = 5 * 60 * 1e3;
1984
+ var SUBMIT_RETRY_INTERVAL_MS = 1e3;
1970
1985
  function numberArrayToUint8Array(arr) {
1971
1986
  return new Uint8Array(arr);
1972
1987
  }
@@ -1983,93 +1998,193 @@ function convertSealedData(sealed) {
1983
1998
  nonce: numberArrayToUint8Array(sealed.nonce)
1984
1999
  };
1985
2000
  }
1986
- async function submitSealOutputRequest(thresholdNetworkUrl, ctHash, chainId, permission) {
1987
- const body = {
1988
- ct_tempkey: BigInt(ctHash).toString(16).padStart(64, "0"),
1989
- host_chain_id: chainId,
1990
- permit: permission
1991
- };
1992
- let response;
1993
- try {
1994
- response = await fetch(`${thresholdNetworkUrl}/v2/sealoutput`, {
1995
- method: "POST",
1996
- headers: {
1997
- "Content-Type": "application/json"
1998
- },
1999
- body: JSON.stringify(body)
2000
- });
2001
- } catch (e) {
2002
- throw new CofheError({
2003
- code: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */,
2004
- message: `sealOutput request failed`,
2005
- hint: "Ensure the threshold network URL is valid and reachable.",
2006
- cause: e instanceof Error ? e : void 0,
2007
- context: {
2008
- thresholdNetworkUrl,
2009
- body
2010
- }
2011
- });
2001
+ function getSealedDataFromSubmitResponse(value) {
2002
+ if (value.sealed)
2003
+ return value.sealed;
2004
+ if (Array.isArray(value.sealed_data) && Array.isArray(value.ephemeral_public_key) && Array.isArray(value.nonce)) {
2005
+ return {
2006
+ data: value.sealed_data,
2007
+ public_key: value.ephemeral_public_key,
2008
+ nonce: value.nonce
2009
+ };
2012
2010
  }
2013
- if (!response.ok) {
2014
- let errorMessage = `HTTP ${response.status}`;
2015
- try {
2016
- const errorBody = await response.json();
2017
- errorMessage = errorBody.error_message || errorBody.message || errorMessage;
2018
- } catch {
2019
- errorMessage = response.statusText || errorMessage;
2020
- }
2011
+ return void 0;
2012
+ }
2013
+ function parseCompletedSealOutputResponse(params) {
2014
+ const { value, thresholdNetworkUrl, requestId } = params;
2015
+ if (value.is_succeed === false) {
2016
+ const errorMessage = value.error_message || "Unknown error";
2021
2017
  throw new CofheError({
2022
2018
  code: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */,
2023
2019
  message: `sealOutput request failed: ${errorMessage}`,
2024
- hint: "Check the threshold network URL and request parameters.",
2025
2020
  context: {
2026
2021
  thresholdNetworkUrl,
2027
- status: response.status,
2028
- statusText: response.statusText,
2029
- body
2022
+ requestId,
2023
+ response: value
2030
2024
  }
2031
2025
  });
2032
2026
  }
2033
- let submitResponse;
2034
- try {
2035
- submitResponse = await response.json();
2036
- } catch (e) {
2027
+ const sealed = "sealed" in value ? value.sealed : getSealedDataFromSubmitResponse(value);
2028
+ if (!sealed) {
2037
2029
  throw new CofheError({
2038
- code: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */,
2039
- message: `Failed to parse sealOutput submit response`,
2040
- cause: e instanceof Error ? e : void 0,
2030
+ code: "SEAL_OUTPUT_RETURNED_NULL" /* SealOutputReturnedNull */,
2031
+ message: `sealOutput request completed but returned no sealed data`,
2041
2032
  context: {
2042
2033
  thresholdNetworkUrl,
2043
- body
2034
+ requestId,
2035
+ response: value
2044
2036
  }
2045
2037
  });
2046
2038
  }
2047
- if (!submitResponse.request_id) {
2039
+ return convertSealedData(sealed);
2040
+ }
2041
+ async function submitSealOutputRequest(thresholdNetworkUrl, ctHash, chainId, permission, overallStartTime, onPoll) {
2042
+ const body = {
2043
+ ct_tempkey: BigInt(ctHash).toString(16).padStart(64, "0"),
2044
+ host_chain_id: chainId,
2045
+ permit: permission
2046
+ };
2047
+ let attemptIndex = 0;
2048
+ for (; ; ) {
2049
+ let response;
2050
+ try {
2051
+ response = await fetch(`${thresholdNetworkUrl}/v2/sealoutput`, {
2052
+ method: "POST",
2053
+ headers: {
2054
+ "Content-Type": "application/json"
2055
+ },
2056
+ body: JSON.stringify(body)
2057
+ });
2058
+ } catch (e) {
2059
+ throw new CofheError({
2060
+ code: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */,
2061
+ message: `sealOutput request failed`,
2062
+ hint: "Ensure the threshold network URL is valid and reachable.",
2063
+ cause: e instanceof Error ? e : void 0,
2064
+ context: {
2065
+ thresholdNetworkUrl,
2066
+ body,
2067
+ attemptIndex
2068
+ }
2069
+ });
2070
+ }
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
+ }
2079
+ throw new CofheError({
2080
+ code: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */,
2081
+ message: `sealOutput request failed: ${errorMessage}`,
2082
+ hint: "Check the threshold network URL and request parameters.",
2083
+ context: {
2084
+ thresholdNetworkUrl,
2085
+ status: response.status,
2086
+ statusText: response.statusText,
2087
+ body,
2088
+ attemptIndex
2089
+ }
2090
+ });
2091
+ }
2092
+ let submitResponse;
2093
+ if (response.status !== 204) {
2094
+ try {
2095
+ submitResponse = await response.json();
2096
+ } catch (e) {
2097
+ throw new CofheError({
2098
+ code: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */,
2099
+ message: `Failed to parse sealOutput submit response`,
2100
+ cause: e instanceof Error ? e : void 0,
2101
+ context: {
2102
+ thresholdNetworkUrl,
2103
+ body,
2104
+ attemptIndex
2105
+ }
2106
+ });
2107
+ }
2108
+ if (getSealedDataFromSubmitResponse(submitResponse)) {
2109
+ return {
2110
+ kind: "completed",
2111
+ sealed: parseCompletedSealOutputResponse({
2112
+ value: submitResponse,
2113
+ thresholdNetworkUrl,
2114
+ requestId: submitResponse.request_id
2115
+ })
2116
+ };
2117
+ }
2118
+ if (submitResponse.request_id) {
2119
+ return { kind: "request_id", requestId: submitResponse.request_id };
2120
+ }
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
2146
+ });
2147
+ await new Promise((resolve) => setTimeout(resolve, SUBMIT_RETRY_INTERVAL_MS));
2148
+ attemptIndex += 1;
2149
+ continue;
2150
+ }
2048
2151
  throw new CofheError({
2049
2152
  code: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */,
2050
2153
  message: `sealOutput submit response missing request_id`,
2051
2154
  context: {
2052
2155
  thresholdNetworkUrl,
2053
2156
  body,
2054
- submitResponse
2157
+ submitResponse,
2158
+ attemptIndex
2055
2159
  }
2056
2160
  });
2057
2161
  }
2058
- return submitResponse.request_id;
2059
2162
  }
2060
- async function pollSealOutputStatus(thresholdNetworkUrl, requestId) {
2061
- const startTime = Date.now();
2062
- let completed = false;
2063
- while (!completed) {
2064
- if (Date.now() - startTime > POLL_TIMEOUT_MS) {
2163
+ async function pollSealOutputStatus(thresholdNetworkUrl, requestId, overallStartTime, onPoll) {
2164
+ let attemptIndex = 0;
2165
+ while (true) {
2166
+ const elapsedMs = Date.now() - overallStartTime;
2167
+ const intervalMs = computeMinuteRampPollIntervalMs(elapsedMs, {
2168
+ minIntervalMs: POLL_INTERVAL_MS,
2169
+ maxIntervalMs: POLL_MAX_INTERVAL_MS
2170
+ });
2171
+ onPoll?.({
2172
+ operation: "sealoutput",
2173
+ requestId,
2174
+ attemptIndex,
2175
+ elapsedMs,
2176
+ intervalMs,
2177
+ timeoutMs: SEAL_OUTPUT_TIMEOUT_MS
2178
+ });
2179
+ if (elapsedMs > SEAL_OUTPUT_TIMEOUT_MS) {
2065
2180
  throw new CofheError({
2066
2181
  code: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */,
2067
- message: `sealOutput polling timed out after ${POLL_TIMEOUT_MS}ms`,
2182
+ message: `sealOutput polling timed out after ${SEAL_OUTPUT_TIMEOUT_MS}ms`,
2068
2183
  hint: "The request may still be processing. Try again later.",
2069
2184
  context: {
2070
2185
  thresholdNetworkUrl,
2071
2186
  requestId,
2072
- timeoutMs: POLL_TIMEOUT_MS
2187
+ timeoutMs: SEAL_OUTPUT_TIMEOUT_MS
2073
2188
  }
2074
2189
  });
2075
2190
  }
@@ -2138,32 +2253,14 @@ async function pollSealOutputStatus(thresholdNetworkUrl, requestId) {
2138
2253
  });
2139
2254
  }
2140
2255
  if (statusResponse.status === "COMPLETED") {
2141
- if (statusResponse.is_succeed === false) {
2142
- const errorMessage = statusResponse.error_message || "Unknown error";
2143
- throw new CofheError({
2144
- code: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */,
2145
- message: `sealOutput request failed: ${errorMessage}`,
2146
- context: {
2147
- thresholdNetworkUrl,
2148
- requestId,
2149
- statusResponse
2150
- }
2151
- });
2152
- }
2153
- if (!statusResponse.sealed) {
2154
- throw new CofheError({
2155
- code: "SEAL_OUTPUT_RETURNED_NULL" /* SealOutputReturnedNull */,
2156
- message: `sealOutput request completed but returned no sealed data`,
2157
- context: {
2158
- thresholdNetworkUrl,
2159
- requestId,
2160
- statusResponse
2161
- }
2162
- });
2163
- }
2164
- return convertSealedData(statusResponse.sealed);
2256
+ return parseCompletedSealOutputResponse({
2257
+ value: statusResponse,
2258
+ thresholdNetworkUrl,
2259
+ requestId
2260
+ });
2165
2261
  }
2166
- await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
2262
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
2263
+ attemptIndex += 1;
2167
2264
  }
2168
2265
  throw new CofheError({
2169
2266
  code: "SEAL_OUTPUT_FAILED" /* SealOutputFailed */,
@@ -2174,9 +2271,21 @@ async function pollSealOutputStatus(thresholdNetworkUrl, requestId) {
2174
2271
  }
2175
2272
  });
2176
2273
  }
2177
- async function tnSealOutputV2(ctHash, chainId, permission, thresholdNetworkUrl) {
2178
- const requestId = await submitSealOutputRequest(thresholdNetworkUrl, ctHash, chainId, permission);
2179
- return await pollSealOutputStatus(thresholdNetworkUrl, requestId);
2274
+ async function tnSealOutputV2(params) {
2275
+ const { thresholdNetworkUrl, ctHash, chainId, permission, onPoll } = params;
2276
+ const overallStartTime = Date.now();
2277
+ const submitResult = await submitSealOutputRequest(
2278
+ thresholdNetworkUrl,
2279
+ ctHash,
2280
+ chainId,
2281
+ permission,
2282
+ overallStartTime,
2283
+ onPoll
2284
+ );
2285
+ if (submitResult.kind === "completed") {
2286
+ return submitResult.sealed;
2287
+ }
2288
+ return await pollSealOutputStatus(thresholdNetworkUrl, submitResult.requestId, overallStartTime, onPoll);
2180
2289
  }
2181
2290
 
2182
2291
  // core/decrypt/decryptForViewBuilder.ts
@@ -2185,6 +2294,7 @@ var DecryptForViewBuilder = class extends BaseBuilder {
2185
2294
  utype;
2186
2295
  permitHash;
2187
2296
  permit;
2297
+ pollCallback;
2188
2298
  constructor(params) {
2189
2299
  super({
2190
2300
  config: params.config,
@@ -2241,6 +2351,10 @@ var DecryptForViewBuilder = class extends BaseBuilder {
2241
2351
  getAccount() {
2242
2352
  return this.account;
2243
2353
  }
2354
+ onPoll(callback) {
2355
+ this.pollCallback = callback;
2356
+ return this;
2357
+ }
2244
2358
  withPermit(permitOrPermitHash) {
2245
2359
  if (typeof permitOrPermitHash === "string") {
2246
2360
  this.permitHash = permitOrPermitHash;
@@ -2364,7 +2478,13 @@ var DecryptForViewBuilder = class extends BaseBuilder {
2364
2478
  this.assertPublicClient();
2365
2479
  const thresholdNetworkUrl = await this.getThresholdNetworkUrl();
2366
2480
  const permission = PermitUtils.getPermission(permit, true);
2367
- const sealed = await tnSealOutputV2(this.ctHash, this.chainId, permission, thresholdNetworkUrl);
2481
+ const sealed = await tnSealOutputV2({
2482
+ ctHash: this.ctHash,
2483
+ chainId: this.chainId,
2484
+ permission,
2485
+ thresholdNetworkUrl,
2486
+ onPoll: this.pollCallback
2487
+ });
2368
2488
  return PermitUtils.unseal(permit, sealed);
2369
2489
  }
2370
2490
  /**
@@ -2392,7 +2512,6 @@ var DecryptForViewBuilder = class extends BaseBuilder {
2392
2512
  this.validateUtypeOrThrow();
2393
2513
  const permit = await this.getResolvedPermit();
2394
2514
  PermitUtils.validate(permit);
2395
- PermitUtils.isValid(permit);
2396
2515
  const chainId = permit._signedDomain.chainId;
2397
2516
  let unsealed;
2398
2517
  if (chainId === hardhat$1.id) {
@@ -2403,6 +2522,9 @@ var DecryptForViewBuilder = class extends BaseBuilder {
2403
2522
  return convertViaUtype(this.utype, unsealed);
2404
2523
  }
2405
2524
  };
2525
+ var UINT_TYPE_MASK = 0x7fn;
2526
+ var TYPE_BYTE_OFFSET = 8n;
2527
+ var getEncryptionTypeFromCtHash = (ctHash) => Number(ctHash >> TYPE_BYTE_OFFSET & UINT_TYPE_MASK);
2406
2528
  async function cofheMocksDecryptForTx(ctHash, utype, permit, publicClient) {
2407
2529
  let allowed;
2408
2530
  let error;
@@ -2440,7 +2562,13 @@ async function cofheMocksDecryptForTx(ctHash, utype, permit, publicClient) {
2440
2562
  message: `mocks decryptForTx call failed: ACL Access Denied (NotAllowed)`
2441
2563
  });
2442
2564
  }
2443
- const packed = encodePacked(["uint256", "uint256"], [BigInt(ctHash), decryptedValue]);
2565
+ const chainId = publicClient.chain?.id ?? await publicClient.getChainId();
2566
+ const normalizedCtHash = BigInt(ctHash);
2567
+ const encryptionType = getEncryptionTypeFromCtHash(normalizedCtHash);
2568
+ const packed = encodePacked(
2569
+ ["uint256", "uint32", "uint64", "uint256"],
2570
+ [decryptedValue, encryptionType, BigInt(chainId), normalizedCtHash]
2571
+ );
2444
2572
  const messageHash = keccak256(packed);
2445
2573
  const signature = await sign({
2446
2574
  hash: messageHash,
@@ -2453,7 +2581,7 @@ async function cofheMocksDecryptForTx(ctHash, utype, permit, publicClient) {
2453
2581
  signature
2454
2582
  };
2455
2583
  }
2456
- function normalizeSignature(signature) {
2584
+ function normalizeTnSignature(signature) {
2457
2585
  if (typeof signature !== "string") {
2458
2586
  throw new CofheError({
2459
2587
  code: "DECRYPT_RETURNED_NULL" /* DecryptReturnedNull */,
@@ -2509,141 +2637,384 @@ function parseDecryptedBytesToBigInt(decrypted) {
2509
2637
  }
2510
2638
  return BigInt(`0x${hex}`);
2511
2639
  }
2512
- function assertTnDecryptResponse(value) {
2640
+
2641
+ // core/decrypt/tnDecryptV2.ts
2642
+ var POLL_INTERVAL_MS2 = 1e3;
2643
+ var POLL_MAX_INTERVAL_MS2 = 1e4;
2644
+ var DECRYPT_TIMEOUT_MS = 5 * 60 * 1e3;
2645
+ var SUBMIT_RETRY_INTERVAL_MS2 = 1e3;
2646
+ function assertDecryptSubmitResponseV2(value) {
2513
2647
  if (value == null || typeof value !== "object") {
2514
2648
  throw new CofheError({
2515
2649
  code: "DECRYPT_FAILED" /* DecryptFailed */,
2516
- message: "decrypt response must be a JSON object",
2650
+ message: "decrypt submit response must be a JSON object",
2517
2651
  context: {
2518
2652
  value
2519
2653
  }
2520
2654
  });
2521
2655
  }
2522
2656
  const v = value;
2523
- const decrypted = v.decrypted;
2524
- const signature = v.signature;
2525
- const encryptionType = v.encryption_type;
2526
- const errorMessage = v.error_message;
2527
- if (!Array.isArray(decrypted)) {
2657
+ if (v.request_id !== null && typeof v.request_id !== "string") {
2528
2658
  throw new CofheError({
2529
- code: "DECRYPT_RETURNED_NULL" /* DecryptReturnedNull */,
2530
- message: "decrypt response missing <decrypted> byte array",
2531
- context: { decryptResponse: value }
2659
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
2660
+ message: "decrypt submit response has invalid request_id",
2661
+ context: {
2662
+ value
2663
+ }
2532
2664
  });
2533
2665
  }
2534
- if (typeof signature !== "string") {
2666
+ return {
2667
+ request_id: v.request_id ?? null,
2668
+ status: typeof v.status === "string" ? v.status : void 0,
2669
+ is_succeed: typeof v.is_succeed === "boolean" ? v.is_succeed : void 0,
2670
+ decrypted: Array.isArray(v.decrypted) ? v.decrypted : void 0,
2671
+ signature: typeof v.signature === "string" ? v.signature : void 0,
2672
+ encryption_type: typeof v.encryption_type === "number" ? v.encryption_type : void 0,
2673
+ error_message: typeof v.error_message === "string" || v.error_message === null ? v.error_message : void 0,
2674
+ message: typeof v.message === "string" ? v.message : void 0
2675
+ };
2676
+ }
2677
+ function parseCompletedDecryptResponseV2(params) {
2678
+ const { value, thresholdNetworkUrl, requestId } = params;
2679
+ if (value.is_succeed === false) {
2680
+ const errorMessage = value.error_message || "Unknown error";
2535
2681
  throw new CofheError({
2536
- code: "DECRYPT_RETURNED_NULL" /* DecryptReturnedNull */,
2537
- message: "decrypt response missing <signature> string",
2538
- context: { decryptResponse: value }
2682
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
2683
+ message: `decrypt request failed: ${errorMessage}`,
2684
+ context: {
2685
+ thresholdNetworkUrl,
2686
+ requestId,
2687
+ response: value
2688
+ }
2539
2689
  });
2540
2690
  }
2541
- if (typeof encryptionType !== "number") {
2691
+ if (value.error_message) {
2542
2692
  throw new CofheError({
2543
2693
  code: "DECRYPT_FAILED" /* DecryptFailed */,
2544
- message: "decrypt response missing <encryption_type> number",
2545
- context: { decryptResponse: value }
2694
+ message: `decrypt request failed: ${value.error_message}`,
2695
+ context: {
2696
+ thresholdNetworkUrl,
2697
+ requestId,
2698
+ response: value
2699
+ }
2546
2700
  });
2547
2701
  }
2548
- if (!(typeof errorMessage === "string" || errorMessage === null)) {
2702
+ if (!Array.isArray(value.decrypted)) {
2549
2703
  throw new CofheError({
2550
- code: "DECRYPT_FAILED" /* DecryptFailed */,
2551
- message: "decrypt response field <error_message> must be string or null",
2552
- context: { decryptResponse: value }
2704
+ code: "DECRYPT_RETURNED_NULL" /* DecryptReturnedNull */,
2705
+ message: "decrypt completed but response missing <decrypted> byte array",
2706
+ context: {
2707
+ thresholdNetworkUrl,
2708
+ requestId,
2709
+ response: value
2710
+ }
2553
2711
  });
2554
2712
  }
2555
- return {
2556
- decrypted,
2557
- signature,
2558
- encryption_type: encryptionType,
2559
- error_message: errorMessage
2560
- };
2713
+ const decryptedValue = parseDecryptedBytesToBigInt(value.decrypted);
2714
+ const signature = normalizeTnSignature(value.signature);
2715
+ return { decryptedValue, signature };
2561
2716
  }
2562
- async function tnDecrypt(ctHash, chainId, permission, thresholdNetworkUrl) {
2563
- const body = {
2564
- ct_tempkey: BigInt(ctHash).toString(16).padStart(64, "0"),
2565
- host_chain_id: chainId
2566
- };
2567
- if (permission) {
2568
- body.permit = permission;
2569
- }
2570
- let response;
2571
- try {
2572
- response = await fetch(`${thresholdNetworkUrl}/decrypt`, {
2573
- method: "POST",
2574
- headers: {
2575
- "Content-Type": "application/json"
2576
- },
2577
- body: JSON.stringify(body)
2717
+ function assertDecryptStatusResponseV2(value) {
2718
+ if (value == null || typeof value !== "object") {
2719
+ throw new CofheError({
2720
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
2721
+ message: "decrypt status response must be a JSON object",
2722
+ context: {
2723
+ value
2724
+ }
2578
2725
  });
2579
- } catch (e) {
2726
+ }
2727
+ const v = value;
2728
+ const requestId = v.request_id;
2729
+ const status = v.status;
2730
+ const submittedAt = v.submitted_at;
2731
+ if (typeof requestId !== "string" || requestId.trim().length === 0) {
2580
2732
  throw new CofheError({
2581
2733
  code: "DECRYPT_FAILED" /* DecryptFailed */,
2582
- message: `decrypt request failed`,
2583
- hint: "Ensure the threshold network URL is valid and reachable.",
2584
- cause: e instanceof Error ? e : void 0,
2734
+ message: "decrypt status response missing request_id",
2585
2735
  context: {
2586
- thresholdNetworkUrl,
2587
- body
2736
+ value
2588
2737
  }
2589
2738
  });
2590
2739
  }
2591
- const responseText = await response.text();
2592
- if (!response.ok) {
2593
- let errorMessage = response.statusText || `HTTP ${response.status}`;
2594
- try {
2595
- const errorBody = JSON.parse(responseText);
2596
- const maybeMessage = errorBody.error_message || errorBody.message;
2597
- if (typeof maybeMessage === "string" && maybeMessage.length > 0)
2598
- errorMessage = maybeMessage;
2599
- } catch {
2600
- const trimmed = responseText.trim();
2601
- if (trimmed.length > 0)
2602
- errorMessage = trimmed;
2603
- }
2740
+ if (status !== "PROCESSING" && status !== "COMPLETED") {
2604
2741
  throw new CofheError({
2605
2742
  code: "DECRYPT_FAILED" /* DecryptFailed */,
2606
- message: `decrypt request failed: ${errorMessage}`,
2607
- hint: "Check the threshold network URL and request parameters.",
2743
+ message: "decrypt status response has invalid status",
2608
2744
  context: {
2609
- thresholdNetworkUrl,
2610
- status: response.status,
2611
- statusText: response.statusText,
2612
- body,
2613
- responseText
2745
+ value,
2746
+ status
2614
2747
  }
2615
2748
  });
2616
2749
  }
2617
- let rawJson;
2618
- try {
2619
- rawJson = JSON.parse(responseText);
2620
- } catch (e) {
2750
+ if (typeof submittedAt !== "string" || submittedAt.trim().length === 0) {
2621
2751
  throw new CofheError({
2622
2752
  code: "DECRYPT_FAILED" /* DecryptFailed */,
2623
- message: `Failed to parse decrypt response`,
2624
- cause: e instanceof Error ? e : void 0,
2753
+ message: "decrypt status response missing submitted_at",
2625
2754
  context: {
2626
- thresholdNetworkUrl,
2627
- body,
2628
- responseText
2755
+ value
2629
2756
  }
2630
2757
  });
2631
2758
  }
2632
- const decryptResponse = assertTnDecryptResponse(rawJson);
2633
- if (decryptResponse.error_message) {
2759
+ return value;
2760
+ }
2761
+ async function submitDecryptRequestV2(thresholdNetworkUrl, ctHash, chainId, permission, overallStartTime, onPoll) {
2762
+ const body = {
2763
+ ct_tempkey: BigInt(ctHash).toString(16).padStart(64, "0"),
2764
+ host_chain_id: chainId
2765
+ };
2766
+ if (permission) {
2767
+ body.permit = permission;
2768
+ }
2769
+ let attemptIndex = 0;
2770
+ for (; ; ) {
2771
+ let response;
2772
+ try {
2773
+ response = await fetch(`${thresholdNetworkUrl}/v2/decrypt`, {
2774
+ method: "POST",
2775
+ headers: {
2776
+ "Content-Type": "application/json"
2777
+ },
2778
+ body: JSON.stringify(body)
2779
+ });
2780
+ } catch (e) {
2781
+ throw new CofheError({
2782
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
2783
+ message: `decrypt request failed`,
2784
+ hint: "Ensure the threshold network URL is valid and reachable.",
2785
+ cause: e instanceof Error ? e : void 0,
2786
+ context: {
2787
+ thresholdNetworkUrl,
2788
+ body,
2789
+ attemptIndex
2790
+ }
2791
+ });
2792
+ }
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
+ }
2803
+ throw new CofheError({
2804
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
2805
+ message: `decrypt request failed: ${errorMessage}`,
2806
+ hint: "Check the threshold network URL and request parameters.",
2807
+ context: {
2808
+ thresholdNetworkUrl,
2809
+ status: response.status,
2810
+ statusText: response.statusText,
2811
+ body,
2812
+ attemptIndex
2813
+ }
2814
+ });
2815
+ }
2816
+ let submitResponse;
2817
+ if (response.status !== 204) {
2818
+ let rawJson;
2819
+ try {
2820
+ rawJson = await response.json();
2821
+ } catch (e) {
2822
+ throw new CofheError({
2823
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
2824
+ message: `Failed to parse decrypt submit response`,
2825
+ cause: e instanceof Error ? e : void 0,
2826
+ context: {
2827
+ thresholdNetworkUrl,
2828
+ body,
2829
+ attemptIndex
2830
+ }
2831
+ });
2832
+ }
2833
+ submitResponse = assertDecryptSubmitResponseV2(rawJson);
2834
+ if (Array.isArray(submitResponse.decrypted) && typeof submitResponse.signature === "string") {
2835
+ return {
2836
+ kind: "completed",
2837
+ ...parseCompletedDecryptResponseV2({
2838
+ value: submitResponse,
2839
+ thresholdNetworkUrl,
2840
+ requestId: submitResponse.request_id
2841
+ })
2842
+ };
2843
+ }
2844
+ if (submitResponse.request_id) {
2845
+ return { kind: "request_id", requestId: submitResponse.request_id };
2846
+ }
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
2872
+ });
2873
+ await new Promise((resolve) => setTimeout(resolve, SUBMIT_RETRY_INTERVAL_MS2));
2874
+ attemptIndex += 1;
2875
+ continue;
2876
+ }
2634
2877
  throw new CofheError({
2635
2878
  code: "DECRYPT_FAILED" /* DecryptFailed */,
2636
- message: `decrypt request failed: ${decryptResponse.error_message}`,
2879
+ message: `decrypt submit response missing request_id`,
2637
2880
  context: {
2638
2881
  thresholdNetworkUrl,
2639
2882
  body,
2640
- decryptResponse
2883
+ submitResponse,
2884
+ attemptIndex
2641
2885
  }
2642
2886
  });
2643
2887
  }
2644
- const decryptedValue = parseDecryptedBytesToBigInt(decryptResponse.decrypted);
2645
- const signature = normalizeSignature(decryptResponse.signature);
2646
- return { decryptedValue, signature };
2888
+ }
2889
+ async function pollDecryptStatusV2(thresholdNetworkUrl, requestId, overallStartTime, onPoll) {
2890
+ let attemptIndex = 0;
2891
+ while (true) {
2892
+ const elapsedMs = Date.now() - overallStartTime;
2893
+ const intervalMs = computeMinuteRampPollIntervalMs(elapsedMs, {
2894
+ minIntervalMs: POLL_INTERVAL_MS2,
2895
+ maxIntervalMs: POLL_MAX_INTERVAL_MS2
2896
+ });
2897
+ onPoll?.({
2898
+ operation: "decrypt",
2899
+ requestId,
2900
+ attemptIndex,
2901
+ elapsedMs,
2902
+ intervalMs,
2903
+ timeoutMs: DECRYPT_TIMEOUT_MS
2904
+ });
2905
+ if (elapsedMs > DECRYPT_TIMEOUT_MS) {
2906
+ throw new CofheError({
2907
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
2908
+ message: `decrypt polling timed out after ${DECRYPT_TIMEOUT_MS}ms`,
2909
+ hint: "The request may still be processing. Try again later.",
2910
+ context: {
2911
+ thresholdNetworkUrl,
2912
+ requestId,
2913
+ timeoutMs: DECRYPT_TIMEOUT_MS
2914
+ }
2915
+ });
2916
+ }
2917
+ let response;
2918
+ try {
2919
+ response = await fetch(`${thresholdNetworkUrl}/v2/decrypt/${requestId}`, {
2920
+ method: "GET",
2921
+ headers: {
2922
+ "Content-Type": "application/json"
2923
+ }
2924
+ });
2925
+ } catch (e) {
2926
+ throw new CofheError({
2927
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
2928
+ message: `decrypt status poll failed`,
2929
+ hint: "Ensure the threshold network URL is valid and reachable.",
2930
+ cause: e instanceof Error ? e : void 0,
2931
+ context: {
2932
+ thresholdNetworkUrl,
2933
+ requestId
2934
+ }
2935
+ });
2936
+ }
2937
+ if (response.status === 404) {
2938
+ throw new CofheError({
2939
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
2940
+ message: `decrypt request not found: ${requestId}`,
2941
+ hint: "The request may have expired or been invalid.",
2942
+ context: {
2943
+ thresholdNetworkUrl,
2944
+ requestId
2945
+ }
2946
+ });
2947
+ }
2948
+ if (!response.ok) {
2949
+ let errorMessage = `HTTP ${response.status}`;
2950
+ try {
2951
+ const errorBody = await response.json();
2952
+ const maybeMessage = errorBody.error_message || errorBody.message;
2953
+ if (typeof maybeMessage === "string" && maybeMessage.length > 0)
2954
+ errorMessage = maybeMessage;
2955
+ } catch {
2956
+ errorMessage = response.statusText || errorMessage;
2957
+ }
2958
+ throw new CofheError({
2959
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
2960
+ message: `decrypt status poll failed: ${errorMessage}`,
2961
+ context: {
2962
+ thresholdNetworkUrl,
2963
+ requestId,
2964
+ status: response.status,
2965
+ statusText: response.statusText
2966
+ }
2967
+ });
2968
+ }
2969
+ let rawJson;
2970
+ try {
2971
+ rawJson = await response.json();
2972
+ } catch (e) {
2973
+ throw new CofheError({
2974
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
2975
+ message: `Failed to parse decrypt status response`,
2976
+ cause: e instanceof Error ? e : void 0,
2977
+ context: {
2978
+ thresholdNetworkUrl,
2979
+ requestId
2980
+ }
2981
+ });
2982
+ }
2983
+ const statusResponse = assertDecryptStatusResponseV2(rawJson);
2984
+ if (statusResponse.status === "COMPLETED") {
2985
+ return parseCompletedDecryptResponseV2({
2986
+ value: statusResponse,
2987
+ thresholdNetworkUrl,
2988
+ requestId
2989
+ });
2990
+ }
2991
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
2992
+ attemptIndex += 1;
2993
+ }
2994
+ throw new CofheError({
2995
+ code: "DECRYPT_FAILED" /* DecryptFailed */,
2996
+ message: "Polling loop exited unexpectedly",
2997
+ context: {
2998
+ thresholdNetworkUrl,
2999
+ requestId
3000
+ }
3001
+ });
3002
+ }
3003
+ async function tnDecryptV2(params) {
3004
+ const { thresholdNetworkUrl, ctHash, chainId, permission, onPoll } = params;
3005
+ const overallStartTime = Date.now();
3006
+ const submitResult = await submitDecryptRequestV2(
3007
+ thresholdNetworkUrl,
3008
+ ctHash,
3009
+ chainId,
3010
+ permission,
3011
+ overallStartTime,
3012
+ onPoll
3013
+ );
3014
+ if (submitResult.kind === "completed") {
3015
+ return submitResult;
3016
+ }
3017
+ return await pollDecryptStatusV2(thresholdNetworkUrl, submitResult.requestId, overallStartTime, onPoll);
2647
3018
  }
2648
3019
 
2649
3020
  // core/decrypt/decryptForTxBuilder.ts
@@ -2652,6 +3023,7 @@ var DecryptForTxBuilder = class extends BaseBuilder {
2652
3023
  permitHash;
2653
3024
  permit;
2654
3025
  permitSelection = "unset";
3026
+ pollCallback;
2655
3027
  constructor(params) {
2656
3028
  super({
2657
3029
  config: params.config,
@@ -2677,6 +3049,10 @@ var DecryptForTxBuilder = class extends BaseBuilder {
2677
3049
  getAccount() {
2678
3050
  return this.account;
2679
3051
  }
3052
+ onPoll(callback) {
3053
+ this.pollCallback = callback;
3054
+ return this;
3055
+ }
2680
3056
  withPermit(permitOrPermitHash) {
2681
3057
  if (this.permitSelection === "with-permit") {
2682
3058
  throw new CofheError({
@@ -2804,7 +3180,13 @@ var DecryptForTxBuilder = class extends BaseBuilder {
2804
3180
  this.assertPublicClient();
2805
3181
  const thresholdNetworkUrl = await this.getThresholdNetworkUrl();
2806
3182
  const permission = permit ? PermitUtils.getPermission(permit, true) : null;
2807
- const { decryptedValue, signature } = await tnDecrypt(this.ctHash, this.chainId, permission, thresholdNetworkUrl);
3183
+ const { decryptedValue, signature } = await tnDecryptV2({
3184
+ ctHash: this.ctHash,
3185
+ chainId: this.chainId,
3186
+ permission,
3187
+ thresholdNetworkUrl,
3188
+ onPoll: this.pollCallback
3189
+ });
2808
3190
  return {
2809
3191
  ctHash: this.ctHash,
2810
3192
  decryptedValue,
@@ -2822,7 +3204,6 @@ var DecryptForTxBuilder = class extends BaseBuilder {
2822
3204
  const permit = await this.getResolvedPermit();
2823
3205
  if (permit !== null) {
2824
3206
  PermitUtils.validate(permit);
2825
- PermitUtils.isValid(permit);
2826
3207
  const chainId = permit._signedDomain.chainId;
2827
3208
  if (chainId === hardhat$1.id) {
2828
3209
  return await this.mocksDecryptForTx(permit);
@@ -2843,6 +3224,35 @@ var DecryptForTxBuilder = class extends BaseBuilder {
2843
3224
  }
2844
3225
  }
2845
3226
  };
3227
+ var decryptResultSignerAbi = parseAbi(["function decryptResultSigner() view returns (address)"]);
3228
+ var UINT_TYPE_MASK2 = 0x7fn;
3229
+ var TYPE_BYTE_OFFSET2 = 8n;
3230
+ var getEncryptionTypeFromCtHash2 = (ctHash) => Number(ctHash >> TYPE_BYTE_OFFSET2 & UINT_TYPE_MASK2);
3231
+ var buildDecryptResultHash = (ctHash, cleartext, chainId) => {
3232
+ const encryptionType = getEncryptionTypeFromCtHash2(ctHash);
3233
+ return keccak256(
3234
+ encodePacked(["uint256", "uint32", "uint64", "uint256"], [cleartext, encryptionType, BigInt(chainId), ctHash])
3235
+ );
3236
+ };
3237
+ async function verifyDecryptResult(handle, cleartext, signature, publicClient) {
3238
+ const chainId = publicClient.chain?.id ?? await publicClient.getChainId();
3239
+ const expectedSigner = await publicClient.readContract({
3240
+ address: TASK_MANAGER_ADDRESS,
3241
+ abi: decryptResultSignerAbi,
3242
+ functionName: "decryptResultSigner",
3243
+ args: []
3244
+ });
3245
+ if (isAddressEqual(expectedSigner, zeroAddress))
3246
+ return true;
3247
+ const ctHash = BigInt(handle);
3248
+ const messageHash = buildDecryptResultHash(ctHash, cleartext, chainId);
3249
+ try {
3250
+ const recovered = await recoverAddress({ hash: messageHash, signature });
3251
+ return isAddressEqual(recovered, expectedSigner);
3252
+ } catch {
3253
+ return false;
3254
+ }
3255
+ }
2846
3256
 
2847
3257
  // core/client.ts
2848
3258
  var InitialConnectStore = {
@@ -2961,6 +3371,11 @@ function createCofheClientBase(opts) {
2961
3371
  requireConnected: _requireConnected
2962
3372
  });
2963
3373
  }
3374
+ function verifyDecryptResult2(handle, cleartext, signature) {
3375
+ _requireConnected();
3376
+ const { publicClient } = connectStore.getState();
3377
+ return verifyDecryptResult(handle, cleartext, signature, publicClient);
3378
+ }
2964
3379
  const _getChainIdAndAccount = (chainId, account) => {
2965
3380
  const state = connectStore.getState();
2966
3381
  const _chainId = chainId ?? state.chainId;
@@ -3012,19 +3427,19 @@ function createCofheClientBase(opts) {
3012
3427
  return permits.getOrCreateSharingPermit(publicClient, walletClient, options, _chainId, _account);
3013
3428
  },
3014
3429
  // Retrieval methods (auto-fill chainId/account)
3015
- getPermit: async (hash, chainId, account) => {
3430
+ getPermit: (hash, chainId, account) => {
3016
3431
  const { chainId: _chainId, account: _account } = _getChainIdAndAccount(chainId, account);
3017
3432
  return permits.getPermit(_chainId, _account, hash);
3018
3433
  },
3019
- getPermits: async (chainId, account) => {
3434
+ getPermits: (chainId, account) => {
3020
3435
  const { chainId: _chainId, account: _account } = _getChainIdAndAccount(chainId, account);
3021
3436
  return permits.getPermits(_chainId, _account);
3022
3437
  },
3023
- getActivePermit: async (chainId, account) => {
3438
+ getActivePermit: (chainId, account) => {
3024
3439
  const { chainId: _chainId, account: _account } = _getChainIdAndAccount(chainId, account);
3025
3440
  return permits.getActivePermit(_chainId, _account);
3026
3441
  },
3027
- getActivePermitHash: async (chainId, account) => {
3442
+ getActivePermitHash: (chainId, account) => {
3028
3443
  const { chainId: _chainId, account: _account } = _getChainIdAndAccount(chainId, account);
3029
3444
  return permits.getActivePermitHash(_chainId, _account);
3030
3445
  },
@@ -3043,6 +3458,7 @@ function createCofheClientBase(opts) {
3043
3458
  },
3044
3459
  // Utils (no context needed)
3045
3460
  getHash: permits.getHash,
3461
+ export: permits.export,
3046
3462
  serialize: permits.serialize,
3047
3463
  deserialize: permits.deserialize
3048
3464
  };
@@ -3071,6 +3487,7 @@ function createCofheClientBase(opts) {
3071
3487
  */
3072
3488
  decryptHandle: decryptForView,
3073
3489
  decryptForTx,
3490
+ verifyDecryptResult: verifyDecryptResult2,
3074
3491
  permits: clientPermits
3075
3492
  // Add SDK-specific methods below that require connection
3076
3493
  // Example:
@@ -3081,4 +3498,4 @@ function createCofheClientBase(opts) {
3081
3498
  };
3082
3499
  }
3083
3500
 
3084
- export { CofheError, CofheErrorCode, DecryptForTxBuilder, DecryptForViewBuilder, EncryptInputsBuilder, EncryptStep, Encryptable, FheAllUTypes, FheTypes, FheUintUTypes, InitialConnectStore, assertCorrectEncryptedItemInput, createCofheClientBase, createCofheConfigBase, createKeysStore, fetchKeys, fheTypeToString, getCofheConfigItem, isCofheError, isEncryptableItem, isLastEncryptionStep, zkProveWithWorker };
3501
+ export { CofheError, CofheErrorCode, DecryptForTxBuilder, DecryptForViewBuilder, EncryptInputsBuilder, EncryptStep, Encryptable, FheAllUTypes, FheTypes, FheUintUTypes, InitialConnectStore, assertCorrectEncryptedItemInput, createCofheClientBase, createCofheConfigBase, createKeysStore, fetchKeys, fheTypeToString, getCofheConfigItem, isCofheError, isEncryptableItem, isLastEncryptionStep, verifyDecryptResult, zkProveWithWorker };