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