@aztec/foundation 0.62.0 → 0.63.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/dest/abi/abi.d.ts +708 -228
  2. package/dest/abi/abi.d.ts.map +1 -1
  3. package/dest/abi/abi.js +92 -8
  4. package/dest/abi/encoder.d.ts.map +1 -1
  5. package/dest/abi/encoder.js +4 -1
  6. package/dest/abi/event_selector.d.ts +4 -0
  7. package/dest/abi/event_selector.d.ts.map +1 -1
  8. package/dest/abi/event_selector.js +7 -1
  9. package/dest/aztec-address/index.d.ts +19 -3
  10. package/dest/aztec-address/index.d.ts.map +1 -1
  11. package/dest/aztec-address/index.js +43 -14
  12. package/dest/buffer/buffer32.d.ts +1 -0
  13. package/dest/buffer/buffer32.d.ts.map +1 -1
  14. package/dest/buffer/buffer32.js +4 -1
  15. package/dest/config/env_var.d.ts +1 -1
  16. package/dest/config/env_var.d.ts.map +1 -1
  17. package/dest/crypto/index.d.ts +1 -0
  18. package/dest/crypto/index.d.ts.map +1 -1
  19. package/dest/crypto/index.js +2 -1
  20. package/dest/crypto/keys/index.d.ts +5 -0
  21. package/dest/crypto/keys/index.d.ts.map +1 -0
  22. package/dest/crypto/keys/index.js +8 -0
  23. package/dest/eth-address/index.d.ts +2 -6
  24. package/dest/eth-address/index.d.ts.map +1 -1
  25. package/dest/eth-address/index.js +3 -7
  26. package/dest/eth-signature/eth_signature.d.ts +2 -0
  27. package/dest/eth-signature/eth_signature.d.ts.map +1 -1
  28. package/dest/eth-signature/eth_signature.js +7 -1
  29. package/dest/fields/fields.d.ts +1 -3
  30. package/dest/fields/fields.d.ts.map +1 -1
  31. package/dest/fields/fields.js +2 -1
  32. package/dest/json-rpc/client/fetch.d.ts +21 -0
  33. package/dest/json-rpc/client/fetch.d.ts.map +1 -0
  34. package/dest/json-rpc/client/fetch.js +66 -0
  35. package/dest/json-rpc/client/index.d.ts +2 -1
  36. package/dest/json-rpc/client/index.d.ts.map +1 -1
  37. package/dest/json-rpc/client/index.js +3 -2
  38. package/dest/json-rpc/client/safe_json_rpc_client.d.ts +13 -0
  39. package/dest/json-rpc/client/safe_json_rpc_client.d.ts.map +1 -0
  40. package/dest/json-rpc/client/safe_json_rpc_client.js +45 -0
  41. package/dest/json-rpc/convert.d.ts +11 -19
  42. package/dest/json-rpc/convert.d.ts.map +1 -1
  43. package/dest/json-rpc/convert.js +30 -123
  44. package/dest/json-rpc/fixtures/test_state.d.ts +45 -3
  45. package/dest/json-rpc/fixtures/test_state.d.ts.map +1 -1
  46. package/dest/json-rpc/fixtures/test_state.js +58 -2
  47. package/dest/json-rpc/index.d.ts +1 -2
  48. package/dest/json-rpc/index.d.ts.map +1 -1
  49. package/dest/json-rpc/index.js +2 -3
  50. package/dest/json-rpc/js_utils.d.ts.map +1 -1
  51. package/dest/json-rpc/js_utils.js +2 -1
  52. package/dest/json-rpc/server/index.d.ts +1 -2
  53. package/dest/json-rpc/server/index.d.ts.map +1 -1
  54. package/dest/json-rpc/server/index.js +2 -3
  55. package/dest/json-rpc/server/safe_json_rpc_server.d.ts +112 -0
  56. package/dest/json-rpc/server/safe_json_rpc_server.d.ts.map +1 -0
  57. package/dest/json-rpc/server/safe_json_rpc_server.js +275 -0
  58. package/dest/json-rpc/test/index.d.ts +2 -0
  59. package/dest/json-rpc/test/index.d.ts.map +1 -0
  60. package/dest/json-rpc/test/index.js +2 -0
  61. package/dest/json-rpc/test/integration.d.ts +13 -0
  62. package/dest/json-rpc/test/integration.d.ts.map +1 -0
  63. package/dest/json-rpc/test/integration.js +12 -0
  64. package/dest/log/logger.d.ts.map +1 -1
  65. package/dest/log/logger.js +2 -2
  66. package/dest/schemas/api.d.ts +21 -0
  67. package/dest/schemas/api.d.ts.map +1 -0
  68. package/dest/schemas/api.js +8 -0
  69. package/dest/schemas/index.d.ts +6 -0
  70. package/dest/schemas/index.d.ts.map +1 -0
  71. package/dest/schemas/index.js +6 -0
  72. package/dest/schemas/parse.d.ts +9 -0
  73. package/dest/schemas/parse.d.ts.map +1 -0
  74. package/dest/schemas/parse.js +26 -0
  75. package/dest/schemas/schemas.d.ts +79 -0
  76. package/dest/schemas/schemas.d.ts.map +1 -0
  77. package/dest/schemas/schemas.js +87 -0
  78. package/dest/schemas/types.d.ts +3 -0
  79. package/dest/schemas/types.d.ts.map +1 -0
  80. package/dest/schemas/types.js +2 -0
  81. package/dest/schemas/utils.d.ts +40 -0
  82. package/dest/schemas/utils.d.ts.map +1 -0
  83. package/dest/schemas/utils.js +56 -0
  84. package/dest/string/index.d.ts +7 -0
  85. package/dest/string/index.d.ts.map +1 -0
  86. package/dest/string/index.js +13 -0
  87. package/dest/types/index.d.ts +2 -0
  88. package/dest/types/index.d.ts.map +1 -1
  89. package/dest/validation/index.d.ts +6 -0
  90. package/dest/validation/index.d.ts.map +1 -1
  91. package/dest/validation/index.js +11 -1
  92. package/package.json +7 -4
  93. package/src/abi/abi.ts +203 -233
  94. package/src/abi/encoder.ts +2 -0
  95. package/src/abi/event_selector.ts +7 -0
  96. package/src/aztec-address/index.ts +64 -18
  97. package/src/buffer/buffer32.ts +5 -0
  98. package/src/config/env_var.ts +18 -8
  99. package/src/crypto/index.ts +1 -0
  100. package/src/crypto/keys/index.ts +9 -0
  101. package/src/eth-address/index.ts +2 -6
  102. package/src/eth-signature/eth_signature.ts +8 -0
  103. package/src/fields/fields.ts +2 -3
  104. package/src/json-rpc/client/fetch.ts +81 -0
  105. package/src/json-rpc/client/index.ts +2 -1
  106. package/src/json-rpc/client/safe_json_rpc_client.ts +61 -0
  107. package/src/json-rpc/convert.ts +29 -142
  108. package/src/json-rpc/fixtures/test_state.ts +87 -3
  109. package/src/json-rpc/index.ts +1 -8
  110. package/src/json-rpc/js_utils.ts +1 -0
  111. package/src/json-rpc/server/index.ts +1 -2
  112. package/src/json-rpc/server/safe_json_rpc_server.ts +336 -0
  113. package/src/json-rpc/test/index.ts +1 -0
  114. package/src/json-rpc/test/integration.ts +24 -0
  115. package/src/log/logger.ts +2 -1
  116. package/src/schemas/api.ts +47 -0
  117. package/src/schemas/index.ts +5 -0
  118. package/src/schemas/parse.ts +29 -0
  119. package/src/schemas/schemas.ts +111 -0
  120. package/src/schemas/types.ts +3 -0
  121. package/src/schemas/utils.ts +85 -0
  122. package/src/string/index.ts +15 -0
  123. package/src/types/index.ts +3 -0
  124. package/src/validation/index.ts +11 -0
  125. package/dest/json-rpc/class_converter.d.ts +0 -144
  126. package/dest/json-rpc/class_converter.d.ts.map +0 -1
  127. package/dest/json-rpc/class_converter.js +0 -102
  128. package/dest/json-rpc/client/json_rpc_client.d.ts +0 -35
  129. package/dest/json-rpc/client/json_rpc_client.d.ts.map +0 -1
  130. package/dest/json-rpc/client/json_rpc_client.js +0 -117
  131. package/dest/json-rpc/server/json_proxy.d.ts +0 -30
  132. package/dest/json-rpc/server/json_proxy.d.ts.map +0 -1
  133. package/dest/json-rpc/server/json_proxy.js +0 -46
  134. package/dest/json-rpc/server/json_rpc_server.d.ts +0 -102
  135. package/dest/json-rpc/server/json_rpc_server.d.ts.map +0 -1
  136. package/dest/json-rpc/server/json_rpc_server.js +0 -265
  137. package/src/json-rpc/class_converter.ts +0 -213
  138. package/src/json-rpc/client/json_rpc_client.ts +0 -148
  139. package/src/json-rpc/server/json_proxy.ts +0 -60
  140. package/src/json-rpc/server/json_rpc_server.ts +0 -332
@@ -1,9 +1,16 @@
1
+ /* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */
1
2
  import { inspect } from 'util';
2
3
 
3
4
  import { Fr, fromBuffer } from '../fields/index.js';
4
5
  import { type BufferReader, FieldReader } from '../serialize/index.js';
5
6
  import { TypeRegistry } from '../serialize/type_registry.js';
7
+ import { hexToBuffer } from '../string/index.js';
6
8
 
9
+ /** Branding to ensure fields are not interchangeable types. */
10
+ export interface AztecAddress {
11
+ /** Brand. */
12
+ _branding: 'AztecAddress';
13
+ }
7
14
  /**
8
15
  * AztecAddress represents a 32-byte address in the Aztec Protocol.
9
16
  * It provides methods to create, manipulate, and compare addresses.
@@ -11,51 +18,90 @@ import { TypeRegistry } from '../serialize/type_registry.js';
11
18
  * It should have a value less than or equal to this max value.
12
19
  * This class also provides helper functions to convert addresses from strings, buffers, and other formats.
13
20
  */
14
- export class AztecAddress extends Fr {
15
- constructor(buffer: Buffer) {
16
- if (buffer.length !== 32) {
21
+ export class AztecAddress {
22
+ private value: Fr;
23
+
24
+ constructor(buffer: Buffer | Fr) {
25
+ if ('length' in buffer && buffer.length !== 32) {
17
26
  throw new Error(`Invalid AztecAddress length ${buffer.length}.`);
18
27
  }
19
- super(buffer);
28
+ this.value = new Fr(buffer);
20
29
  }
21
30
 
22
31
  [inspect.custom]() {
23
32
  return `AztecAddress<${this.toString()}>`;
24
33
  }
25
34
 
26
- static override ZERO = new AztecAddress(Buffer.alloc(32));
35
+ static isAddress(str: string) {
36
+ return /^(0x)?[a-fA-F0-9]{64}$/.test(str);
37
+ }
38
+
39
+ static SIZE_IN_BYTES = Fr.SIZE_IN_BYTES;
40
+
41
+ static ZERO = new AztecAddress(Buffer.alloc(32, 0));
27
42
 
28
- static override zero(): AztecAddress {
43
+ static zero(): AztecAddress {
29
44
  return AztecAddress.ZERO;
30
45
  }
31
46
 
32
- static override fromBuffer(buffer: Buffer | BufferReader) {
33
- return fromBuffer(buffer, AztecAddress);
47
+ static fromField(fr: Fr) {
48
+ return new AztecAddress(fr);
34
49
  }
35
50
 
36
- static fromField(fr: Fr) {
37
- return new AztecAddress(fr.toBuffer());
51
+ static fromBuffer(buffer: Buffer | BufferReader) {
52
+ return new AztecAddress(fromBuffer(buffer, Fr));
38
53
  }
39
54
 
40
55
  static fromFields(fields: Fr[] | FieldReader) {
41
56
  const reader = FieldReader.asReader(fields);
42
- return AztecAddress.fromField(reader.readField());
57
+ return new AztecAddress(reader.readField());
43
58
  }
44
59
 
45
60
  static fromBigInt(value: bigint) {
46
- return AztecAddress.fromField(new Fr(value));
61
+ return new AztecAddress(new Fr(value));
62
+ }
63
+
64
+ static fromNumber(value: number) {
65
+ return new AztecAddress(new Fr(value));
66
+ }
67
+
68
+ static fromString(buf: string) {
69
+ return new AztecAddress(hexToBuffer(buf));
70
+ }
71
+
72
+ static random() {
73
+ return new AztecAddress(Fr.random());
74
+ }
75
+
76
+ get size() {
77
+ return this.value.size;
78
+ }
79
+
80
+ equals(other: AztecAddress) {
81
+ return this.value.equals(other.value);
82
+ }
83
+
84
+ isZero() {
85
+ return this.value.isZero();
86
+ }
87
+
88
+ toBuffer() {
89
+ return this.value.toBuffer();
90
+ }
91
+
92
+ toBigInt() {
93
+ return this.value.toBigInt();
47
94
  }
48
95
 
49
- static override fromString(buf: string) {
50
- const buffer = Buffer.from(buf.replace(/^0x/i, ''), 'hex');
51
- return new AztecAddress(buffer);
96
+ toField() {
97
+ return this.value;
52
98
  }
53
99
 
54
- static override random() {
55
- return new AztecAddress(super.random().toBuffer());
100
+ toString() {
101
+ return this.value.toString();
56
102
  }
57
103
 
58
- override toJSON() {
104
+ toJSON() {
59
105
  return {
60
106
  type: 'AztecAddress',
61
107
  value: this.toString(),
@@ -70,6 +70,10 @@ export class Buffer32 {
70
70
  return this.buffer.toString('hex');
71
71
  }
72
72
 
73
+ toJSON() {
74
+ return this.toString();
75
+ }
76
+
73
77
  public to0xString(): `0x${string}` {
74
78
  return `0x${this.buffer.toString('hex')}`;
75
79
  }
@@ -89,6 +93,7 @@ export class Buffer32 {
89
93
  public static fromBigInt(hash: bigint) {
90
94
  return new Buffer32(serializeBigInt(hash, Buffer32.SIZE));
91
95
  }
96
+
92
97
  public static fromField(hash: Fr) {
93
98
  return new Buffer32(serializeBigInt(hash.toBigInt()));
94
99
  }
@@ -1,7 +1,7 @@
1
1
  export type EnvVar =
2
2
  | 'ACVM_BINARY_PATH'
3
3
  | 'ACVM_WORKING_DIRECTORY'
4
- | 'APELLA_CONTRACT_ADDRESS'
4
+ | 'GOVERNANCE_CONTRACT_ADDRESS'
5
5
  | 'API_KEY'
6
6
  | 'API_PREFIX'
7
7
  | 'ARCHIVER_MAX_LOGS'
@@ -37,6 +37,7 @@ export type EnvVar =
37
37
  | 'BOT_STOP_WHEN_UNHEALTHY'
38
38
  | 'COINBASE'
39
39
  | 'DATA_DIRECTORY'
40
+ | 'DATA_STORE_MAP_SIZE_KB'
40
41
  | 'DEBUG'
41
42
  | 'DEPLOY_AZTEC_CONTRACTS_SALT'
42
43
  | 'DEPLOY_AZTEC_CONTRACTS'
@@ -46,8 +47,8 @@ export type EnvVar =
46
47
  | 'FEE_JUICE_CONTRACT_ADDRESS'
47
48
  | 'FEE_JUICE_PORTAL_CONTRACT_ADDRESS'
48
49
  | 'FEE_RECIPIENT'
49
- | 'GEROUSIA_CONTRACT_ADDRESS'
50
- | 'GEROUSIA_PAYLOAD_ADDRESS'
50
+ | 'GOVERNANCE_PROPOSER_CONTRACT_ADDRESS'
51
+ | 'GOVERNANCE_PROPOSER_PAYLOAD_ADDRESS'
51
52
  | 'INBOX_CONTRACT_ADDRESS'
52
53
  | 'L1_CHAIN_ID'
53
54
  | 'L1_PRIVATE_KEY'
@@ -57,11 +58,13 @@ export type EnvVar =
57
58
  | 'MNEMONIC'
58
59
  | 'NETWORK_NAME'
59
60
  | 'NETWORK'
60
- | 'NOMISMATOKOPIO_CONTRACT_ADDRESS'
61
+ | 'COIN_ISSUER_CONTRACT_ADDRESS'
61
62
  | 'OTEL_EXPORTER_OTLP_METRICS_ENDPOINT'
62
63
  | 'OTEL_EXPORTER_OTLP_TRACES_ENDPOINT'
63
64
  | 'OTEL_EXPORTER_OTLP_LOGS_ENDPOINT'
64
65
  | 'OTEL_SERVICE_NAME'
66
+ | 'OTEL_COLLECT_INTERVAL_MS'
67
+ | 'OTEL_EXPORT_TIMEOUT_MS'
65
68
  | 'OUTBOX_CONTRACT_ADDRESS'
66
69
  | 'P2P_BLOCK_CHECK_INTERVAL_MS'
67
70
  | 'P2P_BLOCK_REQUEST_BATCH_SIZE'
@@ -110,7 +113,6 @@ export type EnvVar =
110
113
  | 'PROVER_REQUIRED_CONFIRMATIONS'
111
114
  | 'PROVER_TEST_DELAY_MS'
112
115
  | 'PXE_BLOCK_POLLING_INTERVAL_MS'
113
- | 'PXE_DATA_DIRECTORY'
114
116
  | 'PXE_L2_STARTING_BLOCK'
115
117
  | 'PXE_PROVER_ENABLED'
116
118
  | 'QUOTE_PROVIDER_BASIS_POINT_FEE'
@@ -131,12 +133,13 @@ export type EnvVar =
131
133
  | 'SEQ_PUBLISHER_PRIVATE_KEY'
132
134
  | 'SEQ_REQUIRED_CONFIRMATIONS'
133
135
  | 'SEQ_TX_POLLING_INTERVAL_MS'
134
- | 'SYSSTIA_CONTRACT_ADDRESS'
136
+ | 'SEQ_ENFORCE_TIME_TABLE'
137
+ | 'REWARD_DISTRIBUTOR_CONTRACT_ADDRESS'
135
138
  | 'TELEMETRY'
136
139
  | 'TEST_ACCOUNTS'
137
140
  | 'TX_GOSSIP_VERSION'
138
141
  | 'TXE_PORT'
139
- | 'VALIDATOR_ATTESTATIONS_POOLING_INTERVAL_MS'
142
+ | 'VALIDATOR_ATTESTATIONS_POLLING_INTERVAL_MS'
140
143
  | 'VALIDATOR_ATTESTATIONS_WAIT_TIMEOUT_MS'
141
144
  | 'VALIDATOR_DISABLED'
142
145
  | 'VALIDATOR_PRIVATE_KEY'
@@ -147,4 +150,11 @@ export type EnvVar =
147
150
  | 'VERIFIER_VIEM_POLLING_INTERVAL_MS'
148
151
  | 'L1_READER_VIEM_POLLING_INTERVAL_MS'
149
152
  | 'PROVER_VIEM_POLLING_INTERVAL_MS'
150
- | 'SEQ_VIEM_POLLING_INTERVAL_MS';
153
+ | 'SEQ_VIEM_POLLING_INTERVAL_MS'
154
+ | 'WS_DB_MAP_SIZE_KB'
155
+ | 'WS_DATA_DIRECTORY'
156
+ | 'ETHEREUM_SLOT_DURATION'
157
+ | 'AZTEC_SLOT_DURATION'
158
+ | 'AZTEC_EPOCH_DURATION'
159
+ | 'AZTEC_TARGET_COMMITTEE_SIZE'
160
+ | 'AZTEC_EPOCH_PROOF_CLAIM_WINDOW_IN_L2_SLOTS';
@@ -7,6 +7,7 @@ export * from './sha512/index.js';
7
7
  export * from './pedersen/index.js';
8
8
  export * from './poseidon/index.js';
9
9
  export * from './secp256k1-signer/index.js';
10
+ export * from './keys/index.js';
10
11
 
11
12
  /**
12
13
  * Init the bb singleton. This constructs (if not already) the barretenberg sync api within bb.js itself.
@@ -0,0 +1,9 @@
1
+ import { BarretenbergSync, RawBuffer } from '@aztec/bb.js';
2
+
3
+ import { Fr } from '../../fields/fields.js';
4
+
5
+ export function vkAsFieldsMegaHonk(input: Buffer): Fr[] {
6
+ return BarretenbergSync.getSingleton()
7
+ .acirVkAsFieldsMegaHonk(new RawBuffer(input))
8
+ .map(bbFr => Fr.fromBuffer(Buffer.from(bbFr.toBuffer()))); // TODO(#4189): remove this conversion
9
+ }
@@ -13,13 +13,9 @@ import { TypeRegistry } from '../serialize/type_registry.js';
13
13
  * and can be serialized/deserialized from a buffer or BufferReader.
14
14
  */
15
15
  export class EthAddress {
16
- /**
17
- * The size of an Ethereum address in bytes.
18
- */
16
+ /** The size of an Ethereum address in bytes. */
19
17
  public static SIZE_IN_BYTES = 20;
20
- /**
21
- * Represents a zero Ethereum address with 20 bytes filled with zeros.
22
- */
18
+ /** Represents a zero Ethereum address with 20 bytes filled with zeros. */
23
19
  public static ZERO = new EthAddress(Buffer.alloc(EthAddress.SIZE_IN_BYTES));
24
20
 
25
21
  constructor(private buffer: Buffer) {
@@ -45,6 +45,10 @@ export class Signature {
45
45
  return new Signature(r, s, v, isEmpty);
46
46
  }
47
47
 
48
+ static isValid0xString(sig: `0x${string}`): boolean {
49
+ return /^0x[0-9a-f]{129,}$/i.test(sig);
50
+ }
51
+
48
52
  /**
49
53
  * A seperate method exists for this as when signing locally with viem, as when
50
54
  * parsing from viem, we can expect the v value to be a u8, rather than our
@@ -106,4 +110,8 @@ export class Signature {
106
110
  isEmpty: this.isEmpty,
107
111
  };
108
112
  }
113
+
114
+ toJSON() {
115
+ return this.to0xString();
116
+ }
109
117
  }
@@ -182,9 +182,7 @@ function fromHexString<T extends BaseField>(buf: string, f: DerivedField<T>) {
182
182
  return new f(buffer);
183
183
  }
184
184
 
185
- /**
186
- * Branding to ensure fields are not interchangeable types.
187
- */
185
+ /** Branding to ensure fields are not interchangeable types. */
188
186
  export interface Fr {
189
187
  /** Brand. */
190
188
  _branding: 'Fr';
@@ -302,6 +300,7 @@ export class Fr extends BaseField {
302
300
  return Fr.fromBuffer(rootBuf);
303
301
  }
304
302
 
303
+ // TODO(palla/schemas): Use toString instead of structured type
305
304
  toJSON() {
306
305
  return {
307
306
  type: 'Fr',
@@ -0,0 +1,81 @@
1
+ import { format } from 'util';
2
+
3
+ import { type DebugLogger, createDebugLogger } from '../../log/index.js';
4
+ import { NoRetryError, makeBackoff, retry } from '../../retry/index.js';
5
+ import { jsonStringify } from '../convert.js';
6
+
7
+ const log = createDebugLogger('json-rpc:json_rpc_client');
8
+
9
+ /**
10
+ * A normal fetch function that does not retry.
11
+ * Alternatives are a fetch function with retries, or a mocked fetch.
12
+ * @param host - The host URL.
13
+ * @param method - The RPC method name.
14
+ * @param body - The RPC payload.
15
+ * @param noRetry - Whether to throw a `NoRetryError` in case the response is a 5xx error and the body contains an error
16
+ * message (see `retry` function for more details).
17
+ * @returns The parsed JSON response, or throws an error.
18
+ */
19
+ export async function defaultFetch(
20
+ host: string,
21
+ rpcMethod: string,
22
+ body: any,
23
+ useApiEndpoints: boolean,
24
+ noRetry = false,
25
+ ) {
26
+ log.debug(format(`JsonRpcClient.fetch`, host, rpcMethod, '->', body));
27
+ let resp: Response;
28
+ if (useApiEndpoints) {
29
+ resp = await fetch(`${host}/${rpcMethod}`, {
30
+ method: 'POST',
31
+ body: jsonStringify(body),
32
+ headers: { 'content-type': 'application/json' },
33
+ });
34
+ } else {
35
+ resp = await fetch(host, {
36
+ method: 'POST',
37
+ body: jsonStringify({ ...body, method: rpcMethod }),
38
+ headers: { 'content-type': 'application/json' },
39
+ });
40
+ }
41
+
42
+ let responseJson;
43
+ try {
44
+ responseJson = await resp.json();
45
+ } catch (err) {
46
+ if (!resp.ok) {
47
+ throw new Error(resp.statusText);
48
+ }
49
+ throw new Error(`Failed to parse body as JSON: ${resp.text()}`);
50
+ }
51
+
52
+ if (!resp.ok) {
53
+ const errorMessage = `(JSON-RPC PROPAGATED) (host ${host}) (method ${rpcMethod}) (code ${resp.status}) ${responseJson.error.message}`;
54
+ if (noRetry || (resp.status >= 400 && resp.status < 500)) {
55
+ throw new NoRetryError(errorMessage);
56
+ } else {
57
+ throw new Error(errorMessage);
58
+ }
59
+ }
60
+
61
+ return responseJson;
62
+ }
63
+
64
+ /**
65
+ * Makes a fetch function that retries based on the given attempts.
66
+ * @param retries - Sequence of intervals (in seconds) to retry.
67
+ * @param noRetry - Whether to stop retries on server errors.
68
+ * @param log - Optional logger for logging attempts.
69
+ * @returns A fetch function.
70
+ */
71
+ export function makeFetch(retries: number[], defaultNoRetry: boolean, log?: DebugLogger) {
72
+ return async (host: string, rpcMethod: string, body: any, useApiEndpoints: boolean, noRetry?: boolean) => {
73
+ return await retry(
74
+ () => defaultFetch(host, rpcMethod, body, useApiEndpoints, noRetry ?? defaultNoRetry),
75
+ `JsonRpcClient request ${rpcMethod} to ${host}`,
76
+ makeBackoff(retries),
77
+ log,
78
+ false,
79
+ );
80
+ };
81
+ }
@@ -1 +1,2 @@
1
- export { createJsonRpcClient, defaultFetch, makeFetch } from './json_rpc_client.js';
1
+ export * from './safe_json_rpc_client.js';
2
+ export * from './fetch.js';
@@ -0,0 +1,61 @@
1
+ import { format } from 'util';
2
+
3
+ import { createDebugLogger } from '../../log/logger.js';
4
+ import { type ApiSchema, type ApiSchemaFor, schemaHasMethod } from '../../schemas/api.js';
5
+ import { defaultFetch } from './fetch.js';
6
+
7
+ /**
8
+ * Creates a Proxy object that delegates over RPC and validates outputs against a given schema.
9
+ * The server is expected to be a JsonRpcServer.
10
+ * @param host - The host URL.
11
+ * @param schema - The api schema to validate returned data against.
12
+ * @param useApiEndpoints - Whether to use the API endpoints or the default RPC endpoint.
13
+ * @param namespaceMethods - String value (or false/empty) to namespace all methods sent to the server. e.g. 'getInfo' -\> 'pxe_getInfo'
14
+ * @param fetch - The fetch implementation to use.
15
+ */
16
+ export function createSafeJsonRpcClient<T extends object>(
17
+ host: string,
18
+ schema: ApiSchemaFor<T>,
19
+ useApiEndpoints: boolean = false,
20
+ namespaceMethods?: string | false,
21
+ fetch = defaultFetch,
22
+ log = createDebugLogger('json-rpc:client'),
23
+ ): T {
24
+ let id = 0;
25
+ const request = async (methodName: string, params: any[]): Promise<any> => {
26
+ if (!schemaHasMethod(schema, methodName)) {
27
+ throw new Error(`Unspecified method ${methodName} in client schema`);
28
+ }
29
+ const method = namespaceMethods ? `${namespaceMethods}_${methodName}` : methodName;
30
+ const body = { jsonrpc: '2.0', id: id++, method, params };
31
+
32
+ log.debug(format(`request`, method, params));
33
+ const res = await fetch(host, method, body, useApiEndpoints);
34
+ log.debug(format(`result`, method, res));
35
+
36
+ if (res.error) {
37
+ throw res.error;
38
+ }
39
+ // TODO(palla/schemas): Find a better way to handle null responses (JSON.stringify(null) is string "null").
40
+ if ([null, undefined, 'null', 'undefined'].includes(res.result)) {
41
+ return;
42
+ }
43
+
44
+ return (schema as ApiSchema)[methodName].returnType().parse(res.result);
45
+ };
46
+
47
+ // Intercept any RPC methods with a proxy
48
+ const proxy = new Proxy(
49
+ {},
50
+ {
51
+ get: (target, method: string) => {
52
+ if (['then', 'catch'].includes(method)) {
53
+ return Reflect.get(target, method);
54
+ }
55
+ return (...params: any[]) => request(method, params);
56
+ },
57
+ },
58
+ ) as T;
59
+
60
+ return proxy;
61
+ }
@@ -1,163 +1,50 @@
1
1
  import { Buffer } from 'buffer';
2
- import cloneDeepWith from 'lodash.clonedeepwith';
3
2
 
4
- import { type ClassConverter } from './class_converter.js';
3
+ import { type ZodFor } from '../schemas/types.js';
5
4
 
6
5
  /**
7
- * Check prototype chain to determine if an object is 'plain' (not a class instance).
8
- * @param obj - The object to check.
9
- * @returns True if the object is 'plain'.
6
+ * Parses a json string and then feeds it to a zod schema.
7
+ * @param json - JSON string.
8
+ * @param schema - Zod schema.
9
+ * @returns Result of parsing json with schema.
10
10
  */
11
- function isPlainObject(obj: any) {
12
- if (obj === null) {
13
- return false;
14
- }
15
-
16
- let proto = obj;
17
- let counter = 0;
18
- const MAX_PROTOTYPE_CHAIN_LENGTH = 1000; // Adjust as needed
19
- while (Object.getPrototypeOf(proto) !== null) {
20
- if (counter >= MAX_PROTOTYPE_CHAIN_LENGTH) {
21
- // This is a failsafe in case circular prototype chain has been created. It should not be hit
22
- return false;
23
- }
24
- proto = Object.getPrototypeOf(proto);
25
- counter++;
26
- }
27
-
28
- return Object.getPrototypeOf(obj) === proto;
11
+ export function jsonParseWithSchema<T>(json: string, schema: ZodFor<T>): T {
12
+ return schema.parse(JSON.parse(json));
29
13
  }
30
14
 
31
15
  /**
32
- * Recursively looks through an object for bigints and converts them to object format.
33
- * @param obj - The object to convert.
34
- * @returns The converted object with stringified bigints.
35
- */
36
- export const convertBigintsInObj = (obj: any) => {
37
- return cloneDeepWith(obj, (value: any) => {
38
- if (typeof value === 'bigint') {
39
- return { type: 'bigint', data: value.toString() };
40
- }
41
- });
42
- };
43
-
44
- /**
45
- * JSON.stringify helper that handles bigints.
16
+ * JSON.stringify helper that stringifies bigints, buffers, maps, and sets.
46
17
  * @param obj - The object to be stringified.
47
18
  * @returns The resulting string.
48
19
  */
49
- export function JsonStringify(obj: object, prettify?: boolean): string {
20
+ export function jsonStringify(obj: object, prettify?: boolean): string {
50
21
  return JSON.stringify(
51
22
  obj,
52
- (key, value) =>
53
- typeof value === 'bigint'
54
- ? JSON.stringify({
55
- type: 'bigint',
56
- data: value.toString(),
57
- })
58
- : value,
23
+ (_key, value) => {
24
+ if (typeof value === 'bigint') {
25
+ return value.toString();
26
+ } else if (typeof value === 'object' && Buffer.isBuffer(value)) {
27
+ return value.toString('base64');
28
+ } else if (typeof value === 'object' && value instanceof Map) {
29
+ return Array.from(value.entries());
30
+ } else if (typeof value === 'object' && value instanceof Set) {
31
+ return Array.from(value.values());
32
+ } else {
33
+ return value;
34
+ }
35
+ },
59
36
  prettify ? 2 : 0,
60
37
  );
61
38
  }
62
39
 
63
40
  /**
64
- * Convert a JSON-friendly object, which may encode a class object.
65
- * @param cc - The class converter.
66
- * @param obj - The encoded object.
67
- * @returns The decoded object.
41
+ * Calls jsonStringify but swallows errors.
42
+ * Use for logging, when you don't want to potentially introduce another thing that throws.
68
43
  */
69
- export function convertFromJsonObj(cc: ClassConverter, obj: any): any {
70
- if (obj === null) {
71
- return undefined; // `null` doesn't work with default args.
72
- }
73
-
74
- if (!obj) {
75
- return obj; // Primitive type
76
- }
77
- // Is this a serialized Node buffer?
78
- if (obj.type === 'Buffer' && typeof obj.data === 'string') {
79
- return Buffer.from(obj.data, 'base64');
80
- }
81
-
82
- if (obj.type === 'bigint' && typeof obj.data === 'string') {
83
- return BigInt(obj.data);
84
- }
85
-
86
- // Is this a convertible type?
87
- if (typeof obj.type === 'string') {
88
- if (cc.isRegisteredClassName(obj.type)) {
89
- return cc.toClassObj(obj);
90
- } else {
91
- throw new Error(`Object ${obj.type} not registered for serialization FROM JSON`);
92
- }
93
- }
94
-
95
- // Is this an array?
96
- if (Array.isArray(obj)) {
97
- return obj.map((x: any) => convertFromJsonObj(cc, x));
98
- }
99
-
100
- // Is this a dictionary?
101
- if (typeof obj === 'object') {
102
- const newObj: any = {};
103
- for (const key of Object.keys(obj)) {
104
- newObj[key] = convertFromJsonObj(cc, obj[key]);
105
- }
106
- return newObj;
44
+ export function tryJsonStringify(obj: any, prettify?: boolean): string | undefined {
45
+ try {
46
+ return jsonStringify(obj, prettify);
47
+ } catch (e) {
48
+ return undefined;
107
49
  }
108
-
109
- // Leave alone, assume JSON primitive
110
- return obj;
111
- }
112
-
113
- /**
114
- * Convert objects or classes to a JSON-friendly object.
115
- * @param cc - The class converter.
116
- * @param obj - The object.
117
- * @returns The encoded object.
118
- */
119
- export function convertToJsonObj(cc: ClassConverter, obj: any): any {
120
- // Bigint is a primitive type that needs special handling since it's not serializable
121
- if (typeof obj === 'bigint') {
122
- return {
123
- type: 'bigint',
124
- data: obj.toString(),
125
- };
126
- }
127
-
128
- if (!obj) {
129
- return obj; // Primitive type
130
- }
131
-
132
- // Is this a Node buffer?
133
- if (Buffer.isBuffer(obj)) {
134
- return { type: 'Buffer', data: obj.toString('base64') };
135
- }
136
-
137
- // Is this a convertible type?
138
- if (cc.isRegisteredClass(obj.constructor)) {
139
- return cc.toJsonObj(obj);
140
- }
141
-
142
- // Is this an array?
143
- if (Array.isArray(obj)) {
144
- return obj.map((x: any) => convertToJsonObj(cc, x));
145
- }
146
-
147
- if (typeof obj === 'object') {
148
- // Is this a dictionary?
149
- if (isPlainObject(obj)) {
150
- const newObj: any = {};
151
- for (const key of Object.keys(obj)) {
152
- newObj[key] = convertToJsonObj(cc, obj[key]);
153
- }
154
- return newObj;
155
- } else {
156
- // Throw if this is a non-primitive class that was not registered
157
- throw new Error(`Object ${obj.constructor.name} not registered for serialization TO JSON`);
158
- }
159
- }
160
-
161
- // Leave alone, assume JSON primitive
162
- return obj;
163
50
  }