@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
@@ -0,0 +1,131 @@
1
+ import assert from '@quentinadam/assert';
2
+ import * as z from '@quentinadam/zod';
3
+ import ABI from './ABI.ts';
4
+ import DataEncoder from './DataEncoder.ts';
5
+ import type Log from './Log.ts';
6
+ import type Transaction from './Transaction.ts';
7
+ import type TransactionReceipt from './TransactionReceipt.ts';
8
+
9
+ export default class ClientHelper {
10
+ readonly #addressFromBytes: (bytes: Uint8Array<ArrayBuffer>) => string;
11
+ readonly #bytesFromAddress: (address: string) => Uint8Array<ArrayBuffer>;
12
+ readonly #deserializeHash: (hash: string) => string;
13
+ readonly #serializeHash: (hash: string) => string;
14
+ readonly #dataEncoder: DataEncoder;
15
+
16
+ readonly BytesSchema: z.Schema<Uint8Array<ArrayBuffer>> = z.string().transform((string) => {
17
+ assert(/0x[0-9a-f]*/.test(string));
18
+ return Uint8Array.fromHex(string.slice(2));
19
+ });
20
+
21
+ readonly HexNumberSchema: z.Schema<number> = z.string().transform((string) => {
22
+ assert(/0x[0-9a-f]+/.test(string));
23
+ return Number(string);
24
+ });
25
+
26
+ readonly HexBigIntSchema: z.Schema<bigint> = z.string().transform((string) => {
27
+ assert(/0x[0-9a-f]+/.test(string));
28
+ return BigInt(string);
29
+ });
30
+
31
+ readonly AddressSchema: z.Schema<string> = z.string().transform((value) => this.deserializeAddress(value));
32
+ readonly HashSchema: z.Schema<string> = z.string().transform((value) => this.#deserializeHash(value));
33
+
34
+ readonly LogSchema: z.ObjectSchema<Log> = z.object({
35
+ address: this.AddressSchema,
36
+ topics: z.array(this.BytesSchema),
37
+ data: this.BytesSchema,
38
+ blockHash: this.HashSchema,
39
+ transactionHash: this.HashSchema,
40
+ blockNumber: this.HexNumberSchema,
41
+ transactionIndex: this.HexNumberSchema,
42
+ logIndex: this.HexNumberSchema,
43
+ removed: z.boolean(),
44
+ });
45
+
46
+ readonly TransactionSchema: z.Schema<Transaction | undefined> = z.union([
47
+ z.null().transform(() => undefined),
48
+ z.object({
49
+ blockHash: this.HashSchema,
50
+ blockNumber: this.HexNumberSchema,
51
+ from: this.AddressSchema,
52
+ gasPrice: this.HexNumberSchema,
53
+ hash: this.HashSchema,
54
+ nonce: this.HexNumberSchema,
55
+ r: this.HexBigIntSchema,
56
+ s: this.HexBigIntSchema,
57
+ to: this.AddressSchema,
58
+ transactionIndex: this.HexNumberSchema,
59
+ type: this.HexNumberSchema,
60
+ value: this.HexNumberSchema,
61
+ v: this.HexNumberSchema,
62
+ }),
63
+ ]);
64
+
65
+ readonly TransactionReceiptSchema: z.Schema<TransactionReceipt | undefined> = z.union([
66
+ z.null().transform(() => undefined),
67
+ z.object({
68
+ blockHash: this.HashSchema,
69
+ blockNumber: this.HexNumberSchema,
70
+ from: this.AddressSchema,
71
+ logs: z.array(this.LogSchema),
72
+ status: this.HexNumberSchema,
73
+ to: this.AddressSchema,
74
+ transactionHash: this.HashSchema,
75
+ transactionIndex: this.HexNumberSchema,
76
+ type: this.HexNumberSchema,
77
+ }).transform(({ status, transactionHash, ...rest }) => ({
78
+ ...rest,
79
+ hash: transactionHash,
80
+ success: (() => {
81
+ if (status === 0) {
82
+ return false;
83
+ }
84
+ if (status === 1) {
85
+ return true;
86
+ }
87
+ throw new Error('Invalid transaction status');
88
+ })(),
89
+ })),
90
+ ]);
91
+
92
+ constructor({ addressFromBytes, bytesFromAddress, deserializeHash, serializeHash }: {
93
+ addressFromBytes: (bytes: Uint8Array<ArrayBuffer>) => string;
94
+ bytesFromAddress: (address: string) => Uint8Array<ArrayBuffer>;
95
+ deserializeHash: (hash: string) => string;
96
+ serializeHash: (hash: string) => string;
97
+ }) {
98
+ this.#addressFromBytes = addressFromBytes;
99
+ this.#bytesFromAddress = bytesFromAddress;
100
+ this.#deserializeHash = deserializeHash;
101
+ this.#serializeHash = serializeHash;
102
+ this.#dataEncoder = new DataEncoder((type) => this.createABI(type));
103
+ }
104
+
105
+ deserializeAddress(address: string): string {
106
+ assert(/0x[0-9a-f]{40}/.test(address));
107
+ return this.#addressFromBytes(Uint8Array.fromHex(address.slice(2)));
108
+ }
109
+
110
+ serializeAddress(address: string): string {
111
+ return '0x' + this.#bytesFromAddress(address).toHex();
112
+ }
113
+
114
+ deserializeHash(hash: string): string {
115
+ return this.#deserializeHash(hash);
116
+ }
117
+
118
+ serializeHash(hash: string): string {
119
+ return this.#serializeHash(hash);
120
+ }
121
+
122
+ createABI(type: string): ABI {
123
+ return new ABI(type, { bytesFromAddress: this.#bytesFromAddress, addressFromBytes: this.#addressFromBytes });
124
+ }
125
+
126
+ normalizeData(
127
+ data: Uint8Array<ArrayBuffer> | { method: string; parameters: Uint8Array<ArrayBuffer> | unknown[] },
128
+ ): Uint8Array<ArrayBuffer> {
129
+ return this.#dataEncoder.normalize(data);
130
+ }
131
+ }
@@ -0,0 +1,28 @@
1
+ import concat from '@quentinadam/uint8array-extension/concat';
2
+ import keccak256 from '@quentinadam/hash/keccak256';
3
+ import type ABI from './ABI.ts';
4
+
5
+ export default class DataEncoder {
6
+ readonly #createABI: (type: string) => ABI;
7
+
8
+ constructor(createABI: (type: string) => ABI) {
9
+ this.#createABI = createABI;
10
+ }
11
+
12
+ encode({ method, parameters }: {
13
+ method: string;
14
+ parameters: Uint8Array<ArrayBuffer> | unknown[];
15
+ }): Uint8Array<ArrayBuffer> {
16
+ if (parameters instanceof Uint8Array) {
17
+ return concat([keccak256(method).slice(0, 4), parameters]);
18
+ } else {
19
+ return this.#createABI(method).encode(parameters);
20
+ }
21
+ }
22
+
23
+ normalize(
24
+ data: Uint8Array<ArrayBuffer> | { method: string; parameters: Uint8Array<ArrayBuffer> | unknown[] },
25
+ ): Uint8Array<ArrayBuffer> {
26
+ return (data instanceof Uint8Array) ? data : this.encode(data);
27
+ }
28
+ }
package/src/Log.ts ADDED
@@ -0,0 +1,13 @@
1
+ type Log = {
2
+ address: string;
3
+ topics: Uint8Array<ArrayBuffer>[];
4
+ data: Uint8Array<ArrayBuffer>;
5
+ blockHash: string;
6
+ transactionHash: string;
7
+ blockNumber: number;
8
+ transactionIndex: number;
9
+ logIndex: number;
10
+ removed: boolean;
11
+ };
12
+
13
+ export default Log;
@@ -0,0 +1,87 @@
1
+ import ensure from '@quentinadam/ensure';
2
+ import * as z from '@quentinadam/zod';
3
+ import type Client from './Client.ts';
4
+
5
+ export default class MulticallClient {
6
+ readonly #client;
7
+ readonly #multicallAddress;
8
+ #pendingCalls = new Array<{
9
+ contract: string;
10
+ data: Uint8Array<ArrayBuffer>;
11
+ resolve: (value: Uint8Array<ArrayBuffer>) => void;
12
+ reject: (reason: unknown) => void;
13
+ }>();
14
+
15
+ constructor({ client, multicallAddress }: {
16
+ client: Client;
17
+ multicallAddress: string;
18
+ }) {
19
+ this.#client = client;
20
+ this.#multicallAddress = multicallAddress;
21
+ }
22
+
23
+ async getBlockNumber(): Promise<number> {
24
+ const result = await this.call({
25
+ contract: this.#multicallAddress,
26
+ data: { method: 'getBlockNumber()', parameters: [] },
27
+ });
28
+ return Number(z.bigint().parse(this.#client.createABI('uint256').decode(result)));
29
+ }
30
+
31
+ async call({ contract, data }: {
32
+ contract: string;
33
+ data: Uint8Array<ArrayBuffer> | { method: string; parameters: unknown[] };
34
+ }): Promise<Uint8Array<ArrayBuffer>> {
35
+ return await new Promise<Uint8Array<ArrayBuffer>>((resolve, reject) => {
36
+ if (data instanceof Uint8Array === false) {
37
+ const { method, parameters } = data;
38
+ data = this.#client.createABI(method).encode(parameters);
39
+ }
40
+ this.#pendingCalls.push({ contract, data, resolve, reject });
41
+ setTimeout(async () => {
42
+ if (this.#pendingCalls.length > 0) {
43
+ const pendingCalls = this.#pendingCalls;
44
+ this.#pendingCalls = [];
45
+ if (pendingCalls.length > 1) {
46
+ const data = this.#client.createABI('aggregate3((address,bool,bytes)[])').encode(
47
+ [pendingCalls.map(({ contract, data }) => [contract, true, data])],
48
+ );
49
+ try {
50
+ const result = await this.#client.call({ contract: this.#multicallAddress, data });
51
+ const [decoded] = z.tuple([z.array(z.tuple([z.boolean(), z.instanceof(Uint8Array)]))]).parse(
52
+ this.#client.createABI('((bool,bytes)[])').decode(result),
53
+ );
54
+ decoded.forEach(([success, data], index) => {
55
+ const { resolve, reject } = ensure(pendingCalls[index]);
56
+ if (success) {
57
+ resolve(data);
58
+ } else {
59
+ reject(new Error(`Execution reverted ${data.toHex()}`));
60
+ }
61
+ });
62
+ } catch (error) {
63
+ for (const { reject } of pendingCalls) {
64
+ reject(error);
65
+ }
66
+ }
67
+ } else {
68
+ const { contract, data, resolve, reject } = ensure(pendingCalls[0]);
69
+ try {
70
+ resolve(await this.#client.call({ contract, data }));
71
+ } catch (error) {
72
+ reject(error);
73
+ }
74
+ }
75
+ }
76
+ }, 0);
77
+ });
78
+ }
79
+
80
+ async getBalance(address: string): Promise<bigint> {
81
+ const result = await this.call({
82
+ contract: this.#multicallAddress,
83
+ data: { method: 'getEthBalance(address)', parameters: [address] },
84
+ });
85
+ return z.bigint().parse(this.#client.createABI('uint256').decode(result));
86
+ }
87
+ }
@@ -0,0 +1,17 @@
1
+ type Transaction = {
2
+ blockHash: string;
3
+ blockNumber: number;
4
+ from: string;
5
+ gasPrice: number;
6
+ hash: string;
7
+ nonce: number;
8
+ r: bigint;
9
+ s: bigint;
10
+ to: string;
11
+ transactionIndex: number;
12
+ type: number;
13
+ value: number;
14
+ v: number;
15
+ };
16
+
17
+ export default Transaction;
@@ -0,0 +1,15 @@
1
+ import type Log from './Log.ts';
2
+
3
+ type TransactionReceipt = {
4
+ hash: string;
5
+ success: boolean;
6
+ blockHash: string;
7
+ blockNumber: number;
8
+ from: string;
9
+ logs: Log[];
10
+ to: string;
11
+ transactionIndex: number;
12
+ type: number;
13
+ };
14
+
15
+ export default TransactionReceipt;
@@ -0,0 +1,32 @@
1
+ import keccak256 from '@quentinadam/hash/keccak256';
2
+ import { concat, fromUintBE } from '@quentinadam/uint8array-extension';
3
+
4
+ export default function computeCREATE2Address(
5
+ params:
6
+ & { deployer: string; salt: bigint | number | Uint8Array<ArrayBuffer> }
7
+ & (
8
+ | { bytecodeHash: Uint8Array<ArrayBuffer>; bytecode?: undefined; constructorArguments?: undefined }
9
+ | { bytecodeHash?: undefined; bytecode: Uint8Array<ArrayBuffer>; constructorArguments?: Uint8Array<ArrayBuffer> }
10
+ ),
11
+ { prefixByte, addressFromBytes, bytesFromAddress }: {
12
+ prefixByte: number;
13
+ addressFromBytes: (bytes: Uint8Array<ArrayBuffer>) => string;
14
+ bytesFromAddress: (address: string) => Uint8Array<ArrayBuffer>;
15
+ },
16
+ ): string {
17
+ const { deployer, salt } = params;
18
+ const bytecodeHash = (() => {
19
+ const { bytecodeHash, bytecode, constructorArguments } = params;
20
+ if (bytecodeHash !== undefined) {
21
+ return bytecodeHash;
22
+ }
23
+ return keccak256(concat([bytecode, constructorArguments].filter((chunk) => chunk !== undefined)));
24
+ })();
25
+ const bytes = keccak256(concat([
26
+ new Uint8Array(prefixByte),
27
+ bytesFromAddress(deployer),
28
+ salt instanceof Uint8Array ? salt : fromUintBE(salt, 32),
29
+ bytecodeHash,
30
+ ])).slice(-20);
31
+ return addressFromBytes(bytes);
32
+ }
@@ -0,0 +1,13 @@
1
+ import keccak256 from '@quentinadam/hash/keccak256';
2
+ import * as rlp from '@quentinadam/rlp';
3
+
4
+ export default function computeCREATEAddress(
5
+ { deployer, nonce }: { deployer: string; nonce: bigint | number },
6
+ { addressFromBytes, bytesFromAddress }: {
7
+ addressFromBytes: (bytes: Uint8Array<ArrayBuffer>) => string;
8
+ bytesFromAddress: (address: string) => Uint8Array<ArrayBuffer>;
9
+ },
10
+ ): string {
11
+ const bytes = keccak256(rlp.encode([bytesFromAddress(deployer), nonce])).slice(-20);
12
+ return addressFromBytes(bytes);
13
+ }
package/src/mod.ts ADDED
@@ -0,0 +1,25 @@
1
+ import ABI from './ABI.ts';
2
+ import type Block from './Block.ts';
3
+ import computeCREATE2Address from './computeCREATE2Address.ts';
4
+ import computeCREATEAddress from './computeCREATEAddress.ts';
5
+ import Client from './Client.ts';
6
+ import ClientError from './ClientError.ts';
7
+ import DataEncoder from './DataEncoder.ts';
8
+ import ClientHelper from './ClientHelper.ts';
9
+ import type Log from './Log.ts';
10
+ import MulticallClient from './MulticallClient.ts';
11
+ import type Transaction from './Transaction.ts';
12
+ import type TransactionReceipt from './TransactionReceipt.ts';
13
+
14
+ export {
15
+ ABI,
16
+ Client,
17
+ ClientError,
18
+ ClientHelper,
19
+ computeCREATE2Address,
20
+ computeCREATEAddress,
21
+ DataEncoder,
22
+ MulticallClient,
23
+ };
24
+
25
+ export type { Block, Log, Transaction, TransactionReceipt };