@quentinadam/evm-base 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +12 -0
  2. package/dist/ABI.d.ts +21 -0
  3. package/dist/ABI.d.ts.map +1 -0
  4. package/dist/ABI.js +393 -0
  5. package/dist/ABI.js.map +1 -0
  6. package/dist/Block.d.ts +9 -0
  7. package/dist/Block.d.ts.map +1 -0
  8. package/dist/Block.js +2 -0
  9. package/dist/Block.js.map +1 -0
  10. package/dist/Client.d.ts +43 -0
  11. package/dist/Client.d.ts.map +1 -0
  12. package/dist/Client.js +116 -0
  13. package/dist/Client.js.map +1 -0
  14. package/dist/ClientError.d.ts +10 -0
  15. package/dist/ClientError.d.ts.map +1 -0
  16. package/dist/ClientError.js +10 -0
  17. package/dist/ClientError.js.map +1 -0
  18. package/dist/ClientHelper.d.ts +32 -0
  19. package/dist/ClientHelper.d.ts.map +1 -0
  20. package/dist/ClientHelper.js +107 -0
  21. package/dist/ClientHelper.js.map +1 -0
  22. package/dist/DataEncoder.d.ts +14 -0
  23. package/dist/DataEncoder.d.ts.map +1 -0
  24. package/dist/DataEncoder.js +20 -0
  25. package/dist/DataEncoder.js.map +1 -0
  26. package/dist/Log.d.ts +13 -0
  27. package/dist/Log.d.ts.map +1 -0
  28. package/dist/Log.js +2 -0
  29. package/dist/Log.js.map +1 -0
  30. package/dist/MulticallClient.d.ts +18 -0
  31. package/dist/MulticallClient.d.ts.map +1 -0
  32. package/dist/MulticallClient.js +71 -0
  33. package/dist/MulticallClient.js.map +1 -0
  34. package/dist/Transaction.d.ts +17 -0
  35. package/dist/Transaction.d.ts.map +1 -0
  36. package/dist/Transaction.js +2 -0
  37. package/dist/Transaction.js.map +1 -0
  38. package/dist/TransactionReceipt.d.ts +14 -0
  39. package/dist/TransactionReceipt.d.ts.map +1 -0
  40. package/dist/TransactionReceipt.js +2 -0
  41. package/dist/TransactionReceipt.js.map +1 -0
  42. package/dist/computeCREATE2Address.d.ts +17 -0
  43. package/dist/computeCREATE2Address.d.ts.map +1 -0
  44. package/dist/computeCREATE2Address.js +20 -0
  45. package/dist/computeCREATE2Address.js.map +1 -0
  46. package/dist/computeCREATEAddress.d.ts +8 -0
  47. package/dist/computeCREATEAddress.d.ts.map +1 -0
  48. package/dist/computeCREATEAddress.js +7 -0
  49. package/dist/computeCREATEAddress.js.map +1 -0
  50. package/dist/mod.d.ts +15 -0
  51. package/dist/mod.d.ts.map +1 -0
  52. package/dist/mod.js +10 -0
  53. package/dist/mod.js.map +1 -0
  54. package/package.json +33 -0
  55. package/src/ABI.ts +434 -0
  56. package/src/Block.ts +9 -0
  57. package/src/Client.ts +152 -0
  58. package/src/ClientError.ts +10 -0
  59. package/src/ClientHelper.ts +131 -0
  60. package/src/DataEncoder.ts +28 -0
  61. package/src/Log.ts +13 -0
  62. package/src/MulticallClient.ts +87 -0
  63. package/src/Transaction.ts +17 -0
  64. package/src/TransactionReceipt.ts +15 -0
  65. package/src/computeCREATE2Address.ts +32 -0
  66. package/src/computeCREATEAddress.ts +13 -0
  67. package/src/mod.ts +25 -0
package/src/ABI.ts ADDED
@@ -0,0 +1,434 @@
1
+ import assert from '@quentinadam/assert';
2
+ import * as Uint8ArrayExtension from '@quentinadam/uint8array-extension';
3
+ import keccak256 from '@quentinadam/hash/keccak256';
4
+ import ensure from '@quentinadam/ensure';
5
+
6
+ abstract class Element {
7
+ readonly encodedLength: number | undefined;
8
+
9
+ constructor(encodedLength: number | undefined) {
10
+ this.encodedLength = encodedLength;
11
+ }
12
+
13
+ get dynamic(): boolean {
14
+ return this.encodedLength === undefined;
15
+ }
16
+
17
+ abstract encode(value: unknown): Uint8Array<ArrayBuffer>;
18
+
19
+ abstract decode(bytes: Uint8Array<ArrayBuffer>): unknown;
20
+ }
21
+
22
+ class IntElement extends Element {
23
+ readonly bits;
24
+
25
+ constructor(bits: number) {
26
+ super(32);
27
+ assert(Number.isSafeInteger(bits), 'Bits must be an integer');
28
+ assert(bits % 8 === 0, 'Bits must be a multiple of 8');
29
+ assert(bits >= 8 && bits <= 256, 'Bits must be between 8 and 256');
30
+ this.bits = bits;
31
+ }
32
+
33
+ override encode(value: unknown) {
34
+ assert(typeof value === 'number' || typeof value === 'bigint', 'Value must be a number or a bigint');
35
+ return ((value) => {
36
+ if (typeof value === 'number') {
37
+ assert(Number.isSafeInteger(value), 'Value must be a safe integer');
38
+ value = BigInt(value);
39
+ }
40
+ return Uint8ArrayExtension.fromIntBE(value, 32);
41
+ })(value);
42
+ }
43
+
44
+ override decode(bytes: Uint8Array<ArrayBuffer>) {
45
+ assert(bytes.length >= 32);
46
+ const value = Uint8ArrayExtension.toBigUintBE(bytes.slice(0, 32));
47
+ return (value >= (1n << 255n)) ? value - (1n << 256n) : value;
48
+ }
49
+ }
50
+
51
+ class UintElement extends Element {
52
+ readonly bits;
53
+
54
+ constructor(bits: number) {
55
+ super(32);
56
+ assert(Number.isSafeInteger(bits), 'Bits must be an integer');
57
+ assert(bits % 8 === 0, 'Bits must be a multiple of 8');
58
+ assert(bits >= 8 && bits <= 256, 'Bits must be between 8 and 256');
59
+ this.bits = bits;
60
+ }
61
+
62
+ override encode(value: unknown) {
63
+ assert(typeof value === 'number' || typeof value === 'bigint', 'Value must be a number or a bigint');
64
+ return ((value) => {
65
+ if (typeof value === 'number') {
66
+ assert(Number.isSafeInteger(value), 'Value must be a safe integer');
67
+ value = BigInt(value);
68
+ }
69
+ return Uint8ArrayExtension.fromUintBE(value, 32);
70
+ })(value);
71
+ }
72
+
73
+ override decode(bytes: Uint8Array<ArrayBuffer>) {
74
+ assert(bytes.length >= 32);
75
+ return Uint8ArrayExtension.toBigUintBE(bytes.slice(0, 32));
76
+ }
77
+ }
78
+
79
+ class BoolElement extends Element {
80
+ constructor() {
81
+ super(32);
82
+ }
83
+
84
+ override encode(value: unknown) {
85
+ assert(typeof value === 'boolean', 'Value must be a boolean');
86
+ return ((value) => {
87
+ return Uint8ArrayExtension.fromUintBE(value ? 1 : 0, 32);
88
+ })(value);
89
+ }
90
+
91
+ override decode(bytes: Uint8Array<ArrayBuffer>) {
92
+ assert(bytes.length >= 32);
93
+ const value = Uint8ArrayExtension.toBigUintBE(bytes.slice(0, 32));
94
+ if (value === 0n) {
95
+ return false;
96
+ } else {
97
+ assert(value === 1n);
98
+ return true;
99
+ }
100
+ }
101
+ }
102
+
103
+ abstract class AddressElement extends Element {
104
+ abstract bytesFromAddress(address: string): Uint8Array<ArrayBuffer>;
105
+ abstract addressFromBytes(bytes: Uint8Array<ArrayBuffer>): string;
106
+
107
+ constructor() {
108
+ super(32);
109
+ }
110
+
111
+ override encode(value: unknown) {
112
+ assert(typeof value === 'string', 'Value must be a string');
113
+ const bytes = this.bytesFromAddress(value);
114
+ assert(bytes.length === 20, 'Buffer must be 20 bytes long');
115
+ return Uint8ArrayExtension.padStart(bytes, 32);
116
+ }
117
+
118
+ override decode(bytes: Uint8Array<ArrayBuffer>) {
119
+ assert(bytes.length >= 32);
120
+ assert(bytes.slice(0, 12).every((byte) => byte === 0), 'First 12 bytes must be zero');
121
+ return this.addressFromBytes(bytes.slice(12, 32));
122
+ }
123
+ }
124
+
125
+ class StringElement extends Element {
126
+ constructor() {
127
+ super(undefined);
128
+ }
129
+
130
+ override encode(value: unknown) {
131
+ assert(typeof value === 'string', 'Value must be a string');
132
+ let bytes = new TextEncoder().encode(value);
133
+ const length = bytes.length;
134
+ bytes = padEnd(bytes);
135
+ return Uint8ArrayExtension.concat([Uint8ArrayExtension.fromUintBE(length, 32), bytes]);
136
+ }
137
+
138
+ override decode(bytes: Uint8Array<ArrayBuffer>) {
139
+ assert(bytes.length >= 32);
140
+ const length = Number(Uint8ArrayExtension.toBigUintBE(bytes.slice(0, 32)));
141
+ assert(bytes.length >= 32 + padLength(length));
142
+ return new TextDecoder().decode(bytes.slice(32, 32 + length));
143
+ }
144
+ }
145
+
146
+ class BytesElement extends Element {
147
+ readonly length;
148
+
149
+ constructor(length?: number) {
150
+ super(length === undefined ? undefined : 32);
151
+ if (length !== undefined) {
152
+ assert(Number.isSafeInteger(length), 'Length must be an integer');
153
+ assert(length >= 1 && length <= 32, 'Length must be between 1 and 32');
154
+ }
155
+ this.length = length;
156
+ }
157
+
158
+ #encode(bytes: Uint8Array<ArrayBuffer>) {
159
+ const length = bytes.length;
160
+ bytes = padEnd(bytes);
161
+ if (this.length !== undefined) {
162
+ return bytes;
163
+ } else {
164
+ return Uint8ArrayExtension.concat([Uint8ArrayExtension.fromUintBE(length, 32), bytes]);
165
+ }
166
+ }
167
+
168
+ override encode(value: unknown) {
169
+ if (typeof value === 'string') {
170
+ const match = value.match(/^(?:0x)?(?<hex>(?:[0-9a-f][0-9a-f])*)$/i);
171
+ if (match !== null) {
172
+ return this.#encode(Uint8Array.fromHex(ensure(ensure(match.groups).hex)));
173
+ }
174
+ }
175
+ if (value instanceof Uint8Array && Uint8ArrayExtension.isArrayBufferBacked(value)) {
176
+ return this.#encode(value);
177
+ }
178
+ throw new Error('Value must be a hex string or a Uint8Array<ArrayBuffer>');
179
+ }
180
+
181
+ override decode(bytes: Uint8Array<ArrayBuffer>) {
182
+ assert(bytes.length >= 32);
183
+ if (this.length !== undefined) {
184
+ return bytes.slice(0, this.length);
185
+ } else {
186
+ const length = Number(Uint8ArrayExtension.toBigUintBE(bytes.slice(0, 32)));
187
+ assert(bytes.length >= 32 + padLength(length));
188
+ return bytes.slice(32, 32 + length);
189
+ }
190
+ }
191
+ }
192
+
193
+ function padLength(length: number) {
194
+ return length + (32 - length % 32) % 32;
195
+ }
196
+
197
+ function padEnd(bytes: Uint8Array<ArrayBuffer>) {
198
+ return Uint8ArrayExtension.padEnd(bytes, padLength(bytes.length));
199
+ }
200
+
201
+ class StructElement extends Element {
202
+ readonly elements;
203
+
204
+ constructor(elements: Element[]) {
205
+ super((() => {
206
+ let encodedLength = 0;
207
+ for (const element of elements) {
208
+ if (element.encodedLength === undefined) {
209
+ return undefined;
210
+ }
211
+ encodedLength += element.encodedLength;
212
+ }
213
+ return encodedLength;
214
+ })());
215
+ this.elements = elements;
216
+ }
217
+
218
+ override encode(value: unknown) {
219
+ assert(Array.isArray(value), 'Value must be an array');
220
+ return encode({ elements: this.elements, values: value });
221
+ }
222
+
223
+ override decode(bytes: Uint8Array<ArrayBuffer>) {
224
+ let offset = 0;
225
+ return this.elements.map((element) => {
226
+ const encodedLength = element.encodedLength;
227
+ if (encodedLength === undefined) {
228
+ const _offset = Number(Uint8ArrayExtension.toBigUintBE(bytes.slice(offset, offset + 32)));
229
+ const value = element.decode(bytes.slice(_offset));
230
+ offset += 32;
231
+ return value;
232
+ } else {
233
+ const value = element.decode(bytes.slice(offset));
234
+ offset += encodedLength;
235
+ return value;
236
+ }
237
+ });
238
+ }
239
+ }
240
+
241
+ function encode({ elements, values }: { elements: Element[]; values: unknown[] }) {
242
+ assert(values.length === elements.length, 'Array length must match');
243
+ return ((elements) => {
244
+ let offset = 0;
245
+ for (const { dynamic, encodedValue } of elements) {
246
+ if (dynamic) {
247
+ offset += 32;
248
+ } else {
249
+ offset += encodedValue.length;
250
+ }
251
+ }
252
+ const heads = new Array<Uint8Array<ArrayBuffer>>();
253
+ const tails = new Array<Uint8Array<ArrayBuffer>>();
254
+ for (const { dynamic, encodedValue } of elements) {
255
+ if (dynamic) {
256
+ heads.push(Uint8ArrayExtension.fromUintBE(offset, 32));
257
+ tails.push(encodedValue);
258
+ offset += encodedValue.length;
259
+ } else {
260
+ heads.push(encodedValue);
261
+ }
262
+ }
263
+ return Uint8ArrayExtension.concat([...heads, ...tails]);
264
+ })(elements.map((element, index) => {
265
+ const value = values[index];
266
+ const encodedValue = element.encode(value);
267
+ return { dynamic: element.dynamic, encodedValue };
268
+ }));
269
+ }
270
+
271
+ class ArrayElement extends Element {
272
+ readonly element;
273
+ readonly length;
274
+
275
+ constructor(element: Element, length?: number) {
276
+ super(length === undefined || element.encodedLength === undefined ? undefined : element.encodedLength * length);
277
+ this.element = element;
278
+ this.length = length;
279
+ }
280
+
281
+ override encode(values: unknown) {
282
+ assert(Array.isArray(values), 'Value must be an array');
283
+ const encodedValue = encode({ elements: new Array(values.length).fill(this.element), values });
284
+ if (this.length !== undefined) {
285
+ assert(values.length === this.length, 'Array length must match');
286
+ return encodedValue;
287
+ } else {
288
+ return Uint8ArrayExtension.concat([Uint8ArrayExtension.fromUintBE(values.length, 32), encodedValue]);
289
+ }
290
+ }
291
+
292
+ override decode(bytes: Uint8Array<ArrayBuffer>) {
293
+ const { length, dataOffset } = (() => {
294
+ if (this.length === undefined) {
295
+ // Dynamic array: read length from first 32 bytes
296
+ assert(bytes.length >= 32);
297
+ return { length: Number(Uint8ArrayExtension.toBigUintBE(bytes.slice(0, 32))), dataOffset: 32 };
298
+ } else {
299
+ // Fixed array: use predefined length, no length prefix
300
+ return { length: this.length, dataOffset: 0 };
301
+ }
302
+ })();
303
+ const encodedElementLength = this.element.encodedLength;
304
+ if (encodedElementLength === undefined) {
305
+ // Dynamic elements: use offsets
306
+ assert(bytes.length >= dataOffset + length * 32);
307
+ return Array.from({ length }, (_, index) => {
308
+ const offset = Number(
309
+ Uint8ArrayExtension.toBigUintBE(bytes.slice(dataOffset + index * 32, dataOffset + (index + 1) * 32)),
310
+ );
311
+ return this.element.decode(bytes.slice(dataOffset + offset));
312
+ });
313
+ } else {
314
+ // Static elements: decode sequentially
315
+ assert(bytes.length >= dataOffset + length * encodedElementLength);
316
+ return Array.from({ length }, (_, index) => {
317
+ const offset = dataOffset + index * encodedElementLength;
318
+ return this.element.decode(bytes.slice(offset, offset + encodedElementLength));
319
+ });
320
+ }
321
+ }
322
+ }
323
+
324
+ export default class ABI {
325
+ readonly #addressFromBytes: (bytes: Uint8Array<ArrayBuffer>) => string;
326
+ readonly #bytesFromAddress: (address: string) => Uint8Array<ArrayBuffer>;
327
+
328
+ readonly selector?: Uint8Array<ArrayBuffer>;
329
+ readonly type: string;
330
+ readonly element: Element;
331
+
332
+ constructor(type: string, { addressFromBytes, bytesFromAddress }: {
333
+ addressFromBytes: (bytes: Uint8Array<ArrayBuffer>) => string;
334
+ bytesFromAddress: (address: string) => Uint8Array<ArrayBuffer>;
335
+ }) {
336
+ this.type = type;
337
+ this.#addressFromBytes = addressFromBytes;
338
+ this.#bytesFromAddress = bytesFromAddress;
339
+ const match = type.match(/^([^\(\)]+)(\(.*\))$/);
340
+ if (match !== null) {
341
+ const method = ensure(match[1]);
342
+ type = ensure(match[2]);
343
+ this.selector = keccak256(new TextEncoder().encode(`${method}${type}`)).slice(0, 4);
344
+ }
345
+ this.element = this.#parse(type);
346
+ }
347
+
348
+ decode(bytes: Uint8Array<ArrayBuffer>): unknown {
349
+ if (this.selector !== undefined) {
350
+ assert(bytes.length >= 4);
351
+ assert(Uint8ArrayExtension.equals(bytes.slice(0, 4), this.selector));
352
+ bytes = bytes.slice(4);
353
+ }
354
+ return this.element.decode(bytes);
355
+ }
356
+
357
+ encode(value: unknown): Uint8Array<ArrayBuffer> {
358
+ let bytes = this.element.encode(value);
359
+ if (this.selector !== undefined) {
360
+ bytes = Uint8ArrayExtension.concat([this.selector, bytes]);
361
+ }
362
+ return bytes;
363
+ }
364
+
365
+ #parse(type: string): Element {
366
+ const parsers: { regex: RegExp; fn: (match: RegExpMatchArray) => Element }[] = [
367
+ {
368
+ regex: /^(.+)\[(\d+)\]$/,
369
+ fn: (match) => new ArrayElement(this.#parse(ensure(match[1])), Number(ensure(match[2]))),
370
+ },
371
+ { regex: /^(.+)\[\]$/, fn: (match) => new ArrayElement(this.#parse(ensure(match[1]))) },
372
+ {
373
+ regex: /^\((.*)\)$/,
374
+ fn: (match) => {
375
+ const inner = ensure(match[1]);
376
+ const elements = new Array<Element>();
377
+ let element = '';
378
+ let depth = 0;
379
+ for (let i = 0; i < inner.length; i++) {
380
+ const char = inner[i];
381
+ if (char === ',' && depth === 0) {
382
+ elements.push(this.#parse(element));
383
+ element = '';
384
+ } else {
385
+ if (char === '(') {
386
+ depth++;
387
+ } else if (char === ')') {
388
+ depth--;
389
+ }
390
+ element += char;
391
+ }
392
+ }
393
+ assert(depth === 0, 'Unbalanced parentheses');
394
+ if (element !== '') {
395
+ elements.push(this.#parse(element));
396
+ }
397
+ return new StructElement(elements);
398
+ },
399
+ },
400
+ { regex: /^uint(\d+)$/, fn: (match) => new UintElement(Number(ensure(match[1]))) },
401
+ { regex: /^int(\d+)$/, fn: (match) => new IntElement(Number(ensure(match[1]))) },
402
+ { regex: /^bytes(\d+)$/, fn: (match) => new BytesElement(Number(ensure(match[1]))) },
403
+ { regex: /^bytes$/, fn: () => new BytesElement() },
404
+ { regex: /^string$/, fn: () => new StringElement() },
405
+ { regex: /^bool$/, fn: () => new BoolElement() },
406
+ {
407
+ regex: /^address$/,
408
+ fn: () => {
409
+ const bytesFromAddress = this.#bytesFromAddress.bind(this);
410
+ const addressFromBytes = this.#addressFromBytes.bind(this);
411
+ return new class extends AddressElement {
412
+ bytesFromAddress(address: string) {
413
+ return bytesFromAddress(address);
414
+ }
415
+ addressFromBytes(bytes: Uint8Array<ArrayBuffer>) {
416
+ return addressFromBytes(bytes);
417
+ }
418
+ }();
419
+ },
420
+ },
421
+ ];
422
+ try {
423
+ for (const { regex, fn } of parsers) {
424
+ const match = type.match(regex);
425
+ if (match) {
426
+ return fn(match);
427
+ }
428
+ }
429
+ throw new Error('No match');
430
+ } catch (_) {
431
+ throw new Error(`Failed to parse "${type}"`);
432
+ }
433
+ }
434
+ }
package/src/Block.ts ADDED
@@ -0,0 +1,9 @@
1
+ type Block = {
2
+ hash: string;
3
+ parentHash: string;
4
+ number: number;
5
+ transactions: string[];
6
+ timestamp: Date;
7
+ };
8
+
9
+ export default Block;
package/src/Client.ts ADDED
@@ -0,0 +1,152 @@
1
+ import * as z from '@quentinadam/zod';
2
+ import type ABI from './ABI.ts';
3
+ import type Block from './Block.ts';
4
+ import ClientError from './ClientError.ts';
5
+ import type ClientHelper from './ClientHelper.ts';
6
+ import type Log from './Log.ts';
7
+ import type Transaction from './Transaction.ts';
8
+ import type TransactionReceipt from './TransactionReceipt.ts';
9
+
10
+ export default class Client {
11
+ readonly #url: string;
12
+ readonly #helper: ClientHelper;
13
+ #id = 0;
14
+
15
+ constructor(url: string, helper: ClientHelper) {
16
+ this.#url = url;
17
+ this.#helper = helper;
18
+ }
19
+
20
+ createABI(type: string): ABI {
21
+ return this.#helper.createABI(type);
22
+ }
23
+
24
+ async request({ method, params }: { method: string; params?: unknown }): Promise<unknown> {
25
+ const body = JSON.stringify({ method, params, id: this.#id++, jsonrpc: '2.0' });
26
+ const response = await fetch(this.#url, {
27
+ method: 'POST',
28
+ headers: { 'Content-Type': 'application/json' },
29
+ body,
30
+ });
31
+ if (!response.ok) {
32
+ throw new Error(`HTTP ${response.status}: ${await response.text()}`);
33
+ }
34
+ const result = z.union([
35
+ z.object({
36
+ error: z.object({ code: z.number(), message: z.string(), data: z.unknown() }),
37
+ }).transform(({ error }) => {
38
+ return { success: false as const, code: error.code, message: error.message, data: error.data };
39
+ }),
40
+ z.object({ result: z.unknown() }).transform(({ result }) => ({ success: true as const, value: result })),
41
+ ]).parse(await response.json());
42
+ if (!result.success) {
43
+ throw new ClientError({ message: result.message, code: result.code, data: result.data });
44
+ }
45
+ return result.value;
46
+ }
47
+
48
+ async call({ contract, data }: {
49
+ contract: string;
50
+ data: Uint8Array<ArrayBuffer> | { method: string; parameters: Uint8Array<ArrayBuffer> | unknown[] };
51
+ }): Promise<Uint8Array<ArrayBuffer>> {
52
+ data = this.#helper.normalizeData(data);
53
+ return this.#helper.BytesSchema.parse(
54
+ await this.request({
55
+ method: 'eth_call',
56
+ params: [{ to: this.#helper.serializeAddress(contract), data: '0x' + data.toHex() }, 'latest'],
57
+ }),
58
+ );
59
+ }
60
+
61
+ async getBlockNumber(): Promise<number> {
62
+ const response = await this.request({ method: 'eth_blockNumber' });
63
+ return this.#helper.HexNumberSchema.parse(response);
64
+ }
65
+
66
+ async getBlockByNumber(blockNumber: number): Promise<Block | undefined> {
67
+ const response = await this.request({
68
+ method: 'eth_getBlockByNumber',
69
+ params: [`0x${blockNumber.toString(16)}`, false],
70
+ });
71
+ return z.union([
72
+ z.null().transform(() => undefined),
73
+ z.object({
74
+ hash: z.string(),
75
+ parentHash: z.string(),
76
+ number: this.#helper.HexNumberSchema,
77
+ transactions: z.array(z.string()),
78
+ timestamp: this.#helper.HexNumberSchema.transform((value) => new Date(value * 1000)),
79
+ }),
80
+ ]).parse(response);
81
+ }
82
+
83
+ async getLogs({ address, fromBlock, toBlock, topics }: {
84
+ fromBlock?: number;
85
+ toBlock?: number;
86
+ address?: string | string[];
87
+ topics?: (null | Uint8Array | Uint8Array[])[];
88
+ }): Promise<Log[]> {
89
+ const response = await this.request({
90
+ method: 'eth_getLogs',
91
+ params: [{
92
+ fromBlock: fromBlock !== undefined ? `0x${fromBlock.toString(16)}` : undefined,
93
+ toBlock: toBlock !== undefined ? `0x${toBlock.toString(16)}` : undefined,
94
+ address: address === undefined
95
+ ? undefined
96
+ : typeof address === 'string'
97
+ ? this.#helper.serializeAddress(address)
98
+ : address.map((address) => this.#helper.serializeAddress(address)),
99
+ topics: topics?.map((topic) => {
100
+ return topic !== null
101
+ ? topic instanceof Uint8Array ? '0x' + topic.toHex() : topic.map((topic) => '0x' + topic.toHex())
102
+ : null;
103
+ }),
104
+ }],
105
+ });
106
+ return z.array(this.#helper.LogSchema).parse(response);
107
+ }
108
+
109
+ async getTransaction(hash: string): Promise<Transaction | undefined> {
110
+ return this.#helper.TransactionSchema.parse(
111
+ await this.request({
112
+ method: 'eth_getTransactionByHash',
113
+ params: [this.#helper.serializeHash(hash)],
114
+ }),
115
+ );
116
+ }
117
+
118
+ async getTransactionReceipt(hash: string): Promise<TransactionReceipt | undefined> {
119
+ return this.#helper.TransactionReceiptSchema.parse(
120
+ await this.request({
121
+ method: 'eth_getTransactionReceipt',
122
+ params: [this.#helper.serializeHash(hash)],
123
+ }),
124
+ );
125
+ }
126
+
127
+ async estimateGas({ from, to, value, data }: {
128
+ from: string;
129
+ to?: string;
130
+ value?: bigint;
131
+ data?: Uint8Array<ArrayBuffer> | { method: string; parameters: Uint8Array<ArrayBuffer> | unknown[] };
132
+ }): Promise<number> {
133
+ if (data !== undefined) {
134
+ data = this.#helper.normalizeData(data);
135
+ }
136
+ const result = await this.request({
137
+ method: 'eth_estimateGas',
138
+ params: [{
139
+ from,
140
+ value: `0x${(value ?? 0n).toString(16)}`,
141
+ to,
142
+ data: '0x' + (data ?? new Uint8Array(0)).toHex(),
143
+ }],
144
+ });
145
+ return this.#helper.HexNumberSchema.parse(result);
146
+ }
147
+
148
+ async getChainId(): Promise<number> {
149
+ const result = await this.request({ method: 'eth_chainId', params: [] });
150
+ return this.#helper.HexNumberSchema.parse(result);
151
+ }
152
+ }
@@ -0,0 +1,10 @@
1
+ export default class ClientError extends Error {
2
+ readonly code: number;
3
+ readonly data: unknown;
4
+
5
+ constructor({ message, code, data }: { message: string; code: number; data: unknown }) {
6
+ super(data === undefined ? message : `${message} (${JSON.stringify(data)})`);
7
+ this.code = code;
8
+ this.data = data;
9
+ }
10
+ }