@zama-fhe/relayer-sdk 0.3.0-7 → 0.4.0-alpha.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/lib/node.js CHANGED
@@ -1,43 +1,11 @@
1
1
  import * as TFHEPkg from 'node-tfhe';
2
2
  import { ShortintParameters, ShortintParametersName, ShortintCompactPublicKeyEncryptionParameters, ShortintCompactPublicKeyEncryptionParametersName, TfheConfigBuilder, TfheClientKey, TfheCompactPublicKey, CompactPkeCrs } from 'node-tfhe';
3
3
  import * as TKMSPkg from 'node-tkms';
4
- import { JsonRpcProvider, BrowserProvider, Contract, getAddress as getAddress$2, ethers, isAddress, AbiCoder } from 'ethers';
5
- import createHash from 'keccak';
6
- import fetchRetry from 'fetch-retry';
4
+ import { JsonRpcProvider, BrowserProvider, Contract, getAddress as getAddress$2, ethers, isAddress as isAddress$1, keccak256, AbiCoder } from 'ethers';
7
5
 
8
6
  const SERIALIZED_SIZE_LIMIT_CIPHERTEXT = BigInt(1024 * 1024 * 512);
9
7
  const SERIALIZED_SIZE_LIMIT_PK = BigInt(1024 * 1024 * 512);
10
8
  const SERIALIZED_SIZE_LIMIT_CRS = BigInt(1024 * 1024 * 512);
11
- const cleanURL = (url) => {
12
- if (!url)
13
- return '';
14
- return url.endsWith('/') ? url.slice(0, -1) : url;
15
- };
16
- const numberToHex = (num) => {
17
- let hex = num.toString(16);
18
- return hex.length % 2 ? '0' + hex : hex;
19
- };
20
- const fromHexString = (hexString) => {
21
- const arr = hexString.replace(/^(0x)/, '').match(/.{1,2}/g);
22
- if (!arr)
23
- return new Uint8Array();
24
- return Uint8Array.from(arr.map((byte) => parseInt(byte, 16)));
25
- };
26
- function toHexString(bytes, with0x = false) {
27
- return `${with0x ? '0x' : ''}${bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), '')}`;
28
- }
29
- const bytesToBigInt = function (byteArray) {
30
- if (!byteArray || byteArray?.length === 0) {
31
- return BigInt(0);
32
- }
33
- const hex = Array.from(byteArray)
34
- .map((b) => b.toString(16).padStart(2, '0')) // byte to hex
35
- .join('');
36
- return BigInt(`0x${hex}`);
37
- };
38
- function ensure0x(s) {
39
- return !s.startsWith('0x') ? `0x${s}` : s;
40
- }
41
9
 
42
10
  function setAuth(init, auth) {
43
11
  if (auth) {
@@ -118,18 +86,27 @@ async function throwRelayerResponseError(operation, response) {
118
86
  }
119
87
  }
120
88
  }
89
+ let responseJson;
90
+ try {
91
+ responseJson = await response.json();
92
+ }
93
+ catch {
94
+ responseJson = '';
95
+ }
121
96
  const cause = {
122
97
  code: 'RELAYER_FETCH_ERROR',
123
98
  operation,
124
99
  status: response.status,
125
100
  statusText: response.statusText,
126
101
  url: response.url,
102
+ response,
103
+ responseJson,
127
104
  };
128
105
  throw new Error(message, {
129
106
  cause,
130
107
  });
131
108
  }
132
- function throwRelayerJSONError(operation, error) {
109
+ function throwRelayerJSONError(operation, error, response) {
133
110
  let message;
134
111
  switch (operation) {
135
112
  case 'PUBLIC_DECRYPT': {
@@ -149,6 +126,7 @@ function throwRelayerJSONError(operation, error) {
149
126
  code: 'RELAYER_NO_JSON_ERROR',
150
127
  operation,
151
128
  error,
129
+ response,
152
130
  };
153
131
  throw new Error(message, {
154
132
  cause,
@@ -235,7 +213,8 @@ function throwRelayerUnknownError(operation, error, message) {
235
213
  });
236
214
  }
237
215
 
238
- function assertIsRelayerFetchResponseJson(json) {
216
+ ////////////////////////////////////////////////////////////////////////////////
217
+ function assertIsRelayerV1FetchResponseJson(json) {
239
218
  if (!json || typeof json !== 'object') {
240
219
  throw new Error('Unexpected response JSON.');
241
220
  }
@@ -269,10 +248,10 @@ async function fetchRelayerJsonRpcPost(relayerOperation, url, payload, options)
269
248
  parsed = await response.json();
270
249
  }
271
250
  catch (e) {
272
- throwRelayerJSONError(relayerOperation, e);
251
+ throwRelayerJSONError(relayerOperation, e, response);
273
252
  }
274
253
  try {
275
- assertIsRelayerFetchResponseJson(parsed);
254
+ assertIsRelayerV1FetchResponseJson(parsed);
276
255
  json = parsed;
277
256
  }
278
257
  catch (e) {
@@ -287,6 +266,7 @@ async function fetchRelayerGet(relayerOperation, url) {
287
266
  response = await fetch(url);
288
267
  }
289
268
  catch (e) {
269
+ console.log(e);
290
270
  throwRelayerUnknownError(relayerOperation, e);
291
271
  }
292
272
  if (!response.ok) {
@@ -297,10 +277,10 @@ async function fetchRelayerGet(relayerOperation, url) {
297
277
  parsed = await response.json();
298
278
  }
299
279
  catch (e) {
300
- throwRelayerJSONError(relayerOperation, e);
280
+ throwRelayerJSONError(relayerOperation, e, response);
301
281
  }
302
282
  try {
303
- assertIsRelayerFetchResponseJson(parsed);
283
+ assertIsRelayerV1FetchResponseJson(parsed);
304
284
  json = parsed;
305
285
  }
306
286
  catch (e) {
@@ -309,49 +289,13 @@ async function fetchRelayerGet(relayerOperation, url) {
309
289
  return json;
310
290
  }
311
291
 
312
- // export type RelayerKeysItem = {
313
- // data_id: string;
314
- // param_choice: number;
315
- // urls: string[];
316
- // signatures: string[];
317
- // };
318
- // export type RelayerKey = {
319
- // data_id: string;
320
- // param_choice: number;
321
- // signatures: string[];
322
- // urls: string[];
323
- // };
324
- // export type RelayerKeys = {
325
- // response: {
326
- // fhe_key_info: {
327
- // fhe_public_key: RelayerKey;
328
- // fhe_server_key: RelayerKey;
329
- // }[];
330
- // verf_public_key: {
331
- // key_id: string;
332
- // server_id: number;
333
- // verf_public_key_address: string;
334
- // verf_public_key_url: string;
335
- // }[];
336
- // crs: {
337
- // [key: string]: RelayerKeysItem;
338
- // };
339
- // };
340
- // status: string;
341
- // };
342
292
  const keyurlCache = {};
343
- const getKeysFromRelayer = async (url, publicKeyId) => {
344
- if (keyurlCache[url]) {
345
- return keyurlCache[url];
293
+ const getKeysFromRelayer = async (versionUrl, publicKeyId) => {
294
+ if (keyurlCache[versionUrl]) {
295
+ return keyurlCache[versionUrl];
346
296
  }
347
- const data = await fetchRelayerGet('KEY_URL', `${url}/v1/keyurl`);
297
+ const data = await fetchRelayerGet('KEY_URL', `${versionUrl}/keyurl`);
348
298
  try {
349
- // const response = await fetch(`${url}/v1/keyurl`);
350
- // if (!response.ok) {
351
- // await throwRelayerResponseError("KEY_URL", response);
352
- // }
353
- //const data: RelayerKeys = await response.json();
354
- //if (data) {
355
299
  let pubKeyUrl;
356
300
  // If no publicKeyId is provided, use the first one
357
301
  // Warning: if there are multiple keys available, the first one will most likely never be the
@@ -423,11 +367,8 @@ const getKeysFromRelayer = async (url, publicKeyId) => {
423
367
  },
424
368
  },
425
369
  };
426
- keyurlCache[url] = result;
370
+ keyurlCache[versionUrl] = result;
427
371
  return result;
428
- // } else {
429
- // throw new Error('No public key available');
430
- // }
431
372
  }
432
373
  catch (e) {
433
374
  throw new Error('Impossible to fetch public key: wrong relayer url.', {
@@ -436,6 +377,422 @@ const getKeysFromRelayer = async (url, publicKeyId) => {
436
377
  }
437
378
  };
438
379
 
380
+ class RelayerErrorBase extends Error {
381
+ name = 'RelayerErrorBase';
382
+ _details;
383
+ _docsPath;
384
+ _docsUrl;
385
+ _version;
386
+ static VERSION = '0.3.0-6';
387
+ static DEFAULT_DOCS_BASE_URL = 'https//docs.zama.org';
388
+ static FULL_VERSION = `@zama-fhe/relayer-sdk@${RelayerErrorBase.VERSION}`;
389
+ constructor(params) {
390
+ let details;
391
+ let docsPath;
392
+ if (params.cause instanceof RelayerErrorBase) {
393
+ docsPath = params.docsPath || params.cause.docsPath;
394
+ details = params.details || params.cause.details;
395
+ }
396
+ else {
397
+ docsPath = params.docsPath;
398
+ details = params.details || params.cause?.message;
399
+ }
400
+ const docsUrl = docsPath
401
+ ? `${params.docsBaseUrl ?? RelayerErrorBase.DEFAULT_DOCS_BASE_URL}${docsPath}${params.docsSlug ? `#${params.docsSlug}` : ''}`
402
+ : undefined;
403
+ const message = [
404
+ params.message || 'An error occurred.',
405
+ '',
406
+ ...(params.metaMessages ? [...params.metaMessages, ''] : []),
407
+ ...(docsUrl ? [`Docs: ${docsUrl}`] : []),
408
+ ...(details ? [`Details: ${details}`] : []),
409
+ `Version: ${RelayerErrorBase.FULL_VERSION}`,
410
+ ].join('\n');
411
+ super(message, params.cause ? { cause: params.cause } : undefined);
412
+ // This line is critical. If removed 'instanceof' will always fail
413
+ // Restore prototype chain (required when extending Error in TypeScript)
414
+ Object.setPrototypeOf(this, new.target.prototype);
415
+ this._details = details;
416
+ this._docsPath = docsPath;
417
+ this._docsUrl = docsUrl;
418
+ this._version = RelayerErrorBase.VERSION;
419
+ this.name = params.name ?? this.name;
420
+ }
421
+ get docsPath() {
422
+ return this._docsPath;
423
+ }
424
+ get docsUrl() {
425
+ return this._docsUrl;
426
+ }
427
+ get details() {
428
+ return this._details;
429
+ }
430
+ get version() {
431
+ return this._version;
432
+ }
433
+ }
434
+
435
+ class InvalidPropertyError extends RelayerErrorBase {
436
+ _objName;
437
+ _property;
438
+ _expectedType;
439
+ _index;
440
+ _value;
441
+ _type;
442
+ _expectedValue;
443
+ constructor({ objName, property, index, type, value, expectedValue, expectedType, }) {
444
+ let missing = type === 'undefined' && expectedValue !== undefined;
445
+ let varname;
446
+ if (!property || property === '') {
447
+ varname = index !== undefined ? `${objName}[${index}]` : `${objName}`;
448
+ }
449
+ else {
450
+ varname =
451
+ index !== undefined
452
+ ? `${objName}.${property}[${index}]`
453
+ : `${objName}.${property}`;
454
+ }
455
+ let message = missing
456
+ ? `InvalidPropertyError: Missing '${varname}'`
457
+ : `InvalidPropertyError: ${varname}`;
458
+ if (type === expectedType) {
459
+ if (value !== undefined) {
460
+ message += ` unexpected value ${value}`;
461
+ }
462
+ }
463
+ else {
464
+ if (missing) {
465
+ if (Array.isArray(expectedValue)) {
466
+ expectedValue = expectedValue.join('|');
467
+ }
468
+ message += `, expected '${varname}: ${expectedValue}'.`;
469
+ }
470
+ else if (expectedType !== 'unknown' && type !== 'unknown') {
471
+ message += ` not a ${expectedType}`;
472
+ if (type) {
473
+ message += `, type is ${type}`;
474
+ }
475
+ }
476
+ }
477
+ super({
478
+ message,
479
+ name: 'InvalidPropertyError',
480
+ });
481
+ this._objName = objName;
482
+ this._property = property;
483
+ this._value = value;
484
+ this._type = type;
485
+ this._expectedValue = expectedValue;
486
+ this._expectedType = expectedType;
487
+ this._index = index;
488
+ }
489
+ static missingProperty({ objName, property, expectedType, expectedValue, }) {
490
+ return new InvalidPropertyError({
491
+ objName,
492
+ property,
493
+ expectedType,
494
+ expectedValue,
495
+ type: 'undefined',
496
+ });
497
+ }
498
+ static invalidFormat({ objName, property, }) {
499
+ return new InvalidPropertyError({
500
+ objName,
501
+ property,
502
+ expectedType: 'unknown',
503
+ });
504
+ }
505
+ static invalidObject({ objName, expectedType, type, }) {
506
+ return new InvalidPropertyError({
507
+ objName,
508
+ property: '',
509
+ expectedType,
510
+ type,
511
+ });
512
+ }
513
+ }
514
+
515
+ /**
516
+ * Type guard that checks if a property exists on an object and is non-null/non-undefined.
517
+ *
518
+ * @template K - The property key type (string literal)
519
+ * @param o - The value to check (can be any type)
520
+ * @param property - The property name to check for
521
+ * @returns True if `o` is an object with the specified property that is not null or undefined
522
+ *
523
+ * @example
524
+ * ```typescript
525
+ * const data: unknown = { name: "Alice", age: 30 };
526
+ * if (isNonNullableRecordProperty(data, 'name')) {
527
+ * console.log(data.name); // OK
528
+ * }
529
+ * ```
530
+ */
531
+ function isNonNullableRecordProperty(o, property) {
532
+ if (!o ||
533
+ typeof o !== 'object' ||
534
+ !(property in o) ||
535
+ o[property] === undefined ||
536
+ o[property] === null) {
537
+ return false;
538
+ }
539
+ return true;
540
+ }
541
+ /**
542
+ * Assertion function that validates a property exists on an object and is non-null/non-undefined.
543
+ * Throws an `InvalidPropertyError` if validation fails.
544
+ *
545
+ * @template K - The property key type (string literal)
546
+ * @param o - The value to validate (can be any type)
547
+ * @param property - The property name to check for
548
+ * @param objName - The name of the object being validated (used in error messages)
549
+ * @throws {InvalidPropertyError} When the property is missing, null, or undefined
550
+ * @throws {never} No other errors are thrown
551
+ *
552
+ * @example
553
+ * ```typescript
554
+ * function processUser(data: unknown) {
555
+ * assertNonNullableRecordProperty(data, 'userId', 'user');
556
+ * console.log(data.userId);
557
+ * }
558
+ * ```
559
+ */
560
+ function assertNonNullableRecordProperty(o, property, objName) {
561
+ if (!isNonNullableRecordProperty(o, property)) {
562
+ throw new InvalidPropertyError({
563
+ objName,
564
+ property,
565
+ expectedType: 'non-nullable',
566
+ type: typeofProperty(o, property),
567
+ });
568
+ }
569
+ }
570
+ /**
571
+ * Type guard that checks if a property exists on an object and is an array.
572
+ *
573
+ * @template K - The property key type (string literal)
574
+ * @param o - The value to check (can be any type)
575
+ * @param property - The property name to check for
576
+ * @returns True if `o` is an object with the specified property that is a non-null array
577
+ *
578
+ * @example
579
+ * ```typescript
580
+ * const data: unknown = { items: [1, 2, 3], count: 42 };
581
+ * if (isRecordArrayProperty(data, 'items')) {
582
+ * console.log(data.items.length); // OK
583
+ * data.items.forEach(item => console.log(item)); // OK
584
+ * }
585
+ * ```
586
+ */
587
+ function isRecordArrayProperty(o, property) {
588
+ if (!isNonNullableRecordProperty(o, property)) {
589
+ return false;
590
+ }
591
+ return Array.isArray(o[property]);
592
+ }
593
+ /**
594
+ * Assertion function that validates a property exists on an object and is an array.
595
+ * Throws an `InvalidPropertyError` if validation fails.
596
+ *
597
+ * @template K - The property key type (string literal)
598
+ * @param o - The value to validate (can be any type)
599
+ * @param property - The property name to check for
600
+ * @param objName - The name of the object being validated (used in error messages)
601
+ * @throws {InvalidPropertyError} When the property is missing, null, or not an array
602
+ * @throws {never} No other errors are thrown
603
+ *
604
+ * @example
605
+ * ```typescript
606
+ * function processResults(data: unknown) {
607
+ * assertRecordArrayProperty(data, 'results', 'response');
608
+ * console.log(`Found ${data.results.length} results`);
609
+ * data.results.forEach(result => processResult(result));
610
+ * }
611
+ * ```
612
+ */
613
+ function assertRecordArrayProperty(o, property, objName) {
614
+ if (!isRecordArrayProperty(o, property)) {
615
+ throw new InvalidPropertyError({
616
+ objName,
617
+ property,
618
+ expectedType: 'Array',
619
+ type: typeofProperty(o, property),
620
+ });
621
+ }
622
+ }
623
+ function isRecordBooleanProperty(o, property) {
624
+ if (!isNonNullableRecordProperty(o, property)) {
625
+ return false;
626
+ }
627
+ return typeof o[property] === 'boolean';
628
+ }
629
+ function assertRecordBooleanProperty(o, property, objName, expectedValue) {
630
+ if (!isRecordBooleanProperty(o, property))
631
+ throw new InvalidPropertyError({
632
+ objName,
633
+ property,
634
+ expectedType: 'boolean',
635
+ type: typeofProperty(o, property),
636
+ });
637
+ if (expectedValue !== undefined) {
638
+ if (o[property] !== expectedValue) {
639
+ throw new InvalidPropertyError({
640
+ objName,
641
+ property,
642
+ expectedType: 'boolean',
643
+ expectedValue: String(expectedValue),
644
+ type: typeof o[property],
645
+ value: String(o[property]),
646
+ });
647
+ }
648
+ }
649
+ }
650
+ function typeofProperty(o, property) {
651
+ if (isNonNullableRecordProperty(o, property)) {
652
+ return typeof o[property];
653
+ }
654
+ return 'undefined';
655
+ }
656
+
657
+ class InternalError extends RelayerErrorBase {
658
+ constructor(params) {
659
+ super({
660
+ ...params,
661
+ name: 'InternalError',
662
+ message: params.message ?? 'internal error',
663
+ });
664
+ }
665
+ }
666
+ function assertRelayer(condition) {
667
+ if (!condition) {
668
+ throw new InternalError({ message: 'Assertion failed' });
669
+ }
670
+ }
671
+
672
+ function removeSuffix(s, suffix) {
673
+ if (!s) {
674
+ return '';
675
+ }
676
+ if (suffix.length === 0) {
677
+ return s;
678
+ }
679
+ return s.endsWith(suffix) ? s.slice(0, -suffix.length) : s;
680
+ }
681
+ function is0x(s) {
682
+ return typeof s === 'string' && s.startsWith('0x');
683
+ }
684
+ function isNo0x(s) {
685
+ return typeof s === 'string' && !s.startsWith('0x');
686
+ }
687
+ function ensure0x(s) {
688
+ return !s.startsWith('0x') ? `0x${s}` : s;
689
+ }
690
+ function remove0x(s) {
691
+ return s.startsWith('0x') ? s.substring(2) : s;
692
+ }
693
+ /**
694
+ * Type guard that checks if a property exists on an object and is a string.
695
+ *
696
+ * @template K - The property key type (string literal)
697
+ * @param o - The value to check (can be any type)
698
+ * @param property - The property name to check for
699
+ * @returns True if `o` is an object with the specified property that is a non-null string
700
+ *
701
+ * @example
702
+ * ```typescript
703
+ * const data: unknown = { status: "active", count: 42 };
704
+ * if (isRecordStringProperty(data, 'status')) {
705
+ * console.log(data.status.toUpperCase()); // OK
706
+ * }
707
+ * ```
708
+ */
709
+ function isRecordStringProperty(o, property) {
710
+ if (!isNonNullableRecordProperty(o, property)) {
711
+ return false;
712
+ }
713
+ return typeof o[property] === 'string';
714
+ }
715
+ /**
716
+ * Assertion function that validates a property exists on an object, is a string,
717
+ * and optionally matches specific expected value(s).
718
+ * Throws an `InvalidPropertyError` if validation fails.
719
+ *
720
+ * @template K - The property key type (string literal)
721
+ * @param o - The value to validate (can be any type)
722
+ * @param property - The property name to check for
723
+ * @param objName - The name of the object being validated (used in error messages)
724
+ * @param expectedValue - Optional specific string value or array of allowed values to match against
725
+ * @throws {InvalidPropertyError} When the property is missing, not a string, or doesn't match expectedValue
726
+ * @throws {never} No other errors are thrown
727
+ *
728
+ * @example
729
+ * ```typescript
730
+ * // Check property is a string (any value)
731
+ * assertRecordStringProperty(data, 'name', 'user');
732
+ *
733
+ * // Check property equals a specific value
734
+ * assertRecordStringProperty(data, 'status', 'response', 'active');
735
+ *
736
+ * // Check property is one of multiple allowed values
737
+ * assertRecordStringProperty(data, 'status', 'response', ['queued', 'processing', 'completed']);
738
+ * ```
739
+ */
740
+ function assertRecordStringProperty(o, property, objName, expectedValue) {
741
+ if (!isRecordStringProperty(o, property)) {
742
+ throw new InvalidPropertyError({
743
+ objName,
744
+ property,
745
+ expectedType: 'string',
746
+ expectedValue,
747
+ type: typeofProperty(o, property),
748
+ });
749
+ }
750
+ if (expectedValue !== undefined) {
751
+ if (Array.isArray(expectedValue)) {
752
+ // Check if value matches any of the allowed values
753
+ for (let i = 0; i < expectedValue.length; ++i) {
754
+ if (o[property] === expectedValue[i]) {
755
+ return;
756
+ }
757
+ }
758
+ throw new InvalidPropertyError({
759
+ objName,
760
+ property,
761
+ expectedType: 'string',
762
+ expectedValue,
763
+ type: typeof o[property], // === "string"
764
+ value: o[property],
765
+ });
766
+ }
767
+ else {
768
+ if (o[property] !== expectedValue) {
769
+ throw new InvalidPropertyError({
770
+ objName,
771
+ property,
772
+ expectedType: 'string',
773
+ expectedValue,
774
+ type: typeof o[property], // === "string"
775
+ value: o[property],
776
+ });
777
+ }
778
+ }
779
+ }
780
+ }
781
+ function assertRecordStringArrayProperty(o, property, objName) {
782
+ assertRecordArrayProperty(o, property, objName);
783
+ const arr = o[property];
784
+ for (let i = 0; i < arr.length; ++i) {
785
+ if (typeof arr[i] !== 'string') {
786
+ throw new InvalidPropertyError({
787
+ objName,
788
+ property: `${property}[${i}]`,
789
+ expectedType: 'string',
790
+ type: typeof arr[i],
791
+ });
792
+ }
793
+ }
794
+ }
795
+
439
796
  const abiKmsVerifier = [
440
797
  'function getKmsSigners() view returns (address[])',
441
798
  'function getThreshold() view returns (uint256)',
@@ -444,12 +801,12 @@ const abiInputVerifier = [
444
801
  'function getCoprocessorSigners() view returns (address[])',
445
802
  'function getThreshold() view returns (uint256)',
446
803
  ];
447
- const getProvider = (config) => {
448
- if (typeof config.network === 'string') {
449
- return new JsonRpcProvider(config.network);
804
+ const getProvider = (network) => {
805
+ if (typeof network === 'string') {
806
+ return new JsonRpcProvider(network);
450
807
  }
451
- else if (config.network) {
452
- return new BrowserProvider(config.network);
808
+ else if (network) {
809
+ return new BrowserProvider(network);
453
810
  }
454
811
  throw new Error('You must provide a network URL or a EIP1193 object (eg: window.ethereum)');
455
812
  };
@@ -466,8 +823,8 @@ const getChainId = async (provider, config) => {
466
823
  }
467
824
  };
468
825
  const getTfheCompactPublicKey = async (config) => {
469
- if (config.relayerUrl && !config.publicKey) {
470
- const inputs = await getKeysFromRelayer(cleanURL(config.relayerUrl));
826
+ if (config.relayerVersionUrl && !config.publicKey) {
827
+ const inputs = await getKeysFromRelayer(removeSuffix(config.relayerVersionUrl, '/'));
471
828
  return { publicKey: inputs.publicKey, publicKeyId: inputs.publicKeyId };
472
829
  }
473
830
  else if (config.publicKey && config.publicKey.data && config.publicKey.id) {
@@ -489,8 +846,8 @@ const getTfheCompactPublicKey = async (config) => {
489
846
  }
490
847
  };
491
848
  const getPublicParams = async (config) => {
492
- if (config.relayerUrl && !config.publicParams) {
493
- const inputs = await getKeysFromRelayer(cleanURL(config.relayerUrl));
849
+ if (config.relayerVersionUrl && !config.publicParams) {
850
+ const inputs = await getKeysFromRelayer(removeSuffix(config.relayerVersionUrl, '/'));
494
851
  return inputs.publicParams;
495
852
  }
496
853
  else if (config.publicParams && config.publicParams['2048']) {
@@ -513,23 +870,23 @@ const getPublicParams = async (config) => {
513
870
  throw new Error('You must provide a valid CRS with its CRS ID.');
514
871
  }
515
872
  };
516
- const getKMSSigners = async (provider, config) => {
517
- const kmsContract = new Contract(config.kmsContractAddress, abiKmsVerifier, provider);
873
+ const getKMSSigners = async (provider, kmsContractAddress) => {
874
+ const kmsContract = new Contract(kmsContractAddress, abiKmsVerifier, provider);
518
875
  const signers = await kmsContract.getKmsSigners();
519
876
  return signers;
520
877
  };
521
- const getKMSSignersThreshold = async (provider, config) => {
522
- const kmsContract = new Contract(config.kmsContractAddress, abiKmsVerifier, provider);
878
+ const getKMSSignersThreshold = async (provider, kmsContractAddress) => {
879
+ const kmsContract = new Contract(kmsContractAddress, abiKmsVerifier, provider);
523
880
  const threshold = await kmsContract.getThreshold();
524
881
  return Number(threshold); // threshold is always supposed to fit in a number
525
882
  };
526
- const getCoprocessorSigners = async (provider, config) => {
527
- const inputContract = new Contract(config.inputVerifierContractAddress, abiInputVerifier, provider);
883
+ const getCoprocessorSigners = async (provider, inputVerifierContractAddress) => {
884
+ const inputContract = new Contract(inputVerifierContractAddress, abiInputVerifier, provider);
528
885
  const signers = await inputContract.getCoprocessorSigners();
529
886
  return signers;
530
887
  };
531
- const getCoprocessorSignersThreshold = async (provider, config) => {
532
- const inputContract = new Contract(config.inputVerifierContractAddress, abiInputVerifier, provider);
888
+ const getCoprocessorSignersThreshold = async (provider, inputVerifierContractAddress) => {
889
+ const inputContract = new Contract(inputVerifierContractAddress, abiInputVerifier, provider);
533
890
  const threshold = await inputContract.getThreshold();
534
891
  return Number(threshold); // threshold is always supposed to fit in a number
535
892
  };
@@ -576,6 +933,344 @@ function checkEncryptedBits(handles) {
576
933
  return total;
577
934
  }
578
935
 
936
+ class InvalidTypeError extends RelayerErrorBase {
937
+ _objName;
938
+ _type;
939
+ _expectedType;
940
+ _expectedCustomType;
941
+ constructor({ objName, type, expectedType, expectedCustomType, }) {
942
+ super({
943
+ message: `InvalidTypeError ${objName} ${expectedType} ${type}`,
944
+ name: 'InvalidTypeError',
945
+ });
946
+ this._objName = objName;
947
+ this._type = type;
948
+ this._expectedType = expectedType;
949
+ this._expectedCustomType = expectedCustomType;
950
+ }
951
+ get objName() {
952
+ return this._objName;
953
+ }
954
+ get type() {
955
+ return this._type;
956
+ }
957
+ get expectedType() {
958
+ return this._expectedType;
959
+ }
960
+ get expectedCustomType() {
961
+ return this._expectedCustomType;
962
+ }
963
+ }
964
+
965
+ function isBytes(value, bytewidth) {
966
+ if (!value) {
967
+ return false;
968
+ }
969
+ if (!(value instanceof Uint8Array)) {
970
+ return false;
971
+ }
972
+ return value.length === bytewidth ;
973
+ }
974
+ function isBytesHex(value, bytewidth) {
975
+ if (!is0x(value)) {
976
+ return false;
977
+ }
978
+ if (bytewidth !== undefined && value.length !== 2 * bytewidth + 2) {
979
+ return false;
980
+ }
981
+ if ((value.length - 2) % 2 !== 0) {
982
+ return false;
983
+ }
984
+ const hexRegex = /^0x[a-fA-F0-9]*$/;
985
+ if (!hexRegex.test(value)) {
986
+ return false;
987
+ }
988
+ return true;
989
+ }
990
+ function isBytesHexNo0x(value, bytewidth) {
991
+ if (!isNo0x(value)) {
992
+ return false;
993
+ }
994
+ if ((value.length - 2) % 2 !== 0) {
995
+ return false;
996
+ }
997
+ const hexRegex = /^[a-fA-F0-9]*$/;
998
+ if (!hexRegex.test(value)) {
999
+ return false;
1000
+ }
1001
+ return true;
1002
+ }
1003
+ function isBytes32Hex(value) {
1004
+ return isBytesHex(value, 32);
1005
+ }
1006
+ function isBytes65Hex(value) {
1007
+ return isBytesHex(value, 65);
1008
+ }
1009
+ function isBytes32(value) {
1010
+ return isBytes(value, 32);
1011
+ }
1012
+ function assertIsBytes32(value) {
1013
+ if (!isBytes32(value)) {
1014
+ throw new InvalidTypeError({
1015
+ type: typeof value,
1016
+ expectedType: 'Bytes32',
1017
+ });
1018
+ }
1019
+ }
1020
+ /**
1021
+ * Type guard that checks if a property exists on an object and is a valid hex bytes string.
1022
+ * A valid BytesHex string starts with "0x" followed by an even number of hexadecimal characters.
1023
+ *
1024
+ * @template K - The property key type (string literal)
1025
+ * @param o - The value to check (can be any type)
1026
+ * @param property - The property name to check for
1027
+ * @returns True if `o` is an object with the specified property that is a valid BytesHex string
1028
+ *
1029
+ * @example
1030
+ * ```typescript
1031
+ * const data: unknown = { hash: "0x1234abcd", value: 42 };
1032
+ * if (isRecordBytesHexProperty(data, 'hash')) {
1033
+ * console.log(data.hash); // "0x1234abcd"
1034
+ * }
1035
+ * ```
1036
+ */
1037
+ function isRecordBytesHexProperty(o, property) {
1038
+ if (!isNonNullableRecordProperty(o, property)) {
1039
+ return false;
1040
+ }
1041
+ return isBytesHex(o[property]);
1042
+ }
1043
+ /**
1044
+ * Assertion function that validates a property exists on an object and is a valid hex bytes string.
1045
+ * A valid BytesHex string must start with "0x" followed by an even number of hexadecimal characters.
1046
+ * Throws an `InvalidPropertyError` if validation fails.
1047
+ *
1048
+ * @template K - The property key type (string literal)
1049
+ * @param o - The value to validate (can be any type)
1050
+ * @param property - The property name to check for
1051
+ * @param objName - The name of the object being validated (used in error messages)
1052
+ * @throws {InvalidPropertyError} When the property is missing, not a string, or not valid BytesHex format
1053
+ * @throws {never} No other errors are thrown
1054
+ *
1055
+ * @example
1056
+ * ```typescript
1057
+ * function processTransaction(data: unknown) {
1058
+ * assertRecordBytesHexProperty(data, 'txHash', 'transaction');
1059
+ * console.log(data.txHash); // e.g., "0x1234..."
1060
+ * }
1061
+ * ```
1062
+ */
1063
+ function assertRecordBytesHexProperty(o, property, objName) {
1064
+ if (!isRecordBytesHexProperty(o, property)) {
1065
+ throw new InvalidPropertyError({
1066
+ objName,
1067
+ property,
1068
+ expectedType: 'BytesHex',
1069
+ type: typeofProperty(o, property),
1070
+ });
1071
+ }
1072
+ }
1073
+ function isRecordBytesHexNo0xProperty(o, property) {
1074
+ if (!isNonNullableRecordProperty(o, property)) {
1075
+ return false;
1076
+ }
1077
+ return isBytesHexNo0x(o[property]);
1078
+ }
1079
+ function assertRecordBytesHexNo0xProperty(o, property, objName) {
1080
+ if (!isRecordBytesHexNo0xProperty(o, property)) {
1081
+ throw new InvalidPropertyError({
1082
+ objName,
1083
+ property,
1084
+ expectedType: 'BytesHexNo0x',
1085
+ type: typeofProperty(o, property),
1086
+ });
1087
+ }
1088
+ }
1089
+ function assertRecordBytes32HexArrayProperty(o, property, objName) {
1090
+ assertRecordArrayProperty(o, property, objName);
1091
+ const arr = o[property];
1092
+ for (let i = 0; i < arr.length; ++i) {
1093
+ if (!isBytes32Hex(arr[i])) {
1094
+ throw new InvalidPropertyError({
1095
+ objName,
1096
+ property: `${property}[${i}]`,
1097
+ expectedType: 'Bytes32Hex',
1098
+ type: typeof arr[i],
1099
+ });
1100
+ }
1101
+ }
1102
+ }
1103
+ function assertRecordBytes65HexArrayProperty(o, property, objName) {
1104
+ assertRecordArrayProperty(o, property, objName);
1105
+ const arr = o[property];
1106
+ for (let i = 0; i < arr.length; ++i) {
1107
+ if (!isBytes65Hex(arr[i])) {
1108
+ throw new InvalidPropertyError({
1109
+ objName,
1110
+ property: `${property}[${i}]`,
1111
+ expectedType: 'Bytes65Hex',
1112
+ type: typeof arr[i],
1113
+ });
1114
+ }
1115
+ }
1116
+ }
1117
+ /**
1118
+ * Assertion function that validates a property exists on an object, is an array,
1119
+ * and every element is a valid hex bytes string (with "0x" prefix).
1120
+ * Throws an `InvalidPropertyError` if validation fails.
1121
+ *
1122
+ * @template K - The property key type (string literal)
1123
+ * @param o - The value to validate (can be any type)
1124
+ * @param property - The property name to check for
1125
+ * @param objName - The name of the object being validated (used in error messages)
1126
+ * @throws {InvalidPropertyError} When the property is missing, not an array, or any element is not valid BytesHex
1127
+ * @throws {never} No other errors are thrown
1128
+ *
1129
+ * @example
1130
+ * ```typescript
1131
+ * function processHashes(data: unknown) {
1132
+ * assertRecordBytesHexArrayProperty(data, 'txHashes', 'transaction');
1133
+ * data.txHashes.forEach(hash => {
1134
+ * console.log(hash); // e.g., "0x1234abcd..."
1135
+ * });
1136
+ * }
1137
+ * ```
1138
+ */
1139
+ function assertRecordBytesHexArrayProperty(o, property, objName) {
1140
+ assertRecordArrayProperty(o, property, objName);
1141
+ const arr = o[property];
1142
+ for (let i = 0; i < arr.length; ++i) {
1143
+ if (!isBytesHex(arr[i])) {
1144
+ throw new InvalidPropertyError({
1145
+ objName,
1146
+ property: `${property}[${i}]`,
1147
+ expectedType: 'BytesHex',
1148
+ type: typeof arr[i],
1149
+ });
1150
+ }
1151
+ }
1152
+ }
1153
+ /**
1154
+ * Assertion function that validates a property exists on an object, is an array,
1155
+ * and every element is a valid hex bytes string (without "0x" prefix).
1156
+ * Throws an `InvalidPropertyError` if validation fails.
1157
+ *
1158
+ * @template K - The property key type (string literal)
1159
+ * @param o - The value to validate (can be any type)
1160
+ * @param property - The property name to check for
1161
+ * @param objName - The name of the object being validated (used in error messages)
1162
+ * @throws {InvalidPropertyError} When the property is missing, not an array, or any element is not valid BytesHexNo0x
1163
+ * @throws {never} No other errors are thrown
1164
+ *
1165
+ * @example
1166
+ * ```typescript
1167
+ * function processSignatures(data: unknown) {
1168
+ * assertRecordBytesHexNo0xArrayProperty(data, 'signatures', 'response');
1169
+ * data.signatures.forEach(sig => {
1170
+ * console.log(sig); // e.g., "1234abcd..." (no 0x prefix)
1171
+ * });
1172
+ * }
1173
+ * ```
1174
+ */
1175
+ function assertRecordBytesHexNo0xArrayProperty(o, property, objName) {
1176
+ assertRecordArrayProperty(o, property, objName);
1177
+ const arr = o[property];
1178
+ for (let i = 0; i < arr.length; ++i) {
1179
+ if (!isBytesHexNo0x(arr[i])) {
1180
+ throw new InvalidPropertyError({
1181
+ objName,
1182
+ property: `${property}[${i}]`,
1183
+ expectedType: 'BytesHexNo0x',
1184
+ type: typeof arr[i],
1185
+ });
1186
+ }
1187
+ }
1188
+ }
1189
+ function isRecordUint8ArrayProperty(o, property) {
1190
+ if (!isNonNullableRecordProperty(o, property)) {
1191
+ return false;
1192
+ }
1193
+ return o[property] instanceof Uint8Array;
1194
+ }
1195
+ function assertUint8ArrayProperty(o, property, objName) {
1196
+ if (!isRecordUint8ArrayProperty(o, property)) {
1197
+ throw new InvalidPropertyError({
1198
+ objName,
1199
+ property,
1200
+ expectedType: 'Uint8Array',
1201
+ type: typeofProperty(o, property),
1202
+ });
1203
+ }
1204
+ }
1205
+ /**
1206
+ * Convert a Uint8Array to a hex string (without 0x prefix).
1207
+ */
1208
+ function bytesToHexNo0x(bytes) {
1209
+ if (!bytes || bytes?.length === 0) {
1210
+ return '';
1211
+ }
1212
+ let hex = '';
1213
+ for (let i = 0; i < bytes.length; i++) {
1214
+ hex += bytes[i].toString(16).padStart(2, '0');
1215
+ }
1216
+ return hex;
1217
+ }
1218
+ /**
1219
+ * Convert a Uint8Array to a 0x prefixed hex string
1220
+ */
1221
+ function bytesToHex(bytes) {
1222
+ return `0x${bytesToHexNo0x(bytes)}`;
1223
+ }
1224
+ /**
1225
+ * Convert a hex string prefixed by 0x or not to a Uint8Array
1226
+ */
1227
+ function hexToBytes(hexString) {
1228
+ const arr = hexString.replace(/^(0x)/, '').match(/.{1,2}/g);
1229
+ if (!arr)
1230
+ return new Uint8Array();
1231
+ return Uint8Array.from(arr.map((byte) => parseInt(byte, 16)));
1232
+ }
1233
+ /**
1234
+ * Convert a Uint8Array to a bigint
1235
+ */
1236
+ function bytesToBigInt(byteArray) {
1237
+ if (!byteArray || byteArray.length === 0) {
1238
+ return BigInt(0);
1239
+ }
1240
+ let result = BigInt(0);
1241
+ for (let i = 0; i < byteArray.length; i++) {
1242
+ result = (result << BigInt(8)) | BigInt(byteArray[i]);
1243
+ }
1244
+ return result;
1245
+ }
1246
+ function toHexString(bytes, with0x = false) {
1247
+ return `${with0x ? '0x' : ''}${bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), '')}`;
1248
+ }
1249
+ async function fetchBytes(url) {
1250
+ const response = await fetch(url);
1251
+ if (!response.ok) {
1252
+ throw new Error(`HTTP error! status: ${response.status} on ${response.url}`);
1253
+ }
1254
+ // Warning : bytes is not widely supported yet!
1255
+ const bytes = typeof response.bytes === 'function'
1256
+ ? await response.bytes()
1257
+ : new Uint8Array(await response.arrayBuffer());
1258
+ return bytes;
1259
+ }
1260
+ function concatBytes(...arrays) {
1261
+ let totalLength = 0;
1262
+ for (const arr of arrays) {
1263
+ totalLength += arr.length;
1264
+ }
1265
+ const result = new Uint8Array(totalLength);
1266
+ let offset = 0;
1267
+ for (const arr of arrays) {
1268
+ result.set(arr, offset);
1269
+ offset += arr.length;
1270
+ }
1271
+ return result;
1272
+ }
1273
+
579
1274
  // Add type checking
580
1275
  const getAddress$1 = (value) => getAddress$2(value);
581
1276
  const aclABI$1 = [
@@ -626,13 +1321,15 @@ function checkDeadlineValidity(startTimestamp, durationDays) {
626
1321
  throw Error('User decrypt request has expired');
627
1322
  }
628
1323
  }
629
- const userDecryptRequest = (kmsSigners, gatewayChainId, chainId, verifyingContractAddress, aclContractAddress, relayerUrl, provider, instanceOptions) => async (_handles, privateKey, publicKey, signature, contractAddresses, userAddress, startTimestamp, durationDays, options) => {
1324
+ const userDecryptRequest = (kmsSigners, gatewayChainId, chainId, verifyingContractAddress, aclContractAddress,
1325
+ //relayerUrl: string,
1326
+ relayerProvider, provider, instanceOptions) => async (_handles, privateKey, publicKey, signature, contractAddresses, userAddress, startTimestamp, durationDays, options) => {
630
1327
  const extraData = '0x00';
631
1328
  let pubKey;
632
1329
  let privKey;
633
1330
  try {
634
- pubKey = TKMS.u8vec_to_ml_kem_pke_pk(fromHexString(publicKey));
635
- privKey = TKMS.u8vec_to_ml_kem_pke_sk(fromHexString(privateKey));
1331
+ pubKey = TKMS.u8vec_to_ml_kem_pke_pk(hexToBytes(publicKey));
1332
+ privKey = TKMS.u8vec_to_ml_kem_pke_sk(hexToBytes(privateKey));
636
1333
  }
637
1334
  catch (e) {
638
1335
  throw new Error('Invalid public or private key', { cause: e });
@@ -642,7 +1339,7 @@ const userDecryptRequest = (kmsSigners, gatewayChainId, chainId, verifyingContra
642
1339
  const publicKeySanitized = publicKey.replace(/^(0x)/, '');
643
1340
  const handles = _handles.map((h) => ({
644
1341
  handle: typeof h.handle === 'string'
645
- ? toHexString(fromHexString(h.handle), true)
1342
+ ? toHexString(hexToBytes(h.handle), true)
646
1343
  : toHexString(h.handle, true),
647
1344
  contractAddress: getAddress$1(h.contractAddress),
648
1345
  }));
@@ -685,7 +1382,13 @@ const userDecryptRequest = (kmsSigners, gatewayChainId, chainId, verifyingContra
685
1382
  publicKey: publicKeySanitized,
686
1383
  extraData,
687
1384
  };
688
- const json = await fetchRelayerJsonRpcPost('USER_DECRYPT', `${relayerUrl}/v1/user-decrypt`, payloadForRequest, instanceOptions ?? options);
1385
+ const json = await relayerProvider.fetchPostUserDecrypt(payloadForRequest, options ?? instanceOptions);
1386
+ // const json = await fetchRelayerJsonRpcPost(
1387
+ // 'USER_DECRYPT',
1388
+ // `${relayerUrl}/v1/user-decrypt`,
1389
+ // payloadForRequest,
1390
+ // instanceOptions ?? options,
1391
+ // );
689
1392
  // assume the KMS Signers have the correct order
690
1393
  let indexedKmsSigners = kmsSigners.map((signer, index) => {
691
1394
  return TKMS.new_server_id_addr(index + 1, signer);
@@ -710,7 +1413,8 @@ const userDecryptRequest = (kmsSigners, gatewayChainId, chainId, verifyingContra
710
1413
  ciphertext_handles: handles.map((h) => h.handle.replace(/^0x/, '')),
711
1414
  eip712_verifying_contract: verifyingContractAddress,
712
1415
  };
713
- const decryption = TKMS.process_user_decryption_resp_from_js(client, payloadForVerification, eip712Domain, json.response, pubKey, privKey, true);
1416
+ const decryption = TKMS.process_user_decryption_resp_from_js(client, payloadForVerification, eip712Domain, json, //json.response,
1417
+ pubKey, privKey, true);
714
1418
  const listBigIntDecryptions = decryption.map((d) => bytesToBigInt(d.bytes));
715
1419
  const results = buildUserDecryptResults(handles.map((h) => h.handle), listBigIntDecryptions);
716
1420
  return results;
@@ -737,10 +1441,10 @@ const checkEncryptedValue = (value, bits) => {
737
1441
  }
738
1442
  };
739
1443
  const createEncryptedInput = ({ aclContractAddress, chainId, tfheCompactPublicKey, publicParams, contractAddress, userAddress, }) => {
740
- if (!isAddress(contractAddress)) {
1444
+ if (!isAddress$1(contractAddress)) {
741
1445
  throw new Error('Contract address is not a valid address.');
742
1446
  }
743
- if (!isAddress(userAddress)) {
1447
+ if (!isAddress$1(userAddress)) {
744
1448
  throw new Error('User address is not a valid address.');
745
1449
  }
746
1450
  const publicKey = tfheCompactPublicKey;
@@ -806,7 +1510,7 @@ const createEncryptedInput = ({ aclContractAddress, chainId, tfheCompactPublicKe
806
1510
  return this;
807
1511
  },
808
1512
  addAddress(value) {
809
- if (!isAddress(value)) {
1513
+ if (!isAddress$1(value)) {
810
1514
  throw new Error('The value must be a valid address.');
811
1515
  }
812
1516
  checkLimit(160);
@@ -837,10 +1541,10 @@ const createEncryptedInput = ({ aclContractAddress, chainId, tfheCompactPublicKe
837
1541
  };
838
1542
  const closestPP = getClosestPP();
839
1543
  const pp = publicParams[closestPP].publicParams;
840
- const buffContract = fromHexString(contractAddress);
841
- const buffUser = fromHexString(userAddress);
842
- const buffAcl = fromHexString(aclContractAddress);
843
- const buffChainId = fromHexString(chainId.toString(16).padStart(64, '0'));
1544
+ const buffContract = hexToBytes(contractAddress);
1545
+ const buffUser = hexToBytes(userAddress);
1546
+ const buffAcl = hexToBytes(aclContractAddress);
1547
+ const buffChainId = hexToBytes(chainId.toString(16).padStart(64, '0'));
844
1548
  const auxData = new Uint8Array(buffContract.length + buffUser.length + buffAcl.length + 32);
845
1549
  auxData.set(buffContract, 0);
846
1550
  auxData.set(buffUser, 20);
@@ -853,77 +1557,324 @@ const createEncryptedInput = ({ aclContractAddress, chainId, tfheCompactPublicKe
853
1557
  };
854
1558
  };
855
1559
 
856
- /**
857
- * **FHE Type Mapping for Input Builders**
858
- * * Maps the **number of encrypted bits** used by a FHEVM primary type
859
- * to its corresponding **FheTypeId**. This constant is primarily used by
860
- * `EncryptedInput` and `RelayerEncryptedInput` builders to determine the correct
861
- * input type and calculate the total required bit-length.
862
- *
863
- * **Structure: \{ Encrypted Bit Length: FheTypeId \}**
864
- *
865
- * | Bits | FheTypeId | FHE Type Name | Note |
866
- * | :--- | :-------- | :------------ | :--- |
867
- * | 2 | 0 | `ebool` | The boolean type. |
868
- * | (N/A)| 1 | `euint4` | **Deprecated** and omitted from this map. |
869
- * | 8 | 2 | `euint8` | |
870
- * | 16 | 3 | `euint16` | |
871
- * | 32 | 4 | `euint32` | |
872
- * | 64 | 5 | `euint64` | |
873
- * | 128 | 6 | `euint128` | |
874
- * | 160 | 7 | `eaddress` | Used for encrypted Ethereum addresses. |
875
- * | 256 | 8 | `euint256` | The maximum supported integer size. |
876
- */
877
- const ENCRYPTION_TYPES = {
878
- 2: 0, // ebool (FheTypeId=0) is using 2 encrypted bits
879
- // euint4 (FheTypeId=1) is deprecated
880
- 8: 2, // euint8 (FheTypeId=2) is using 8 encrypted bits
881
- 16: 3, // euint16 (FheTypeId=3) is using 16 encrypted bits
882
- 32: 4, // euint32 (FheTypeId=4) is using 32 encrypted bits
883
- 64: 5, // euint64 (FheTypeId=5) is using 64 encrypted bits
884
- 128: 6, // euint128 (FheTypeId=128) is using 128 encrypted bits
885
- 160: 7, // eaddress (FheTypeId=7) is using 160 encrypted bits
886
- 256: 8, // euint256 (FheTypeId=8) is using 256 encrypted bits
887
- };
888
-
889
1560
  const MAX_UINT64 = BigInt('18446744073709551615'); // 2^64 - 1
890
- const RAW_CT_HASH_DOMAIN_SEPARATOR = 'ZK-w_rct';
891
- const HANDLE_HASH_DOMAIN_SEPARATOR = 'ZK-w_hdl';
892
- const computeHandles = (ciphertextWithZKProof, bitwidths, aclContractAddress, chainId, ciphertextVersion) => {
893
- // Should be identical to:
894
- // https://github.com/zama-ai/fhevm-backend/blob/bae00d1b0feafb63286e94acdc58dc88d9c481bf/fhevm-engine/zkproof-worker/src/verifier.rs#L301
895
- const blob_hash = createHash('keccak256')
896
- .update(Buffer.from(RAW_CT_HASH_DOMAIN_SEPARATOR))
897
- .update(Buffer.from(ciphertextWithZKProof))
898
- .digest();
899
- const aclContractAddress20Bytes = Buffer.from(fromHexString(aclContractAddress));
900
- const hex = chainId.toString(16).padStart(64, '0'); // 64 hex chars = 32 bytes
901
- const chainId32Bytes = Buffer.from(hex, 'hex');
902
- const handles = bitwidths.map((bitwidth, encryptionIndex) => {
903
- const encryptionType = ENCRYPTION_TYPES[bitwidth];
904
- const encryptionIndex1Byte = Buffer.from([encryptionIndex]);
905
- const handleHash = createHash('keccak256')
906
- .update(Buffer.from(HANDLE_HASH_DOMAIN_SEPARATOR))
907
- .update(blob_hash)
908
- .update(encryptionIndex1Byte)
909
- .update(aclContractAddress20Bytes)
910
- .update(chainId32Bytes)
911
- .digest();
912
- const dataInput = new Uint8Array(32);
913
- dataInput.set(handleHash, 0);
914
- // Check if chainId exceeds 8 bytes
1561
+ BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff');
1562
+ const MAX_UINT32 = 0xffffffff;
1563
+ const MAX_UINT8 = 0xff;
1564
+ function numberToHex(num) {
1565
+ let hex = num.toString(16);
1566
+ return hex.length % 2 ? '0' + hex : hex;
1567
+ }
1568
+ function isUintNumber(value) {
1569
+ if (typeof value === 'number') {
1570
+ if (value < 0) {
1571
+ return false;
1572
+ }
1573
+ return Number.isInteger(value);
1574
+ }
1575
+ return false;
1576
+ }
1577
+ function isUintBigInt(value) {
1578
+ if (typeof value === 'bigint') {
1579
+ return value >= 0;
1580
+ }
1581
+ return false;
1582
+ }
1583
+ function isUint(value) {
1584
+ if (isUintNumber(value)) {
1585
+ return true;
1586
+ }
1587
+ else if (isUintBigInt(value)) {
1588
+ return true;
1589
+ }
1590
+ return false;
1591
+ }
1592
+ function isUint8(value) {
1593
+ if (!isUint(value)) {
1594
+ return false;
1595
+ }
1596
+ return value <= MAX_UINT8;
1597
+ }
1598
+ function isUint32(value) {
1599
+ if (!isUint(value)) {
1600
+ return false;
1601
+ }
1602
+ return value <= MAX_UINT32;
1603
+ }
1604
+ function isUint64(value) {
1605
+ if (!isUint(value)) {
1606
+ return false;
1607
+ }
1608
+ return value <= MAX_UINT64;
1609
+ }
1610
+ function uint32ToBytes32(uint32) {
1611
+ if (!isUint32(uint32)) {
1612
+ throw new InvalidTypeError({ expectedType: 'Uint32' });
1613
+ }
1614
+ const buffer = new ArrayBuffer(32);
1615
+ const view = new DataView(buffer);
1616
+ view.setUint32(28, Number(uint32), false);
1617
+ return new Uint8Array(buffer);
1618
+ }
1619
+ function assertIsUint8(value) {
1620
+ if (!isUint8(value)) {
1621
+ throw new InvalidTypeError({
1622
+ type: typeof value,
1623
+ expectedType: 'Uint8',
1624
+ });
1625
+ }
1626
+ }
1627
+ function assertIsUint64(value) {
1628
+ if (!isUint64(value)) {
1629
+ throw new InvalidTypeError({
1630
+ type: typeof value,
1631
+ expectedType: 'Uint64',
1632
+ });
1633
+ }
1634
+ }
1635
+ function isRecordUintProperty(o, property) {
1636
+ if (!isNonNullableRecordProperty(o, property)) {
1637
+ return false;
1638
+ }
1639
+ return isUint(o[property]);
1640
+ }
1641
+ function assertRecordUintProperty(o, property, objName) {
1642
+ if (!isRecordUintProperty(o, property)) {
1643
+ throw new InvalidPropertyError({
1644
+ objName,
1645
+ property,
1646
+ type: typeofProperty(o, property),
1647
+ expectedType: 'Uint',
1648
+ });
1649
+ }
1650
+ }
1651
+
1652
+ class ChecksummedAddressError extends RelayerErrorBase {
1653
+ constructor({ address }) {
1654
+ super({
1655
+ message: `Checksummed address "${address}" is invalid.`,
1656
+ name: 'ChecksummedAddressError',
1657
+ });
1658
+ }
1659
+ }
1660
+
1661
+ function isChecksummedAddress(value) {
1662
+ if (typeof value !== 'string') {
1663
+ return false;
1664
+ }
1665
+ if (!value.startsWith('0x')) {
1666
+ return false;
1667
+ }
1668
+ if (value.length !== 42) {
1669
+ return false;
1670
+ }
1671
+ try {
1672
+ const a = getAddress$2(value);
1673
+ return a === value;
1674
+ }
1675
+ catch (e) {
1676
+ return false;
1677
+ }
1678
+ }
1679
+ function assertIsChecksummedAddress(value) {
1680
+ if (!isChecksummedAddress(value)) {
1681
+ throw new ChecksummedAddressError({ address: String(value) });
1682
+ }
1683
+ }
1684
+ function isAddress(value) {
1685
+ if (typeof value !== 'string') {
1686
+ return false;
1687
+ }
1688
+ if (!value.startsWith('0x')) {
1689
+ return false;
1690
+ }
1691
+ if (value.length !== 42) {
1692
+ return false;
1693
+ }
1694
+ if (!isAddress$1(value)) {
1695
+ return false;
1696
+ }
1697
+ return true;
1698
+ }
1699
+ function checksummedAddressToBytes20(address) {
1700
+ if (!isAddress(address)) {
1701
+ throw new InvalidTypeError({ expectedType: 'ChecksummedAddress' });
1702
+ }
1703
+ const hex = remove0x(address);
1704
+ const bytes = new Uint8Array(20);
1705
+ for (let i = 0; i < 20; i++) {
1706
+ bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
1707
+ }
1708
+ return bytes;
1709
+ }
1710
+
1711
+ ////////////////////////////////////////////////////////////////////////////////
1712
+ // FhevmHandle
1713
+ ////////////////////////////////////////////////////////////////////////////////
1714
+ class FhevmHandle {
1715
+ _hash21;
1716
+ _chainId;
1717
+ _fheTypeId;
1718
+ _version;
1719
+ _computed;
1720
+ _index;
1721
+ static RAW_CT_HASH_DOMAIN_SEPARATOR = 'ZK-w_rct';
1722
+ static HANDLE_HASH_DOMAIN_SEPARATOR = 'ZK-w_hdl';
1723
+ static FheTypeIdToEncryptionBitwidths = {
1724
+ 0: 2,
1725
+ 2: 8,
1726
+ 3: 16,
1727
+ 4: 32,
1728
+ 5: 64,
1729
+ 6: 128,
1730
+ 7: 160,
1731
+ 8: 256,
1732
+ };
1733
+ static FheTypeEncryptionBitwidthsToId = {
1734
+ 2: 0,
1735
+ 8: 2,
1736
+ 16: 3,
1737
+ 32: 4,
1738
+ 64: 5,
1739
+ 128: 6,
1740
+ 160: 7,
1741
+ 256: 8,
1742
+ };
1743
+ static {
1744
+ Object.freeze(FhevmHandle.FheTypeIdToEncryptionBitwidths);
1745
+ Object.freeze(FhevmHandle.FheTypeEncryptionBitwidthsToId);
1746
+ }
1747
+ constructor(hash21, chainId, fheTypeId, version, computed, index) {
915
1748
  if (BigInt(chainId) > MAX_UINT64) {
916
- throw new Error('ChainId exceeds maximum allowed value (8 bytes)'); // fhevm assumes chainID is only taking up to 8 bytes
917
- }
918
- const chainId8Bytes = fromHexString(hex).slice(24, 32);
919
- dataInput[21] = encryptionIndex;
920
- dataInput.set(chainId8Bytes, 22);
921
- dataInput[30] = encryptionType;
922
- dataInput[31] = ciphertextVersion;
923
- return dataInput;
924
- });
925
- return handles;
926
- };
1749
+ // fhevm assumes chainID is only taking up to 8 bytes
1750
+ throw new Error('ChainId exceeds maximum allowed value (8 bytes)');
1751
+ }
1752
+ this._hash21 = hash21;
1753
+ this._chainId = chainId;
1754
+ this._fheTypeId = fheTypeId;
1755
+ this._version = version;
1756
+ this._computed = computed;
1757
+ if (index !== undefined) {
1758
+ this._index = index;
1759
+ }
1760
+ }
1761
+ get hash21() {
1762
+ return this._hash21;
1763
+ }
1764
+ get chainId() {
1765
+ return this._chainId;
1766
+ }
1767
+ get fheTypeId() {
1768
+ return this._fheTypeId;
1769
+ }
1770
+ get version() {
1771
+ return this._version;
1772
+ }
1773
+ get computed() {
1774
+ return this._computed;
1775
+ }
1776
+ get index() {
1777
+ return this._index;
1778
+ }
1779
+ static fromZKProof(params) {
1780
+ assertIsChecksummedAddress(params.aclAddress);
1781
+ assertIsUint64(params.chainId);
1782
+ assertIsUint8(params.ciphertextVersion);
1783
+ let fheTypeIds;
1784
+ if (params.fheTypeIds !== undefined) {
1785
+ fheTypeIds = params.fheTypeIds;
1786
+ }
1787
+ else if (params.fheTypeEncryptionBitwidths !== undefined) {
1788
+ fheTypeIds = params.fheTypeEncryptionBitwidths.map((w) => FhevmHandle.FheTypeEncryptionBitwidthsToId[w]);
1789
+ }
1790
+ else {
1791
+ throw new InternalError({
1792
+ message: 'createInputHandles requires either fheTypeIds or fheTypeEncryptionBitwidths',
1793
+ });
1794
+ }
1795
+ assertIsUint8(fheTypeIds.length);
1796
+ let ciphertextWithZKProof;
1797
+ if (typeof params.ciphertextWithZKProof === 'string') {
1798
+ ciphertextWithZKProof = hexToBytes(params.ciphertextWithZKProof);
1799
+ }
1800
+ else if (params.ciphertextWithZKProof instanceof Uint8Array) {
1801
+ ciphertextWithZKProof = params.ciphertextWithZKProof;
1802
+ }
1803
+ else {
1804
+ throw new InternalError({
1805
+ message: 'Invalid ciphertextWithZKProof argument',
1806
+ });
1807
+ }
1808
+ if (ciphertextWithZKProof.length === 0) {
1809
+ throw new InternalError({
1810
+ message: 'Invalid ciphertextWithZKProof argument',
1811
+ });
1812
+ }
1813
+ const encoder = new TextEncoder();
1814
+ const domainSepBytes = encoder.encode(FhevmHandle.RAW_CT_HASH_DOMAIN_SEPARATOR);
1815
+ const blobHashBytes32Hex = keccak256(concatBytes(domainSepBytes, ciphertextWithZKProof));
1816
+ const handles = [];
1817
+ for (let i = 0; i < fheTypeIds.length; ++i) {
1818
+ const hash21 = FhevmHandle._computeInputHash21(hexToBytes(blobHashBytes32Hex), params.aclAddress, params.chainId, i);
1819
+ handles.push(new FhevmHandle(hash21, params.chainId, fheTypeIds[i], params.ciphertextVersion, false, i));
1820
+ }
1821
+ return handles;
1822
+ }
1823
+ /**
1824
+ * blobHashBytes32 = keccak256(ciphertextWithZKProof)
1825
+ */
1826
+ static _computeInputHash21(blobHashBytes32, aclAddress, chainId, index) {
1827
+ /*
1828
+ https://github.com/zama-ai/fhevm/blob/8ffbd5906ab3d57af178e049930e3fc065c9d4b3/coprocessor/fhevm-engine/zkproof-worker/src/verifier.rs#L431C7-L431C8
1829
+
1830
+ handle_hash = Bytes("ZK-w_hdl") + blobHash 32 Bytes + index 1 Byte + aclAddress 20 Bytes + chainId 32 bytes
1831
+ ===========================================================================================================
1832
+
1833
+ const HANDLE_HASH_DOMAIN_SEPARATOR: [u8; 8] = *b"ZK-w_hdl";
1834
+
1835
+ let mut handle_hash = Keccak256::new();
1836
+ handle_hash.update(HANDLE_HASH_DOMAIN_SEPARATOR);
1837
+ handle_hash.update(blob_hash);
1838
+ handle_hash.update([ct_idx as u8]);
1839
+ handle_hash.update(
1840
+ Address::from_str(&aux_data.acl_contract_address)
1841
+ .expect("valid acl_contract_address")
1842
+ .into_array(),
1843
+ );
1844
+ handle_hash.update(chain_id_bytes);
1845
+ let mut handle = handle_hash.finalize().to_vec();
1846
+ assert_eq!(handle.len(), 32);
1847
+
1848
+ */
1849
+ assertIsBytes32(blobHashBytes32);
1850
+ assertIsChecksummedAddress(aclAddress);
1851
+ assertIsUint8(index);
1852
+ assertIsUint64(chainId);
1853
+ const encryptionIndexByte1 = new Uint8Array([index]);
1854
+ const aclContractAddressBytes20 = checksummedAddressToBytes20(aclAddress);
1855
+ const chainIdBytes32 = uint32ToBytes32(chainId);
1856
+ const encoder = new TextEncoder();
1857
+ const domainSepBytes = encoder.encode(FhevmHandle.HANDLE_HASH_DOMAIN_SEPARATOR);
1858
+ return keccak256(concatBytes(domainSepBytes, blobHashBytes32, encryptionIndexByte1, aclContractAddressBytes20, chainIdBytes32));
1859
+ }
1860
+ toBytes32() {
1861
+ assertRelayer((this._index === undefined && this._computed) ||
1862
+ (this._index !== undefined && this._index < 255 && !this._computed));
1863
+ const chainId32Bytes = uint32ToBytes32(this._chainId);
1864
+ const chainId8Bytes = chainId32Bytes.subarray(24, 32);
1865
+ const handleHash = hexToBytes(this._hash21);
1866
+ const handleBytes32AsBytes = new Uint8Array(32);
1867
+ handleBytes32AsBytes.set(handleHash, 0);
1868
+ handleBytes32AsBytes[21] = this._index === undefined ? 255 : this._index;
1869
+ handleBytes32AsBytes.set(chainId8Bytes, 22);
1870
+ handleBytes32AsBytes[30] = this._fheTypeId;
1871
+ handleBytes32AsBytes[31] = this._version;
1872
+ return handleBytes32AsBytes;
1873
+ }
1874
+ toBytes32Hex() {
1875
+ return bytesToHex(this.toBytes32());
1876
+ }
1877
+ }
927
1878
 
928
1879
  // Add type checking
929
1880
  const getAddress = (value) => getAddress$2(value);
@@ -947,7 +1898,8 @@ function isThresholdReached$1(coprocessorSigners, recoveredAddresses, threshold)
947
1898
  return recoveredAddresses.length >= threshold;
948
1899
  }
949
1900
  function isFhevmRelayerInputProofResponse(json) {
950
- const response = json.response;
1901
+ const response = json;
1902
+ // const response = json.response as unknown;
951
1903
  if (typeof response !== 'object' || response === null) {
952
1904
  return false;
953
1905
  }
@@ -960,11 +1912,13 @@ function isFhevmRelayerInputProofResponse(json) {
960
1912
  return (response.signatures.every((s) => typeof s === 'string') &&
961
1913
  response.handles.every((h) => typeof h === 'string'));
962
1914
  }
963
- const createRelayerEncryptedInput = (aclContractAddress, verifyingContractAddressInputVerification, chainId, gatewayChainId, relayerUrl, tfheCompactPublicKey, publicParams, coprocessorSigners, thresholdCoprocessorSigners, instanceOptions) => (contractAddress, userAddress) => {
964
- if (!isAddress(contractAddress)) {
1915
+ const createRelayerEncryptedInput = (aclContractAddress, verifyingContractAddressInputVerification, chainId, gatewayChainId,
1916
+ //relayerUrl: string,
1917
+ relayerProvider, tfheCompactPublicKey, publicParams, coprocessorSigners, thresholdCoprocessorSigners, instanceOptions) => (contractAddress, userAddress) => {
1918
+ if (!isAddress$1(contractAddress)) {
965
1919
  throw new Error('Contract address is not a valid address.');
966
1920
  }
967
- if (!isAddress(userAddress)) {
1921
+ if (!isAddress$1(userAddress)) {
968
1922
  throw new Error('User address is not a valid address.');
969
1923
  }
970
1924
  const input = createEncryptedInput({
@@ -1016,6 +1970,7 @@ const createRelayerEncryptedInput = (aclContractAddress, verifyingContractAddres
1016
1970
  const extraData = '0x00';
1017
1971
  const bits = input.getBits();
1018
1972
  const ciphertext = input.encrypt();
1973
+ //console.log(`ciphertext=${toHexString(ciphertext)}`);
1019
1974
  const payload = {
1020
1975
  contractAddress: getAddress(contractAddress),
1021
1976
  userAddress: getAddress(userAddress),
@@ -1023,14 +1978,29 @@ const createRelayerEncryptedInput = (aclContractAddress, verifyingContractAddres
1023
1978
  contractChainId: ('0x' + chainId.toString(16)),
1024
1979
  extraData,
1025
1980
  };
1026
- const json = await fetchRelayerJsonRpcPost('INPUT_PROOF', `${relayerUrl}/v1/input-proof`, payload, options ?? instanceOptions);
1981
+ const json = await relayerProvider.fetchPostInputProof(payload, options ?? instanceOptions);
1982
+ // const json = await fetchRelayerJsonRpcPost(
1983
+ // 'INPUT_PROOF',
1984
+ // `${relayerUrl}/v1/input-proof`,
1985
+ // payload,
1986
+ // options ?? instanceOptions,
1987
+ // );
1027
1988
  if (!isFhevmRelayerInputProofResponse(json)) {
1028
1989
  throwRelayerInternalError('INPUT_PROOF', json);
1029
1990
  }
1030
- const handles = computeHandles(ciphertext, bits, aclContractAddress, chainId, currentCiphertextVersion());
1991
+ const fhevmHandles = FhevmHandle.fromZKProof({
1992
+ ciphertextWithZKProof: ciphertext,
1993
+ chainId,
1994
+ aclAddress: aclContractAddress,
1995
+ ciphertextVersion: currentCiphertextVersion(),
1996
+ fheTypeEncryptionBitwidths: bits,
1997
+ });
1998
+ const handles = fhevmHandles.map((h) => h.toBytes32());
1999
+ //const result = json.response;
2000
+ const result = json;
1031
2001
  // Note that the hex strings returned by the relayer do have have the 0x prefix
1032
- if (json.response.handles && json.response.handles.length > 0) {
1033
- const responseHandles = json.response.handles.map(fromHexString);
2002
+ if (result.handles && result.handles.length > 0) {
2003
+ const responseHandles = result.handles.map(hexToBytes);
1034
2004
  if (handles.length != responseHandles.length) {
1035
2005
  throw new Error(`Incorrect Handles list sizes: (expected) ${handles.length} != ${responseHandles.length} (received)`);
1036
2006
  }
@@ -1044,7 +2014,7 @@ const createRelayerEncryptedInput = (aclContractAddress, verifyingContractAddres
1044
2014
  }
1045
2015
  }
1046
2016
  }
1047
- const signatures = json.response.signatures;
2017
+ const signatures = result.signatures;
1048
2018
  // verify signatures for inputs:
1049
2019
  const domain = {
1050
2020
  name: 'InputVerification',
@@ -1087,7 +2057,7 @@ const createRelayerEncryptedInput = (aclContractAddress, verifyingContractAddres
1087
2057
  inputProof += extraData.slice(2);
1088
2058
  return {
1089
2059
  handles,
1090
- inputProof: fromHexString(inputProof),
2060
+ inputProof: hexToBytes(inputProof),
1091
2061
  };
1092
2062
  },
1093
2063
  };
@@ -1213,14 +2183,16 @@ function deserializeClearValues(handles, decryptedResult) {
1213
2183
  handles.forEach((handle, idx) => (results[handle] = rawValues[idx]));
1214
2184
  return results;
1215
2185
  }
1216
- const publicDecryptRequest = (kmsSigners, thresholdSigners, gatewayChainId, verifyingContractAddress, aclContractAddress, relayerUrl, provider, instanceOptions) => async (_handles, options) => {
2186
+ const publicDecryptRequest = (kmsSigners, thresholdSigners, gatewayChainId, verifyingContractAddress, aclContractAddress,
2187
+ //relayerUrl: string,
2188
+ relayerProvider, provider, instanceOptions) => async (_handles, options) => {
1217
2189
  const extraData = '0x00';
1218
2190
  const acl = new ethers.Contract(aclContractAddress, aclABI, provider);
1219
2191
  let handles;
1220
2192
  try {
1221
2193
  handles = await Promise.all(_handles.map(async (_handle) => {
1222
2194
  const handle = typeof _handle === 'string'
1223
- ? toHexString(fromHexString(_handle), true)
2195
+ ? toHexString(hexToBytes(_handle), true)
1224
2196
  : toHexString(_handle, true);
1225
2197
  const isAllowedForDecryption = await acl.isAllowedForDecryption(handle);
1226
2198
  if (!isAllowedForDecryption) {
@@ -1238,7 +2210,13 @@ const publicDecryptRequest = (kmsSigners, thresholdSigners, gatewayChainId, veri
1238
2210
  ciphertextHandles: handles,
1239
2211
  extraData,
1240
2212
  };
1241
- const json = await fetchRelayerJsonRpcPost('PUBLIC_DECRYPT', `${relayerUrl}/v1/public-decrypt`, payloadForRequest, options ?? instanceOptions);
2213
+ const json = await relayerProvider.fetchPostPublicDecrypt(payloadForRequest, options ?? instanceOptions);
2214
+ // const json = await fetchRelayerJsonRpcPost(
2215
+ // 'PUBLIC_DECRYPT',
2216
+ // `${relayerUrl}/v1/public-decrypt`,
2217
+ // payloadForRequest,
2218
+ // options ?? instanceOptions,
2219
+ // );
1242
2220
  // verify signatures on decryption:
1243
2221
  const domain = {
1244
2222
  name: 'Decryption',
@@ -1253,10 +2231,11 @@ const publicDecryptRequest = (kmsSigners, thresholdSigners, gatewayChainId, veri
1253
2231
  { name: 'extraData', type: 'bytes' },
1254
2232
  ],
1255
2233
  };
1256
- const result = json.response[0];
1257
- const decryptedResult = ensure0x(result.decrypted_value);
2234
+ //const result = json.response[0];
2235
+ const result = json;
2236
+ const decryptedResult = ensure0x(result.decryptedValue);
1258
2237
  const kmsSignatures = result.signatures.map(ensure0x);
1259
- // TODO result.extra_data (RelayerPublicDecryptJsonResponse)
2238
+ // TODO result.extraData (RelayerPublicDecryptJsonResponse)
1260
2239
  const signedExtraData = '0x';
1261
2240
  const recoveredAddresses = kmsSignatures.map((kmsSignature) => {
1262
2241
  const recoveredAddress = ethers.verifyTypedData(domain, types, { ctHandles: handles, decryptedResult, extraData: signedExtraData }, kmsSignature);
@@ -1291,12 +2270,12 @@ const publicDecryptRequest = (kmsSigners, thresholdSigners, gatewayChainId, veri
1291
2270
  */
1292
2271
  const createEIP712 = (verifyingContract, contractsChainId) => (publicKey, contractAddresses, startTimestamp, durationDays, delegatedAccount) => {
1293
2272
  const extraData = '0x00';
1294
- if (delegatedAccount && !isAddress(delegatedAccount))
2273
+ if (delegatedAccount && !isAddress$1(delegatedAccount))
1295
2274
  throw new Error('Invalid delegated account.');
1296
- if (!isAddress(verifyingContract)) {
2275
+ if (!isAddress$1(verifyingContract)) {
1297
2276
  throw new Error('Invalid verifying contract address.');
1298
2277
  }
1299
- if (!contractAddresses.every((c) => isAddress(c))) {
2278
+ if (!contractAddresses.every((c) => isAddress$1(c))) {
1300
2279
  throw new Error('Invalid contract address.');
1301
2280
  }
1302
2281
  // Format the public key based on its type
@@ -1380,77 +2359,2669 @@ const generateKeypair = () => {
1380
2359
  };
1381
2360
  };
1382
2361
 
1383
- global.fetch = fetchRetry(global.fetch, { retries: 5, retryDelay: 500 });
1384
- const SepoliaConfig = {
1385
- // ACL_CONTRACT_ADDRESS (FHEVM Host chain)
1386
- aclContractAddress: '0xf0Ffdc93b7E186bC2f8CB3dAA75D86d1930A433D',
1387
- // KMS_VERIFIER_CONTRACT_ADDRESS (FHEVM Host chain)
1388
- kmsContractAddress: '0xbE0E383937d564D7FF0BC3b46c51f0bF8d5C311A',
1389
- // INPUT_VERIFIER_CONTRACT_ADDRESS (FHEVM Host chain)
1390
- inputVerifierContractAddress: '0xBBC1fFCdc7C316aAAd72E807D9b0272BE8F84DA0',
1391
- // DECRYPTION_ADDRESS (Gateway chain)
1392
- verifyingContractAddressDecryption: '0x5D8BD78e2ea6bbE41f26dFe9fdaEAa349e077478',
1393
- // INPUT_VERIFICATION_ADDRESS (Gateway chain)
1394
- verifyingContractAddressInputVerification: '0x483b9dE06E4E4C7D35CCf5837A1668487406D955',
1395
- // FHEVM Host chain id
1396
- chainId: 11155111,
1397
- // Gateway chain id
1398
- gatewayChainId: 10901,
1399
- // Optional RPC provider to host chain
1400
- network: 'https://ethereum-sepolia-rpc.publicnode.com',
1401
- // Relayer URL
1402
- relayerUrl: 'https://relayer.testnet.zama.org',
1403
- };
1404
- const createInstance = async (config) => {
1405
- const { verifyingContractAddressDecryption, verifyingContractAddressInputVerification, publicKey, kmsContractAddress, aclContractAddress, gatewayChainId, auth, } = config;
1406
- if (!kmsContractAddress || !isAddress(kmsContractAddress)) {
1407
- throw new Error('KMS contract address is not valid or empty');
2362
+ class AbstractRelayerProvider {
2363
+ _relayerUrl;
2364
+ constructor(relayerUrl) {
2365
+ this._relayerUrl = relayerUrl;
1408
2366
  }
1409
- if (!verifyingContractAddressDecryption ||
1410
- !isAddress(verifyingContractAddressDecryption)) {
1411
- throw new Error('Verifying contract for Decryption address is not valid or empty');
2367
+ get url() {
2368
+ return this._relayerUrl;
1412
2369
  }
1413
- if (!verifyingContractAddressInputVerification ||
1414
- !isAddress(verifyingContractAddressInputVerification)) {
1415
- throw new Error('Verifying contract for InputVerification address is not valid or empty');
2370
+ get keyUrl() {
2371
+ return `${this.url}/keyurl`;
1416
2372
  }
1417
- if (!aclContractAddress || !isAddress(aclContractAddress)) {
1418
- throw new Error('ACL contract address is not valid or empty');
2373
+ get inputProof() {
2374
+ return `${this.url}/input-proof`;
1419
2375
  }
1420
- if (publicKey && !(publicKey.data instanceof Uint8Array))
1421
- throw new Error('publicKey must be a Uint8Array');
1422
- const provider = getProvider(config);
1423
- if (!provider) {
1424
- throw new Error('No network has been provided!');
2376
+ get publicDecrypt() {
2377
+ return `${this.url}/public-decrypt`;
1425
2378
  }
1426
- const chainId = await getChainId(provider, config);
1427
- const publicKeyData = await getTfheCompactPublicKey(config);
1428
- const publicParamsData = await getPublicParams(config);
1429
- const kmsSigners = await getKMSSigners(provider, config);
1430
- const thresholdKMSSigners = await getKMSSignersThreshold(provider, config);
1431
- const coprocessorSigners = await getCoprocessorSigners(provider, config);
1432
- const thresholdCoprocessorSigners = await getCoprocessorSignersThreshold(provider, config);
1433
- return {
1434
- createEncryptedInput: createRelayerEncryptedInput(aclContractAddress, verifyingContractAddressInputVerification, chainId, gatewayChainId, cleanURL(config.relayerUrl), publicKeyData.publicKey, publicParamsData, coprocessorSigners, thresholdCoprocessorSigners, auth && { auth }),
1435
- generateKeypair,
1436
- createEIP712: createEIP712(verifyingContractAddressDecryption, chainId),
1437
- publicDecrypt: publicDecryptRequest(kmsSigners, thresholdKMSSigners, gatewayChainId, verifyingContractAddressDecryption, aclContractAddress, cleanURL(config.relayerUrl), provider, auth && { auth }),
1438
- userDecrypt: userDecryptRequest(kmsSigners, gatewayChainId, chainId, verifyingContractAddressDecryption, aclContractAddress, cleanURL(config.relayerUrl), provider, auth && { auth }),
1439
- getPublicKey: () => publicKeyData.publicKey
1440
- ? {
1441
- publicKey: publicKeyData.publicKey.safe_serialize(SERIALIZED_SIZE_LIMIT_PK),
1442
- publicKeyId: publicKeyData.publicKeyId,
1443
- }
1444
- : null,
1445
- getPublicParams: (bits) => {
1446
- if (publicParamsData[bits]) {
1447
- return {
1448
- publicParams: publicParamsData[bits].publicParams.safe_serialize(SERIALIZED_SIZE_LIMIT_CRS),
1449
- publicParamsId: publicParamsData[bits].publicParamsId,
1450
- };
1451
- }
1452
- return null;
1453
- },
2379
+ get userDecrypt() {
2380
+ return `${this.url}/user-decrypt`;
2381
+ }
2382
+ }
2383
+ function assertIsRelayerInputProofResult(value, name) {
2384
+ assertRecordBytes32HexArrayProperty(value, 'handles', name);
2385
+ assertRecordBytes65HexArrayProperty(value, 'signatures', name);
2386
+ }
2387
+ function assertIsRelayerPublicDecryptResult(value, name) {
2388
+ assertRecordBytesHexNo0xArrayProperty(value, 'signatures', name);
2389
+ assertRecordStringProperty(value, 'decryptedValue', name);
2390
+ assertRecordBytesHexProperty(value, 'extraData', name);
2391
+ }
2392
+ function assertIsRelayerUserDecryptResult(value, name) {
2393
+ if (!Array.isArray(value)) {
2394
+ throw InvalidPropertyError.invalidObject({
2395
+ objName: name,
2396
+ expectedType: 'Array',
2397
+ type: typeof value,
2398
+ });
2399
+ }
2400
+ for (let i = 0; i < value.length; ++i) {
2401
+ // Missing extraData
2402
+ assertRecordBytesHexNo0xProperty(value[i], 'payload', `${name}[i]`);
2403
+ assertRecordBytesHexNo0xProperty(value[i], 'signature', `${name}[i]`);
2404
+ }
2405
+ }
2406
+
2407
+ class RelayerV1Provider extends AbstractRelayerProvider {
2408
+ constructor(relayerUrl) {
2409
+ super(relayerUrl);
2410
+ }
2411
+ get version() {
2412
+ return 1;
2413
+ }
2414
+ async fetchGetKeyUrl() {
2415
+ const response = await fetchRelayerGet('KEY_URL', this.keyUrl);
2416
+ return response;
2417
+ }
2418
+ async fetchPostInputProof(payload, options) {
2419
+ /*
2420
+ Expected v1 format:
2421
+ ===================
2422
+ {
2423
+ "response": {
2424
+ "handles": [
2425
+ "0xb0b1af7734450c2b7d944571af7e5b438cc62a2a26000000000000aa36a70400"
2426
+ ],
2427
+ "signatures": [
2428
+ "0x70dcb78534f05c4448d3441b4704d3ff4a8478af56a3464497533c2e3c476d77165b09028847f0c3ed4b342b1e8b4252a93b521a3d8d07b724bcff740383e1361b"
2429
+ ]
2430
+ }
2431
+ }
2432
+ */
2433
+ const json = await fetchRelayerJsonRpcPost('INPUT_PROOF', this.inputProof, payload, options);
2434
+ assertIsRelayerInputProofResult(json.response, 'fetchPostInputProof()');
2435
+ return json.response;
2436
+ }
2437
+ async fetchPostPublicDecrypt(payload, options) {
2438
+ const json = await fetchRelayerJsonRpcPost('PUBLIC_DECRYPT', this.publicDecrypt, payload, options);
2439
+ const response = json.response[0];
2440
+ const result = {
2441
+ signatures: response.signatures,
2442
+ decryptedValue: response.decrypted_value,
2443
+ extraData: '0x',
2444
+ };
2445
+ assertIsRelayerPublicDecryptResult(result, 'fetchPostPublicDecrypt()');
2446
+ return result;
2447
+ }
2448
+ async fetchPostUserDecrypt(payload, options) {
2449
+ const json = await fetchRelayerJsonRpcPost('USER_DECRYPT', this.userDecrypt, payload, options);
2450
+ assertIsRelayerUserDecryptResult(json.response, 'RelayerUserDecryptResult()');
2451
+ return json.response;
2452
+ }
2453
+ }
2454
+
2455
+ function ensureError(e) {
2456
+ if (e instanceof Error) {
2457
+ return e;
2458
+ }
2459
+ const message = e.message ?? 'Non-Error value caught in exception handler';
2460
+ const name = e.name ?? 'ErrorWrapper';
2461
+ const cause = e.cause ?? e;
2462
+ const err = new Error(message, { cause });
2463
+ err.name = name;
2464
+ return err;
2465
+ }
2466
+
2467
+ class RelayerV2ProviderError extends RelayerErrorBase {
2468
+ _operation;
2469
+ constructor(params) {
2470
+ super(params);
2471
+ this._operation = params.operation;
2472
+ }
2473
+ get operation() {
2474
+ return this._operation;
2475
+ }
2476
+ }
2477
+ class RelayerV2GetKeyUrlError extends RelayerV2ProviderError {
2478
+ constructor({ cause }) {
2479
+ super({
2480
+ message: `Invalid relayer response.`,
2481
+ name: 'RelayerV2GetKeyUrlError',
2482
+ operation: 'KEY_URL',
2483
+ cause,
2484
+ });
2485
+ }
2486
+ }
2487
+ class RelayerV2GetKeyUrlInvalidResponseError extends RelayerV2GetKeyUrlError {
2488
+ constructor({ cause }) {
2489
+ super({ cause });
2490
+ }
2491
+ }
2492
+
2493
+ function assertIsRelayerV2ApiError400NoDetails(value, name) {
2494
+ assertRecordStringProperty(value, 'label', name);
2495
+ if (!(value.label === 'malformed_json' ||
2496
+ value.label === 'request_error' ||
2497
+ value.label === 'not_ready_for_decryption')) {
2498
+ throw new InvalidPropertyError({
2499
+ objName: name,
2500
+ property: 'label',
2501
+ expectedType: 'string',
2502
+ expectedValue: [
2503
+ 'malformed_json',
2504
+ 'request_error',
2505
+ 'not_ready_for_decryption',
2506
+ ],
2507
+ type: typeof value.label, // === "string"
2508
+ value: value.label,
2509
+ });
2510
+ }
2511
+ assertRecordStringProperty(value, 'message', name);
2512
+ }
2513
+
2514
+ /*
2515
+ type RelayerV2ApiError400WithDetails = {
2516
+ label: 'missing_fields' | 'validation_failed';
2517
+ message: string;
2518
+ details: Array<RelayerV2ErrorDetail>;
2519
+ };
2520
+ type RelayerV2ErrorDetail = {
2521
+ field: string;
2522
+ issue: string;
2523
+ }
2524
+ */
2525
+ function assertIsRelayerV2ApiError400WithDetails(value, name) {
2526
+ assertRecordStringProperty(value, 'label', name);
2527
+ if (!(value.label === 'missing_fields' || value.label === 'validation_failed')) {
2528
+ throw new InvalidPropertyError({
2529
+ objName: name,
2530
+ property: 'label',
2531
+ expectedType: 'string',
2532
+ expectedValue: ['missing_fields', 'validation_failed'],
2533
+ type: typeof value.label,
2534
+ value: value.label,
2535
+ });
2536
+ }
2537
+ assertRecordStringProperty(value, 'message', name);
2538
+ assertRecordArrayProperty(value, 'details', name);
2539
+ const arr = value.details;
2540
+ for (let i = 0; i < arr.length; ++i) {
2541
+ const detail = arr[i];
2542
+ assertRecordStringProperty(detail, 'field', `${name}.details[${i}]`);
2543
+ assertRecordStringProperty(detail, 'issue', `${name}.details[${i}]`);
2544
+ }
2545
+ }
2546
+
2547
+ /*
2548
+ export type RelayerV2ApiError429 = {
2549
+ label: 'rate_limited';
2550
+ message: string;
2551
+ };
2552
+ */
2553
+ function assertIsRelayerV2ApiError429(value, name) {
2554
+ assertRecordStringProperty(value, 'label', name, 'rate_limited');
2555
+ assertRecordStringProperty(value, 'message', name);
2556
+ }
2557
+
2558
+ /*
2559
+ export type RelayerV2ApiError500 = {
2560
+ label: 'internal_server_error';
2561
+ message: string;
2562
+ };
2563
+ */
2564
+ function assertIsRelayerV2ApiError500(value, name) {
2565
+ assertRecordStringProperty(value, 'label', name, 'internal_server_error');
2566
+ assertRecordStringProperty(value, 'message', name);
2567
+ }
2568
+
2569
+ /*
2570
+ export type RelayerV2ApiError404 = {
2571
+ label: 'not_found';
2572
+ message: string;
2573
+ };
2574
+ */
2575
+ function assertIsRelayerV2ApiError404(value, name) {
2576
+ assertRecordStringProperty(value, 'label', name, 'not_found');
2577
+ assertRecordStringProperty(value, 'message', name);
2578
+ }
2579
+
2580
+ /*
2581
+ export type RelayerV2ApiError503 = {
2582
+ label: "protocol_paused" | "gateway_not_reachable";
2583
+ message: string;
2584
+ };
2585
+ */
2586
+ function assertIsRelayerV2ApiError503(value, name) {
2587
+ assertRecordStringProperty(value, 'label', name, [
2588
+ 'protocol_paused',
2589
+ 'gateway_not_reachable',
2590
+ ]);
2591
+ assertRecordStringProperty(value, 'message', name);
2592
+ }
2593
+
2594
+ /*
2595
+ export type RelayerV2ApiError504 = {
2596
+ label: 'readiness_check_timedout' | 'response_timedout';
2597
+ message: string;
2598
+ };
2599
+ */
2600
+ function assertIsRelayerV2ApiError504(value, name) {
2601
+ assertRecordStringProperty(value, 'label', name, [
2602
+ 'readiness_check_timedout',
2603
+ 'response_timedout',
2604
+ ]);
2605
+ assertRecordStringProperty(value, 'message', name);
2606
+ }
2607
+
2608
+ function assertIsRelayerV2ResponseFailed(value, name) {
2609
+ assertRecordStringProperty(value, 'status', name, 'failed');
2610
+ assertNonNullableRecordProperty(value, 'error', name);
2611
+ assertIsRelayerV2ApiError(value.error, `${name}.error`);
2612
+ }
2613
+ function assertIsRelayerV2ApiError(value, name) {
2614
+ assertRecordStringProperty(value, 'label', name);
2615
+ // 400
2616
+ if (value.label === 'malformed_json' ||
2617
+ value.label === 'request_error' ||
2618
+ value.label === 'not_ready_for_decryption') {
2619
+ assertIsRelayerV2ApiError400NoDetails(value, name);
2620
+ }
2621
+ // 400 (with details)
2622
+ else if (value.label === 'missing_fields' ||
2623
+ value.label === 'validation_failed') {
2624
+ assertIsRelayerV2ApiError400WithDetails(value, name);
2625
+ }
2626
+ // 404
2627
+ else if (value.label === 'not_found') {
2628
+ assertIsRelayerV2ApiError404(value, name);
2629
+ }
2630
+ // 429
2631
+ else if (value.label === 'rate_limited') {
2632
+ assertIsRelayerV2ApiError429(value, name);
2633
+ }
2634
+ // 500
2635
+ else if (value.label === 'internal_server_error') {
2636
+ assertIsRelayerV2ApiError500(value, name);
2637
+ }
2638
+ // 503
2639
+ else if (value.label === 'protocol_paused' ||
2640
+ value.label === 'gateway_not_reachable') {
2641
+ assertIsRelayerV2ApiError503(value, name);
2642
+ }
2643
+ // 504
2644
+ else if (value.label === 'readiness_check_timedout' ||
2645
+ value.label === 'response_timedout') {
2646
+ assertIsRelayerV2ApiError504(value, name);
2647
+ }
2648
+ // Unsupported
2649
+ else {
2650
+ throw new InvalidPropertyError({
2651
+ objName: name,
2652
+ property: 'label',
2653
+ expectedType: 'string',
2654
+ expectedValue: [
2655
+ 'malformed_json',
2656
+ 'request_error',
2657
+ 'not_ready_for_decryption',
2658
+ 'missing_fields',
2659
+ 'validation_failed',
2660
+ 'rate_limited',
2661
+ 'internal_server_error',
2662
+ 'protocol_paused',
2663
+ 'gateway_not_reachable',
2664
+ 'readiness_check_timedout',
2665
+ 'response_timedout',
2666
+ ],
2667
+ type: typeof value.label,
2668
+ value: value.label,
2669
+ });
2670
+ }
2671
+ }
2672
+ ////////////////////////////////////////////////////////////////////////////////
2673
+ // 400
2674
+ ////////////////////////////////////////////////////////////////////////////////
2675
+ function assertIsRelayerV2ResponseFailedWithError400(value, name) {
2676
+ assertIsRelayerV2ResponseFailed(value, name);
2677
+ if (value.error.label === 'malformed_json' ||
2678
+ value.error.label === 'request_error' ||
2679
+ value.error.label === 'not_ready_for_decryption') {
2680
+ assertIsRelayerV2ApiError400NoDetails(value.error, `${name}.error`);
2681
+ }
2682
+ else if (value.error.label === 'missing_fields' ||
2683
+ value.error.label === 'validation_failed') {
2684
+ assertIsRelayerV2ApiError400WithDetails(value.error, `${name}.error`);
2685
+ }
2686
+ else {
2687
+ throw new InvalidPropertyError({
2688
+ objName: `${name}.error`,
2689
+ property: 'label',
2690
+ expectedType: 'string',
2691
+ expectedValue: [
2692
+ 'malformed_json',
2693
+ 'request_error',
2694
+ 'not_ready_for_decryption',
2695
+ 'missing_fields',
2696
+ 'validation_failed',
2697
+ ],
2698
+ type: typeof value.error.label,
2699
+ value: value.error.label,
2700
+ });
2701
+ }
2702
+ }
2703
+ ////////////////////////////////////////////////////////////////////////////////
2704
+ // 404
2705
+ ////////////////////////////////////////////////////////////////////////////////
2706
+ function assertIsRelayerV2ResponseFailedWithError404(value, name) {
2707
+ assertIsRelayerV2ResponseFailed(value, name);
2708
+ assertIsRelayerV2ApiError404(value.error, `${name}.error`);
2709
+ }
2710
+ ////////////////////////////////////////////////////////////////////////////////
2711
+ // 429
2712
+ ////////////////////////////////////////////////////////////////////////////////
2713
+ function assertIsRelayerV2ResponseFailedWithError429(value, name) {
2714
+ assertIsRelayerV2ResponseFailed(value, name);
2715
+ assertIsRelayerV2ApiError429(value.error, `${name}.error`);
2716
+ }
2717
+ ////////////////////////////////////////////////////////////////////////////////
2718
+ // 500
2719
+ ////////////////////////////////////////////////////////////////////////////////
2720
+ function assertIsRelayerV2ResponseFailedWithError500(value, name) {
2721
+ assertIsRelayerV2ResponseFailed(value, name);
2722
+ assertIsRelayerV2ApiError500(value.error, `${name}.error`);
2723
+ }
2724
+ ////////////////////////////////////////////////////////////////////////////////
2725
+ // 503
2726
+ ////////////////////////////////////////////////////////////////////////////////
2727
+ function assertIsRelayerV2ResponseFailedWithError503(value, name) {
2728
+ assertIsRelayerV2ResponseFailed(value, name);
2729
+ assertIsRelayerV2ApiError503(value.error, `${name}.error`);
2730
+ }
2731
+ ////////////////////////////////////////////////////////////////////////////////
2732
+ // 504
2733
+ ////////////////////////////////////////////////////////////////////////////////
2734
+ function assertIsRelayerV2ResponseFailedWithError504(value, name) {
2735
+ assertIsRelayerV2ResponseFailed(value, name);
2736
+ assertIsRelayerV2ApiError504(value.error, `${name}.error`);
2737
+ }
2738
+
2739
+ class RelayerV2FetchErrorBase extends RelayerErrorBase {
2740
+ _fetchMethod;
2741
+ _url;
2742
+ _jobId;
2743
+ _operation;
2744
+ _retryCount;
2745
+ _elapsed;
2746
+ _state;
2747
+ constructor(params) {
2748
+ super({
2749
+ ...params,
2750
+ name: params.name ?? 'RelayerV2FetchErrorBase',
2751
+ });
2752
+ this._fetchMethod = params.fetchMethod;
2753
+ this._url = params.url;
2754
+ this._operation = params.operation;
2755
+ this._elapsed = params.elapsed;
2756
+ this._retryCount = params.retryCount;
2757
+ this._state = params.state;
2758
+ this._jobId = params.jobId;
2759
+ }
2760
+ get url() {
2761
+ return this._url;
2762
+ }
2763
+ get operation() {
2764
+ return this._operation;
2765
+ }
2766
+ get fetchMethod() {
2767
+ return this._fetchMethod;
2768
+ }
2769
+ get jobId() {
2770
+ return this._jobId;
2771
+ }
2772
+ get retryCount() {
2773
+ return this._retryCount;
2774
+ }
2775
+ get elapsed() {
2776
+ return this._elapsed;
2777
+ }
2778
+ get state() {
2779
+ return this._state;
2780
+ }
2781
+ get isAbort() {
2782
+ // AbortError is not an instance of Error!
2783
+ return this.cause ? this.cause.name === 'AbortError' : false;
2784
+ }
2785
+ }
2786
+
2787
+ class RelayerV2ResponseErrorBase extends RelayerV2FetchErrorBase {
2788
+ _status;
2789
+ constructor(params) {
2790
+ super({
2791
+ ...params,
2792
+ name: params.name ?? 'RelayerV2ResponseErrorBase',
2793
+ });
2794
+ this._status = params.status;
2795
+ }
2796
+ get status() {
2797
+ return this._status;
2798
+ }
2799
+ }
2800
+
2801
+ class RelayerV2ResponseInvalidBodyError extends RelayerV2ResponseErrorBase {
2802
+ constructor(params) {
2803
+ super({
2804
+ ...params,
2805
+ cause: ensureError(params.cause),
2806
+ name: 'RelayerV2ResponseInvalidBodyError',
2807
+ message: `fetchMethod: ${params.fetchMethod} status:${params.status} url:${params.url} operation:${params.operation}`,
2808
+ });
2809
+ }
2810
+ }
2811
+
2812
+ class RelayerV2ResponseStatusError extends RelayerV2ResponseErrorBase {
2813
+ constructor(params) {
2814
+ super({
2815
+ ...params,
2816
+ name: 'RelayerV2ResponseStatusError',
2817
+ message: `fetchMethod: ${params.fetchMethod} status:${params.status} url:${params.url} operation:${params.operation}`,
2818
+ });
2819
+ }
2820
+ }
2821
+
2822
+ function assertIsRelayerV2ResultInputProof(value, name) {
2823
+ assertRecordBooleanProperty(value, 'accepted', name);
2824
+ if (value.accepted) {
2825
+ assertIsRelayerV2ResultInputProofAccepted(value, name);
2826
+ }
2827
+ else {
2828
+ assertIsRelayerV2ResultInputProofRejected(value, name);
2829
+ }
2830
+ }
2831
+ /*
2832
+ type RelayerV2ResultInputProofAccepted = {
2833
+ accepted: true;
2834
+ extra_data: BytesHex;
2835
+ handles: Bytes32Hex[];
2836
+ signatures: BytesHex[];
2837
+ }
2838
+ */
2839
+ function assertIsRelayerV2ResultInputProofAccepted(value, name) {
2840
+ assertRecordBooleanProperty(value, 'accepted', name, true);
2841
+ assertRecordBytes32HexArrayProperty(value, 'handles', name);
2842
+ assertRecordBytesHexArrayProperty(value, 'signatures', name);
2843
+ assertRecordBytesHexProperty(value, 'extraData', name);
2844
+ }
2845
+ /*
2846
+ type RelayerV2ResultInputProofRejected = {
2847
+ accepted: false;
2848
+ extra_data: BytesHex;
2849
+ }
2850
+ */
2851
+ function assertIsRelayerV2ResultInputProofRejected(value, name) {
2852
+ assertRecordBooleanProperty(value, 'accepted', name, false);
2853
+ assertRecordBytesHexProperty(value, 'extraData', name);
2854
+ }
2855
+
2856
+ function assertIsRelayerV2GetResponseInputProofSucceeded(value, name) {
2857
+ assertNonNullableRecordProperty(value, 'result', name);
2858
+ assertRecordStringProperty(value, 'status', name, 'succeeded');
2859
+ assertRecordStringProperty(value, 'requestId', name);
2860
+ assertIsRelayerV2ResultInputProof(value.result, `${name}.result`);
2861
+ }
2862
+
2863
+ function assertIsRelayerV2ResultPublicDecrypt(value, name) {
2864
+ assertRecordBytesHexNo0xArrayProperty(value, 'signatures', name);
2865
+ assertRecordBytesHexNo0xProperty(value, 'decryptedValue', name);
2866
+ assertRecordBytesHexProperty(value, 'extraData', name);
2867
+ }
2868
+
2869
+ function assertIsRelayerV2GetResponsePublicDecryptSucceeded(value, name) {
2870
+ assertNonNullableRecordProperty(value, 'result', name);
2871
+ assertRecordStringProperty(value, 'status', name, 'succeeded');
2872
+ assertRecordStringProperty(value, 'requestId', name);
2873
+ assertIsRelayerV2ResultPublicDecrypt(value.result, `${name}.result`);
2874
+ }
2875
+
2876
+ /**
2877
+ * Assertion function that validates a value is a valid `RelayerV2ResultUserDecrypt` object.
2878
+ * Validates the structure returned from the relayer for user decryption operations.
2879
+ * Throws an `InvalidPropertyError` if validation fails.
2880
+ *
2881
+ * @param value - The value to validate (can be any type)
2882
+ * @param name - The name of the value being validated (used in error messages)
2883
+ * @throws {InvalidPropertyError} When any required property is missing or has an invalid format
2884
+ * @throws {never} No other errors are thrown
2885
+ */
2886
+ function assertIsRelayerV2ResultUserDecrypt(value, name) {
2887
+ assertRecordArrayProperty(value, 'result', name);
2888
+ for (let i = 0; i < value.result.length; ++i) {
2889
+ // Missing extraData
2890
+ assertRecordBytesHexNo0xProperty(value.result[i], 'payload', `${name}.result[${i}]`);
2891
+ assertRecordBytesHexNo0xProperty(value.result[i], 'signature', `${name}.result[${i}]`);
2892
+ }
2893
+ }
2894
+
2895
+ function assertIsRelayerV2GetResponseUserDecryptSucceeded(value, name) {
2896
+ assertNonNullableRecordProperty(value, 'result', name);
2897
+ assertRecordStringProperty(value, 'status', name, 'succeeded');
2898
+ assertRecordStringProperty(value, 'requestId', name);
2899
+ assertIsRelayerV2ResultUserDecrypt(value.result, `${name}.result`);
2900
+ }
2901
+
2902
+ class RelayerV2RequestErrorBase extends RelayerErrorBase {
2903
+ _url;
2904
+ _operation;
2905
+ _jobId;
2906
+ constructor(params) {
2907
+ super({ ...params, name: params.name ?? 'RelayerV2RequestErrorBase' });
2908
+ this._url = params.url;
2909
+ this._operation = params.operation;
2910
+ this._jobId = params.jobId;
2911
+ }
2912
+ get url() {
2913
+ return this._url;
2914
+ }
2915
+ get jobId() {
2916
+ return this._jobId;
2917
+ }
2918
+ get operation() {
2919
+ return this._operation;
2920
+ }
2921
+ }
2922
+
2923
+ class RelayerV2RequestInternalError extends RelayerV2RequestErrorBase {
2924
+ _status;
2925
+ _state;
2926
+ constructor(params) {
2927
+ super({
2928
+ ...params,
2929
+ name: 'RelayerV2RequestInternalError',
2930
+ message: params.message ?? 'internal error',
2931
+ });
2932
+ this._status = params.status;
2933
+ this._state = params.state;
2934
+ }
2935
+ get status() {
2936
+ return this._status;
2937
+ }
2938
+ get state() {
2939
+ return this._state;
2940
+ }
2941
+ }
2942
+
2943
+ function assertIsRelayerV2ResultQueued(value, name) {
2944
+ assertRecordStringProperty(value, 'jobId', name);
2945
+ }
2946
+
2947
+ /*
2948
+ type RelayerV2ResponseQueued = {
2949
+ status: "queued";
2950
+ result: RelayerV2ResultQueued;
2951
+ }
2952
+ */
2953
+ function assertIsRelayerV2ResponseQueued(value, name) {
2954
+ assertRecordStringProperty(value, 'status', name, 'queued');
2955
+ assertNonNullableRecordProperty(value, 'result', name);
2956
+ assertIsRelayerV2ResultQueued(value.result, `${name}.result`);
2957
+ }
2958
+
2959
+ class RelayerV2ResponseApiError extends RelayerV2ResponseErrorBase {
2960
+ constructor(params) {
2961
+ super({
2962
+ ...params,
2963
+ name: 'RelayerV2ResponseApiError',
2964
+ message: params.relayerApiError.message,
2965
+ });
2966
+ }
2967
+ }
2968
+
2969
+ class RelayerV2FetchError extends RelayerV2FetchErrorBase {
2970
+ constructor(params) {
2971
+ super({
2972
+ ...params,
2973
+ name: 'RelayerV2FetchError',
2974
+ message: `Fetch ${params.fetchMethod} error`,
2975
+ cause: ensureError(params.cause),
2976
+ });
2977
+ }
2978
+ }
2979
+
2980
+ class RelayerV2ResponseInputProofRejectedError extends RelayerV2ResponseErrorBase {
2981
+ _result;
2982
+ constructor(params) {
2983
+ super({
2984
+ ...params,
2985
+ name: 'RelayerV2ResponseInputProofRejectedError',
2986
+ message: `InputProof rejected`,
2987
+ });
2988
+ this._result = params.result;
2989
+ }
2990
+ get result() {
2991
+ return this._result;
2992
+ }
2993
+ }
2994
+
2995
+ class RelayerV2StateError extends RelayerErrorBase {
2996
+ _state;
2997
+ constructor(params) {
2998
+ super({
2999
+ ...params,
3000
+ name: 'RelayerV2StateError',
3001
+ });
3002
+ this._state = { ...params.state };
3003
+ Object.freeze(this._state);
3004
+ }
3005
+ get state() {
3006
+ return this._state;
3007
+ }
3008
+ }
3009
+
3010
+ class RelayerV2MaxRetryError extends RelayerV2FetchErrorBase {
3011
+ constructor(params) {
3012
+ super({
3013
+ ...params,
3014
+ name: 'RelayerV2MaxRetryError',
3015
+ message: 'max retry error',
3016
+ });
3017
+ }
3018
+ }
3019
+
3020
+ class RelayerV2AsyncRequest {
3021
+ _jobId;
3022
+ _jobIdTimestamp;
3023
+ _state;
3024
+ _relayerOperation;
3025
+ _publicAPINoReentrancy;
3026
+ _internalAbortController;
3027
+ _internalAbortSignal;
3028
+ _externalAbortSignal;
3029
+ _terminateReason;
3030
+ _terminateError;
3031
+ _retryCount;
3032
+ _retryAfterTimeoutID;
3033
+ _url;
3034
+ _payload;
3035
+ _fhevmInstanceOptions;
3036
+ _retryAfterTimeoutPromiseFuncReject;
3037
+ _onProgress;
3038
+ _requestMaxDurationInSecs;
3039
+ _requestStartTimestamp;
3040
+ _requestGlobalTimeoutID;
3041
+ _throwErrorIfNoRetryAfter;
3042
+ static DEFAULT_RETRY_AFTER_SECS = 2;
3043
+ static DEFAULT_GLOBAL_REQUEST_TIMEOUT_SECS = 60 * 60;
3044
+ static MAX_GET_RETRY = 100;
3045
+ static MAX_POST_RETRY = 100;
3046
+ constructor(params) {
3047
+ if (params.relayerOperation !== 'INPUT_PROOF' &&
3048
+ params.relayerOperation !== 'PUBLIC_DECRYPT' &&
3049
+ params.relayerOperation !== 'USER_DECRYPT') {
3050
+ throw new InvalidPropertyError({
3051
+ objName: 'RelayerV2AsyncRequestParams',
3052
+ property: 'relayerOperation',
3053
+ expectedType: 'string',
3054
+ value: params.relayerOperation,
3055
+ expectedValue: 'INPUT_PROOF | PUBLIC_DECRYPT | USER_DECRYPT',
3056
+ });
3057
+ }
3058
+ this._relayerOperation = params.relayerOperation;
3059
+ this._internalAbortController = new AbortController();
3060
+ this._internalAbortSignal = this._internalAbortController.signal;
3061
+ this._internalAbortSignal.addEventListener('abort', this._handleInternalSignalAbort);
3062
+ this._externalAbortSignal = params.signal;
3063
+ if (this._externalAbortSignal) {
3064
+ this._externalAbortSignal.addEventListener('abort', this._handleExternalSignalAbort);
3065
+ }
3066
+ this._url = params.url;
3067
+ this._payload = params.payload;
3068
+ this._fhevmInstanceOptions = params.instanceOptions;
3069
+ this._onProgress = params.onProgress;
3070
+ this._state = {
3071
+ aborted: false,
3072
+ canceled: false,
3073
+ failed: false,
3074
+ fetching: false,
3075
+ running: false,
3076
+ succeeded: false,
3077
+ terminated: false,
3078
+ };
3079
+ this._retryCount = 0;
3080
+ this._retryAfterTimeoutID = undefined;
3081
+ this._requestGlobalTimeoutID = undefined;
3082
+ this._terminateReason = undefined;
3083
+ this._publicAPINoReentrancy = false;
3084
+ this._throwErrorIfNoRetryAfter = params.throwErrorIfNoRetryAfter ?? false;
3085
+ this._requestMaxDurationInSecs =
3086
+ params.timeoutInSeconds ??
3087
+ RelayerV2AsyncRequest.DEFAULT_GLOBAL_REQUEST_TIMEOUT_SECS;
3088
+ }
3089
+ //////////////////////////////////////////////////////////////////////////////
3090
+ // Public API: run
3091
+ //////////////////////////////////////////////////////////////////////////////
3092
+ async run() {
3093
+ if (this._publicAPINoReentrancy) {
3094
+ throw new RelayerV2StateError({
3095
+ message: `Relayer.run() failed. Call not permitted.`,
3096
+ state: { ...this._state },
3097
+ });
3098
+ }
3099
+ if (this._state.terminated) {
3100
+ throw new RelayerV2StateError({
3101
+ message: `Relayer.run() failed. Request already terminated.`,
3102
+ state: { ...this._state },
3103
+ });
3104
+ }
3105
+ if (this._state.canceled) {
3106
+ throw new RelayerV2StateError({
3107
+ message: `Relayer.run() failed. Request already canceled.`,
3108
+ state: { ...this._state },
3109
+ });
3110
+ }
3111
+ if (this._state.succeeded) {
3112
+ throw new RelayerV2StateError({
3113
+ message: `Relayer.run() failed. Request already succeeded.`,
3114
+ state: { ...this._state },
3115
+ });
3116
+ }
3117
+ if (this._state.failed) {
3118
+ throw new RelayerV2StateError({
3119
+ message: `Relayer.run() failed. Request already failed.`,
3120
+ state: { ...this._state },
3121
+ });
3122
+ }
3123
+ if (this._state.aborted) {
3124
+ throw new RelayerV2StateError({
3125
+ message: `Relayer.run() failed. Request already aborted.`,
3126
+ state: { ...this._state },
3127
+ });
3128
+ }
3129
+ if (this._externalAbortSignal?.aborted === true) {
3130
+ throw new RelayerV2StateError({
3131
+ message: `Relayer.run() failed. External AbortSignal already aborted (reason:${this._externalAbortSignal?.reason}).`,
3132
+ state: { ...this._state },
3133
+ });
3134
+ }
3135
+ if (this._internalAbortSignal?.aborted === true) {
3136
+ throw new RelayerV2StateError({
3137
+ message: `Relayer.run() failed. Internal AbortSignal already aborted (reason:${this._internalAbortSignal?.reason}).`,
3138
+ state: { ...this._state },
3139
+ });
3140
+ }
3141
+ if (this._state.running) {
3142
+ throw new RelayerV2StateError({
3143
+ message: `Relayer.run() failed. Request already running.`,
3144
+ state: { ...this._state },
3145
+ });
3146
+ }
3147
+ this._state.running = true;
3148
+ this._requestStartTimestamp = Date.now();
3149
+ this._setGlobalRequestTimeout(this._requestMaxDurationInSecs * 1000);
3150
+ try {
3151
+ const json = await this._runPostLoop();
3152
+ this._state.succeeded = true;
3153
+ this._terminate('succeeded');
3154
+ return json;
3155
+ }
3156
+ catch (e) {
3157
+ this._state.failed = true;
3158
+ if (e.name === 'AbortError') {
3159
+ this._assert(this._state.aborted, 'this._state.aborted');
3160
+ this._assert(this._state.terminated, 'this._state.terminated');
3161
+ }
3162
+ // Ignored if already terminated. For example, if abort has been previously called.
3163
+ this._terminate('failed', e);
3164
+ throw e;
3165
+ }
3166
+ }
3167
+ //////////////////////////////////////////////////////////////////////////////
3168
+ // Public API: cancel
3169
+ //////////////////////////////////////////////////////////////////////////////
3170
+ _canContinue() {
3171
+ return !(this._state.canceled ||
3172
+ this._state.terminated ||
3173
+ this._state.succeeded ||
3174
+ this._state.aborted);
3175
+ }
3176
+ cancel() {
3177
+ if (this._publicAPINoReentrancy) {
3178
+ throw new RelayerV2StateError({
3179
+ message: `Relayer.cancel() failed. Call not permitted.`,
3180
+ state: { ...this._state },
3181
+ });
3182
+ }
3183
+ if (!this._canContinue()) {
3184
+ this._trace('cancel', '!this._canContinue()');
3185
+ return;
3186
+ }
3187
+ this._state.canceled = true;
3188
+ this._internalAbortController?.abort('cancel');
3189
+ // Debug
3190
+ this._assert(this._state.aborted, 'this._state.aborted');
3191
+ this._assert(this._state.terminated, 'this._state.terminated');
3192
+ }
3193
+ //////////////////////////////////////////////////////////////////////////////
3194
+ // Public API: getters
3195
+ //////////////////////////////////////////////////////////////////////////////
3196
+ get state() {
3197
+ return { ...this._state };
3198
+ }
3199
+ get canceled() {
3200
+ return this._state.canceled;
3201
+ }
3202
+ get terminated() {
3203
+ return this._state.terminated;
3204
+ }
3205
+ get terminateReason() {
3206
+ return this._terminateReason;
3207
+ }
3208
+ get terminateError() {
3209
+ return this._terminateError;
3210
+ }
3211
+ get running() {
3212
+ return this._state.running;
3213
+ }
3214
+ get fetching() {
3215
+ return this._state.fetching;
3216
+ }
3217
+ get failed() {
3218
+ return this._state.failed;
3219
+ }
3220
+ get succeeded() {
3221
+ return this._state.succeeded;
3222
+ }
3223
+ get startTimeMs() {
3224
+ return this._requestStartTimestamp;
3225
+ }
3226
+ get elapsedTimeMs() {
3227
+ if (this._requestStartTimestamp === undefined) {
3228
+ return undefined;
3229
+ }
3230
+ return Date.now() - this._requestStartTimestamp;
3231
+ }
3232
+ get retryCount() {
3233
+ return this._retryCount;
3234
+ }
3235
+ //////////////////////////////////////////////////////////////////////////////
3236
+ // Post Loop
3237
+ //////////////////////////////////////////////////////////////////////////////
3238
+ // POST : 202 | 400 | 429 | 500 | 503
3239
+ async _runPostLoop() {
3240
+ // No infinite loop!
3241
+ let i = 0;
3242
+ while (i < RelayerV2AsyncRequest.MAX_POST_RETRY) {
3243
+ ++i;
3244
+ this._assertCanContinueAfterAwait();
3245
+ // At this stage: `terminated` is guaranteed to be `false`.
3246
+ // However, the `fetch` call can potentially throw an `AbortError`. In this case
3247
+ // in the error catch the `terminated` flag will be `true`! But, that's ok because the
3248
+ // next part of the function will never be executed (thrown error).
3249
+ const elapsed = this._jobId ? Date.now() - this._jobIdTimestamp : 0;
3250
+ const response = await this._fetchPost(elapsed);
3251
+ // At this stage: `terminated` is guaranteed to be `false`.
3252
+ const responseStatus = response.status;
3253
+ switch (responseStatus) {
3254
+ // RelayerV2ResponseQueued
3255
+ case 202: {
3256
+ // response.json() errors:
3257
+ // 1. if body is already read (call json() 2 times)
3258
+ // - TypeError: Body is unusable: Body has already been read
3259
+ // 2. if body is invalid JSON
3260
+ // - SyntaxError: Unexpected end of JSON input
3261
+ // - SyntaxError: Expected property name or '}' in JSON at position 1 (line 1 column 2) at JSON.parse (<anonymous>)
3262
+ const bodyJson = await this._getResponseJson(response);
3263
+ try {
3264
+ assertIsRelayerV2ResponseQueued(bodyJson, 'body');
3265
+ }
3266
+ catch (cause) {
3267
+ this._throwInvalidResponseError({
3268
+ fetchMethod: 'POST',
3269
+ status: responseStatus,
3270
+ cause: cause,
3271
+ elapsed,
3272
+ });
3273
+ }
3274
+ let retry_after_sec = this._getRetryAfterHeaderValueInSecs(response);
3275
+ if (retry_after_sec < 1)
3276
+ retry_after_sec = 1;
3277
+ // Debug: will throw an assert failed error if jobId has already been set
3278
+ this._setJobIdOnce(bodyJson.result.jobId);
3279
+ // Async onProgress callback
3280
+ this._postAsyncOnProgressCallback({
3281
+ type: 'queued',
3282
+ url: this._url,
3283
+ method: 'POST',
3284
+ status: responseStatus,
3285
+ requestId: bodyJson.requestId,
3286
+ jobId: this.jobId,
3287
+ operation: this._relayerOperation,
3288
+ retryCount: this._retryCount,
3289
+ retryAfter: retry_after_sec,
3290
+ elapsed,
3291
+ });
3292
+ // Wait if needed (minimum 1s)
3293
+ await this._setRetryAfterTimeout(retry_after_sec * 1000);
3294
+ const json = await this._runGetLoop();
3295
+ return json;
3296
+ }
3297
+ // RelayerV2ResponseFailed
3298
+ // RelayerV2ApiError400
3299
+ // RelayerV2ApiError400WithDetails
3300
+ case 400: {
3301
+ const bodyJson = await this._getResponseJson(response);
3302
+ try {
3303
+ assertIsRelayerV2ResponseFailedWithError400(bodyJson, 'body');
3304
+ }
3305
+ catch (cause) {
3306
+ this._throwInvalidResponseError({
3307
+ fetchMethod: 'POST',
3308
+ status: responseStatus,
3309
+ cause: cause,
3310
+ elapsed,
3311
+ });
3312
+ }
3313
+ this._throwRelayerV2ResponseApiError({
3314
+ fetchMethod: 'POST',
3315
+ status: responseStatus,
3316
+ relayerApiError: bodyJson.error,
3317
+ elapsed,
3318
+ });
3319
+ }
3320
+ // RelayerV2ResponseFailed
3321
+ // RelayerV2ApiError429
3322
+ case 429: {
3323
+ // Retry
3324
+ // Rate Limit error (Cloudflare/Kong/Relayer), reason in message
3325
+ const bodyJson = await this._getResponseJson(response);
3326
+ try {
3327
+ assertIsRelayerV2ResponseFailedWithError429(bodyJson, 'body');
3328
+ }
3329
+ catch (cause) {
3330
+ this._throwInvalidResponseError({
3331
+ fetchMethod: 'POST',
3332
+ status: responseStatus,
3333
+ cause: cause,
3334
+ elapsed,
3335
+ });
3336
+ }
3337
+ let retry_after_sec = this._getRetryAfterHeaderValueInSecs(response);
3338
+ if (retry_after_sec < 1)
3339
+ retry_after_sec = 1;
3340
+ // Async onProgress callback
3341
+ this._postAsyncOnProgressCallback({
3342
+ type: 'ratelimited',
3343
+ url: this._url,
3344
+ method: 'POST',
3345
+ status: responseStatus,
3346
+ retryAfter: retry_after_sec,
3347
+ retryCount: this._retryCount,
3348
+ elapsed,
3349
+ message: bodyJson.error.message,
3350
+ });
3351
+ // Wait if needed (minimum 1s)
3352
+ await this._setRetryAfterTimeout(retry_after_sec * 1000);
3353
+ continue;
3354
+ }
3355
+ // RelayerV2ResponseFailed
3356
+ // RelayerV2ApiError500
3357
+ case 500: {
3358
+ // Abort
3359
+ // Relayer internal error
3360
+ const bodyJson = await this._getResponseJson(response);
3361
+ try {
3362
+ assertIsRelayerV2ResponseFailedWithError500(bodyJson, 'body');
3363
+ }
3364
+ catch (cause) {
3365
+ this._throwInvalidResponseError({
3366
+ fetchMethod: 'POST',
3367
+ status: responseStatus,
3368
+ cause: cause,
3369
+ elapsed,
3370
+ });
3371
+ }
3372
+ this._throwRelayerV2ResponseApiError({
3373
+ fetchMethod: 'POST',
3374
+ status: responseStatus,
3375
+ relayerApiError: bodyJson.error,
3376
+ elapsed,
3377
+ });
3378
+ }
3379
+ // RelayerV2ResponseFailed
3380
+ // RelayerV2ApiError503
3381
+ case 503: {
3382
+ // Abort
3383
+ // Possible Reasons: Gateway has some internal error (unknown)
3384
+ const bodyJson = await this._getResponseJson(response);
3385
+ try {
3386
+ assertIsRelayerV2ResponseFailedWithError503(bodyJson, 'body');
3387
+ }
3388
+ catch (cause) {
3389
+ this._throwInvalidResponseError({
3390
+ fetchMethod: 'POST',
3391
+ status: responseStatus,
3392
+ cause: cause,
3393
+ elapsed,
3394
+ });
3395
+ }
3396
+ this._throwRelayerV2ResponseApiError({
3397
+ fetchMethod: 'POST',
3398
+ status: responseStatus,
3399
+ relayerApiError: bodyJson.error,
3400
+ elapsed,
3401
+ });
3402
+ }
3403
+ default: {
3404
+ // Use TS compiler + `never` to guarantee the switch integrity
3405
+ const throwUnsupportedStatus = (unsupportedStatus) => {
3406
+ throw new RelayerV2ResponseStatusError({
3407
+ fetchMethod: 'POST',
3408
+ status: unsupportedStatus,
3409
+ url: this._url,
3410
+ operation: this._relayerOperation,
3411
+ elapsed,
3412
+ retryCount: this._retryCount,
3413
+ state: { ...this._state },
3414
+ });
3415
+ };
3416
+ throwUnsupportedStatus(responseStatus);
3417
+ }
3418
+ }
3419
+ }
3420
+ // Max retry error
3421
+ this._throwMaxRetryError({ fetchMethod: 'POST' });
3422
+ }
3423
+ //////////////////////////////////////////////////////////////////////////////
3424
+ // Get Loop
3425
+ //////////////////////////////////////////////////////////////////////////////
3426
+ // GET: 200 | 202 | 404 | 500 | 503 | 504
3427
+ // GET is not rate-limited, therefore there is not 429 error
3428
+ async _runGetLoop() {
3429
+ this._assert(this._jobId !== undefined, 'this._jobId !== undefined');
3430
+ this._assert(this._jobIdTimestamp !== undefined, 'this._jobIdTimestamp !== undefined');
3431
+ let i = 0;
3432
+ while (i < RelayerV2AsyncRequest.MAX_GET_RETRY) {
3433
+ ++i;
3434
+ this._assertCanContinueAfterAwait();
3435
+ const elapsed = Date.now() - this._jobIdTimestamp;
3436
+ const response = await this._fetchGet(elapsed);
3437
+ // At this stage: `terminated` is guaranteed to be `false`.
3438
+ const responseStatus = response.status;
3439
+ switch (responseStatus) {
3440
+ // RelayerV2GetResponseSucceeded
3441
+ case 200: {
3442
+ const bodyJson = await this._getResponseJson(response);
3443
+ try {
3444
+ if (this._relayerOperation === 'INPUT_PROOF') {
3445
+ assertIsRelayerV2GetResponseInputProofSucceeded(bodyJson, 'body');
3446
+ // Async onProgress callback
3447
+ this._postAsyncOnProgressCallback({
3448
+ type: 'succeeded',
3449
+ url: this._url,
3450
+ method: 'GET',
3451
+ status: responseStatus,
3452
+ jobId: this.jobId,
3453
+ requestId: bodyJson.requestId,
3454
+ operation: this._relayerOperation,
3455
+ retryCount: this._retryCount,
3456
+ elapsed,
3457
+ result: bodyJson.result,
3458
+ });
3459
+ if (!bodyJson.result.accepted) {
3460
+ const e = new RelayerV2ResponseInputProofRejectedError({
3461
+ url: this._url,
3462
+ fetchMethod: 'GET',
3463
+ jobId: this.jobId,
3464
+ operation: this._relayerOperation,
3465
+ retryCount: this._retryCount,
3466
+ status: responseStatus,
3467
+ state: { ...this._state },
3468
+ elapsed,
3469
+ result: bodyJson.result,
3470
+ });
3471
+ throw e;
3472
+ }
3473
+ }
3474
+ else if (this._relayerOperation === 'PUBLIC_DECRYPT') {
3475
+ assertIsRelayerV2GetResponsePublicDecryptSucceeded(bodyJson, 'body');
3476
+ // Async onProgress callback
3477
+ this._postAsyncOnProgressCallback({
3478
+ type: 'succeeded',
3479
+ url: this._url,
3480
+ method: 'GET',
3481
+ status: responseStatus,
3482
+ jobId: this.jobId,
3483
+ requestId: bodyJson.requestId,
3484
+ operation: this._relayerOperation,
3485
+ retryCount: this._retryCount,
3486
+ elapsed,
3487
+ result: bodyJson.result,
3488
+ });
3489
+ }
3490
+ else if (this._relayerOperation === 'USER_DECRYPT') {
3491
+ assertIsRelayerV2GetResponseUserDecryptSucceeded(bodyJson, 'body');
3492
+ // Async onProgress callback
3493
+ this._postAsyncOnProgressCallback({
3494
+ type: 'succeeded',
3495
+ url: this._url,
3496
+ method: 'GET',
3497
+ status: responseStatus,
3498
+ jobId: this.jobId,
3499
+ requestId: bodyJson.requestId,
3500
+ operation: this._relayerOperation,
3501
+ retryCount: this._retryCount,
3502
+ elapsed,
3503
+ result: bodyJson.result,
3504
+ });
3505
+ }
3506
+ }
3507
+ catch (cause) {
3508
+ // Special case for InputProof rejected
3509
+ if (cause instanceof RelayerV2ResponseInputProofRejectedError) {
3510
+ throw cause;
3511
+ }
3512
+ this._throwResponseInvalidBodyError({
3513
+ fetchMethod: 'GET',
3514
+ status: responseStatus,
3515
+ elapsed,
3516
+ cause: cause,
3517
+ });
3518
+ }
3519
+ // RelayerV2ResultPublicDecrypt
3520
+ // RelayerV2ResultUserDecrypt
3521
+ // RelayerV2ResultInputProof;
3522
+ return bodyJson.result;
3523
+ }
3524
+ // RelayerV2ResponseQueued
3525
+ case 202: {
3526
+ const bodyJson = await this._getResponseJson(response);
3527
+ try {
3528
+ assertIsRelayerV2ResponseQueued(bodyJson, 'body');
3529
+ }
3530
+ catch (cause) {
3531
+ this._throwResponseInvalidBodyError({
3532
+ fetchMethod: 'GET',
3533
+ status: responseStatus,
3534
+ elapsed,
3535
+ cause: cause,
3536
+ });
3537
+ }
3538
+ let retry_after_sec = this._getRetryAfterHeaderValueInSecs(response);
3539
+ if (retry_after_sec < 1)
3540
+ retry_after_sec = 1;
3541
+ // Async onProgress callback
3542
+ this._postAsyncOnProgressCallback({
3543
+ type: 'queued',
3544
+ url: this._url,
3545
+ method: 'GET',
3546
+ status: responseStatus,
3547
+ requestId: bodyJson.requestId,
3548
+ operation: this._relayerOperation,
3549
+ jobId: this.jobId,
3550
+ retryAfter: retry_after_sec,
3551
+ retryCount: this._retryCount,
3552
+ elapsed,
3553
+ });
3554
+ // Wait if needed (minimum 1s)
3555
+ await this._setRetryAfterTimeout(retry_after_sec * 1000);
3556
+ continue;
3557
+ }
3558
+ case 400: {
3559
+ // Abort
3560
+ // Wrong jobId, incorrect format or unknown value etc.
3561
+ const bodyJson = await this._getResponseJson(response);
3562
+ try {
3563
+ assertIsRelayerV2ResponseFailedWithError400(bodyJson, 'body');
3564
+ }
3565
+ catch (cause) {
3566
+ this._throwResponseInvalidBodyError({
3567
+ fetchMethod: 'GET',
3568
+ status: responseStatus,
3569
+ elapsed,
3570
+ cause: cause,
3571
+ });
3572
+ }
3573
+ this._throwRelayerV2ResponseApiError({
3574
+ fetchMethod: 'GET',
3575
+ status: responseStatus,
3576
+ relayerApiError: bodyJson.error,
3577
+ elapsed,
3578
+ });
3579
+ }
3580
+ case 404: {
3581
+ // Abort
3582
+ // Wrong jobId, incorrect format or unknown value etc.
3583
+ const bodyJson = await this._getResponseJson(response);
3584
+ try {
3585
+ assertIsRelayerV2ResponseFailedWithError404(bodyJson, 'body');
3586
+ }
3587
+ catch (cause) {
3588
+ this._throwResponseInvalidBodyError({
3589
+ fetchMethod: 'GET',
3590
+ status: responseStatus,
3591
+ elapsed,
3592
+ cause: cause,
3593
+ });
3594
+ }
3595
+ this._throwRelayerV2ResponseApiError({
3596
+ fetchMethod: 'GET',
3597
+ status: responseStatus,
3598
+ relayerApiError: bodyJson.error,
3599
+ elapsed,
3600
+ });
3601
+ }
3602
+ // RelayerV2ResponseFailed
3603
+ // RelayerV2ApiError500
3604
+ case 500: {
3605
+ // Abort
3606
+ // Relayer internal error
3607
+ const bodyJson = await this._getResponseJson(response);
3608
+ try {
3609
+ assertIsRelayerV2ResponseFailedWithError500(bodyJson, 'body');
3610
+ }
3611
+ catch (cause) {
3612
+ this._throwResponseInvalidBodyError({
3613
+ fetchMethod: 'GET',
3614
+ status: responseStatus,
3615
+ elapsed,
3616
+ cause: cause,
3617
+ });
3618
+ }
3619
+ this._throwRelayerV2ResponseApiError({
3620
+ fetchMethod: 'GET',
3621
+ status: responseStatus,
3622
+ relayerApiError: bodyJson.error,
3623
+ elapsed,
3624
+ });
3625
+ }
3626
+ // RelayerV2ResponseFailed
3627
+ // RelayerV2ApiError503
3628
+ case 503: {
3629
+ // Abort
3630
+ // Possible Reasons: Gateway has some internal error (unknown)
3631
+ const bodyJson = await this._getResponseJson(response);
3632
+ try {
3633
+ assertIsRelayerV2ResponseFailedWithError503(bodyJson, 'body');
3634
+ }
3635
+ catch (cause) {
3636
+ this._throwResponseInvalidBodyError({
3637
+ fetchMethod: 'GET',
3638
+ status: responseStatus,
3639
+ elapsed,
3640
+ cause: cause,
3641
+ });
3642
+ }
3643
+ this._throwRelayerV2ResponseApiError({
3644
+ fetchMethod: 'GET',
3645
+ status: responseStatus,
3646
+ relayerApiError: bodyJson.error,
3647
+ elapsed,
3648
+ });
3649
+ }
3650
+ // RelayerV2ResponseFailed
3651
+ // RelayerV2ApiError504
3652
+ case 504: {
3653
+ // Abort
3654
+ // Possible Reasons: Gateway has not responded in time (gateway timeout)
3655
+ const bodyJson = await this._getResponseJson(response);
3656
+ try {
3657
+ assertIsRelayerV2ResponseFailedWithError504(bodyJson, 'body');
3658
+ }
3659
+ catch (cause) {
3660
+ this._throwResponseInvalidBodyError({
3661
+ fetchMethod: 'GET',
3662
+ status: responseStatus,
3663
+ elapsed,
3664
+ cause: cause,
3665
+ });
3666
+ }
3667
+ this._throwRelayerV2ResponseApiError({
3668
+ fetchMethod: 'GET',
3669
+ status: responseStatus,
3670
+ relayerApiError: bodyJson.error,
3671
+ elapsed,
3672
+ });
3673
+ }
3674
+ default: {
3675
+ // Use TS compiler + `never` to guarantee the switch integrity
3676
+ const throwUnsupportedStatus = (unsupportedStatus) => {
3677
+ throw new RelayerV2ResponseStatusError({
3678
+ fetchMethod: 'GET',
3679
+ status: unsupportedStatus,
3680
+ url: this._url,
3681
+ jobId: this.jobId,
3682
+ operation: this._relayerOperation,
3683
+ elapsed,
3684
+ retryCount: this._retryCount,
3685
+ state: { ...this._state },
3686
+ });
3687
+ };
3688
+ throwUnsupportedStatus(responseStatus);
3689
+ }
3690
+ }
3691
+ }
3692
+ // Max retry error
3693
+ this._throwMaxRetryError({ fetchMethod: 'GET' });
3694
+ }
3695
+ //////////////////////////////////////////////////////////////////////////////
3696
+ async _getResponseJson(response) {
3697
+ const bodyJson = await response.json();
3698
+ this._assertCanContinueAfterAwait();
3699
+ return bodyJson;
3700
+ }
3701
+ //////////////////////////////////////////////////////////////////////////////
3702
+ _getRetryAfterHeaderValueInSecs(response) {
3703
+ if (!response.headers.has('Retry-After')) {
3704
+ if (this._throwErrorIfNoRetryAfter) {
3705
+ throw new Error(`Missing 'Retry-After' header key`);
3706
+ }
3707
+ return RelayerV2AsyncRequest.DEFAULT_RETRY_AFTER_SECS;
3708
+ }
3709
+ try {
3710
+ const n = Number.parseInt(response.headers.get('Retry-After'));
3711
+ if (isUint(n)) {
3712
+ return n;
3713
+ }
3714
+ }
3715
+ catch {
3716
+ //
3717
+ }
3718
+ if (this._throwErrorIfNoRetryAfter) {
3719
+ throw new Error(`Invalid 'Retry-After' header key`);
3720
+ }
3721
+ return RelayerV2AsyncRequest.DEFAULT_RETRY_AFTER_SECS;
3722
+ }
3723
+ //////////////////////////////////////////////////////////////////////////////
3724
+ // JobId
3725
+ //////////////////////////////////////////////////////////////////////////////
3726
+ /**
3727
+ * Sets the unique job identifier for this request.
3728
+ *
3729
+ * This function enforces a strict initialization constraint: the jobId must be
3730
+ * set exactly once during the entire lifecycle of the state machine instance.
3731
+ *
3732
+ * This immutability ensures that all subsequent operations, logging, and state
3733
+ * transitions are consistently associated with the correct external request.
3734
+ *
3735
+ * @param jobId - The unique identifier associated with the asynchronous job request.
3736
+ * @private
3737
+ * @throws {RelayerV2RequestInternalError} Thrown if jobId is undefined or if the jobId has already been set.
3738
+ */
3739
+ _setJobIdOnce(jobId) {
3740
+ this._assert(jobId !== undefined, 'jobId !== undefined');
3741
+ this._assert(this._jobId === undefined, 'this._jobId === undefined');
3742
+ this._jobId = jobId;
3743
+ this._jobIdTimestamp = Date.now();
3744
+ }
3745
+ get jobId() {
3746
+ this._assert(this._jobId !== undefined, 'this._jobId !== undefined');
3747
+ return this._jobId;
3748
+ }
3749
+ //////////////////////////////////////////////////////////////////////////////
3750
+ // Fetch functions
3751
+ //////////////////////////////////////////////////////////////////////////////
3752
+ async _fetchPost(elapsed) {
3753
+ // Debug state-check guards:
3754
+ // - the jobId is guaranteed to be undefined.
3755
+ // - `terminated` is guaranteed to be `false`
3756
+ // - `fetching` is guaranteed to be `false`
3757
+ this._assert(this._jobId === undefined, 'this._jobId === undefined');
3758
+ this._assert(!this._state.terminated, '!this._state.terminated');
3759
+ this._assert(!this._state.fetching, '!this._state.fetching');
3760
+ this._trace('_fetchPost', 'enter');
3761
+ const init = setAuth({
3762
+ method: 'POST',
3763
+ headers: {
3764
+ 'Content-Type': 'application/json',
3765
+ },
3766
+ body: JSON.stringify(this._payload),
3767
+ ...(this._internalAbortSignal
3768
+ ? { signal: this._internalAbortSignal }
3769
+ : {}),
3770
+ }, this._fhevmInstanceOptions?.auth);
3771
+ this._state.fetching = true;
3772
+ let response;
3773
+ try {
3774
+ response = await fetch(this._url, init);
3775
+ }
3776
+ catch (cause) {
3777
+ this._state.fetching = false;
3778
+ // Warning: `terminated` can be `true` here!
3779
+ // (ex: if `controller.abort()` has been called from the outside while still executing `fetch`)
3780
+ this._trace('_fetchPost', 'catch(e) + throw e: ' + cause);
3781
+ // Keep the standard 'AbortError'
3782
+ if (cause.name === 'AbortError') {
3783
+ throw cause;
3784
+ }
3785
+ else {
3786
+ this._throwFetchError({
3787
+ fetchMethod: 'POST',
3788
+ cause,
3789
+ elapsed,
3790
+ });
3791
+ }
3792
+ }
3793
+ this._state.fetching = false;
3794
+ // Debug state-check guards:
3795
+ // - the jobId is guaranteed to be undefined.
3796
+ // - `terminated` is guaranteed to be `false`
3797
+ this._assert(this._jobId === undefined, 'this._jobId === undefined');
3798
+ this._assert(!this._state.terminated, '!this._state.terminated');
3799
+ // Debug
3800
+ this._assertCanContinueAfterAwait();
3801
+ this._trace('_fetchPost', 'return response Ok');
3802
+ return response;
3803
+ }
3804
+ //////////////////////////////////////////////////////////////////////////////
3805
+ async _fetchGet(elapsed) {
3806
+ // Debug state-check guards:
3807
+ // - the jobId is guaranteed to be set.
3808
+ // - `terminated` is guaranteed to be `false`
3809
+ // - `fetching` is guaranteed to be `false`
3810
+ this._assert(this._jobId !== undefined, 'this._jobId !== undefined');
3811
+ this._assert(!this._state.terminated, '!this._state.terminated');
3812
+ this._assert(!this._state.fetching, '!this._state.fetching');
3813
+ this._trace('_fetchGet', `jobId=${this.jobId}`);
3814
+ const init = this._internalAbortSignal
3815
+ ? { signal: this._internalAbortSignal }
3816
+ : undefined;
3817
+ this._state.fetching = true;
3818
+ let response;
3819
+ try {
3820
+ response = await fetch(`${this._url}/${this.jobId}`, init);
3821
+ }
3822
+ catch (cause) {
3823
+ this._state.fetching = false;
3824
+ // Warning: `terminated` can be `true` here!
3825
+ // (ex: if `controller.abort()` has been called from the outside while still executing `fetch`)
3826
+ this._trace('_fetchGet', `jobId=${this.jobId}, catch(e) + throw e: ${cause}`);
3827
+ // Keep the standard 'AbortError'
3828
+ if (cause.name === 'AbortError') {
3829
+ throw cause;
3830
+ }
3831
+ else {
3832
+ this._throwFetchError({
3833
+ fetchMethod: 'GET',
3834
+ cause,
3835
+ elapsed,
3836
+ });
3837
+ }
3838
+ }
3839
+ this._state.fetching = false;
3840
+ // Debug state-check guards:
3841
+ // - the jobId is guaranteed to be set.
3842
+ // - `terminated` is guaranteed to be `false`
3843
+ this._assert(this._jobId !== undefined, 'this._jobId !== undefined');
3844
+ this._assert(!this._state.terminated, '!this._state.terminated');
3845
+ // Debug
3846
+ this._assertCanContinueAfterAwait();
3847
+ this._trace('_fetchGet', `jobId=${this.jobId}, return response Ok, status=${response.status}`);
3848
+ return response;
3849
+ }
3850
+ //////////////////////////////////////////////////////////////////////////////
3851
+ // AbortSignal
3852
+ //////////////////////////////////////////////////////////////////////////////
3853
+ // Warning: Use arrow function only!
3854
+ _handleExternalSignalAbort = (ev) => {
3855
+ const signal = ev.currentTarget;
3856
+ // TESTING: the following sequences must be extensively tested:
3857
+ // ============================================================
3858
+ //
3859
+ // Each steps could potentially be called synchronously one after the other
3860
+ // or asynchronously: step 2 is called from the next microtick
3861
+ //
3862
+ // 1. externalSignal.abort();
3863
+ // 2. request.cancel();
3864
+ //
3865
+ // 1. externalSignal.abort();
3866
+ // 2. externalSignal.abort();
3867
+ //
3868
+ // 1. request.cancel();
3869
+ // 2. externalSignal.abort();
3870
+ // Debug state-check guards:
3871
+ this._assert(this instanceof RelayerV2AsyncRequest, `this instanceof RelayerV2AsyncRequest`);
3872
+ this._assert(signal === this._externalAbortSignal, 'signal === this._externalAbortSignal');
3873
+ this._assert(!this._state.terminated, `!this._state.terminated`);
3874
+ this._assert(!this._state.aborted, '!this._state.aborted');
3875
+ this._assert(!this._state.canceled, '!this._state.canceled');
3876
+ this.cancel();
3877
+ };
3878
+ // Warning: Use arrow function only!
3879
+ _handleInternalSignalAbort = (ev) => {
3880
+ const signal = ev.currentTarget;
3881
+ // Debug state-check guards:
3882
+ this._assert(this instanceof RelayerV2AsyncRequest, `this instanceof RelayerV2AsyncRequest`);
3883
+ this._assert(signal === this._internalAbortSignal, 'signal === this._internalAbortSignal');
3884
+ this._assert(!this._state.terminated, `!this._state.terminated`);
3885
+ this._assert(!this._state.aborted, '!this._state.aborted');
3886
+ this._state.aborted = true;
3887
+ if (signal.reason !== 'cancel') {
3888
+ this._assert(!this._state.canceled, '!this._state.canceled');
3889
+ }
3890
+ this._terminate('abort');
3891
+ };
3892
+ //////////////////////////////////////////////////////////////////////////////
3893
+ // Terminate
3894
+ //////////////////////////////////////////////////////////////////////////////
3895
+ /**
3896
+ * Can be called multiple times
3897
+ */
3898
+ _terminate(reason, error) {
3899
+ // Warning: this._state.fetching can be true
3900
+ // ex: call cancel while fetch is running
3901
+ if (this._state.terminated) {
3902
+ this._trace(`_terminate`, `reason=${reason}. Already terminated with reason='${this._terminateReason}'. IGNORE`);
3903
+ this._assert(this._terminateReason !== undefined, 'this._terminateReason !== undefined');
3904
+ this._assert(this._internalAbortSignal === undefined, 'this._signal === undefined');
3905
+ this._assert(this._requestGlobalTimeoutID === undefined, 'this._requestGlobalTimeoutID === undefined');
3906
+ this._assert(this._retryAfterTimeoutID === undefined, 'this._retryAfterTimeoutID === undefined');
3907
+ this._assert(this._retryAfterTimeoutPromiseFuncReject === undefined, 'this._retryAfterTimeoutPromiseFuncReject === undefined');
3908
+ return;
3909
+ }
3910
+ this._trace('_terminate', `reason=${reason}`);
3911
+ this._terminateReason = reason;
3912
+ this._terminateError = error;
3913
+ this._state.terminated = true;
3914
+ this._tryClearRetryAfterTimeout();
3915
+ this._tryClearGlobalRequestTimeout();
3916
+ const is = this._internalAbortSignal;
3917
+ const es = this._externalAbortSignal;
3918
+ this._externalAbortSignal = undefined;
3919
+ this._internalAbortSignal = undefined;
3920
+ this._internalAbortController = undefined;
3921
+ if (es) {
3922
+ es.removeEventListener('abort', this._handleExternalSignalAbort);
3923
+ }
3924
+ if (is) {
3925
+ is.removeEventListener('abort', this._handleInternalSignalAbort);
3926
+ }
3927
+ this._trace('_terminate', `reason=${reason} completed.`);
3928
+ }
3929
+ //////////////////////////////////////////////////////////////////////////////
3930
+ // Retry-After timeout
3931
+ //////////////////////////////////////////////////////////////////////////////
3932
+ async _setRetryAfterTimeout(delayMs) {
3933
+ // Debug
3934
+ this._assert(!this._state.terminated, '!this._state.terminated');
3935
+ this._assert(this._retryAfterTimeoutID === undefined, 'this._retryAfterTimeoutID === undefined');
3936
+ this._assert(delayMs >= 1000, 'delayMs >= 1000');
3937
+ this._trace('_setRetryAfterTimeout', `delayMs=${delayMs}`);
3938
+ if (this._retryAfterTimeoutID !== undefined) {
3939
+ return Promise.reject(new Error(`retry-after already running.`));
3940
+ }
3941
+ const p = new Promise((resolve, reject) => {
3942
+ this._retryAfterTimeoutPromiseFuncReject = reject;
3943
+ const callback = () => {
3944
+ this._retryAfterTimeoutID = undefined;
3945
+ this._retryAfterTimeoutPromiseFuncReject = undefined;
3946
+ resolve();
3947
+ };
3948
+ this._retryCount++;
3949
+ this._retryAfterTimeoutID = setTimeout(callback, delayMs);
3950
+ });
3951
+ this._assert(this._retryAfterTimeoutID !== undefined, 'this._retryAfterTimeoutID !== undefined');
3952
+ this._assert(this._retryAfterTimeoutPromiseFuncReject !== undefined, 'this._retryAfterTimeoutPromiseFuncReject !== undefined');
3953
+ return p;
3954
+ }
3955
+ //////////////////////////////////////////////////////////////////////////////
3956
+ _tryClearRetryAfterTimeout() {
3957
+ if (this._retryAfterTimeoutID === undefined) {
3958
+ // Debug
3959
+ this._assert(this._retryAfterTimeoutPromiseFuncReject === undefined, 'this._retryAfterTimeoutPromiseFuncReject === undefined');
3960
+ return;
3961
+ }
3962
+ const reject = this._retryAfterTimeoutPromiseFuncReject;
3963
+ const tid = this._retryAfterTimeoutID;
3964
+ this._retryAfterTimeoutID = undefined;
3965
+ this._retryAfterTimeoutPromiseFuncReject = undefined;
3966
+ clearTimeout(tid);
3967
+ reject(new Error('_tryClearRetryAfterTimeout'));
3968
+ }
3969
+ //////////////////////////////////////////////////////////////////////////////
3970
+ // Global Request Timeout
3971
+ //////////////////////////////////////////////////////////////////////////////
3972
+ _setGlobalRequestTimeout(delayMs) {
3973
+ // Debug
3974
+ this._assert(this._requestGlobalTimeoutID === undefined, 'this._requestGlobalTimeoutID === undefined');
3975
+ const callback = () => {
3976
+ this._requestGlobalTimeoutID = undefined;
3977
+ this._handleGlobalRequestTimeout();
3978
+ };
3979
+ this._requestGlobalTimeoutID = setTimeout(callback, delayMs);
3980
+ }
3981
+ _handleGlobalRequestTimeout() {
3982
+ this._terminate('timeout');
3983
+ }
3984
+ _tryClearGlobalRequestTimeout() {
3985
+ if (this._requestGlobalTimeoutID === undefined) {
3986
+ return;
3987
+ }
3988
+ const tid = this._requestGlobalTimeoutID;
3989
+ this._requestGlobalTimeoutID = undefined;
3990
+ clearTimeout(tid);
3991
+ }
3992
+ //////////////////////////////////////////////////////////////////////////////
3993
+ // Progress
3994
+ //////////////////////////////////////////////////////////////////////////////
3995
+ _postAsyncOnProgressCallback(args) {
3996
+ const onProgressFunc = this._onProgress;
3997
+ if (onProgressFunc) {
3998
+ // setTimeout(() => {
3999
+ // onProgressFunc(args);
4000
+ // }, 0);
4001
+ // onProgressFunc() will execute asynchronously in the next cycle of
4002
+ // the JavaScript event loop (the microtask queue).
4003
+ // Promise.resolve().then(() => {
4004
+ // onProgressFunc(args);
4005
+ // });
4006
+ queueMicrotask(() => {
4007
+ onProgressFunc(args);
4008
+ });
4009
+ }
4010
+ }
4011
+ //////////////////////////////////////////////////////////////////////////////
4012
+ // Errors
4013
+ //////////////////////////////////////////////////////////////////////////////
4014
+ _throwRelayerV2ResponseApiError(params) {
4015
+ // Clone
4016
+ const clonedRelayerApiError = JSON.parse(JSON.stringify(params.relayerApiError));
4017
+ // Async onProgress callback
4018
+ this._postAsyncOnProgressCallback({
4019
+ type: 'failed',
4020
+ url: this._url,
4021
+ method: params.fetchMethod,
4022
+ status: params.status,
4023
+ ...(this._jobId ? { jobId: this._jobId } : {}),
4024
+ operation: this._relayerOperation,
4025
+ retryCount: this._retryCount,
4026
+ elapsed: params.elapsed,
4027
+ relayerApiError: clonedRelayerApiError,
4028
+ });
4029
+ throw new RelayerV2ResponseApiError({
4030
+ url: this._url,
4031
+ fetchMethod: params.fetchMethod,
4032
+ status: params.status,
4033
+ jobId: this._jobId,
4034
+ operation: this._relayerOperation,
4035
+ retryCount: this._retryCount,
4036
+ elapsed: params.elapsed,
4037
+ relayerApiError: params.relayerApiError,
4038
+ state: { ...this._state },
4039
+ });
4040
+ }
4041
+ _assert(condition, message) {
4042
+ if (!condition) {
4043
+ this._throwInternalError(`Assertion failed: ${message}`);
4044
+ }
4045
+ }
4046
+ _throwInternalError(message) {
4047
+ throw new RelayerV2RequestInternalError({
4048
+ operation: this._relayerOperation,
4049
+ url: this._url,
4050
+ message,
4051
+ state: JSON.stringify(this._state),
4052
+ jobId: this._jobId, // internal value
4053
+ });
4054
+ }
4055
+ _throwMaxRetryError(params) {
4056
+ const elapsed = this._jobIdTimestamp
4057
+ ? Date.now() - this._jobIdTimestamp
4058
+ : 0;
4059
+ throw new RelayerV2MaxRetryError({
4060
+ operation: this._relayerOperation,
4061
+ url: this._url,
4062
+ state: { ...this._state },
4063
+ retryCount: this._retryCount,
4064
+ jobId: this._jobId, // internal value
4065
+ fetchMethod: params.fetchMethod,
4066
+ elapsed,
4067
+ });
4068
+ }
4069
+ _throwInvalidResponseError(params) {
4070
+ throw new RelayerV2ResponseInvalidBodyError({
4071
+ ...params,
4072
+ url: this._url,
4073
+ operation: this._relayerOperation,
4074
+ state: { ...this._state },
4075
+ retryCount: this._retryCount,
4076
+ });
4077
+ }
4078
+ _throwResponseInvalidBodyError(params) {
4079
+ throw new RelayerV2ResponseInvalidBodyError({
4080
+ ...params,
4081
+ url: this._url,
4082
+ jobId: this.jobId,
4083
+ operation: this._relayerOperation,
4084
+ state: { ...this._state },
4085
+ retryCount: this._retryCount,
4086
+ });
4087
+ }
4088
+ _throwFetchError(params) {
4089
+ throw new RelayerV2FetchError({
4090
+ ...params,
4091
+ url: this._url,
4092
+ jobId: this._jobId,
4093
+ operation: this._relayerOperation,
4094
+ state: { ...this._state },
4095
+ retryCount: this._retryCount,
4096
+ });
4097
+ }
4098
+ /**
4099
+ * Assert Continuation Guard
4100
+ *
4101
+ * This internal method implements a state-check guard to ensure the state machine
4102
+ * can safely proceed after an asynchronous operation has completed.
4103
+ *
4104
+ * In a state machine with asynchronous calls (e.g., fetch, timer delays), the system's
4105
+ * state (e.g., this._state) might change externally during the 'await' pause
4106
+ * (e.g., due to a timeout, an external abort signal, or a concurrent state transition).
4107
+ *
4108
+ * If the internal check (this._canContinue()) returns false, it means the current
4109
+ * operation is no longer valid, and execution must stop immediately to prevent state corruption.
4110
+ * This pattern is essential for reliable asynchronous state machines.
4111
+ *
4112
+ * @throws {RelayerV2RequestInternalError} Thrown if the state check fails (i.e., this._canContinue() is false).
4113
+ * The error includes relevant state information (like current state and jobId)
4114
+ * to aid in debugging the exact point of the integrity failure.
4115
+ */
4116
+ _assertCanContinueAfterAwait() {
4117
+ if (!this._canContinue()) {
4118
+ this._throwInternalError('cannot continue.');
4119
+ }
4120
+ }
4121
+ //////////////////////////////////////////////////////////////////////////////
4122
+ // Trace
4123
+ //////////////////////////////////////////////////////////////////////////////
4124
+ _trace(functionName, message) {
4125
+ console.log(`[RelayerV2AsyncRequest]:${functionName}: ${message}`);
4126
+ }
4127
+ }
4128
+
4129
+ function assertIsRelayerV2GetResponseKeyUrl(value, name) {
4130
+ assertNonNullableRecordProperty(value, 'response', name);
4131
+ // crs
4132
+ assertNonNullableRecordProperty(value.response, 'crs', `${name}.response`);
4133
+ const crs = value.response.crs;
4134
+ const keys = Object.keys(crs);
4135
+ for (let i = 0; i < keys.length; ++i) {
4136
+ // RelayerV2KeyData
4137
+ assertIsRelayerV2KeyData(crs[keys[i]], `${name}.response.crs.${keys[i]}`);
4138
+ }
4139
+ // fhe_key_info
4140
+ assertRecordArrayProperty(value.response, 'fheKeyInfo', `${name}.response`);
4141
+ const fheKeyInfo = value.response.fheKeyInfo;
4142
+ for (let i = 0; i < fheKeyInfo.length; ++i) {
4143
+ const ki = fheKeyInfo[i];
4144
+ const kiName = `${name}.response.fheKeyInfo[${i}]`;
4145
+ assertNonNullableRecordProperty(ki, 'fhePublicKey', kiName);
4146
+ assertIsRelayerV2KeyData(ki.fhePublicKey, `${kiName}.fhePublicKey`);
4147
+ }
4148
+ }
4149
+ function assertIsRelayerV2KeyData(value, name) {
4150
+ assertRecordStringProperty(value, 'dataId', name);
4151
+ assertRecordStringArrayProperty(value, 'urls', name);
4152
+ }
4153
+ function toRelayerKeyUrlResponse(response) {
4154
+ const fheKeyInfoV1 = response.response.fheKeyInfo.map((v2Info) => ({
4155
+ fhe_public_key: {
4156
+ data_id: v2Info.fhePublicKey.dataId,
4157
+ urls: v2Info.fhePublicKey.urls,
4158
+ },
4159
+ }));
4160
+ const crsV1 = {};
4161
+ for (const [key, v2Data] of Object.entries(response.response.crs)) {
4162
+ crsV1[key] = {
4163
+ data_id: v2Data.dataId,
4164
+ urls: v2Data.urls,
4165
+ };
4166
+ }
4167
+ return {
4168
+ response: {
4169
+ fhe_key_info: fheKeyInfoV1,
4170
+ crs: crsV1,
4171
+ },
4172
+ };
4173
+ }
4174
+
4175
+ class RelayerV2Provider extends AbstractRelayerProvider {
4176
+ constructor(relayerUrl) {
4177
+ super(relayerUrl);
4178
+ }
4179
+ get version() {
4180
+ return 2;
4181
+ }
4182
+ async fetchGetKeyUrlV2() {
4183
+ const response = await fetchRelayerGet('KEY_URL', this.keyUrl);
4184
+ // Relayer error
4185
+ try {
4186
+ assertIsRelayerV2GetResponseKeyUrl(response, 'fetchGetKeyUrl()');
4187
+ }
4188
+ catch (e) {
4189
+ throw new RelayerV2GetKeyUrlInvalidResponseError({
4190
+ cause: ensureError(e),
4191
+ });
4192
+ }
4193
+ return response;
4194
+ }
4195
+ async fetchGetKeyUrl() {
4196
+ const response = await this.fetchGetKeyUrlV2();
4197
+ return toRelayerKeyUrlResponse(response);
4198
+ }
4199
+ async fetchPostInputProof(payload, instanceOptions, fetchOptions) {
4200
+ const request = new RelayerV2AsyncRequest({
4201
+ relayerOperation: 'INPUT_PROOF',
4202
+ url: this.inputProof,
4203
+ payload,
4204
+ instanceOptions,
4205
+ ...fetchOptions,
4206
+ });
4207
+ const result = (await request.run());
4208
+ assertIsRelayerInputProofResult(result, 'fetchPostInputProof()');
4209
+ return result;
4210
+ }
4211
+ async fetchPostPublicDecrypt(payload, instanceOptions, fetchOptions) {
4212
+ const request = new RelayerV2AsyncRequest({
4213
+ relayerOperation: 'PUBLIC_DECRYPT',
4214
+ url: this.publicDecrypt,
4215
+ payload,
4216
+ instanceOptions,
4217
+ ...fetchOptions,
4218
+ });
4219
+ const result = await request.run();
4220
+ assertIsRelayerPublicDecryptResult(result, 'fetchPostPublicDecrypt()');
4221
+ return result;
4222
+ }
4223
+ async fetchPostUserDecrypt(payload, instanceOptions, fetchOptions) {
4224
+ const request = new RelayerV2AsyncRequest({
4225
+ relayerOperation: 'USER_DECRYPT',
4226
+ url: this.userDecrypt,
4227
+ payload,
4228
+ instanceOptions,
4229
+ ...fetchOptions,
4230
+ });
4231
+ const result = (await request.run());
4232
+ assertIsRelayerUserDecryptResult(result.result, 'fetchPostUserDecrypt()');
4233
+ return result.result;
4234
+ }
4235
+ }
4236
+
4237
+ class AbstractRelayerFhevm {
4238
+ }
4239
+
4240
+ class TFHECrs {
4241
+ _id;
4242
+ _compactPkeCrs;
4243
+ _bits;
4244
+ _srcUrl;
4245
+ constructor(params) {
4246
+ this._id = params.id;
4247
+ this._compactPkeCrs = params.compactPkeCrs;
4248
+ this._bits = params.bits;
4249
+ this._srcUrl = params.srcUrl;
4250
+ }
4251
+ /*
4252
+ {
4253
+ id: string,
4254
+ data: Uint8Array,
4255
+ bits: number
4256
+ srcUrl?: string
4257
+ }
4258
+ */
4259
+ static isKeyBytesType(value) {
4260
+ try {
4261
+ TFHECrs.assertKeyBytesType(value, '');
4262
+ return true;
4263
+ }
4264
+ catch {
4265
+ return false;
4266
+ }
4267
+ }
4268
+ /*
4269
+ {
4270
+ id: string,
4271
+ bits: number
4272
+ srcUrl: string
4273
+ }
4274
+ */
4275
+ static isKeyUrlType(value) {
4276
+ try {
4277
+ TFHECrs.assertKeyUrlType(value, '');
4278
+ return true;
4279
+ }
4280
+ catch {
4281
+ return false;
4282
+ }
4283
+ }
4284
+ /*
4285
+ {
4286
+ id: string,
4287
+ data: Uint8Array,
4288
+ bits: number
4289
+ srcUrl?: string
4290
+ }
4291
+ */
4292
+ static assertKeyBytesType(value, name) {
4293
+ assertRecordStringProperty(value, 'id', name);
4294
+ assertUint8ArrayProperty(value, 'data', name);
4295
+ assertRecordUintProperty(value, 'bits', name);
4296
+ if (isNonNullableRecordProperty(value, 'srcUrl')) {
4297
+ assertRecordStringProperty(value, 'srcUrl', name);
4298
+ }
4299
+ }
4300
+ /*
4301
+ {
4302
+ id: string,
4303
+ bits: number
4304
+ srcUrl: string
4305
+ }
4306
+ */
4307
+ static assertKeyUrlType(value, name) {
4308
+ assertRecordStringProperty(value, 'id', name);
4309
+ assertRecordUintProperty(value, 'bits', name);
4310
+ assertRecordStringProperty(value, 'srcUrl', name);
4311
+ }
4312
+ /*
4313
+ {
4314
+ 2048: {
4315
+ publicParamsId: string,
4316
+ publicParams: Uint8Array
4317
+ }
4318
+ }
4319
+ */
4320
+ static assertIsPublicParams2048BytesType(value, name) {
4321
+ assertNonNullableRecordProperty(value, '2048', name);
4322
+ assertRecordStringProperty(value['2048'], 'publicParamsId', `${name}.2048`);
4323
+ assertUint8ArrayProperty(value['2048'], 'publicParams', `${name}.2048`);
4324
+ }
4325
+ /*
4326
+ {
4327
+ 2048: {
4328
+ publicParamsId: string,
4329
+ publicParams: Uint8Array
4330
+ }
4331
+ }
4332
+ */
4333
+ static isPublicParams2048BytesType(value) {
4334
+ try {
4335
+ TFHECrs.assertIsPublicParams2048BytesType(value, '');
4336
+ return true;
4337
+ }
4338
+ catch {
4339
+ return false;
4340
+ }
4341
+ }
4342
+ static async fromBytesOrUrl(params) {
4343
+ if (TFHECrs.isKeyBytesType(params)) {
4344
+ return TFHECrs._fromBytes(params);
4345
+ }
4346
+ else if (TFHECrs.isPublicParams2048BytesType(params)) {
4347
+ return TFHECrs._fromPublicParamsBytes(params);
4348
+ }
4349
+ else if (TFHECrs.isKeyUrlType(params)) {
4350
+ return TFHECrs._fromUrl(params);
4351
+ }
4352
+ else {
4353
+ throw new Error('Invalid public key (deserialization failed)');
4354
+ }
4355
+ }
4356
+ static fromBytes(params) {
4357
+ try {
4358
+ TFHECrs.assertKeyBytesType(params, 'arg');
4359
+ return TFHECrs._fromBytes(params);
4360
+ }
4361
+ catch (e) {
4362
+ throw new Error('Invalid public key (deserialization failed)', {
4363
+ cause: e,
4364
+ });
4365
+ }
4366
+ }
4367
+ static _fromBytes(params) {
4368
+ const _params = {
4369
+ compactPkeCrs: TFHE.CompactPkeCrs.safe_deserialize(params.data, SERIALIZED_SIZE_LIMIT_CRS),
4370
+ id: params.id,
4371
+ bits: params.bits,
4372
+ srcUrl: params.srcUrl,
4373
+ };
4374
+ return new TFHECrs(_params);
4375
+ }
4376
+ static fromPublicParamsBytes(params) {
4377
+ try {
4378
+ TFHECrs.assertIsPublicParams2048BytesType(params, 'arg');
4379
+ return TFHECrs._fromPublicParamsBytes(params);
4380
+ }
4381
+ catch (e) {
4382
+ throw new Error('Invalid public key (deserialization failed)', {
4383
+ cause: e,
4384
+ });
4385
+ }
4386
+ }
4387
+ static _fromPublicParamsBytes(params) {
4388
+ return TFHECrs._fromBytes({
4389
+ bits: 2048,
4390
+ data: params['2048'].publicParams,
4391
+ id: params['2048'].publicParamsId,
4392
+ });
4393
+ }
4394
+ static async fromUrl(params) {
4395
+ try {
4396
+ TFHECrs.assertKeyUrlType(params, 'arg');
4397
+ return TFHECrs._fromUrl(params);
4398
+ }
4399
+ catch (e) {
4400
+ throw new Error('Impossible to fetch public key: wrong relayer url.', {
4401
+ cause: e,
4402
+ });
4403
+ }
4404
+ }
4405
+ static async _fromUrl(params) {
4406
+ TFHECrs.assertKeyUrlType(params, 'arg');
4407
+ const compactPkeCrsBytes = await fetchBytes(params.srcUrl);
4408
+ return TFHECrs.fromBytes({
4409
+ data: compactPkeCrsBytes,
4410
+ id: params.id,
4411
+ bits: params.bits,
4412
+ srcUrl: params.srcUrl,
4413
+ });
4414
+ }
4415
+ /*
4416
+ {
4417
+ id: string,
4418
+ bits: number,
4419
+ data: Uint8Array,
4420
+ srcUrl?: string
4421
+ }
4422
+ */
4423
+ toBytes() {
4424
+ return {
4425
+ data: this._compactPkeCrs.safe_serialize(SERIALIZED_SIZE_LIMIT_CRS),
4426
+ id: this._id,
4427
+ bits: this._bits,
4428
+ ...(this._srcUrl ? { srcUrl: this._srcUrl } : {}),
4429
+ };
4430
+ }
4431
+ /*
4432
+ {
4433
+ 2048: {
4434
+ publicParamsId: string,
4435
+ publicParams: TFHE.CompactPkeCrs
4436
+ }
4437
+ }
4438
+ */
4439
+ toPublicParamsWasm() {
4440
+ if (this._bits !== 2048) {
4441
+ throw new Error(`Unsupported PublicParams bits format ${this._bits}`);
4442
+ }
4443
+ const pp = {
4444
+ 2048: {
4445
+ publicParams: this._compactPkeCrs,
4446
+ publicParamsId: this._id,
4447
+ },
4448
+ };
4449
+ return pp;
4450
+ }
4451
+ /*
4452
+ {
4453
+ 2048: {
4454
+ publicParamsId: string,
4455
+ publicParams: Uint8Array
4456
+ }
4457
+ }
4458
+ */
4459
+ toPublicParamsBytes() {
4460
+ if (this._bits !== 2048) {
4461
+ throw new Error(`Unsupported PublicParams bits format ${this._bits}`);
4462
+ }
4463
+ const pp = {
4464
+ 2048: {
4465
+ publicParams: this.toBytes().data,
4466
+ publicParamsId: this._id,
4467
+ },
4468
+ };
4469
+ return pp;
4470
+ }
4471
+ }
4472
+
4473
+ class TFHEPublicKey {
4474
+ _id;
4475
+ _tfheCompactPublicKey;
4476
+ _srcUrl;
4477
+ constructor(params) {
4478
+ this._id = params.id;
4479
+ this._tfheCompactPublicKey = params.tfheCompactPublicKey;
4480
+ this._srcUrl = params.srcUrl;
4481
+ }
4482
+ /*
4483
+ {
4484
+ id: string,
4485
+ data: Uint8Array,
4486
+ srcUrl?: string
4487
+ }
4488
+ */
4489
+ static isKeyBytesType(value) {
4490
+ try {
4491
+ TFHEPublicKey.assertKeyBytesType(value, '');
4492
+ return true;
4493
+ }
4494
+ catch {
4495
+ return false;
4496
+ }
4497
+ }
4498
+ /*
4499
+ {
4500
+ id: string,
4501
+ srcUrl: string
4502
+ }
4503
+ */
4504
+ static isKeyUrlType(value) {
4505
+ try {
4506
+ TFHEPublicKey.assertKeyUrlType(value, '');
4507
+ return true;
4508
+ }
4509
+ catch {
4510
+ return false;
4511
+ }
4512
+ }
4513
+ /*
4514
+ {
4515
+ id: string,
4516
+ data: Uint8Array,
4517
+ srcUrl?: string
4518
+ }
4519
+ */
4520
+ static assertKeyBytesType(value, name) {
4521
+ assertRecordStringProperty(value, 'id', name);
4522
+ assertUint8ArrayProperty(value, 'data', name);
4523
+ if (isNonNullableRecordProperty(value, 'srcUrl')) {
4524
+ assertRecordStringProperty(value, 'srcUrl', name);
4525
+ }
4526
+ }
4527
+ /*
4528
+ {
4529
+ id: string,
4530
+ srcUrl: string
4531
+ }
4532
+ */
4533
+ static assertKeyUrlType(value, name) {
4534
+ assertRecordStringProperty(value, 'id', name);
4535
+ assertRecordStringProperty(value, 'srcUrl', name);
4536
+ }
4537
+ static async fromBytesOrUrl(params) {
4538
+ if (TFHEPublicKey.isKeyBytesType(params)) {
4539
+ return TFHEPublicKey._fromBytes(params);
4540
+ }
4541
+ else if (TFHEPublicKey.isKeyUrlType(params)) {
4542
+ return TFHEPublicKey._fromUrl(params);
4543
+ }
4544
+ else {
4545
+ throw new Error('Invalid public key (deserialization failed)');
4546
+ }
4547
+ }
4548
+ /*
4549
+ {
4550
+ id: string,
4551
+ data: Uint8Array,
4552
+ srcUrl?: string
4553
+ }
4554
+ */
4555
+ static fromBytes(params) {
4556
+ try {
4557
+ TFHEPublicKey.assertKeyBytesType(params, 'arg');
4558
+ return TFHEPublicKey._fromBytes(params);
4559
+ }
4560
+ catch (e) {
4561
+ throw new Error('Invalid public key (deserialization failed)', {
4562
+ cause: e,
4563
+ });
4564
+ }
4565
+ }
4566
+ /*
4567
+ {
4568
+ id: string,
4569
+ data: Uint8Array,
4570
+ srcUrl?: string
4571
+ }
4572
+ */
4573
+ static _fromBytes(params) {
4574
+ const _params = {
4575
+ tfheCompactPublicKey: TFHE.TfheCompactPublicKey.safe_deserialize(params.data, SERIALIZED_SIZE_LIMIT_PK),
4576
+ id: params.id,
4577
+ srcUrl: params.srcUrl,
4578
+ };
4579
+ return new TFHEPublicKey(_params);
4580
+ }
4581
+ /*
4582
+ {
4583
+ id: string,
4584
+ srcUrl: string
4585
+ }
4586
+ */
4587
+ static async fromUrl(params) {
4588
+ try {
4589
+ TFHEPublicKey.assertKeyUrlType(params, 'arg');
4590
+ return TFHEPublicKey._fromUrl(params);
4591
+ }
4592
+ catch (e) {
4593
+ throw new Error('Impossible to fetch public key: wrong relayer url.', {
4594
+ cause: e,
4595
+ });
4596
+ }
4597
+ }
4598
+ /*
4599
+ {
4600
+ id: string,
4601
+ srcUrl: string
4602
+ }
4603
+ */
4604
+ static async _fromUrl(params) {
4605
+ const tfheCompactPublicKeyBytes = await fetchBytes(params.srcUrl);
4606
+ return TFHEPublicKey.fromBytes({
4607
+ data: tfheCompactPublicKeyBytes,
4608
+ id: params.id,
4609
+ srcUrl: params.srcUrl,
4610
+ });
4611
+ }
4612
+ /*
4613
+ {
4614
+ id: string,
4615
+ data: Uint8Array,
4616
+ srcUrl?: string
4617
+ }
4618
+ */
4619
+ toBytes() {
4620
+ return {
4621
+ data: this._tfheCompactPublicKey.safe_serialize(SERIALIZED_SIZE_LIMIT_PK),
4622
+ id: this._id,
4623
+ ...(this._srcUrl ? { srcUrl: this._srcUrl } : {}),
4624
+ };
4625
+ }
4626
+ /*
4627
+ {
4628
+ publicKey: TFHE.TfheCompactPublicKey
4629
+ publicKeyId: string
4630
+ }
4631
+ */
4632
+ toPublicKeyWasm() {
4633
+ return {
4634
+ publicKey: this._tfheCompactPublicKey,
4635
+ publicKeyId: this._id,
4636
+ };
4637
+ }
4638
+ /*
4639
+ {
4640
+ publicKey: Uint8Array
4641
+ publicKeyId: string
4642
+ }
4643
+ */
4644
+ toPublicKeyBytes() {
4645
+ return {
4646
+ publicKey: this.toBytes().data,
4647
+ publicKeyId: this._id,
4648
+ };
4649
+ }
4650
+ }
4651
+
4652
+ //const __KEY_URL_CACHE__: Record<string, RelayerV2PublicKey> = {};
4653
+ class RelayerV2PublicKey {
4654
+ _crs2048;
4655
+ _publicKey;
4656
+ constructor(params) {
4657
+ this._publicKey = params.publicKey;
4658
+ this._crs2048 = params.crs2048;
4659
+ }
4660
+ static tryFromBytes(value) {
4661
+ if (!isNonNullableRecordProperty(value, 'publicParams')) {
4662
+ return null;
4663
+ }
4664
+ if (!TFHECrs.isPublicParams2048BytesType(value.publicParams)) {
4665
+ return null;
4666
+ }
4667
+ if (!isNonNullableRecordProperty(value, 'publicKey')) {
4668
+ return null;
4669
+ }
4670
+ if (!TFHEPublicKey.isKeyBytesType(value.publicKey)) {
4671
+ return null;
4672
+ }
4673
+ const publicKey = value.publicKey;
4674
+ const publicParams = value.publicParams;
4675
+ return RelayerV2PublicKey.fromBytes({
4676
+ publicKey,
4677
+ publicParams,
4678
+ });
4679
+ }
4680
+ static fromBytes(params) {
4681
+ TFHECrs.assertIsPublicParams2048BytesType(params.publicParams, 'arg.publicParams');
4682
+ const publicKey = TFHEPublicKey.fromBytes(params.publicKey);
4683
+ const crs2048 = TFHECrs.fromBytes({
4684
+ id: params.publicParams[2048].publicParamsId,
4685
+ data: params.publicParams[2048].publicParams,
4686
+ bits: 2048,
4687
+ });
4688
+ return new RelayerV2PublicKey({ publicKey, crs2048 });
4689
+ }
4690
+ static async fromRelayerResponse(response) {
4691
+ try {
4692
+ assertIsRelayerV2GetResponseKeyUrl(response, 'RelayerV2GetResponseKeyUrl');
4693
+ const pub_key_0 = response.response.fheKeyInfo[0].fhePublicKey;
4694
+ const tfheCompactPublicKeyId = pub_key_0.dataId;
4695
+ const tfheCompactPublicKeyUrl = pub_key_0.urls[0];
4696
+ const crs_2048 = response.response.crs['2048'];
4697
+ const compactPkeCrs2048Id = crs_2048.dataId;
4698
+ const compactPkeCrs2048Url = crs_2048.urls[0];
4699
+ const publicKey = await TFHEPublicKey.fromUrl({
4700
+ id: tfheCompactPublicKeyId,
4701
+ srcUrl: tfheCompactPublicKeyUrl,
4702
+ });
4703
+ const crs = await TFHECrs.fromUrl({
4704
+ id: compactPkeCrs2048Id,
4705
+ bits: 2048,
4706
+ srcUrl: compactPkeCrs2048Url,
4707
+ });
4708
+ return new RelayerV2PublicKey({ publicKey, crs2048: crs });
4709
+ }
4710
+ catch (e) {
4711
+ throw new Error('Impossible to fetch public key: wrong relayer url.', {
4712
+ cause: e,
4713
+ });
4714
+ }
4715
+ }
4716
+ getTFHEPublicKey() {
4717
+ return this._publicKey;
4718
+ }
4719
+ getTFHECrs() {
4720
+ return this._crs2048;
4721
+ }
4722
+ toBytes() {
4723
+ return {
4724
+ publicKey: this._publicKey.toBytes(),
4725
+ publicParams: this._crs2048.toPublicParamsBytes(),
4726
+ };
4727
+ }
4728
+ }
4729
+
4730
+ class RelayerV2Fhevm extends AbstractRelayerFhevm {
4731
+ _relayerProvider;
4732
+ _relayerPublicKey;
4733
+ constructor(params) {
4734
+ super();
4735
+ this._relayerProvider = params.relayerProvider;
4736
+ this._relayerPublicKey = params.relayerPublicKey;
4737
+ }
4738
+ get version() {
4739
+ return 2;
4740
+ }
4741
+ get relayerVersionUrl() {
4742
+ return this.relayerProvider.url;
4743
+ }
4744
+ static async fromConfig(config) {
4745
+ const relayerProvider = new RelayerV2Provider(config.relayerVersionUrl);
4746
+ let relayerPublicKey = RelayerV2PublicKey.tryFromBytes(config);
4747
+ if (!relayerPublicKey) {
4748
+ const response = await relayerProvider.fetchGetKeyUrlV2();
4749
+ relayerPublicKey = await RelayerV2PublicKey.fromRelayerResponse(response);
4750
+ }
4751
+ return new RelayerV2Fhevm({
4752
+ relayerProvider,
4753
+ relayerPublicKey,
4754
+ });
4755
+ }
4756
+ get relayerProvider() {
4757
+ return this._relayerProvider;
4758
+ }
4759
+ getPublicKeyBytes() {
4760
+ return this._relayerPublicKey.getTFHEPublicKey().toPublicKeyBytes();
4761
+ }
4762
+ getPublicKeyWasm() {
4763
+ return this._relayerPublicKey.getTFHEPublicKey().toPublicKeyWasm();
4764
+ }
4765
+ getPublicParamsBytes(bits) {
4766
+ if (bits !== 2048) {
4767
+ throw new Error(`Unsupported PublicParams bits format ${bits}`);
4768
+ }
4769
+ return this._relayerPublicKey.getTFHECrs().toPublicParamsBytes()['2048'];
4770
+ }
4771
+ getPublicParamsWasm(bits) {
4772
+ if (bits !== 2048) {
4773
+ throw new Error(`Unsupported PublicParams bits format ${bits}`);
4774
+ }
4775
+ return this._relayerPublicKey.getTFHECrs().toPublicParamsWasm()['2048'];
4776
+ }
4777
+ }
4778
+
4779
+ class RelayerV1Fhevm extends AbstractRelayerFhevm {
4780
+ _relayerProvider;
4781
+ _publicKeyData;
4782
+ _publicParamsData;
4783
+ constructor(params) {
4784
+ super();
4785
+ this._relayerProvider = params.relayerProvider;
4786
+ this._publicKeyData = params.publicKeyData;
4787
+ this._publicParamsData = params.publicParamsData;
4788
+ }
4789
+ get version() {
4790
+ return 1;
4791
+ }
4792
+ get relayerVersionUrl() {
4793
+ return this.relayerProvider.url;
4794
+ }
4795
+ static async fromConfig(config) {
4796
+ const relayerProvider = new RelayerV1Provider(config.relayerVersionUrl);
4797
+ const publicKeyData = await getTfheCompactPublicKey(config);
4798
+ const publicParamsData = await getPublicParams(config);
4799
+ return new RelayerV1Fhevm({
4800
+ relayerProvider,
4801
+ publicKeyData,
4802
+ publicParamsData,
4803
+ });
4804
+ }
4805
+ get relayerProvider() {
4806
+ return this._relayerProvider;
4807
+ }
4808
+ getPublicKeyBytes() {
4809
+ return {
4810
+ publicKey: this._publicKeyData.publicKey.safe_serialize(SERIALIZED_SIZE_LIMIT_PK),
4811
+ publicKeyId: this._publicKeyData.publicKeyId,
4812
+ };
4813
+ }
4814
+ getPublicKeyWasm() {
4815
+ return {
4816
+ publicKey: this._publicKeyData.publicKey,
4817
+ publicKeyId: this._publicKeyData.publicKeyId,
4818
+ };
4819
+ }
4820
+ getPublicParamsBytes(bits) {
4821
+ if (bits !== 2048) {
4822
+ throw new Error(`Unsupported PublicParams bits format ${bits}`);
4823
+ }
4824
+ return {
4825
+ publicParams: this._publicParamsData['2048'].publicParams.safe_serialize(SERIALIZED_SIZE_LIMIT_CRS),
4826
+ publicParamsId: this._publicParamsData['2048'].publicParamsId,
4827
+ };
4828
+ }
4829
+ getPublicParamsWasm(bits) {
4830
+ if (bits !== 2048) {
4831
+ throw new Error(`Unsupported PublicParams bits format ${bits}`);
4832
+ }
4833
+ return {
4834
+ publicParams: this._publicParamsData['2048'].publicParams,
4835
+ publicParamsId: this._publicParamsData['2048'].publicParamsId,
4836
+ };
4837
+ }
4838
+ }
4839
+
4840
+ async function createRelayerFhevm(config) {
4841
+ const resolved = _resolveRelayerUrl(config.relayerUrl, config.defaultRelayerVersion);
4842
+ if (!resolved) {
4843
+ throw new Error(`Invalid relayerUrl: ${config.relayerUrl}`);
4844
+ }
4845
+ if (resolved.version === 2) {
4846
+ return RelayerV2Fhevm.fromConfig({
4847
+ relayerVersionUrl: resolved.url,
4848
+ publicKey: config.publicKey,
4849
+ publicParams: config.publicParams,
4850
+ });
4851
+ }
4852
+ return RelayerV1Fhevm.fromConfig({
4853
+ relayerVersionUrl: resolved.url,
4854
+ publicKey: config.publicKey,
4855
+ publicParams: config.publicParams,
4856
+ });
4857
+ }
4858
+ function _resolveRelayerUrl(value, defaultVersion) {
4859
+ if (!value || typeof value !== 'string') {
4860
+ return null;
4861
+ }
4862
+ const urlNoSlash = removeSuffix(value, '/');
4863
+ if (!URL.canParse(urlNoSlash)) {
4864
+ return null;
4865
+ }
4866
+ if (urlNoSlash.endsWith('/v1')) {
4867
+ return {
4868
+ url: value,
4869
+ version: 1,
4870
+ };
4871
+ }
4872
+ if (urlNoSlash.endsWith('/v2')) {
4873
+ return {
4874
+ url: value,
4875
+ version: 2,
4876
+ };
4877
+ }
4878
+ if (typeof defaultVersion !== 'number') {
4879
+ throw new Error(`relayerUrl cannot be resolved. (value=${value})`);
4880
+ }
4881
+ return {
4882
+ url: `${urlNoSlash}/v${defaultVersion}`,
4883
+ version: defaultVersion,
4884
+ };
4885
+ }
4886
+
4887
+ /**
4888
+ * **FHE Type Mapping for Input Builders**
4889
+ * * Maps the **number of encrypted bits** used by a FHEVM primary type
4890
+ * to its corresponding **FheTypeId**. This constant is primarily used by
4891
+ * `EncryptedInput` and `RelayerEncryptedInput` builders to determine the correct
4892
+ * input type and calculate the total required bit-length.
4893
+ *
4894
+ * **Structure: \{ Encrypted Bit Length: FheTypeId \}**
4895
+ *
4896
+ * | Bits | FheTypeId | FHE Type Name | Note |
4897
+ * | :--- | :-------- | :------------ | :--- |
4898
+ * | 2 | 0 | `ebool` | The boolean type. |
4899
+ * | (N/A)| 1 | `euint4` | **Deprecated** and omitted from this map. |
4900
+ * | 8 | 2 | `euint8` | |
4901
+ * | 16 | 3 | `euint16` | |
4902
+ * | 32 | 4 | `euint32` | |
4903
+ * | 64 | 5 | `euint64` | |
4904
+ * | 128 | 6 | `euint128` | |
4905
+ * | 160 | 7 | `eaddress` | Used for encrypted Ethereum addresses. |
4906
+ * | 256 | 8 | `euint256` | The maximum supported integer size. |
4907
+ */
4908
+ const ENCRYPTION_TYPES = {
4909
+ 2: 0, // ebool (FheTypeId=0) is using 2 encrypted bits
4910
+ // euint4 (FheTypeId=1) is deprecated
4911
+ 8: 2, // euint8 (FheTypeId=2) is using 8 encrypted bits
4912
+ 16: 3, // euint16 (FheTypeId=3) is using 16 encrypted bits
4913
+ 32: 4, // euint32 (FheTypeId=4) is using 32 encrypted bits
4914
+ 64: 5, // euint64 (FheTypeId=5) is using 64 encrypted bits
4915
+ 128: 6, // euint128 (FheTypeId=128) is using 128 encrypted bits
4916
+ 160: 7, // eaddress (FheTypeId=7) is using 160 encrypted bits
4917
+ 256: 8, // euint256 (FheTypeId=8) is using 256 encrypted bits
4918
+ };
4919
+
4920
+ const SepoliaConfig = {
4921
+ // ACL_CONTRACT_ADDRESS (FHEVM Host chain)
4922
+ aclContractAddress: '0xf0Ffdc93b7E186bC2f8CB3dAA75D86d1930A433D',
4923
+ // KMS_VERIFIER_CONTRACT_ADDRESS (FHEVM Host chain)
4924
+ kmsContractAddress: '0xbE0E383937d564D7FF0BC3b46c51f0bF8d5C311A',
4925
+ // INPUT_VERIFIER_CONTRACT_ADDRESS (FHEVM Host chain)
4926
+ inputVerifierContractAddress: '0xBBC1fFCdc7C316aAAd72E807D9b0272BE8F84DA0',
4927
+ // DECRYPTION_ADDRESS (Gateway chain)
4928
+ verifyingContractAddressDecryption: '0x5D8BD78e2ea6bbE41f26dFe9fdaEAa349e077478',
4929
+ // INPUT_VERIFICATION_ADDRESS (Gateway chain)
4930
+ verifyingContractAddressInputVerification: '0x483b9dE06E4E4C7D35CCf5837A1668487406D955',
4931
+ // FHEVM Host chain id
4932
+ chainId: 11155111,
4933
+ // Gateway chain id
4934
+ gatewayChainId: 10901,
4935
+ // Optional RPC provider to host chain
4936
+ network: 'https://ethereum-sepolia-rpc.publicnode.com',
4937
+ // Relayer URL
4938
+ relayerUrl: 'https://relayer.testnet.zama.org',
4939
+ };
4940
+ Object.freeze(SepoliaConfig);
4941
+ const createInstance = async (config) => {
4942
+ const { verifyingContractAddressDecryption, verifyingContractAddressInputVerification, publicKey, inputVerifierContractAddress, kmsContractAddress, aclContractAddress, gatewayChainId, auth, } = config;
4943
+ if (!isChecksummedAddress(aclContractAddress)) {
4944
+ throw new Error('ACL contract address is not valid or empty');
4945
+ }
4946
+ if (!isChecksummedAddress(inputVerifierContractAddress)) {
4947
+ throw new Error('InputVerifier contract address is not valid or empty');
4948
+ }
4949
+ if (!isChecksummedAddress(kmsContractAddress)) {
4950
+ throw new Error('KMS contract address is not valid or empty');
4951
+ }
4952
+ if (!isChecksummedAddress(verifyingContractAddressDecryption)) {
4953
+ throw new Error('Verifying contract for Decryption address is not valid or empty');
4954
+ }
4955
+ if (!isChecksummedAddress(verifyingContractAddressInputVerification)) {
4956
+ throw new Error('Verifying contract for InputVerification address is not valid or empty');
4957
+ }
4958
+ if (publicKey && !(publicKey.data instanceof Uint8Array)) {
4959
+ throw new Error('publicKey must be a Uint8Array');
4960
+ }
4961
+ // TODO change argument
4962
+ // provider is never undefined | null here!
4963
+ const provider = getProvider(config.network);
4964
+ const relayerUrl = config.relayerUrl ?? SepoliaConfig.relayerUrl;
4965
+ const relayerFhevm = await createRelayerFhevm({
4966
+ relayerUrl,
4967
+ publicKey: config.publicKey,
4968
+ publicParams: config.publicParams,
4969
+ defaultRelayerVersion: 1,
4970
+ });
4971
+ const chainId = await getChainId(provider, config);
4972
+ // const relayerVersionUrl = `${config.relayerUrl!}/v1`;
4973
+ // const publicKeyData = await getTfheCompactPublicKey({
4974
+ // relayerVersionUrl: relayerFhevm.relayerVersionUrl,
4975
+ // publicKey: config.publicKey,
4976
+ // });
4977
+ //const aaa = relayerFhevm.getPublicKey();
4978
+ // const publicParamsData = await getPublicParams({
4979
+ // relayerVersionUrl,
4980
+ // publicParams: config.publicParams,
4981
+ // });
4982
+ const kmsSigners = await getKMSSigners(provider, kmsContractAddress);
4983
+ const thresholdKMSSigners = await getKMSSignersThreshold(provider, kmsContractAddress);
4984
+ const coprocessorSigners = await getCoprocessorSigners(provider, inputVerifierContractAddress);
4985
+ const thresholdCoprocessorSigners = await getCoprocessorSignersThreshold(provider, inputVerifierContractAddress);
4986
+ return {
4987
+ createEncryptedInput: createRelayerEncryptedInput(aclContractAddress, verifyingContractAddressInputVerification, chainId, gatewayChainId,
4988
+ //cleanURL(config.relayerUrl),
4989
+ //relayerFhevm.relayerVersionUrl,
4990
+ relayerFhevm.relayerProvider,
4991
+ //publicKeyData.publicKey,
4992
+ relayerFhevm.getPublicKeyWasm().publicKey,
4993
+ //publicParamsData,
4994
+ { 2048: relayerFhevm.getPublicParamsWasm(2048) }, coprocessorSigners, thresholdCoprocessorSigners),
4995
+ generateKeypair,
4996
+ createEIP712: createEIP712(verifyingContractAddressDecryption, chainId),
4997
+ publicDecrypt: publicDecryptRequest(kmsSigners, thresholdKMSSigners, gatewayChainId, verifyingContractAddressDecryption, aclContractAddress,
4998
+ //cleanURL(config.relayerUrl),
4999
+ relayerFhevm.relayerProvider, provider, auth && { auth }),
5000
+ userDecrypt: userDecryptRequest(kmsSigners, gatewayChainId, chainId, verifyingContractAddressDecryption, aclContractAddress,
5001
+ //cleanURL(config.relayerUrl),
5002
+ relayerFhevm.relayerProvider, provider, auth && { auth }),
5003
+ getPublicKey: () => relayerFhevm.getPublicKeyBytes(),
5004
+ getPublicParams: (bits) => relayerFhevm.getPublicParamsBytes(bits),
5005
+ // getPublicKey: () =>
5006
+ // publicKeyData.publicKey
5007
+ // ? {
5008
+ // publicKey: publicKeyData.publicKey.safe_serialize(
5009
+ // SERIALIZED_SIZE_LIMIT_PK,
5010
+ // ),
5011
+ // publicKeyId: publicKeyData.publicKeyId,
5012
+ // }
5013
+ // : null,
5014
+ // getPublicParams: (bits: keyof PublicParams) => {
5015
+ // if (publicParamsData[bits]) {
5016
+ // return {
5017
+ // publicParams: publicParamsData[bits]!.publicParams.safe_serialize(
5018
+ // SERIALIZED_SIZE_LIMIT_CRS,
5019
+ // ),
5020
+ // publicParamsId: publicParamsData[bits]!.publicParamsId,
5021
+ // };
5022
+ // }
5023
+ // return null;
5024
+ // },
1454
5025
  };
1455
5026
  };
1456
5027