@cofhe/sdk 0.6.0 → 0.7.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 (114) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/acps/acp.ts +411 -0
  3. package/acps/index.ts +70 -0
  4. package/{permits → acps}/onchain-utils.ts +49 -24
  5. package/acps/sealing.ts +90 -0
  6. package/acps/signature.ts +89 -0
  7. package/acps/store.ts +172 -0
  8. package/acps/test/acp.test.ts +615 -0
  9. package/acps/test/localstorage.test.ts +105 -0
  10. package/acps/test/sealing.test.ts +77 -0
  11. package/acps/test/store.test.ts +88 -0
  12. package/acps/test/validation.test.ts +361 -0
  13. package/acps/test-utils.ts +32 -0
  14. package/acps/types.ts +252 -0
  15. package/acps/validation.ts +392 -0
  16. package/adapters/test/ethers5.test.ts +4 -1
  17. package/adapters/test/ethers6.test.ts +4 -1
  18. package/adapters/test/wagmi.test.ts +5 -2
  19. package/chains/chains/stagingCofhe.ts +21 -0
  20. package/chains/index.ts +3 -1
  21. package/chains/test/chains.test.ts +2 -1
  22. package/core/acps.ts +625 -0
  23. package/core/client.ts +136 -39
  24. package/core/clientTypes.ts +52 -41
  25. package/core/config.ts +66 -5
  26. package/core/debug.ts +72 -0
  27. package/core/decrypt/MockThresholdNetworkAbi.ts +20 -11
  28. package/core/decrypt/apiError.ts +104 -0
  29. package/core/decrypt/cofheMocksDecryptForTx.ts +11 -11
  30. package/core/decrypt/cofheMocksDecryptForView.ts +7 -7
  31. package/core/decrypt/decryptForTxBuilder.ts +102 -102
  32. package/core/decrypt/decryptForViewBuilder.ts +90 -90
  33. package/core/decrypt/submitRetry.ts +38 -30
  34. package/core/decrypt/tnDecryptV1.ts +5 -5
  35. package/core/decrypt/tnDecryptV2.ts +45 -34
  36. package/core/decrypt/tnSealOutputV1.ts +4 -3
  37. package/core/decrypt/tnSealOutputV2.ts +44 -31
  38. package/core/encrypt/cofheMocksZkVerifySign.ts +59 -74
  39. package/core/encrypt/encryptInputsBuilder.ts +86 -52
  40. package/core/encrypt/zkPackProveVerify.ts +25 -18
  41. package/core/error.ts +34 -6
  42. package/core/index.ts +14 -14
  43. package/core/test/acpDefaults.test.ts +52 -0
  44. package/core/test/acps.test.ts +596 -0
  45. package/core/test/apiError.test.ts +130 -0
  46. package/core/test/client.test.ts +22 -19
  47. package/core/test/config.test.ts +25 -5
  48. package/core/test/decrypt.test.ts +40 -35
  49. package/core/test/decryptBuilders.test.ts +68 -68
  50. package/core/test/decryptErrorCodes.test.ts +217 -0
  51. package/core/test/encryptInputsBuilder.test.ts +72 -41
  52. package/core/test/pollCallbacks.test.ts +89 -18
  53. package/core/test/stagingRedirect.ts +18 -0
  54. package/core/test/submitRetry.test.ts +182 -0
  55. package/core/types.ts +9 -69
  56. package/dist/acp-Wi6isVQI.d.cts +407 -0
  57. package/dist/acp-Wi6isVQI.d.ts +407 -0
  58. package/dist/acps.cjs +1081 -0
  59. package/dist/acps.d.cts +480 -0
  60. package/dist/acps.d.ts +480 -0
  61. package/dist/acps.js +2 -0
  62. package/dist/chains.cjs +14 -1
  63. package/dist/chains.d.cts +30 -1
  64. package/dist/chains.d.ts +30 -1
  65. package/dist/chains.js +1 -1
  66. package/dist/{chunk-PE5V5CCV.js → chunk-43USWPEH.js} +1000 -604
  67. package/dist/{chunk-MTRAXQXC.js → chunk-N6IDQRRU.js} +14 -2
  68. package/dist/chunk-Q7CBWGQX.js +1029 -0
  69. package/dist/{clientTypes-CyUvRRzA.d.ts → clientTypes-BN3nbzYM.d.ts} +342 -165
  70. package/dist/{clientTypes-BDy1qIBu.d.cts → clientTypes-CYZjFznO.d.cts} +342 -165
  71. package/dist/core.cjs +1431 -1026
  72. package/dist/core.d.cts +55 -10
  73. package/dist/core.d.ts +55 -10
  74. package/dist/core.js +3 -3
  75. package/dist/node.cjs +1356 -976
  76. package/dist/node.d.cts +2 -2
  77. package/dist/node.d.ts +2 -2
  78. package/dist/node.js +3 -3
  79. package/dist/web.cjs +1356 -976
  80. package/dist/web.d.cts +2 -2
  81. package/dist/web.d.ts +2 -2
  82. package/dist/web.js +3 -3
  83. package/node/test/inherited.test.ts +75 -65
  84. package/node/test/tfheinit.test.ts +23 -8
  85. package/package.json +6 -6
  86. package/web/test/client.web.test.ts +5 -1
  87. package/web/test/inherited.web.test.ts +75 -65
  88. package/web/test/tfheinit.web.test.ts +14 -5
  89. package/web/test/worker.config.web.test.ts +38 -23
  90. package/web/test/worker.output.web.test.ts +25 -24
  91. package/core/encrypt/encryptUtils.ts +0 -67
  92. package/core/permits.ts +0 -206
  93. package/core/test/permits.test.ts +0 -534
  94. package/dist/chunk-VB62WYPL.js +0 -978
  95. package/dist/permit-DnVMDT5h.d.cts +0 -376
  96. package/dist/permit-DnVMDT5h.d.ts +0 -376
  97. package/dist/permits.cjs +0 -1026
  98. package/dist/permits.d.cts +0 -353
  99. package/dist/permits.d.ts +0 -353
  100. package/dist/permits.js +0 -2
  101. package/permits/index.ts +0 -68
  102. package/permits/permit.ts +0 -385
  103. package/permits/sealing.ts +0 -131
  104. package/permits/signature.ts +0 -79
  105. package/permits/store.ts +0 -157
  106. package/permits/test/localstorage.test.ts +0 -113
  107. package/permits/test/permit.test.ts +0 -557
  108. package/permits/test/sealing.test.ts +0 -84
  109. package/permits/test/store.test.ts +0 -88
  110. package/permits/test/validation.test.ts +0 -361
  111. package/permits/test-utils.ts +0 -28
  112. package/permits/types.ts +0 -204
  113. package/permits/validation.ts +0 -327
  114. /package/{permits → acps}/utils.ts +0 -0
@@ -1,11 +1,11 @@
1
1
  /* eslint-disable no-dupe-class-members */
2
2
  import { hardhat } from '@/chains';
3
- import { type Permit, PermitUtils } from '@/permits';
3
+ import { type ACP, ACPUtils } from '@/acps';
4
4
 
5
5
  import { FheTypes, type UnsealedItem } from '../types.js';
6
6
  import { getThresholdNetworkUrlOrThrow } from '../config.js';
7
7
  import { CofheError, CofheErrorCode } from '../error.js';
8
- import { permits } from '../permits.js';
8
+ import { acps } from '../acps.js';
9
9
  import { isValidUtype, convertViaUtype } from './decryptUtils.js';
10
10
  import { BaseBuilder, type BaseBuilderParams } from '../baseBuilder.js';
11
11
  import { cofheMocksDecryptForView } from './cofheMocksDecryptForView.js';
@@ -22,17 +22,17 @@ const DEFAULT_404_RETRY_TIMEOUT_MS = 10_000;
22
22
  * await client.decryptForView(ctHash, utype)
23
23
  * .setChainId(chainId)
24
24
  * .setAccount(account)
25
- * .withPermit() // optional (active permit)
26
- * // or .withPermit(permitHash) / .withPermit(permit)
25
+ * .withACP() // optional (active acp)
26
+ * // or .withACP(acpHash) / .withACP(acp)
27
27
  * .execute()
28
28
  *
29
29
  * If chainId not set, uses client's chainId
30
30
  * If account not set, uses client's account
31
- * withPermit() uses chainId + account to get the active permit.
32
- * withPermit(permitHash) fetches that permit using chainId + account.
33
- * withPermit(permit) uses the provided permit regardless of chainId/account.
31
+ * withACP() uses chainId + account to get the active acp.
32
+ * withACP(acpHash) fetches that acp using chainId + account.
33
+ * withACP(acp) uses the provided acp regardless of chainId/account.
34
34
  *
35
- * Note: decryptForView always requires a permit (no global-allowance mode).
35
+ * Note: decryptForView always requires an ACP (no global-allowance mode).
36
36
  *
37
37
  * Returns the unsealed item.
38
38
  */
@@ -40,15 +40,15 @@ const DEFAULT_404_RETRY_TIMEOUT_MS = 10_000;
40
40
  type DecryptForViewBuilderParams<U extends FheTypes> = BaseBuilderParams & {
41
41
  ctHash: bigint | string;
42
42
  utype: U;
43
- permitHash?: string;
44
- permit?: Permit;
43
+ acpHash?: string;
44
+ acp?: ACP;
45
45
  };
46
46
 
47
47
  export class DecryptForViewBuilder<U extends FheTypes> extends BaseBuilder {
48
48
  private ctHash: bigint | string;
49
49
  private utype: U;
50
- private permitHash?: string;
51
- private permit?: Permit;
50
+ private acpHash?: string;
51
+ private acp?: ACP;
52
52
  private pollCallback?: DecryptPollCallbackFunction;
53
53
  private retry404TimeoutMs = DEFAULT_404_RETRY_TIMEOUT_MS;
54
54
 
@@ -64,12 +64,12 @@ export class DecryptForViewBuilder<U extends FheTypes> extends BaseBuilder {
64
64
 
65
65
  this.ctHash = params.ctHash;
66
66
  this.utype = params.utype;
67
- this.permitHash = params.permitHash;
68
- this.permit = params.permit;
67
+ this.acpHash = params.acpHash;
68
+ this.acp = params.acp;
69
69
  }
70
70
 
71
71
  /**
72
- * @param chainId - Chain to decrypt values from. Used to fetch the threshold network URL and use the correct permit.
72
+ * @param chainId - Chain to decrypt values from. Used to fetch the threshold network URL and use the correct acp.
73
73
  *
74
74
  * If not provided, the chainId will be fetched from the connected publicClient.
75
75
  *
@@ -92,7 +92,7 @@ export class DecryptForViewBuilder<U extends FheTypes> extends BaseBuilder {
92
92
  }
93
93
 
94
94
  /**
95
- * @param account - Account to decrypt values from. Used to fetch the correct permit.
95
+ * @param account - Account to decrypt values from. Used to fetch the correct acp.
96
96
  *
97
97
  * If not provided, the account will be fetched from the connected walletClient.
98
98
  *
@@ -135,77 +135,77 @@ export class DecryptForViewBuilder<U extends FheTypes> extends BaseBuilder {
135
135
  }
136
136
 
137
137
  /**
138
- * Select "use permit" mode (optional).
138
+ * Select "use acp" mode (optional).
139
139
  *
140
- * - `withPermit(permit)` uses the provided permit.
141
- * - `withPermit(permitHash)` fetches that permit.
142
- * - `withPermit()` uses the active permit for the resolved `chainId + account`.
140
+ * - `withACP(acp)` uses the provided acp.
141
+ * - `withACP(acpHash)` fetches that acp.
142
+ * - `withACP()` uses the active acp for the resolved `chainId + account`.
143
143
  */
144
- withPermit(): DecryptForViewBuilder<U>;
145
- withPermit(permitHash: string): DecryptForViewBuilder<U>;
146
- withPermit(permit: Permit): DecryptForViewBuilder<U>;
147
- withPermit(permitOrPermitHash?: Permit | string): DecryptForViewBuilder<U> {
148
- if (typeof permitOrPermitHash === 'string') {
149
- this.permitHash = permitOrPermitHash;
150
- this.permit = undefined;
151
- } else if (permitOrPermitHash === undefined) {
152
- // Explicitly choose "active permit" resolution at execute()
153
- this.permitHash = undefined;
154
- this.permit = undefined;
144
+ withACP(): DecryptForViewBuilder<U>;
145
+ withACP(acpHash: string): DecryptForViewBuilder<U>;
146
+ withACP(acp: ACP): DecryptForViewBuilder<U>;
147
+ withACP(acpOrACPHash?: ACP | string): DecryptForViewBuilder<U> {
148
+ if (typeof acpOrACPHash === 'string') {
149
+ this.acpHash = acpOrACPHash;
150
+ this.acp = undefined;
151
+ } else if (acpOrACPHash === undefined) {
152
+ // Explicitly choose "active acp" resolution at execute()
153
+ this.acpHash = undefined;
154
+ this.acp = undefined;
155
155
  } else {
156
- // Permit object
157
- this.permit = permitOrPermitHash;
158
- this.permitHash = undefined;
156
+ // ACP object
157
+ this.acp = acpOrACPHash;
158
+ this.acpHash = undefined;
159
159
  }
160
160
 
161
161
  return this;
162
162
  }
163
163
 
164
164
  /**
165
- * @param permitHash - Permit hash to decrypt values from. Used to fetch the correct permit.
165
+ * @param acpHash - ACP hash to decrypt values from. Used to fetch the correct acp.
166
166
  *
167
- * If not provided, the active permit for the chainId and account will be used.
168
- * If `setPermit()` is called, it will be used regardless of chainId, account, or permitHash.
167
+ * If not provided, the active acp for the chainId and account will be used.
168
+ * If `setACP()` is called, it will be used regardless of chainId, account, or acpHash.
169
169
  *
170
170
  * Example:
171
171
  * ```typescript
172
172
  * const unsealed = await client.decryptForView(ctHash, utype)
173
- * .setPermitHash('0x1234567890123456789012345678901234567890')
173
+ * .setACPHash('0x1234567890123456789012345678901234567890')
174
174
  * .execute();
175
175
  * ```
176
176
  *
177
177
  * @returns The chainable DecryptForViewBuilder instance.
178
178
  */
179
- /** @deprecated Use `withPermit(permitHash)` instead. */
180
- setPermitHash(permitHash: string): DecryptForViewBuilder<U> {
181
- return this.withPermit(permitHash);
179
+ /** @deprecated Use `withACP(acpHash)` instead. */
180
+ setACPHash(acpHash: string): DecryptForViewBuilder<U> {
181
+ return this.withACP(acpHash);
182
182
  }
183
183
 
184
- getPermitHash(): string | undefined {
185
- return this.permitHash;
184
+ getACPHash(): string | undefined {
185
+ return this.acpHash;
186
186
  }
187
187
 
188
188
  /**
189
- * @param permit - Permit to decrypt values with. If provided, it will be used regardless of chainId, account, or permitHash.
189
+ * @param acp - ACP to decrypt values with. If provided, it will be used regardless of chainId, account, or acpHash.
190
190
  *
191
- * If not provided, the permit will be determined by chainId, account, and permitHash.
191
+ * If not provided, the acp will be determined by chainId, account, and acpHash.
192
192
  *
193
193
  * Example:
194
194
  * ```typescript
195
195
  * const unsealed = await client.decryptForView(ctHash, utype)
196
- * .setPermit(permit)
196
+ * .setACP(acp)
197
197
  * .execute();
198
198
  * ```
199
199
  *
200
200
  * @returns The chainable DecryptForViewBuilder instance.
201
201
  */
202
- /** @deprecated Use `withPermit(permit)` instead. */
203
- setPermit(permit: Permit): DecryptForViewBuilder<U> {
204
- return this.withPermit(permit);
202
+ /** @deprecated Use `withACP(acp)` instead. */
203
+ setACP(acp: ACP): DecryptForViewBuilder<U> {
204
+ return this.withACP(acp);
205
205
  }
206
206
 
207
- getPermit(): Permit | undefined {
208
- return this.permit;
207
+ getACP(): ACP | undefined {
208
+ return this.acp;
209
209
  }
210
210
 
211
211
  private async getThresholdNetworkUrl(): Promise<string> {
@@ -224,50 +224,50 @@ export class DecryptForViewBuilder<U extends FheTypes> extends BaseBuilder {
224
224
  });
225
225
  }
226
226
 
227
- private async getResolvedPermit(): Promise<Permit> {
228
- if (this.permit) return this.permit;
227
+ private async getResolvedACP(): Promise<ACP> {
228
+ if (this.acp) return this.acp;
229
229
 
230
230
  this.assertChainId();
231
231
  this.assertAccount();
232
232
 
233
- // Fetch with permit hash
234
- if (this.permitHash) {
235
- const permit = await permits.getPermit(this.chainId, this.account, this.permitHash);
236
- if (!permit) {
233
+ // Fetch with acp hash
234
+ if (this.acpHash) {
235
+ const acp = await acps.getACP(this.chainId, this.account, this.acpHash);
236
+ if (!acp) {
237
237
  throw new CofheError({
238
- code: CofheErrorCode.PermitNotFound,
239
- message: `Permit with hash <${this.permitHash}> not found for account <${this.account}> and chainId <${this.chainId}>`,
240
- hint: 'Ensure the permit exists and is valid.',
238
+ code: CofheErrorCode.ACPNotFound,
239
+ message: `ACP with hash <${this.acpHash}> not found for account <${this.account}> and chainId <${this.chainId}>`,
240
+ hint: 'Ensure the acp exists and is valid.',
241
241
  context: {
242
242
  chainId: this.chainId,
243
243
  account: this.account,
244
- permitHash: this.permitHash,
244
+ acpHash: this.acpHash,
245
245
  },
246
246
  });
247
247
  }
248
- return permit;
248
+ return acp;
249
249
  }
250
250
 
251
- // Fetch with active permit
252
- const permit = await permits.getActivePermit(this.chainId, this.account);
253
- if (!permit) {
251
+ // Fetch with active acp
252
+ const acp = await acps.getActiveACP(this.chainId, this.account);
253
+ if (!acp) {
254
254
  throw new CofheError({
255
- code: CofheErrorCode.PermitNotFound,
256
- message: `Active permit not found for chainId <${this.chainId}> and account <${this.account}>`,
257
- hint: 'Ensure a permit exists for this account on this chain.',
255
+ code: CofheErrorCode.ACPNotFound,
256
+ message: `Active acp not found for chainId <${this.chainId}> and account <${this.account}>`,
257
+ hint: 'Ensure an ACP exists for this account on this chain.',
258
258
  context: {
259
259
  chainId: this.chainId,
260
260
  account: this.account,
261
261
  },
262
262
  });
263
263
  }
264
- return permit;
264
+ return acp;
265
265
  }
266
266
 
267
267
  /**
268
268
  * On hardhat, interact with MockZkVerifier contract instead of CoFHE
269
269
  */
270
- private async mocksSealOutput(permit: Permit): Promise<bigint> {
270
+ private async mocksSealOutput(acp: ACP): Promise<bigint> {
271
271
  this.assertPublicClient();
272
272
 
273
273
  // Configurable delay before decrypting the output to simulate the CoFHE decrypt processing time
@@ -276,38 +276,38 @@ export class DecryptForViewBuilder<U extends FheTypes> extends BaseBuilder {
276
276
  const mocksDecryptDelay = this.config.mocks.decryptDelay;
277
277
  if (mocksDecryptDelay > 0) await sleep(mocksDecryptDelay);
278
278
 
279
- return cofheMocksDecryptForView(this.ctHash, this.utype, permit, this.publicClient);
279
+ return cofheMocksDecryptForView(this.ctHash, this.utype, acp, this.publicClient);
280
280
  }
281
281
 
282
282
  /**
283
283
  * In the production context, perform a true decryption with the CoFHE coprocessor.
284
284
  */
285
- private async productionSealOutput(permit: Permit): Promise<bigint> {
285
+ private async productionSealOutput(acp: ACP): Promise<bigint> {
286
286
  this.assertChainId();
287
287
  this.assertPublicClient();
288
288
 
289
289
  const thresholdNetworkUrl = await this.getThresholdNetworkUrl();
290
- const permission = PermitUtils.getPermission(permit, true);
290
+ const wireAcp = ACPUtils.getPublic(acp, true);
291
291
  // const sealed = await tnSealOutputV1(this.ctHash, this.chainId, permission, thresholdNetworkUrl);
292
292
  const sealed = await tnSealOutputV2({
293
293
  ctHash: this.ctHash,
294
294
  chainId: this.chainId,
295
- permission,
295
+ acp: wireAcp,
296
296
  thresholdNetworkUrl,
297
297
  retry404TimeoutMs: this.retry404TimeoutMs,
298
298
  onPoll: this.pollCallback,
299
299
  });
300
- return PermitUtils.unseal(permit, sealed);
300
+ return ACPUtils.unseal(acp, sealed);
301
301
  }
302
302
 
303
303
  /**
304
304
  * Final step of the decryption process. MUST BE CALLED LAST IN THE CHAIN.
305
305
  *
306
306
  * This will:
307
- * - Use a permit based on provided permit OR chainId + account + permitHash
308
- * - Check permit validity
309
- * - Call CoFHE `/sealoutput` with the permit, which returns a sealed (encrypted) item
310
- * - Unseal the sealed item with the permit
307
+ * - Use an ACP based on provided acp OR chainId + account + acpHash
308
+ * - Check acp validity
309
+ * - Call CoFHE `/sealoutput` with the acp, which returns a sealed (encrypted) item
310
+ * - Unseal the sealed item with the acp
311
311
  * - Return the unsealed item
312
312
  *
313
313
  * Example:
@@ -315,7 +315,7 @@ export class DecryptForViewBuilder<U extends FheTypes> extends BaseBuilder {
315
315
  * const unsealed = await client.decryptForView(ctHash, utype)
316
316
  * .setChainId(11155111) // optional
317
317
  * .setAccount('0x123...890') // optional
318
- * .withPermit() // optional
318
+ * .withACP() // optional
319
319
  * .execute(); // execute
320
320
  * ```
321
321
  *
@@ -325,25 +325,25 @@ export class DecryptForViewBuilder<U extends FheTypes> extends BaseBuilder {
325
325
  // Ensure utype is valid
326
326
  this.validateUtypeOrThrow();
327
327
 
328
- // Resolve permit
329
- const permit = await this.getResolvedPermit();
328
+ // Resolve acp
329
+ const acp = await this.getResolvedACP();
330
330
 
331
- // Ensure permit validity
332
- PermitUtils.validate(permit);
331
+ // Ensure acp validity
332
+ ACPUtils.validate(acp);
333
333
 
334
- // Extract chainId from signed permit
334
+ // Extract chainId from signed acp
335
335
  // Use this chainId to fetch the threshold network URL since this.chainId may be undefined
336
- const chainId = permit._signedDomain!.chainId;
336
+ const chainId = acp._signedDomain!.chainId;
337
337
 
338
- // Check permit validity on-chain
339
- // TODO: PermitUtils.validateOnChain(permit, this.publicClient);
338
+ // Check acp validity on-chain
339
+ // TODO: ACPUtils.validateOnChain(acp, this.publicClient);
340
340
 
341
341
  let unsealed: bigint;
342
342
 
343
343
  if (chainId === hardhat.id) {
344
- unsealed = await this.mocksSealOutput(permit);
344
+ unsealed = await this.mocksSealOutput(acp);
345
345
  } else {
346
- unsealed = await this.productionSealOutput(permit);
346
+ unsealed = await this.productionSealOutput(acp);
347
347
  }
348
348
 
349
349
  return convertViaUtype(this.utype, unsealed);
@@ -1,17 +1,21 @@
1
1
  import { CofheError, CofheErrorCode } from '../error.js';
2
+ import { mapApiErrorCodeToCofheErrorCode, parseApiErrorResponseBody } from './apiError.js';
2
3
 
3
4
  export const DEFAULT_404_RETRY_TIMEOUT_MS = 10_000;
4
5
 
5
6
  export type SubmitRetryableStatus = 204 | 404;
6
7
 
7
8
  export type SubmitResponseClassification =
8
- | { kind: 'retryable'; status: SubmitRetryableStatus }
9
+ | { kind: 'retryable'; status: 204 }
10
+ | { kind: 'retryable'; status: 404; apiErrorMessage: string }
9
11
  | { kind: 'parse-json' }
10
- | { kind: 'fatal-http'; errorMessage: string };
11
-
12
- export function isRetryableSubmitStatus(status: number): status is SubmitRetryableStatus {
13
- return status === 204 || status === 404;
14
- }
12
+ | {
13
+ kind: 'fatal-http';
14
+ status: number;
15
+ cofheErrorCode: CofheErrorCode;
16
+ apiErrorCode?: string;
17
+ errorMessage: string;
18
+ };
15
19
 
16
20
  export function normalize404RetryTimeoutMs(params: {
17
21
  timeoutMs: number | undefined;
@@ -35,40 +39,39 @@ export function normalize404RetryTimeoutMs(params: {
35
39
  return timeoutMs;
36
40
  }
37
41
 
42
+ /**
43
+ * Classifies a submit (POST) response into: bodyless-retryable 204, any 404 (still-indexing /
44
+ * not-found — retryable under the 404 retry budget), a 200 to be JSON-parsed, or a fatal error
45
+ * carrying the mapped CofheErrorCode.
46
+ */
38
47
  export async function classifySubmitResponse(params: {
39
48
  response: Response;
40
- extractErrorMessage?: (errorBody: unknown) => string | undefined;
49
+ fallbackErrorCode: CofheErrorCode;
41
50
  }): Promise<SubmitResponseClassification> {
42
- const { response, extractErrorMessage } = params;
51
+ const { response, fallbackErrorCode } = params;
43
52
 
44
- if (isRetryableSubmitStatus(response.status)) {
45
- return { kind: 'retryable', status: response.status };
53
+ // 204 is documented bodyless — never touch response.json() for it.
54
+ if (response.status === 204) {
55
+ return { kind: 'retryable', status: 204 };
46
56
  }
47
57
 
48
58
  if (response.ok) {
49
59
  return { kind: 'parse-json' };
50
60
  }
51
61
 
52
- let errorMessage = `HTTP ${response.status}`;
53
- try {
54
- const errorBody = (await response.json()) as unknown;
55
- const maybeErrorMessage = extractErrorMessage?.(errorBody);
56
- if (typeof maybeErrorMessage === 'string' && maybeErrorMessage.length > 0) {
57
- errorMessage = maybeErrorMessage;
58
- } else if (errorBody && typeof errorBody === 'object') {
59
- const defaultMessage = (errorBody as Record<string, unknown>).error_message;
60
- const fallbackMessage = (errorBody as Record<string, unknown>).message;
61
- if (typeof defaultMessage === 'string' && defaultMessage.length > 0) {
62
- errorMessage = defaultMessage;
63
- } else if (typeof fallbackMessage === 'string' && fallbackMessage.length > 0) {
64
- errorMessage = fallbackMessage;
65
- }
66
- }
67
- } catch {
68
- errorMessage = response.statusText || errorMessage;
62
+ const { apiErrorCode, errorMessage } = await parseApiErrorResponseBody(response);
63
+
64
+ if (response.status === 404) {
65
+ return { kind: 'retryable', status: 404, apiErrorMessage: errorMessage };
69
66
  }
70
67
 
71
- return { kind: 'fatal-http', errorMessage };
68
+ return {
69
+ kind: 'fatal-http',
70
+ status: response.status,
71
+ cofheErrorCode: mapApiErrorCodeToCofheErrorCode(apiErrorCode, fallbackErrorCode),
72
+ apiErrorCode,
73
+ errorMessage,
74
+ };
72
75
  }
73
76
 
74
77
  export function throwIfSubmitRetryTimedOut(params: {
@@ -81,6 +84,7 @@ export function throwIfSubmitRetryTimedOut(params: {
81
84
  thresholdNetworkUrl: string;
82
85
  body: unknown;
83
86
  attemptIndex: number;
87
+ lastKnownErrorMessage?: string;
84
88
  }): void {
85
89
  const {
86
90
  operationLabel,
@@ -92,12 +96,16 @@ export function throwIfSubmitRetryTimedOut(params: {
92
96
  thresholdNetworkUrl,
93
97
  body,
94
98
  attemptIndex,
99
+ lastKnownErrorMessage,
95
100
  } = params;
96
101
 
97
102
  if (status === 404 && elapsedMs > retry404TimeoutMs) {
98
103
  throw new CofheError({
99
- code: errorCode,
100
- message: `${operationLabel} submit retried 404 responses without receiving request_id for ${retry404TimeoutMs}ms`,
104
+ code: CofheErrorCode.CtNotFound,
105
+ apiErrorCode: 'ct_not_found',
106
+ message:
107
+ `${operationLabel} ciphertext not found after retrying for ${retry404TimeoutMs}ms` +
108
+ (lastKnownErrorMessage ? `: ${lastKnownErrorMessage}` : ''),
101
109
  hint: 'The ciphertext may not be indexed yet. Increase set404RetryTimeout(...) if the backend is slow to index ciphertexts.',
102
110
  context: {
103
111
  thresholdNetworkUrl,
@@ -1,4 +1,4 @@
1
- import { type Permission } from '@/permits';
1
+ import { type ACPPublic } from '@/acps';
2
2
 
3
3
  import { CofheError, CofheErrorCode } from '../error';
4
4
  import { normalizeTnSignature, parseDecryptedBytesToBigInt } from './tnDecryptUtils';
@@ -68,20 +68,20 @@ function assertTnDecryptResponseV1(value: unknown): TnDecryptResponseV1 {
68
68
  export async function tnDecryptV1(
69
69
  ctHash: bigint | string,
70
70
  chainId: number,
71
- permission: Permission | null,
71
+ acp: ACPPublic | null,
72
72
  thresholdNetworkUrl: string
73
73
  ): Promise<{ decryptedValue: bigint; signature: `0x${string}` }> {
74
74
  const body: {
75
75
  ct_tempkey: string;
76
76
  host_chain_id: number;
77
- permit?: Permission;
77
+ acp?: ACPPublic;
78
78
  } = {
79
79
  ct_tempkey: BigInt(ctHash).toString(16).padStart(64, '0'),
80
80
  host_chain_id: chainId,
81
81
  };
82
82
 
83
- if (permission) {
84
- body.permit = permission;
83
+ if (acp) {
84
+ body.acp = acp;
85
85
  }
86
86
 
87
87
  let response: Response;