@cofhe/sdk 0.6.1 → 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.
- package/CHANGELOG.md +43 -0
- package/acps/acp.ts +411 -0
- package/acps/index.ts +70 -0
- package/{permits → acps}/onchain-utils.ts +49 -24
- package/acps/sealing.ts +90 -0
- package/acps/signature.ts +89 -0
- package/acps/store.ts +172 -0
- package/acps/test/acp.test.ts +615 -0
- package/acps/test/localstorage.test.ts +105 -0
- package/acps/test/sealing.test.ts +77 -0
- package/acps/test/store.test.ts +88 -0
- package/acps/test/validation.test.ts +361 -0
- package/acps/test-utils.ts +32 -0
- package/acps/types.ts +252 -0
- package/acps/validation.ts +392 -0
- package/adapters/test/ethers5.test.ts +4 -1
- package/adapters/test/ethers6.test.ts +4 -1
- package/adapters/test/wagmi.test.ts +5 -2
- package/chains/chains/stagingCofhe.ts +21 -0
- package/chains/index.ts +3 -1
- package/chains/test/chains.test.ts +2 -1
- package/core/acps.ts +625 -0
- package/core/client.ts +136 -39
- package/core/clientTypes.ts +52 -41
- package/core/config.ts +66 -5
- package/core/decrypt/MockThresholdNetworkAbi.ts +20 -11
- package/core/decrypt/apiError.ts +104 -0
- package/core/decrypt/cofheMocksDecryptForTx.ts +11 -11
- package/core/decrypt/cofheMocksDecryptForView.ts +7 -7
- package/core/decrypt/decryptForTxBuilder.ts +102 -102
- package/core/decrypt/decryptForViewBuilder.ts +90 -90
- package/core/decrypt/submitRetry.ts +38 -30
- package/core/decrypt/tnDecryptV1.ts +5 -5
- package/core/decrypt/tnDecryptV2.ts +25 -21
- package/core/decrypt/tnSealOutputV1.ts +4 -3
- package/core/decrypt/tnSealOutputV2.ts +24 -18
- package/core/encrypt/cofheMocksZkVerifySign.ts +59 -74
- package/core/encrypt/encryptInputsBuilder.ts +86 -52
- package/core/encrypt/zkPackProveVerify.ts +25 -18
- package/core/error.ts +34 -6
- package/core/index.ts +4 -14
- package/core/test/acpDefaults.test.ts +52 -0
- package/core/test/acps.test.ts +596 -0
- package/core/test/apiError.test.ts +130 -0
- package/core/test/client.test.ts +22 -19
- package/core/test/config.test.ts +25 -5
- package/core/test/decrypt.test.ts +40 -35
- package/core/test/decryptBuilders.test.ts +68 -68
- package/core/test/decryptErrorCodes.test.ts +217 -0
- package/core/test/encryptInputsBuilder.test.ts +72 -41
- package/core/test/pollCallbacks.test.ts +89 -18
- package/core/test/stagingRedirect.ts +18 -0
- package/core/test/submitRetry.test.ts +182 -0
- package/core/types.ts +9 -69
- package/dist/acp-Wi6isVQI.d.cts +407 -0
- package/dist/acp-Wi6isVQI.d.ts +407 -0
- package/dist/acps.cjs +1081 -0
- package/dist/acps.d.cts +480 -0
- package/dist/acps.d.ts +480 -0
- package/dist/acps.js +2 -0
- package/dist/chains.cjs +14 -1
- package/dist/chains.d.cts +30 -1
- package/dist/chains.d.ts +30 -1
- package/dist/chains.js +1 -1
- package/dist/{chunk-NOC3PYB7.js → chunk-43USWPEH.js} +930 -580
- package/dist/{chunk-MTRAXQXC.js → chunk-N6IDQRRU.js} +14 -2
- package/dist/chunk-Q7CBWGQX.js +1029 -0
- package/dist/{clientTypes-CyUvRRzA.d.ts → clientTypes-BN3nbzYM.d.ts} +342 -165
- package/dist/{clientTypes-BDy1qIBu.d.cts → clientTypes-CYZjFznO.d.cts} +342 -165
- package/dist/core.cjs +1358 -1002
- package/dist/core.d.cts +31 -9
- package/dist/core.d.ts +31 -9
- package/dist/core.js +3 -3
- package/dist/node.cjs +1293 -952
- package/dist/node.d.cts +2 -2
- package/dist/node.d.ts +2 -2
- package/dist/node.js +3 -3
- package/dist/web.cjs +1293 -952
- package/dist/web.d.cts +2 -2
- package/dist/web.d.ts +2 -2
- package/dist/web.js +3 -3
- package/node/test/inherited.test.ts +75 -65
- package/node/test/tfheinit.test.ts +23 -8
- package/package.json +6 -6
- package/web/test/client.web.test.ts +5 -1
- package/web/test/inherited.web.test.ts +75 -65
- package/web/test/tfheinit.web.test.ts +14 -5
- package/web/test/worker.config.web.test.ts +38 -23
- package/web/test/worker.output.web.test.ts +25 -24
- package/core/encrypt/encryptUtils.ts +0 -67
- package/core/permits.ts +0 -216
- package/core/test/permits.test.ts +0 -596
- package/dist/chunk-VB62WYPL.js +0 -978
- package/dist/permit-DnVMDT5h.d.cts +0 -376
- package/dist/permit-DnVMDT5h.d.ts +0 -376
- package/dist/permits.cjs +0 -1026
- package/dist/permits.d.cts +0 -353
- package/dist/permits.d.ts +0 -353
- package/dist/permits.js +0 -2
- package/permits/index.ts +0 -68
- package/permits/permit.ts +0 -385
- package/permits/sealing.ts +0 -131
- package/permits/signature.ts +0 -79
- package/permits/store.ts +0 -157
- package/permits/test/localstorage.test.ts +0 -113
- package/permits/test/permit.test.ts +0 -557
- package/permits/test/sealing.test.ts +0 -84
- package/permits/test/store.test.ts +0 -88
- package/permits/test/validation.test.ts +0 -361
- package/permits/test-utils.ts +0 -28
- package/permits/types.ts +0 -204
- package/permits/validation.ts +0 -327
- /package/{permits → acps}/utils.ts +0 -0
|
@@ -111,58 +111,58 @@ describe('DecryptForTxBuilder', () => {
|
|
|
111
111
|
});
|
|
112
112
|
});
|
|
113
113
|
|
|
114
|
-
// ---
|
|
114
|
+
// --- withACP / withoutACP selection ---
|
|
115
115
|
|
|
116
|
-
describe('
|
|
117
|
-
it('
|
|
116
|
+
describe('withACP / withoutACP selection', () => {
|
|
117
|
+
it('withACP() should set acp selection', () => {
|
|
118
118
|
const builder = createTxBuilder();
|
|
119
|
-
const selected = builder.
|
|
119
|
+
const selected = builder.withACP();
|
|
120
120
|
expect(selected).toBeDefined();
|
|
121
|
-
expect(selected.
|
|
122
|
-
expect(selected.
|
|
121
|
+
expect(selected.getACP()).toBeUndefined();
|
|
122
|
+
expect(selected.getACPHash()).toBeUndefined();
|
|
123
123
|
});
|
|
124
124
|
|
|
125
|
-
it('
|
|
125
|
+
it('withACP(hash) should store the acp hash', () => {
|
|
126
126
|
const builder = createTxBuilder();
|
|
127
|
-
const selected = builder.
|
|
128
|
-
expect(selected.
|
|
129
|
-
expect(selected.
|
|
127
|
+
const selected = builder.withACP('0xmyacphash');
|
|
128
|
+
expect(selected.getACPHash()).toBe('0xmyacphash');
|
|
129
|
+
expect(selected.getACP()).toBeUndefined();
|
|
130
130
|
});
|
|
131
131
|
|
|
132
|
-
it('
|
|
132
|
+
it('withoutACP() should set acp selection', () => {
|
|
133
133
|
const builder = createTxBuilder();
|
|
134
|
-
const selected = builder.
|
|
134
|
+
const selected = builder.withoutACP();
|
|
135
135
|
expect(selected).toBeDefined();
|
|
136
|
-
expect(selected.
|
|
137
|
-
expect(selected.
|
|
136
|
+
expect(selected.getACP()).toBeUndefined();
|
|
137
|
+
expect(selected.getACPHash()).toBeUndefined();
|
|
138
138
|
});
|
|
139
139
|
|
|
140
|
-
it('should throw when
|
|
140
|
+
it('should throw when withACP() is called twice', () => {
|
|
141
141
|
const builder = createTxBuilder();
|
|
142
|
-
builder.
|
|
142
|
+
builder.withACP();
|
|
143
143
|
|
|
144
|
-
expect(() => (builder as any).
|
|
144
|
+
expect(() => (builder as any).withACP()).toThrow('withACP() can only be selected once');
|
|
145
145
|
});
|
|
146
146
|
|
|
147
|
-
it('should throw when
|
|
147
|
+
it('should throw when withoutACP() is called twice', () => {
|
|
148
148
|
const builder = createTxBuilder();
|
|
149
|
-
builder.
|
|
149
|
+
builder.withoutACP();
|
|
150
150
|
|
|
151
|
-
expect(() => (builder as any).
|
|
151
|
+
expect(() => (builder as any).withoutACP()).toThrow('withoutACP() can only be selected once');
|
|
152
152
|
});
|
|
153
153
|
|
|
154
|
-
it('should throw when
|
|
154
|
+
it('should throw when withACP() is called after withoutACP()', () => {
|
|
155
155
|
const builder = createTxBuilder();
|
|
156
|
-
builder.
|
|
156
|
+
builder.withoutACP();
|
|
157
157
|
|
|
158
|
-
expect(() => (builder as any).
|
|
158
|
+
expect(() => (builder as any).withACP()).toThrow('cannot call withACP() after withoutACP()');
|
|
159
159
|
});
|
|
160
160
|
|
|
161
|
-
it('should throw when
|
|
161
|
+
it('should throw when withoutACP() is called after withACP()', () => {
|
|
162
162
|
const builder = createTxBuilder();
|
|
163
|
-
builder.
|
|
163
|
+
builder.withACP();
|
|
164
164
|
|
|
165
|
-
expect(() => (builder as any).
|
|
165
|
+
expect(() => (builder as any).withoutACP()).toThrow('cannot call withoutACP() after withACP()');
|
|
166
166
|
});
|
|
167
167
|
});
|
|
168
168
|
|
|
@@ -171,16 +171,16 @@ describe('DecryptForTxBuilder', () => {
|
|
|
171
171
|
describe('chaining', () => {
|
|
172
172
|
it('should return the builder from each setter for fluent chaining', () => {
|
|
173
173
|
const builder = createTxBuilder({ chainId: undefined, account: undefined });
|
|
174
|
-
const result = builder.setChainId(TEST_CHAIN_ID).setAccount(account.address).
|
|
174
|
+
const result = builder.setChainId(TEST_CHAIN_ID).setAccount(account.address).withACP();
|
|
175
175
|
|
|
176
176
|
expect(result).toBeDefined();
|
|
177
177
|
expect(result.getChainId()).toBe(TEST_CHAIN_ID);
|
|
178
178
|
expect(result.getAccount()).toBe(account.address);
|
|
179
179
|
});
|
|
180
180
|
|
|
181
|
-
it('should allow setChainId and setAccount after
|
|
181
|
+
it('should allow setChainId and setAccount after withACP', () => {
|
|
182
182
|
const builder = createTxBuilder({ chainId: undefined, account: undefined });
|
|
183
|
-
const selected = builder.
|
|
183
|
+
const selected = builder.withACP();
|
|
184
184
|
selected.setChainId(99);
|
|
185
185
|
selected.setAccount('0xabc');
|
|
186
186
|
|
|
@@ -188,9 +188,9 @@ describe('DecryptForTxBuilder', () => {
|
|
|
188
188
|
expect(selected.getAccount()).toBe('0xabc');
|
|
189
189
|
});
|
|
190
190
|
|
|
191
|
-
it('should allow setChainId and setAccount after
|
|
191
|
+
it('should allow setChainId and setAccount after withoutACP', () => {
|
|
192
192
|
const builder = createTxBuilder({ chainId: undefined, account: undefined });
|
|
193
|
-
const selected = builder.
|
|
193
|
+
const selected = builder.withoutACP();
|
|
194
194
|
selected.setChainId(99);
|
|
195
195
|
selected.setAccount('0xabc');
|
|
196
196
|
|
|
@@ -216,7 +216,7 @@ describe('DecryptForTxBuilder', () => {
|
|
|
216
216
|
// --- execute error paths ---
|
|
217
217
|
|
|
218
218
|
describe('execute – error paths', () => {
|
|
219
|
-
it('should throw when execute() is called without
|
|
219
|
+
it('should throw when execute() is called without acp selection', async () => {
|
|
220
220
|
const builder = createTxBuilder();
|
|
221
221
|
|
|
222
222
|
try {
|
|
@@ -224,31 +224,31 @@ describe('DecryptForTxBuilder', () => {
|
|
|
224
224
|
expect.fail('Expected error');
|
|
225
225
|
} catch (error) {
|
|
226
226
|
expect((error as any).code).toBe(CofheErrorCode.InternalError);
|
|
227
|
-
expect((error as Error).message).toContain('missing
|
|
227
|
+
expect((error as Error).message).toContain('missing acp selection');
|
|
228
228
|
}
|
|
229
229
|
});
|
|
230
230
|
|
|
231
|
-
it('should throw when
|
|
231
|
+
it('should throw when withACP() has no active acp', async () => {
|
|
232
232
|
const builder = createTxBuilder();
|
|
233
233
|
|
|
234
234
|
try {
|
|
235
|
-
await builder.
|
|
236
|
-
expect.fail('Expected
|
|
235
|
+
await builder.withACP().execute();
|
|
236
|
+
expect.fail('Expected ACPNotFound error');
|
|
237
237
|
} catch (error) {
|
|
238
|
-
expect((error as any).code).toBe(CofheErrorCode.
|
|
239
|
-
expect((error as Error).message).toContain('Active
|
|
238
|
+
expect((error as any).code).toBe(CofheErrorCode.ACPNotFound);
|
|
239
|
+
expect((error as Error).message).toContain('Active acp not found');
|
|
240
240
|
}
|
|
241
241
|
});
|
|
242
242
|
|
|
243
|
-
it('should throw when
|
|
243
|
+
it('should throw when withACP(hash) cannot find acp', async () => {
|
|
244
244
|
const builder = createTxBuilder();
|
|
245
245
|
|
|
246
246
|
try {
|
|
247
|
-
await builder.
|
|
248
|
-
expect.fail('Expected
|
|
247
|
+
await builder.withACP('0xnonexistent').execute();
|
|
248
|
+
expect.fail('Expected ACPNotFound error');
|
|
249
249
|
} catch (error) {
|
|
250
|
-
expect((error as any).code).toBe(CofheErrorCode.
|
|
251
|
-
expect((error as Error).message).toContain('
|
|
250
|
+
expect((error as any).code).toBe(CofheErrorCode.ACPNotFound);
|
|
251
|
+
expect((error as Error).message).toContain('ACP with hash');
|
|
252
252
|
}
|
|
253
253
|
});
|
|
254
254
|
});
|
|
@@ -302,30 +302,30 @@ describe('DecryptForViewBuilder', () => {
|
|
|
302
302
|
});
|
|
303
303
|
});
|
|
304
304
|
|
|
305
|
-
// ---
|
|
305
|
+
// --- withACP ---
|
|
306
306
|
|
|
307
|
-
describe('
|
|
308
|
-
it('
|
|
307
|
+
describe('withACP', () => {
|
|
308
|
+
it('withACP() should clear acp and hash', () => {
|
|
309
309
|
const builder = createViewBuilder(FheTypes.Uint32);
|
|
310
|
-
builder.
|
|
311
|
-
expect(builder.
|
|
312
|
-
expect(builder.
|
|
310
|
+
builder.withACP();
|
|
311
|
+
expect(builder.getACP()).toBeUndefined();
|
|
312
|
+
expect(builder.getACPHash()).toBeUndefined();
|
|
313
313
|
});
|
|
314
314
|
|
|
315
|
-
it('
|
|
315
|
+
it('withACP(hash) should store the acp hash', () => {
|
|
316
316
|
const builder = createViewBuilder(FheTypes.Uint32);
|
|
317
|
-
builder.
|
|
318
|
-
expect(builder.
|
|
319
|
-
expect(builder.
|
|
317
|
+
builder.withACP('0xmyacphash');
|
|
318
|
+
expect(builder.getACPHash()).toBe('0xmyacphash');
|
|
319
|
+
expect(builder.getACP()).toBeUndefined();
|
|
320
320
|
});
|
|
321
321
|
|
|
322
|
-
it('should allow overriding
|
|
322
|
+
it('should allow overriding acp selection', () => {
|
|
323
323
|
const builder = createViewBuilder(FheTypes.Uint32);
|
|
324
|
-
builder.
|
|
325
|
-
expect(builder.
|
|
324
|
+
builder.withACP('0xfirst');
|
|
325
|
+
expect(builder.getACPHash()).toBe('0xfirst');
|
|
326
326
|
|
|
327
|
-
builder.
|
|
328
|
-
expect(builder.
|
|
327
|
+
builder.withACP('0xsecond');
|
|
328
|
+
expect(builder.getACPHash()).toBe('0xsecond');
|
|
329
329
|
});
|
|
330
330
|
});
|
|
331
331
|
|
|
@@ -334,7 +334,7 @@ describe('DecryptForViewBuilder', () => {
|
|
|
334
334
|
describe('chaining', () => {
|
|
335
335
|
it('should return the builder from each setter for fluent chaining', () => {
|
|
336
336
|
const builder = createViewBuilder(FheTypes.Uint32, { chainId: undefined, account: undefined });
|
|
337
|
-
const result = builder.setChainId(TEST_CHAIN_ID).setAccount(account.address).
|
|
337
|
+
const result = builder.setChainId(TEST_CHAIN_ID).setAccount(account.address).withACP();
|
|
338
338
|
|
|
339
339
|
expect(result).toBeDefined();
|
|
340
340
|
expect(result.getChainId()).toBe(TEST_CHAIN_ID);
|
|
@@ -359,28 +359,28 @@ describe('DecryptForViewBuilder', () => {
|
|
|
359
359
|
// --- execute error paths ---
|
|
360
360
|
|
|
361
361
|
describe('execute – error paths', () => {
|
|
362
|
-
it('should throw when active
|
|
362
|
+
it('should throw when active acp is not found', async () => {
|
|
363
363
|
const builder = createViewBuilder(FheTypes.Uint32);
|
|
364
364
|
|
|
365
365
|
try {
|
|
366
366
|
await builder.execute();
|
|
367
|
-
expect.fail('Expected
|
|
367
|
+
expect.fail('Expected ACPNotFound error');
|
|
368
368
|
} catch (error) {
|
|
369
|
-
expect((error as any).code).toBe(CofheErrorCode.
|
|
370
|
-
expect((error as Error).message).toContain('Active
|
|
369
|
+
expect((error as any).code).toBe(CofheErrorCode.ACPNotFound);
|
|
370
|
+
expect((error as Error).message).toContain('Active acp not found');
|
|
371
371
|
}
|
|
372
372
|
});
|
|
373
373
|
|
|
374
|
-
it('should throw when
|
|
374
|
+
it('should throw when withACP(hash) cannot find acp', async () => {
|
|
375
375
|
const builder = createViewBuilder(FheTypes.Uint32);
|
|
376
|
-
builder.
|
|
376
|
+
builder.withACP('0xnonexistent');
|
|
377
377
|
|
|
378
378
|
try {
|
|
379
379
|
await builder.execute();
|
|
380
|
-
expect.fail('Expected
|
|
380
|
+
expect.fail('Expected ACPNotFound error');
|
|
381
381
|
} catch (error) {
|
|
382
|
-
expect((error as any).code).toBe(CofheErrorCode.
|
|
383
|
-
expect((error as Error).message).toContain('
|
|
382
|
+
expect((error as any).code).toBe(CofheErrorCode.ACPNotFound);
|
|
383
|
+
expect((error as Error).message).toContain('ACP with hash');
|
|
384
384
|
}
|
|
385
385
|
});
|
|
386
386
|
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { tnDecryptV2 } from '../decrypt/tnDecryptV2.js';
|
|
3
|
+
import { tnSealOutputV2 } from '../decrypt/tnSealOutputV2.js';
|
|
4
|
+
import { CofheErrorCode } from '../error.js';
|
|
5
|
+
|
|
6
|
+
// Cross-reference: API-RESPONSES.md's "Shared errors" table (applies to all decrypt/sealoutput
|
|
7
|
+
// submit and poll endpoints).
|
|
8
|
+
const SHARED_ERROR_CASES: Array<{ status: number; error: string; cofheErrorCode: CofheErrorCode }> = [
|
|
9
|
+
{ status: 400, error: 'bad_request', cofheErrorCode: CofheErrorCode.BadRequest },
|
|
10
|
+
{ status: 400, error: 'unknown_chain', cofheErrorCode: CofheErrorCode.UnknownChain },
|
|
11
|
+
{ status: 400, error: 'acp_malformed', cofheErrorCode: CofheErrorCode.ACPMalformed },
|
|
12
|
+
{ status: 401, error: 'acp_denied', cofheErrorCode: CofheErrorCode.ACPDenied },
|
|
13
|
+
{ status: 401, error: 'acp_expired', cofheErrorCode: CofheErrorCode.ACPExpired },
|
|
14
|
+
{ status: 401, error: 'acp_invalid', cofheErrorCode: CofheErrorCode.ACPInvalid },
|
|
15
|
+
{ status: 403, error: 'not_publicly_allowed', cofheErrorCode: CofheErrorCode.NotPubliclyAllowed },
|
|
16
|
+
{ status: 422, error: 'unsupported_security_zone', cofheErrorCode: CofheErrorCode.UnsupportedSecurityZone },
|
|
17
|
+
{ status: 422, error: 'unsupported_type', cofheErrorCode: CofheErrorCode.UnsupportedType },
|
|
18
|
+
{ status: 500, error: 'internal_error', cofheErrorCode: CofheErrorCode.InternalError },
|
|
19
|
+
{ status: 500, error: 'signing_failed', cofheErrorCode: CofheErrorCode.SigningFailed },
|
|
20
|
+
{ status: 502, error: 'ct_source_error', cofheErrorCode: CofheErrorCode.CtSourceError },
|
|
21
|
+
{ status: 502, error: 'acp_verifier_error', cofheErrorCode: CofheErrorCode.ACPVerifierError },
|
|
22
|
+
{ status: 504, error: 'ct_source_timeout', cofheErrorCode: CofheErrorCode.CtSourceTimeout },
|
|
23
|
+
{ status: 504, error: 'acp_verifier_timeout', cofheErrorCode: CofheErrorCode.ACPVerifierTimeout },
|
|
24
|
+
];
|
|
25
|
+
// ct_not_found (404) is deliberately excluded here: on the submit endpoints it's the
|
|
26
|
+
// conditionally-retryable case (covered by pollCallbacks.test.ts / submitRetry.test.ts), and on
|
|
27
|
+
// the poll endpoints it's documented bodyless (covered separately below).
|
|
28
|
+
|
|
29
|
+
const makeMockResponse = (opts: { ok: boolean; status?: number; statusText?: string; json: () => Promise<any> }) => {
|
|
30
|
+
return {
|
|
31
|
+
ok: opts.ok,
|
|
32
|
+
status: opts.status ?? (opts.ok ? 200 : 500),
|
|
33
|
+
statusText: opts.statusText ?? '',
|
|
34
|
+
json: opts.json,
|
|
35
|
+
} as unknown as Response;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
describe('decrypt/sealoutput API-RESPONSES.md error-code coverage', () => {
|
|
39
|
+
const thresholdNetworkUrl = 'http://threshold.local';
|
|
40
|
+
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
vi.useFakeTimers();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
afterEach(() => {
|
|
46
|
+
vi.useRealTimers();
|
|
47
|
+
vi.restoreAllMocks();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe.each([
|
|
51
|
+
{ operation: 'decrypt' as const, path: '/v2/decrypt', run: tnDecryptV2, fallback: CofheErrorCode.DecryptFailed },
|
|
52
|
+
{
|
|
53
|
+
operation: 'sealoutput' as const,
|
|
54
|
+
path: '/v2/sealoutput',
|
|
55
|
+
run: tnSealOutputV2,
|
|
56
|
+
fallback: CofheErrorCode.SealOutputFailed,
|
|
57
|
+
},
|
|
58
|
+
])('$operation submit ($path)', ({ path, run, fallback }) => {
|
|
59
|
+
it.each(SHARED_ERROR_CASES)('maps $status $error to the dedicated CofheErrorCode', async ({ status, error }) => {
|
|
60
|
+
const fetchMock = vi.fn(async (url: string) => {
|
|
61
|
+
if (url === `${thresholdNetworkUrl}${path}`) {
|
|
62
|
+
return makeMockResponse({
|
|
63
|
+
ok: false,
|
|
64
|
+
status,
|
|
65
|
+
json: async () => ({ error, error_message: `backend said ${error}` }),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
throw new Error(`Unexpected fetch: ${url}`);
|
|
69
|
+
});
|
|
70
|
+
global.fetch = fetchMock as any;
|
|
71
|
+
|
|
72
|
+
const expectedCode = SHARED_ERROR_CASES.find((c) => c.error === error)!.cofheErrorCode;
|
|
73
|
+
|
|
74
|
+
await expect(run({ ctHash: 1n, chainId: 1, acp: {} as any, thresholdNetworkUrl } as any)).rejects.toMatchObject({
|
|
75
|
+
code: expectedCode,
|
|
76
|
+
apiErrorCode: error,
|
|
77
|
+
message: expect.stringContaining(`backend said ${error}`),
|
|
78
|
+
});
|
|
79
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('falls back to the operation-level generic code for an unrecognized future error code', async () => {
|
|
83
|
+
const fetchMock = vi.fn(async () =>
|
|
84
|
+
makeMockResponse({
|
|
85
|
+
ok: false,
|
|
86
|
+
status: 400,
|
|
87
|
+
json: async () => ({ error: 'some_future_code', error_message: 'not yet known to the SDK' }),
|
|
88
|
+
})
|
|
89
|
+
);
|
|
90
|
+
global.fetch = fetchMock as any;
|
|
91
|
+
|
|
92
|
+
await expect(run({ ctHash: 1n, chainId: 1, acp: {} as any, thresholdNetworkUrl } as any)).rejects.toMatchObject({
|
|
93
|
+
code: fallback,
|
|
94
|
+
apiErrorCode: 'some_future_code',
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('degrades gracefully on a plain-text framework rejection (e.g. 415)', async () => {
|
|
99
|
+
const fetchMock = vi.fn(async () =>
|
|
100
|
+
makeMockResponse({
|
|
101
|
+
ok: false,
|
|
102
|
+
status: 415,
|
|
103
|
+
statusText: 'Unsupported Media Type',
|
|
104
|
+
json: async () => {
|
|
105
|
+
throw new Error('body is plain text, not JSON');
|
|
106
|
+
},
|
|
107
|
+
})
|
|
108
|
+
);
|
|
109
|
+
global.fetch = fetchMock as any;
|
|
110
|
+
|
|
111
|
+
await expect(run({ ctHash: 1n, chainId: 1, acp: {} as any, thresholdNetworkUrl } as any)).rejects.toMatchObject({
|
|
112
|
+
code: fallback,
|
|
113
|
+
apiErrorCode: undefined,
|
|
114
|
+
message: expect.stringContaining('Unsupported Media Type'),
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('sealoutput submit maps 400 acp_required (sealoutput-specific)', async () => {
|
|
120
|
+
global.fetch = vi.fn(async () =>
|
|
121
|
+
makeMockResponse({
|
|
122
|
+
ok: false,
|
|
123
|
+
status: 400,
|
|
124
|
+
json: async () => ({ error: 'acp_required', error_message: 'no acp in request' }),
|
|
125
|
+
})
|
|
126
|
+
) as any;
|
|
127
|
+
|
|
128
|
+
await expect(tnSealOutputV2({ ctHash: 1n, chainId: 1, acp: {} as any, thresholdNetworkUrl })).rejects.toMatchObject(
|
|
129
|
+
{
|
|
130
|
+
code: CofheErrorCode.ACPRequired,
|
|
131
|
+
apiErrorCode: 'acp_required',
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('sealoutput submit maps 500 seal_failed (sealoutput-specific)', async () => {
|
|
137
|
+
global.fetch = vi.fn(async () =>
|
|
138
|
+
makeMockResponse({
|
|
139
|
+
ok: false,
|
|
140
|
+
status: 500,
|
|
141
|
+
json: async () => ({ error: 'seal_failed', error_message: 'sealing crypto failed' }),
|
|
142
|
+
})
|
|
143
|
+
) as any;
|
|
144
|
+
|
|
145
|
+
await expect(tnSealOutputV2({ ctHash: 1n, chainId: 1, acp: {} as any, thresholdNetworkUrl })).rejects.toMatchObject(
|
|
146
|
+
{
|
|
147
|
+
code: CofheErrorCode.SealFailed,
|
|
148
|
+
apiErrorCode: 'seal_failed',
|
|
149
|
+
}
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe.each([
|
|
154
|
+
{
|
|
155
|
+
operation: 'decrypt' as const,
|
|
156
|
+
submitPath: '/v2/decrypt',
|
|
157
|
+
statusPath: (id: string) => `/v2/decrypt/${id}`,
|
|
158
|
+
run: tnDecryptV2,
|
|
159
|
+
fallback: CofheErrorCode.DecryptFailed,
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
operation: 'sealoutput' as const,
|
|
163
|
+
submitPath: '/v2/sealoutput',
|
|
164
|
+
statusPath: (id: string) => `/v2/sealoutput/${id}`,
|
|
165
|
+
run: tnSealOutputV2,
|
|
166
|
+
fallback: CofheErrorCode.SealOutputFailed,
|
|
167
|
+
},
|
|
168
|
+
])('$operation poll (GET status endpoint)', ({ submitPath, statusPath, run, fallback }) => {
|
|
169
|
+
const requestId = 'req-poll-errors';
|
|
170
|
+
|
|
171
|
+
it('maps a non-404 non-ok status via the shared error table (e.g. 500 internal_error)', async () => {
|
|
172
|
+
const fetchMock = vi.fn(async (url: string) => {
|
|
173
|
+
if (url === `${thresholdNetworkUrl}${submitPath}`) {
|
|
174
|
+
return makeMockResponse({ ok: true, json: async () => ({ request_id: requestId }) });
|
|
175
|
+
}
|
|
176
|
+
if (url === `${thresholdNetworkUrl}${statusPath(requestId)}`) {
|
|
177
|
+
return makeMockResponse({
|
|
178
|
+
ok: false,
|
|
179
|
+
status: 500,
|
|
180
|
+
json: async () => ({ error: 'internal_error', error_message: 'decrypt task panicked' }),
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
throw new Error(`Unexpected fetch: ${url}`);
|
|
184
|
+
});
|
|
185
|
+
global.fetch = fetchMock as any;
|
|
186
|
+
|
|
187
|
+
const promise = run({ ctHash: 1n, chainId: 1, acp: {} as any, thresholdNetworkUrl } as any);
|
|
188
|
+
|
|
189
|
+
await expect(promise).rejects.toMatchObject({
|
|
190
|
+
code: CofheErrorCode.InternalError,
|
|
191
|
+
apiErrorCode: 'internal_error',
|
|
192
|
+
message: expect.stringContaining('decrypt task panicked'),
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('throws immediately on a bodyless 404 (unknown/evicted request_id) without calling response.json()', async () => {
|
|
197
|
+
const statusJson = vi.fn(async () => {
|
|
198
|
+
throw new Error('json() should not be called for a bodyless 404');
|
|
199
|
+
});
|
|
200
|
+
const fetchMock = vi.fn(async (url: string) => {
|
|
201
|
+
if (url === `${thresholdNetworkUrl}${submitPath}`) {
|
|
202
|
+
return makeMockResponse({ ok: true, json: async () => ({ request_id: requestId }) });
|
|
203
|
+
}
|
|
204
|
+
if (url === `${thresholdNetworkUrl}${statusPath(requestId)}`) {
|
|
205
|
+
return makeMockResponse({ ok: false, status: 404, json: statusJson });
|
|
206
|
+
}
|
|
207
|
+
throw new Error(`Unexpected fetch: ${url}`);
|
|
208
|
+
});
|
|
209
|
+
global.fetch = fetchMock as any;
|
|
210
|
+
|
|
211
|
+
const promise = run({ ctHash: 1n, chainId: 1, acp: {} as any, thresholdNetworkUrl } as any);
|
|
212
|
+
|
|
213
|
+
await expect(promise).rejects.toMatchObject({ code: fallback });
|
|
214
|
+
expect(statusJson).not.toHaveBeenCalled();
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
});
|