@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
@@ -47,8 +47,8 @@ const parseWithBigInt = (str: string): any =>
47
47
 
48
48
  // packMetadata function removed as it's no longer needed
49
49
  const unpackMetadata = (metadata: string) => {
50
- const [signer, securityZone, chainId] = metadata.split('-');
51
- return { signer, securityZone: parseInt(securityZone), chainId: parseInt(chainId) };
50
+ const [signer, securityZone, chainId, contractAddr] = metadata.split('-');
51
+ return { signer, securityZone: parseInt(securityZone), chainId: parseInt(chainId), contractAddr };
52
52
  };
53
53
 
54
54
  export const deconstructZkPoKMetadata = (
@@ -125,17 +125,19 @@ const MockCrs = {
125
125
  safe_serialize: (_serializedSizeLimit: bigint) => new Uint8Array(),
126
126
  };
127
127
 
128
- // Setup fetch mock for http://localhost:3001/verify
129
- // Simulates verification of zk proof
130
- // Returns {ctHash: stringified value, signature: `${account_addr}-${security_zone}-${chain_id}-`, recid: 0}
131
- // Expects the proof to be created by the MockZkListBuilder `build_with_proof_packed` above
128
+ // Setup fetch mock for http://localhost:3001/verifyBatch
129
+ // Simulates batch verification of a zk proof: one signature covering the whole batch.
130
+ // The signature is `${account_addr}-${security_zone}-${chain_id}-${contract_address}-` (a debug
131
+ // string, not a real signature) so tests can recover the request payload that was sent for a
132
+ // given result. Expects the proof to be created by the MockZkListBuilder `build_with_proof_packed`
133
+ // above
132
134
  const mockFetch = vi.fn();
133
135
  global.fetch = mockFetch;
134
136
  const setupZkVerifyMock = () => {
135
137
  mockFetch.mockImplementation((url: string, options: any) => {
136
- if (url === `${MockZkVerifierUrl}/verify`) {
138
+ if (url === `${MockZkVerifierUrl}/verifyBatch`) {
137
139
  const body = JSON.parse(options.body as string);
138
- const { packed_list, account_addr, security_zone, chain_id } = body;
140
+ const { packed_list, account_addr, security_zone, chain_id, contract_address } = body;
139
141
 
140
142
  // Decode the proof data
141
143
  const arr = fromHexString(packed_list);
@@ -143,11 +145,10 @@ const setupZkVerifyMock = () => {
143
145
  const decodedData = parseWithBigInt(decoded);
144
146
  const { items } = decodedData;
145
147
 
146
- // Create mock verify results
147
- const mockResults = items.map((item: EncryptableItem) => ({
148
+ // Create a mock batch verify result: per-item ct_hash/ct_type, and one shared signature
149
+ const ciphertexts = items.map((item: EncryptableItem) => ({
148
150
  ct_hash: BigInt(item.data).toString(),
149
- signature: `${account_addr}-${security_zone}-${chain_id}-`,
150
- recid: 0,
151
+ ct_type: item.utype,
151
152
  }));
152
153
 
153
154
  return Promise.resolve({
@@ -155,7 +156,11 @@ const setupZkVerifyMock = () => {
155
156
  json: () =>
156
157
  Promise.resolve({
157
158
  status: 'success',
158
- data: mockResults,
159
+ data: {
160
+ ciphertexts,
161
+ signature: `${account_addr}-${security_zone}-${chain_id}-${contract_address}-`,
162
+ recid: 0,
163
+ },
159
164
  error: null,
160
165
  }),
161
166
  });
@@ -232,6 +237,7 @@ class MockZkProvenList {
232
237
  describe('EncryptInputsBuilder', () => {
233
238
  const defaultSender = '0x1234567890123456789012345678901234567890';
234
239
  const defaultChainId = 1;
240
+ const defaultConsumingContract = '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef';
235
241
  const createDefaultParams = () => {
236
242
  return {
237
243
  inputs: [Encryptable.uint128(100n)] as [EncryptableUint128],
@@ -260,6 +266,7 @@ describe('EncryptInputsBuilder', () => {
260
266
  setupZkVerifyMock();
261
267
  insertMockKeys(defaultChainId, 0);
262
268
  builder = new EncryptInputsBuilder(createDefaultParams());
269
+ builder.setConsumingContract(defaultConsumingContract);
263
270
  });
264
271
 
265
272
  describe('constructor and initialization', () => {
@@ -320,7 +327,9 @@ describe('EncryptInputsBuilder', () => {
320
327
  await new EncryptInputsBuilder({
321
328
  ...createDefaultParams(),
322
329
  initTfhe: vi.fn().mockRejectedValue(new Error('Failed to initialize TFHE')),
323
- }).execute();
330
+ })
331
+ .setConsumingContract(defaultConsumingContract)
332
+ .execute();
324
333
  } catch (error) {
325
334
  expect(error).toBeInstanceOf(CofheError);
326
335
  expect((error as CofheError).code).toBe(CofheErrorCode.InitTfheFailed);
@@ -331,7 +340,9 @@ describe('EncryptInputsBuilder', () => {
331
340
  const result = await new EncryptInputsBuilder({
332
341
  ...createDefaultParams(),
333
342
  initTfhe: mockInitTfhe,
334
- }).execute();
343
+ })
344
+ .setConsumingContract(defaultConsumingContract)
345
+ .execute();
335
346
  expect(result).toBeDefined();
336
347
  });
337
348
  });
@@ -417,6 +428,25 @@ describe('EncryptInputsBuilder', () => {
417
428
  });
418
429
  });
419
430
 
431
+ describe('setConsumingContract', () => {
432
+ it('should set consuming contract and return builder for chaining', () => {
433
+ const contract = '0xdeaddeaddeaddeaddeaddeaddeaddeaddeaddead';
434
+ const result = builder.setConsumingContract(contract);
435
+ expect(result).toBe(builder);
436
+ expect(result.getConsumingContract()).toBe(contract);
437
+ });
438
+
439
+ it('should throw an error if consumingContract is not set', async () => {
440
+ try {
441
+ await new EncryptInputsBuilder(createDefaultParams()).execute();
442
+ expect.unreachable('execute() should have thrown');
443
+ } catch (error) {
444
+ expect(error).toBeInstanceOf(CofheError);
445
+ expect((error as CofheError).code).toBe(CofheErrorCode.ConsumingContractUninitialized);
446
+ }
447
+ });
448
+ });
449
+
420
450
  describe('zkVerifierUrl', () => {
421
451
  it('should throw if zkVerifierUrl is not set', async () => {
422
452
  try {
@@ -426,7 +456,9 @@ describe('EncryptInputsBuilder', () => {
426
456
  account: '0x1234567890123456789012345678901234567890',
427
457
  chainId: 1,
428
458
  config: createMockCofheConfig(defaultChainId, undefined as unknown as string),
429
- }).execute();
459
+ })
460
+ .setConsumingContract(defaultConsumingContract)
461
+ .execute();
430
462
  } catch (error) {
431
463
  expect(error).toBeInstanceOf(CofheError);
432
464
  expect((error as CofheError).code).toBe(CofheErrorCode.ZkVerifierUrlUninitialized);
@@ -555,8 +587,8 @@ describe('EncryptInputsBuilder', () => {
555
587
  expect(Array.isArray(result)).toBe(true);
556
588
 
557
589
  // Verify result embedded metadata
558
- const [encrypted] = result;
559
- const encryptedMetadata = unpackMetadata(encrypted.signature);
590
+ const signature = result[result.length - 1] as string;
591
+ const encryptedMetadata = unpackMetadata(signature);
560
592
  expect(encryptedMetadata).toBeDefined();
561
593
  expect(encryptedMetadata.signer).toBe(defaultSender);
562
594
  expect(encryptedMetadata.securityZone).toBe(0);
@@ -570,8 +602,8 @@ describe('EncryptInputsBuilder', () => {
570
602
  const result = await builder.execute();
571
603
 
572
604
  // Verify result embedded metadata
573
- const [encrypted] = result;
574
- const encryptedMetadata = unpackMetadata(encrypted.signature);
605
+ const signature = result[result.length - 1] as string;
606
+ const encryptedMetadata = unpackMetadata(signature);
575
607
  expect(encryptedMetadata).toBeDefined();
576
608
  expect(encryptedMetadata.signer).toBe(overriddenSender);
577
609
  expect(encryptedMetadata.securityZone).toBe(0);
@@ -587,8 +619,8 @@ describe('EncryptInputsBuilder', () => {
587
619
  const result = await builder.execute();
588
620
 
589
621
  // Verify result embedded metadata
590
- const [encrypted] = result;
591
- const encryptedMetadata = unpackMetadata(encrypted.signature);
622
+ const signature = result[result.length - 1] as string;
623
+ const encryptedMetadata = unpackMetadata(signature);
592
624
  expect(encryptedMetadata).toBeDefined();
593
625
  expect(encryptedMetadata.signer).toBe(defaultSender);
594
626
  expect(encryptedMetadata.securityZone).toBe(overriddenZone);
@@ -612,6 +644,7 @@ describe('EncryptInputsBuilder', () => {
612
644
  ReturnType<typeof Encryptable.bool>,
613
645
  ],
614
646
  });
647
+ multiInputBuilder.setConsumingContract(defaultConsumingContract);
615
648
 
616
649
  const result = await multiInputBuilder.execute();
617
650
 
@@ -645,7 +678,9 @@ describe('EncryptInputsBuilder', () => {
645
678
  Encryptable.uint128(100n),
646
679
  Encryptable.uint128(100n),
647
680
  ],
648
- }).execute();
681
+ })
682
+ .setConsumingContract(defaultConsumingContract)
683
+ .execute();
649
684
  } catch (error) {
650
685
  expect(error).toBeInstanceOf(CofheError);
651
686
  expect((error as CofheError).code).toBe(CofheErrorCode.ZkPackFailed);
@@ -702,8 +737,8 @@ describe('EncryptInputsBuilder', () => {
702
737
  expect(stepCallback).toHaveBeenCalledTimes(10);
703
738
 
704
739
  // Verify result embedded metadata
705
- const [encrypted] = result;
706
- const encryptedMetadata = unpackMetadata(encrypted.signature);
740
+ const signature = result[result.length - 1] as string;
741
+ const encryptedMetadata = unpackMetadata(signature);
707
742
  expect(encryptedMetadata).toBeDefined();
708
743
  expect(encryptedMetadata.signer).toBe(sender);
709
744
  expect(encryptedMetadata.securityZone).toBe(securityZone);
@@ -727,16 +762,16 @@ describe('EncryptInputsBuilder', () => {
727
762
  expect(result2).toBeDefined();
728
763
 
729
764
  // Verify result embedded metadata
730
- const [encrypted1] = result1;
731
- const encryptedMetadata1 = unpackMetadata(encrypted1.signature);
765
+ const signature1 = result1[result1.length - 1] as string;
766
+ const encryptedMetadata1 = unpackMetadata(signature1);
732
767
  expect(encryptedMetadata1).toBeDefined();
733
768
  expect(encryptedMetadata1.signer).toBe(sender);
734
769
  expect(encryptedMetadata1.securityZone).toBe(securityZone);
735
770
  expect(encryptedMetadata1.chainId).toBe(defaultChainId);
736
771
 
737
772
  // Verify result embedded metadata
738
- const [encrypted2] = result2;
739
- const encryptedMetadata2 = unpackMetadata(encrypted2.signature);
773
+ const signature2 = result2[result2.length - 1] as string;
774
+ const encryptedMetadata2 = unpackMetadata(signature2);
740
775
  expect(encryptedMetadata2).toBeDefined();
741
776
  expect(encryptedMetadata2.signer).toBe(sender);
742
777
  expect(encryptedMetadata2.securityZone).toBe(securityZone);
@@ -744,18 +779,14 @@ describe('EncryptInputsBuilder', () => {
744
779
  });
745
780
  });
746
781
 
747
- describe('asHashPlusProof', () => {
748
- it('should return the same builder instance for chaining', () => {
749
- expect(builder.asHashPlusProof()).toBe(builder);
750
- });
751
-
752
- it('execute() returns [hash, proof] for a single input', async () => {
753
- const result = await builder.asHashPlusProof().execute();
782
+ describe('execute() batch result shape', () => {
783
+ it('execute() returns [hash, signature] for a single input', async () => {
784
+ const result = await builder.execute();
754
785
  expect(Array.isArray(result)).toBe(true);
755
- expect(result).toHaveLength(2); // 1 hash + 1 proof
786
+ expect(result).toHaveLength(2); // 1 hash + 1 signature
756
787
  });
757
788
 
758
- it('execute() returns [hash1, hash2, proof] for two inputs', async () => {
789
+ it('execute() returns [hash1, hash2, signature] for two inputs', async () => {
759
790
  const result = await new EncryptInputsBuilder({
760
791
  ...createDefaultParams(),
761
792
  inputs: [Encryptable.uint128(100n), Encryptable.bool(true)] as [
@@ -763,16 +794,16 @@ describe('EncryptInputsBuilder', () => {
763
794
  ReturnType<typeof Encryptable.bool>,
764
795
  ],
765
796
  })
766
- .asHashPlusProof()
797
+ .setConsumingContract(defaultConsumingContract)
767
798
  .execute();
768
799
 
769
800
  expect(Array.isArray(result)).toBe(true);
770
- expect(result).toHaveLength(3); // 2 hashes + 1 proof
801
+ expect(result).toHaveLength(3); // 2 hashes + 1 signature
771
802
  });
772
803
 
773
804
  it('should be composable with other builder methods', async () => {
774
805
  const overriddenSender = '0x5555555555555555555555555555555555555555';
775
- const result = await builder.asHashPlusProof().setAccount(overriddenSender).execute();
806
+ const result = await builder.setAccount(overriddenSender).execute();
776
807
 
777
808
  expect(Array.isArray(result)).toBe(true);
778
809
  expect(result).toHaveLength(2);
@@ -1,6 +1,7 @@
1
1
  import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
2
  import { tnDecryptV2 } from '../decrypt/tnDecryptV2.js';
3
3
  import { tnSealOutputV2 } from '../decrypt/tnSealOutputV2.js';
4
+ import { CofheErrorCode } from '../error.js';
4
5
 
5
6
  const makeMockResponse = (opts: { ok: boolean; status?: number; statusText?: string; json: () => Promise<any> }) => {
6
7
  return {
@@ -70,7 +71,7 @@ describe('decrypt polling callbacks', () => {
70
71
  const promise = tnDecryptV2({
71
72
  ctHash: 1n,
72
73
  chainId: 1,
73
- permission: null,
74
+ acp: null,
74
75
  thresholdNetworkUrl,
75
76
  onPoll,
76
77
  });
@@ -153,7 +154,7 @@ describe('decrypt polling callbacks', () => {
153
154
  const promise = tnDecryptV2({
154
155
  ctHash: 1n,
155
156
  chainId: 1,
156
- permission: null,
157
+ acp: null,
157
158
  thresholdNetworkUrl,
158
159
  onPoll,
159
160
  });
@@ -201,7 +202,7 @@ describe('decrypt polling callbacks', () => {
201
202
  ok: false,
202
203
  status: 404,
203
204
  statusText: 'Not Found',
204
- json: async () => ({ message: 'Not Found' }),
205
+ json: async () => ({ error: 'ct_not_found', error_message: 'ciphertext not indexed yet' }),
205
206
  });
206
207
  }
207
208
 
@@ -233,7 +234,7 @@ describe('decrypt polling callbacks', () => {
233
234
  const promise = tnDecryptV2({
234
235
  ctHash: 1n,
235
236
  chainId: 1,
236
- permission: null,
237
+ acp: null,
237
238
  thresholdNetworkUrl,
238
239
  onPoll,
239
240
  });
@@ -275,7 +276,7 @@ describe('decrypt polling callbacks', () => {
275
276
  ok: false,
276
277
  status: 404,
277
278
  statusText: 'Not Found',
278
- json: async () => ({ message: 'Not Found' }),
279
+ json: async () => ({ error: 'ct_not_found', error_message: 'ciphertext not indexed yet' }),
279
280
  });
280
281
  }
281
282
 
@@ -287,12 +288,47 @@ describe('decrypt polling callbacks', () => {
287
288
  const promise = tnDecryptV2({
288
289
  ctHash: 1n,
289
290
  chainId: 1,
290
- permission: null,
291
+ acp: null,
291
292
  thresholdNetworkUrl,
292
293
  });
293
294
 
294
295
  const rejection = expect(promise).rejects.toMatchObject({
295
- message: 'decrypt submit retried 404 responses without receiving request_id for 10000ms',
296
+ code: CofheErrorCode.CtNotFound,
297
+ apiErrorCode: 'ct_not_found',
298
+ message: 'decrypt ciphertext not found after retrying for 10000ms: ciphertext not indexed yet',
299
+ });
300
+
301
+ await vi.advanceTimersByTimeAsync(11_000);
302
+ await rejection;
303
+ });
304
+
305
+ it('tnDecryptV2 retries any 404 and times out with the last error message', async () => {
306
+ const fetchMock = vi.fn(async (url: string, options?: any) => {
307
+ if (url === `${thresholdNetworkUrl}/v2/decrypt` && options?.method === 'POST') {
308
+ return makeMockResponse({
309
+ ok: false,
310
+ status: 404,
311
+ statusText: 'Not Found',
312
+ json: async () => ({ error: 'acp_denied', error_message: 'acp was rejected' }),
313
+ });
314
+ }
315
+
316
+ throw new Error(`Unexpected fetch: ${url}`);
317
+ });
318
+
319
+ global.fetch = fetchMock as any;
320
+
321
+ const promise = tnDecryptV2({
322
+ ctHash: 1n,
323
+ chainId: 1,
324
+ acp: null,
325
+ thresholdNetworkUrl,
326
+ });
327
+
328
+ const rejection = expect(promise).rejects.toMatchObject({
329
+ code: CofheErrorCode.CtNotFound,
330
+ apiErrorCode: 'ct_not_found',
331
+ message: 'decrypt ciphertext not found after retrying for 10000ms: acp was rejected',
296
332
  });
297
333
 
298
334
  await vi.advanceTimersByTimeAsync(11_000);
@@ -324,7 +360,7 @@ describe('decrypt polling callbacks', () => {
324
360
  const result = await tnDecryptV2({
325
361
  ctHash: 1n,
326
362
  chainId: 1,
327
- permission: null,
363
+ acp: null,
328
364
  thresholdNetworkUrl,
329
365
  onPoll,
330
366
  });
@@ -379,7 +415,7 @@ describe('decrypt polling callbacks', () => {
379
415
  const promise = tnDecryptV2({
380
416
  ctHash: 1n,
381
417
  chainId: 1,
382
- permission: null,
418
+ acp: null,
383
419
  thresholdNetworkUrl,
384
420
  onPoll,
385
421
  });
@@ -450,7 +486,7 @@ describe('decrypt polling callbacks', () => {
450
486
  const promise = tnSealOutputV2({
451
487
  ctHash: 1n,
452
488
  chainId: 1,
453
- permission: {} as any,
489
+ acp: {} as any,
454
490
  thresholdNetworkUrl,
455
491
  onPoll,
456
492
  });
@@ -534,7 +570,7 @@ describe('decrypt polling callbacks', () => {
534
570
  const promise = tnSealOutputV2({
535
571
  ctHash: 1n,
536
572
  chainId: 1,
537
- permission: {} as any,
573
+ acp: {} as any,
538
574
  thresholdNetworkUrl,
539
575
  onPoll,
540
576
  });
@@ -582,7 +618,7 @@ describe('decrypt polling callbacks', () => {
582
618
  ok: false,
583
619
  status: 404,
584
620
  statusText: 'Not Found',
585
- json: async () => ({ message: 'Not Found' }),
621
+ json: async () => ({ error: 'ct_not_found', error_message: 'ciphertext not indexed yet' }),
586
622
  });
587
623
  }
588
624
 
@@ -617,7 +653,7 @@ describe('decrypt polling callbacks', () => {
617
653
  const promise = tnSealOutputV2({
618
654
  ctHash: 1n,
619
655
  chainId: 1,
620
- permission: {} as any,
656
+ acp: {} as any,
621
657
  thresholdNetworkUrl,
622
658
  onPoll,
623
659
  });
@@ -659,7 +695,7 @@ describe('decrypt polling callbacks', () => {
659
695
  ok: false,
660
696
  status: 404,
661
697
  statusText: 'Not Found',
662
- json: async () => ({ message: 'Not Found' }),
698
+ json: async () => ({ error: 'ct_not_found', error_message: 'ciphertext not indexed yet' }),
663
699
  });
664
700
  }
665
701
 
@@ -671,19 +707,54 @@ describe('decrypt polling callbacks', () => {
671
707
  const promise = tnSealOutputV2({
672
708
  ctHash: 1n,
673
709
  chainId: 1,
674
- permission: {} as any,
710
+ acp: {} as any,
675
711
  thresholdNetworkUrl,
676
712
  retry404TimeoutMs: 2000,
677
713
  });
678
714
 
679
715
  const rejection = expect(promise).rejects.toMatchObject({
680
- message: 'sealOutput submit retried 404 responses without receiving request_id for 2000ms',
716
+ code: CofheErrorCode.CtNotFound,
717
+ apiErrorCode: 'ct_not_found',
718
+ message: 'sealOutput ciphertext not found after retrying for 2000ms: ciphertext not indexed yet',
681
719
  });
682
720
 
683
721
  await vi.advanceTimersByTimeAsync(3000);
684
722
  await rejection;
685
723
  });
686
724
 
725
+ it('tnSealOutputV2 retries any 404 and times out with the last error message', async () => {
726
+ const fetchMock = vi.fn(async (url: string, options?: any) => {
727
+ if (url === `${thresholdNetworkUrl}/v2/sealoutput` && options?.method === 'POST') {
728
+ return makeMockResponse({
729
+ ok: false,
730
+ status: 404,
731
+ statusText: 'Not Found',
732
+ json: async () => ({ error: 'acp_denied', error_message: 'acp was rejected' }),
733
+ });
734
+ }
735
+
736
+ throw new Error(`Unexpected fetch: ${url}`);
737
+ });
738
+
739
+ global.fetch = fetchMock as any;
740
+
741
+ const promise = tnSealOutputV2({
742
+ ctHash: 1n,
743
+ chainId: 1,
744
+ acp: {} as any,
745
+ thresholdNetworkUrl,
746
+ });
747
+
748
+ const rejection = expect(promise).rejects.toMatchObject({
749
+ code: CofheErrorCode.CtNotFound,
750
+ apiErrorCode: 'ct_not_found',
751
+ message: 'sealOutput ciphertext not found after retrying for 10000ms: acp was rejected',
752
+ });
753
+
754
+ await vi.advanceTimersByTimeAsync(11_000);
755
+ await rejection;
756
+ });
757
+
687
758
  it('tnSealOutputV2 returns immediately when submit responds with cached completed payload', async () => {
688
759
  const onPoll = vi.fn();
689
760
 
@@ -710,7 +781,7 @@ describe('decrypt polling callbacks', () => {
710
781
  const sealed = await tnSealOutputV2({
711
782
  ctHash: 1n,
712
783
  chainId: 1,
713
- permission: {} as any,
784
+ acp: {} as any,
714
785
  thresholdNetworkUrl,
715
786
  onPoll,
716
787
  });
@@ -766,7 +837,7 @@ describe('decrypt polling callbacks', () => {
766
837
  const promise = tnSealOutputV2({
767
838
  ctHash: 1n,
768
839
  chainId: 1,
769
- permission: {} as any,
840
+ acp: {} as any,
770
841
  thresholdNetworkUrl,
771
842
  onPoll,
772
843
  });
@@ -0,0 +1,18 @@
1
+ import { defineChain } from 'viem';
2
+ import { STAGING_RPC_URL } from '@cofhe/test-setup';
3
+
4
+ declare const __STAGING_TESTS__: boolean | undefined;
5
+
6
+ /**
7
+ * When TEST_STAGING_ENABLED=true, the sdk tests that exercise live CoFHE
8
+ * backends run against the staging environment instead of their default
9
+ * chain (the primary test chain / hardcoded testnets). Off: unchanged.
10
+ */
11
+ export const STAGING_TESTS = typeof __STAGING_TESTS__ !== 'undefined' ? __STAGING_TESTS__ : false;
12
+
13
+ export const stagingViemChain = defineChain({
14
+ id: 420105,
15
+ name: 'CoFHE Staging',
16
+ nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
17
+ rpcUrls: { default: { http: [STAGING_RPC_URL] } },
18
+ });