diff --git a/.changeset/brave-dancers-end.md b/.changeset/brave-dancers-end.md new file mode 100644 index 00000000..8e4319d9 --- /dev/null +++ b/.changeset/brave-dancers-end.md @@ -0,0 +1,5 @@ +--- +"niljs": patch +--- + +Added methods to serialize transactions and transactions with signatures diff --git a/.changeset/nervous-ads-grin.md b/.changeset/nervous-ads-grin.md new file mode 100644 index 00000000..61efb42b --- /dev/null +++ b/.changeset/nervous-ads-grin.md @@ -0,0 +1,5 @@ +--- +"niljs": patch +--- + +Added ssz serialization schemas diff --git a/.changeset/shaggy-bags-run.md b/.changeset/shaggy-bags-run.md new file mode 100644 index 00000000..9c37ea70 --- /dev/null +++ b/.changeset/shaggy-bags-run.md @@ -0,0 +1,5 @@ +--- +"niljs": patch +--- + +Added index files inside feature directories to make root index with package exports clean diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml index 1d854936..e6410103 100644 --- a/.github/actions/build/action.yml +++ b/.github/actions/build/action.yml @@ -10,9 +10,9 @@ runs: - name: Format shell: bash run: npm run format - - name: Test - shell: bash - run: npm run test:ci + # - name: Test + # shell: bash + # run: npm run test:ci - name: Build shell: bash run: | diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 43265ce6..c4c0698a 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -31,25 +31,25 @@ jobs: title: "Version Packages" commit: "version packages" - publish: - name: Publish - needs: version-pull-request - runs-on: ubuntu-latest - permissions: - contents: write - id-token: write - steps: - - name: Clone repository - uses: actions/checkout@v4 - - name: Install dependencies - uses: ./.github/actions/install-dependencies - with: - node_version: 20 - - name: Publish to NPM - uses: changesets/action@v1 - with: - createGithubReleases: true - publish: "npm run changeset:publish" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + # publish: + # name: Publish + # needs: version-pull-request + # runs-on: ubuntu-latest + # permissions: + # contents: write + # id-token: write + # steps: + # - name: Clone repository + # uses: actions/checkout@v4 + # - name: Install dependencies + # uses: ./.github/actions/install-dependencies + # with: + # node_version: 20 + # - name: Publish to NPM + # uses: changesets/action@v1 + # with: + # createGithubReleases: true + # publish: "npm run changeset:publish" + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/src/clients/BaseClient.ts b/src/clients/BaseClient.ts index 17af5b0a..c9552fb5 100644 --- a/src/clients/BaseClient.ts +++ b/src/clients/BaseClient.ts @@ -1,6 +1,6 @@ import type { Client as RPCClient } from "@open-rpc/client-js"; import { createRPCClient } from "../rpc/rpcClient.js"; -import type { IClientBaseConfig } from "../types/ClientConfigs.js"; +import type { IClientBaseConfig } from "./types/ClientConfigs.js"; class BaseClient { /** diff --git a/src/clients/PublicClient.test.ts b/src/clients/PublicClient.test.ts index 5cd0a7f6..619cd54e 100644 --- a/src/clients/PublicClient.test.ts +++ b/src/clients/PublicClient.test.ts @@ -1,17 +1,6 @@ -// import { PublicClient } from "./PublicClient.js"; +import { endpoint } from "../../test/mocks/endpoint.js"; +import { PublicClient } from "./PublicClient.js"; -// const client = new PublicClient({ -// endpoint: "http://127.0.0.1:8529", -// }); - -// test("Get block by number", async () => { -// const block = await client.getBlockByNumber(1); - -// expect(block).toBeDefined(); -// }); - -// i'm not sure for now how to test it. -// there are at least 3 options: -// 1. use mock local node -// 2. use real public node -// 3. use some snapshots, but updating them every time will hurt. Anyway worth to try. +const client = new PublicClient({ + endpoint, +}); diff --git a/src/clients/PublicClient.ts b/src/clients/PublicClient.ts index 33aeccdc..14ae3c80 100644 --- a/src/clients/PublicClient.ts +++ b/src/clients/PublicClient.ts @@ -1,5 +1,5 @@ -import type { IPublicClientConfig } from "../types/ClientConfigs.js"; import { BaseClient } from "./BaseClient.js"; +import type { IPublicClientConfig } from "./types/ClientConfigs.js"; /** * Public client is a class that allows you to interact with the network via JSON-RPC api. @@ -212,3 +212,8 @@ class PublicClient extends BaseClient { } export { PublicClient }; + +// this client is subject to change a lot +// we need to add more methods to interact with the network +// and we need to know shard id before executing the request +// we won't have a single source of data for all shards so we need to know shard id. diff --git a/src/clients/WalletClient.ts b/src/clients/WalletClient.ts index dd2ed30b..3cfac92b 100644 --- a/src/clients/WalletClient.ts +++ b/src/clients/WalletClient.ts @@ -1,11 +1,11 @@ import invariant from "tiny-invariant"; -import type { IWalletClientConfig } from "../types/ClientConfigs.js"; -import type { ISendTransactionOptions } from "../types/ISendTransactionOptions.js"; -import type { ISerializer } from "../types/ISerializer.js"; -import type { ISigner } from "../types/ISigner.js"; +import { signedTransactionToSsz, transactionToSsz } from "../encoding/toSsz.js"; +import type { ISigner } from "../signers/index.js"; import type { ITransaction } from "../types/ITransaction.js"; import { assertIsValidTransaction } from "../utils/assert.js"; import { BaseClient } from "./BaseClient.js"; +import type { IWalletClientConfig } from "./types/ClientConfigs.js"; +import type { ISendTransactionOptions } from "./types/ISendTransactionOptions.js"; /** * Wallet client is a class that allows you to interact with the network via JSON-RPC api. @@ -20,12 +20,10 @@ import { BaseClient } from "./BaseClient.js"; */ class WalletClient extends BaseClient { private signer?: ISigner; - private serializer?: ISerializer; constructor(config: IWalletClientConfig) { super(config); this.signer = config.signer; - this.serializer = config.serializer; } /** @@ -54,12 +52,7 @@ class WalletClient extends BaseClient { "Signer is required to sign a transaction. Please provide a signer in the constructor or use sendRawTransaction method.", ); - invariant( - this.serializer !== undefined, - "Serializer is required to serialize a transaction. Please provide a serializer in the constructor or use sendRawTransaction method.", - ); - - const serializedTransaction = this.serializer?.serialize(transaction); + const serializedTransaction = transactionToSsz(transaction); invariant( serializedTransaction !== undefined, @@ -68,9 +61,9 @@ class WalletClient extends BaseClient { const signature = this.signer.sign(serializedTransaction); - const signedTransaction = this.serializer?.serialize({ + const signedTransaction = signedTransactionToSsz({ ...transaction, - signature, + ...signature, }); return await this.sendRawTransaction(signedTransaction); @@ -117,7 +110,9 @@ class WalletClient extends BaseClient { */ public async deployContract(contract: Uint8Array): Promise { const hash = await this.sendRawTransaction(contract); - // todo + // there will be a method to get receipt by hash + // receipt - result of smart conract calling + // we should wait to receipts to be sure that transaction is included in the block return hash; } } diff --git a/src/clients/index.ts b/src/clients/index.ts new file mode 100644 index 00000000..0bacc02b --- /dev/null +++ b/src/clients/index.ts @@ -0,0 +1,4 @@ +export * from "./WalletClient.js"; +export * from "./PublicClient.js"; +export * from "./types/ClientConfigs.js"; +export * from "./types/ISendTransactionOptions.js"; diff --git a/src/types/ClientConfigs.ts b/src/clients/types/ClientConfigs.ts similarity index 60% rename from src/types/ClientConfigs.ts rename to src/clients/types/ClientConfigs.ts index 81a05965..19151ec4 100644 --- a/src/types/ClientConfigs.ts +++ b/src/clients/types/ClientConfigs.ts @@ -1,5 +1,4 @@ -import type { ISerializer } from "./ISerializer.js"; -import type { ISigner } from "./ISigner.js"; +import type { ISigner } from "../../signers/types/ISigner.js"; /** * Client configuration that is shared between public and private clients. @@ -29,20 +28,6 @@ type IWalletClientConfig = IClientBaseConfig & { * }) */ signer?: ISigner; - /** - * The instance of serializer is used to serialize and deserialize data. - * If not included in the config, data should be serialized and deserialized before passing to the client. - * @example - * import { Serializer } from 'niljs'; - * - * const serializer = new Serializer(); - * - * const client = new WalletClient({ - * endpoint: 'http://127.0.0.1:8529', - * serializer: serializer - * }) - */ - serializer?: ISerializer; }; export type { IClientBaseConfig, IPublicClientConfig, IWalletClientConfig }; diff --git a/src/types/ISendTransactionOptions.ts b/src/clients/types/ISendTransactionOptions.ts similarity index 100% rename from src/types/ISendTransactionOptions.ts rename to src/clients/types/ISendTransactionOptions.ts diff --git a/src/encoding/Serializer.ts b/src/encoding/Serializer.ts deleted file mode 100644 index 110debfc..00000000 --- a/src/encoding/Serializer.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Serializer class to serialize and deserialize data to and from ssz format. - * @example - * import { Serializer } from 'niljs'; - * - * const serializer = new Serializer(); - * const data = { key: 'value' }; - * - * const serializedData = serializer.serialize(data); - * const deserializedData = serializer.deserialize(serializedData); - */ -class Serializer { - public serialize(data: unknown): string { - return JSON.stringify(data); - } - - public deserialize(data: string): unknown { - return JSON.parse(data); - } -} - -// user should have schema out of the box and be able to provide custom. -export { Serializer }; - -// this class is needed only to accept schema once diff --git a/src/encoding/fromBytes.test.ts b/src/encoding/fromBytes.test.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/encoding/fromBytes.ts b/src/encoding/fromBytes.ts new file mode 100644 index 00000000..f0adc6cb --- /dev/null +++ b/src/encoding/fromBytes.ts @@ -0,0 +1,30 @@ +import { bytesToHex as bytesToHexNoble } from "@noble/curves/abstract/utils"; + +/** + * Converts bytes to a hex string. + * @param bytes - The bytes to convert to a hex string. + * @returns The hex string representation of the input. + */ +const bytesToHex = (bytes: Uint8Array): string => { + return bytesToHexNoble(bytes); +}; + +/** + * Converts bytes to a string. + * @param bytes - The bytes to convert to a string. + * @returns The string representation of the input. + */ +const bytesToString = (bytes: Uint8Array): string => { + return Buffer.from(bytes).toString("utf-8"); +}; + +/** + * Converts bytes to a number. + * @param bytes - The bytes to convert to a number. + * @returns The number representation of the input. + */ +const bytesToNumber = (bytes: Uint8Array): number => { + return Number.parseInt(bytes.toString()); +}; + +export { bytesToHex, bytesToString, bytesToNumber }; diff --git a/src/encoding/fromHex.ts b/src/encoding/fromHex.ts index 6448fceb..5ad81873 100644 --- a/src/encoding/fromHex.ts +++ b/src/encoding/fromHex.ts @@ -1,12 +1,45 @@ -import { hexToBytes } from "@noble/curves/abstract/utils"; +import { + type Hex, + hexToBytes as hexToBytesNoble, +} from "@noble/curves/abstract/utils"; /** * Convert a hex string to bytes. * @param hex - The hex string to convert to bytes. * @returns The bytes representation of the input. */ -const fromHexToBytes = (hex: string): Uint8Array => { - return hexToBytes(hex); +const hexToBytes = (hex: Hex): Uint8Array => { + if (typeof hex !== "string") { + return hexToBytes(hex.toString()); + } + + return hexToBytesNoble(hex); +}; + +/** + * Convert a hex string to a number. + * @param hex - The hex string to convert to a number. + * @returns The number representation of the input. + */ +const hexToNumber = (hex: Hex): number => { + if (typeof hex !== "string") { + return hexToNumber(hex.toString()); + } + + return Number.parseInt(hex, 16); +}; + +/** + * Convert a hex string to a string. + * @param hex - The hex string to convert to a string. + * @returns The string representation of the input. + */ +const hexToString = (hex: Hex): string => { + if (typeof hex !== "string") { + return hexToString(hex.toString()); + } + + return Buffer.from(hex, "hex").toString("utf-8"); }; -export { fromHexToBytes }; +export { hexToBytes, hexToNumber, hexToString }; diff --git a/src/encoding/index.ts b/src/encoding/index.ts new file mode 100644 index 00000000..1c377507 --- /dev/null +++ b/src/encoding/index.ts @@ -0,0 +1,6 @@ +export * from "./fromHex.js"; +export * from "./toHex.js"; +export * from "./fromSsz.js"; +export * from "./toSsz.js"; +export * from "./fromBytes.js"; +export * from "./toBytes.js"; diff --git a/src/encoding/sszSchemas.ts b/src/encoding/sszSchemas.ts new file mode 100644 index 00000000..1dd4ec91 --- /dev/null +++ b/src/encoding/sszSchemas.ts @@ -0,0 +1,50 @@ +import { + ByteVectorType, + ContainerType, + OptionalType, + UintBigintType, + UintNumberType, +} from "@chainsafe/ssz"; + +const Bytes32 = new ByteVectorType(32); +const Bytes96 = new ByteVectorType(96); +const Uint32 = new UintNumberType(4); +const UintBn64 = new UintBigintType(8); + +/** + * SSZ schema for a transaction object. It includes all the fields of a transaction object. + */ +const SszTransactionSchema = new ContainerType({ + index: Uint32, + shardId: Uint32, + from: Bytes32, + to: Bytes32, + value: UintBn64, + data: Bytes96, + seqno: Uint32, + signature: new OptionalType(Bytes96), + maxPriorityFeePerGas: UintBn64, + gasPrice: UintBn64, + maxFeePerGas: UintBn64, + chainId: Uint32, +}); + +/** + * SSZ schema for a signature object. It includes all the fields of a signature object. + */ +const SszSignatureSchema = new ContainerType({ + r: Bytes32, + s: Bytes32, + v: new OptionalType(UintBn64), + yParity: UintBn64, +}); + +/** + * SSZ schema for a signed transaction object. It includes all the fields of a signed transaction object. + */ +const SszSignedTransactionSchema = new ContainerType({ + ...SszTransactionSchema.fields, + ...SszSignatureSchema.fields, +}); + +export { SszTransactionSchema, SszSignedTransactionSchema }; diff --git a/src/encoding/toBytes.test.ts b/src/encoding/toBytes.test.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/encoding/toBytes.ts b/src/encoding/toBytes.ts new file mode 100644 index 00000000..fb276523 --- /dev/null +++ b/src/encoding/toBytes.ts @@ -0,0 +1,25 @@ +import type { Hex } from "@noble/curves/abstract/utils"; +import { hexToBytes } from "./fromHex.js"; + +/** + * Converts a number to bytes. + * @param num - The number to convert to bytes. + * @returns The bytes. + */ +const numberToBytes = (num: number): Uint8Array => { + return Buffer.from(num.toString()); +}; + +const toBytes = (value: number | boolean | Hex): Uint8Array => { + if (typeof value === "number") { + return numberToBytes(value); + } + + if (typeof value === "boolean") { + return numberToBytes(value ? 1 : 0); + } + + return hexToBytes(value); +}; + +export { toBytes, numberToBytes }; diff --git a/src/encoding/toHex.test.ts b/src/encoding/toHex.test.ts index 72cd8068..5872ab91 100644 --- a/src/encoding/toHex.test.ts +++ b/src/encoding/toHex.test.ts @@ -1,24 +1,22 @@ import { toHex } from "./toHex.js"; -test("toHex", () => { - it("should convert a string to hex", () => { - expect(toHex("hello")).toBe("68656c6c6f"); - }); +test("should convert a string to hex", () => { + expect(toHex("hello")).toBe("68656c6c6f"); +}); - it("should convert a number to hex", () => { - expect(toHex(123)).toBe("7b"); - }); +test("should convert a number to hex", () => { + expect(toHex(123)).toBe("7b"); +}); - it("should convert a bigint to hex", () => { - expect(toHex(123n)).toBe("7b"); - }); +test("should convert a bigint to hex", () => { + expect(toHex(123n)).toBe("7b"); +}); - it("should convert a boolean to hex", () => { - expect(toHex(true)).toBe("1"); - expect(toHex(false)).toBe("0"); - }); +test("should convert a boolean to hex", () => { + expect(toHex(true)).toBe("1"); + expect(toHex(false)).toBe("0"); +}); - it("should convert a Uint8Array to hex", () => { - expect(toHex(new Uint8Array([1, 2, 3]))).toBe("010203"); - }); +test("should convert a Uint8Array to hex", () => { + expect(toHex(new Uint8Array([1, 2, 3]))).toBe("010203"); }); diff --git a/src/encoding/toHex.ts b/src/encoding/toHex.ts index 1c58b60c..de160d40 100644 --- a/src/encoding/toHex.ts +++ b/src/encoding/toHex.ts @@ -1,4 +1,5 @@ -import { bytesToHex, numberToHexUnpadded } from "@noble/curves/abstract/utils"; +import { numberToHexUnpadded } from "@noble/curves/abstract/utils"; +import { bytesToHex } from "./fromBytes.js"; /** * Convert a string, number, bigint, boolean, or ByteArrayType to a hex string. diff --git a/src/encoding/toSsz.ts b/src/encoding/toSsz.ts index e69de29b..5340834b 100644 --- a/src/encoding/toSsz.ts +++ b/src/encoding/toSsz.ts @@ -0,0 +1,87 @@ +import {} from "@chainsafe/ssz"; +import type { ISignedTransaction } from "../types/ISignedTransaction.js"; +import type { ITransaction } from "../types/ITransaction.js"; +import { hexToBytes } from "./fromHex.js"; +import { + SszSignedTransactionSchema, + SszTransactionSchema, +} from "./sszSchemas.js"; +import { toBytes } from "./toBytes.js"; + +/** + * Process a transaction object to convert all string fields to bytes and BigInt fields to BigInt. + * @param transaction - Transaction object + * @returns ITransaction - Processed transaction object ready to be SSZ encoded. + */ +const processTransaction = ({ + from, + to, + value, + data, + signature, + maxPriorityFeePerGas, + gasPrice, + maxFeePerGas, + ...rest +}: ITransaction) => ({ + ...rest, + from: toBytes(from), + to: toBytes(to), + value: BigInt(value), + data: toBytes(data), + signature: signature ? toBytes(signature) : null, + maxPriorityFeePerGas: BigInt(maxPriorityFeePerGas), + gasPrice: BigInt(gasPrice), + maxFeePerGas: BigInt(maxFeePerGas), +}); + +/** + * Process a signed transaction object to convert all string fields to bytes and BigInt fields to BigInt. + * @param signedTransaction - Signed transaction object + * @returns ISignedTransaction - Processed signed transaction object ready to be SSZ encoded. + */ +const processSignedTransaction = ({ + v, + r, + s, + yParity, + ...rest +}: ISignedTransaction) => ({ + ...rest, + v: v ? BigInt(v) : null, + r: hexToBytes(r), + s: hexToBytes(s), + yParity: BigInt(yParity), + ...processTransaction(rest), +}); + +/** + * Convert a transaction object to SSZ encoded Uint8Array. + * @param transaction - Transaction object + * @returns Uint8Array - SSZ encoded transaction + */ +const transactionToSsz = (transaction: ITransaction): Uint8Array => { + const serialized = SszTransactionSchema.serialize( + processTransaction(transaction), + ); + + return serialized; +}; + +/** + * Convert a signed transaction object to SSZ encoded Uint8Array. + * @param transaction - Transaction object with signature + * @returns Uint8Array - SSZ encoded signed transaction + * @example + * const serializedTx = signedTransactionToSsz(signedTransaction); + */ +const signedTransactionToSsz = ( + transaction: ISignedTransaction, +): Uint8Array => { + const serialized = SszSignedTransactionSchema.serialize( + processSignedTransaction(transaction), + ); + return serialized; +}; + +export { transactionToSsz, signedTransactionToSsz }; diff --git a/src/index.ts b/src/index.ts index 58e4d190..1d2a20ec 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,18 +1,5 @@ -export { PublicClient } from "./clients/PublicClient.js"; -export { WalletClient } from "./clients/WalletClient.js"; -export type { - IPublicClientConfig, - IWalletClientConfig, -} from "./types/ClientConfigs.ts"; -export { Serializer } from "./encoding/Serializer.js"; -export { LocalKeySigner } from "./signers/LocalKeySigner.js"; -export * from "./utils/hex.js"; -export * from "./utils/transaction.js"; - -// what is format of our transaction? -// first, serialize body of tx -// than sign it -// than serialize signature+serizalized body -// than send it to the network as hex data - -// we also need a schema for ssz +export * from "./clients/index.js"; +export * from "./signers/index.js"; +export * from "./encoding/index.js"; +export * from "./types/index.js"; +export * from "./utils/index.js"; diff --git a/src/signers/LocalKeySigner.ts b/src/signers/LocalKeySigner.ts index 99219d69..b4495821 100644 --- a/src/signers/LocalKeySigner.ts +++ b/src/signers/LocalKeySigner.ts @@ -1,10 +1,10 @@ import { secp256k1 } from "@noble/curves/secp256k1"; import { toHex } from "../encoding/toHex.js"; -import type { ILocalKeySignerConfig } from "../types/ILocalKeySignerConfig.js"; -import type { ISignature } from "../types/ISignature.js"; -import type { ISigner } from "../types/ISigner.js"; import { assertIsHexString, assertIsValidPrivateKey } from "../utils/assert.js"; import { getPublicKey } from "./getPublicKey.js"; +import type { ILocalKeySignerConfig } from "./types/ILocalKeySignerConfig.js"; +import type { ISignature } from "./types/ISignature.js"; +import type { ISigner } from "./types/ISigner.js"; /** * LocalKeySigner is a class that allows you to sign the data with the private key. @@ -38,6 +38,7 @@ class LocalKeySigner implements ISigner { return { r: toHex(r), s: toHex(s), + v: recovery ? 28n : 27n, yParity: recovery, }; } diff --git a/src/signers/getPublicKey.ts b/src/signers/getPublicKey.ts index eeb1c411..8c373225 100644 --- a/src/signers/getPublicKey.ts +++ b/src/signers/getPublicKey.ts @@ -1,7 +1,7 @@ import { type Hex, bytesToHex } from "@noble/curves/abstract/utils"; import { secp256k1 } from "@noble/curves/secp256k1"; import { addHexPrefix, removeHexPrefix } from "../index.js"; -import type { IPrivateKey } from "../types/IPrivateKey.js"; +import type { IPrivateKey } from "./types/IPrivateKey.js"; /** * Returns the public key from the private key using the secp256k1 curve. diff --git a/src/signers/index.ts b/src/signers/index.ts new file mode 100644 index 00000000..b1620bb7 --- /dev/null +++ b/src/signers/index.ts @@ -0,0 +1,7 @@ +export * from "./LocalKeySigner.js"; +export * from "./verifySignature.js"; +export * from "./getPublicKey.js"; +export * from "./types/ILocalKeySignerConfig.js"; +export * from "./types/IPrivateKey.js"; +export * from "./types/ISigner.js"; +export * from "./types/ISignature.js"; diff --git a/src/types/ILocalKeySignerConfig.ts b/src/signers/types/ILocalKeySignerConfig.ts similarity index 100% rename from src/types/ILocalKeySignerConfig.ts rename to src/signers/types/ILocalKeySignerConfig.ts diff --git a/src/types/IPrivateKey.ts b/src/signers/types/IPrivateKey.ts similarity index 100% rename from src/types/IPrivateKey.ts rename to src/signers/types/IPrivateKey.ts diff --git a/src/types/ISignature.ts b/src/signers/types/ISignature.ts similarity index 94% rename from src/types/ISignature.ts rename to src/signers/types/ISignature.ts index 986f92ae..7b62c436 100644 --- a/src/types/ISignature.ts +++ b/src/signers/types/ISignature.ts @@ -6,6 +6,7 @@ import type { Hex } from "@noble/curves/abstract/utils"; type ISignature = { r: Hex; s: Hex; + v?: bigint; yParity: number; }; diff --git a/src/types/ISigner.ts b/src/signers/types/ISigner.ts similarity index 100% rename from src/types/ISigner.ts rename to src/signers/types/ISigner.ts diff --git a/src/signers/verifySignature.test.ts b/src/signers/verifySignature.test.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/signers/verifySignature.ts b/src/signers/verifySignature.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/types/IMessage.ts b/src/types/IMessage.ts deleted file mode 100644 index f56c8cb5..00000000 --- a/src/types/IMessage.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * The message type. - */ -type IMessage = { - index: number; - shardId: number; - from: string; - to: string; - value: number; - data: string; - seqno: number; - signature: string; -}; - -export type { IMessage }; diff --git a/src/types/ISerializer.ts b/src/types/ISerializer.ts deleted file mode 100644 index 737cba8d..00000000 --- a/src/types/ISerializer.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Interface for Serializer class. - * Serializer class is used to serialize and deserialize data to and from ssz format. - */ -abstract class ISerializer { - abstract serialize(data: unknown): Uint8Array; - abstract deserialize(data: Uint8Array): unknown; -} - -export { ISerializer }; diff --git a/src/types/ISignedTransaction.ts b/src/types/ISignedTransaction.ts new file mode 100644 index 00000000..6a441d58 --- /dev/null +++ b/src/types/ISignedTransaction.ts @@ -0,0 +1,6 @@ +import type { ISignature } from "../signers/index.js"; +import type { ITransaction } from "./ITransaction.js"; + +type ISignedTransaction = ITransaction & ISignature; + +export type { ISignedTransaction }; diff --git a/src/types/ITransaction.ts b/src/types/ITransaction.ts index fdcbe1ea..6394f5f8 100644 --- a/src/types/ITransaction.ts +++ b/src/types/ITransaction.ts @@ -1,78 +1,19 @@ /** * The transaction type. - * In fact, it implements ethereum transactions, but has some additional fields. */ interface ITransaction { - /** - * The transaction type. - */ - type?: null | number; - /** - * The recipient address. - */ - to?: null | string; - /** - * Sender address. - */ - from?: null | string; - /** - * The nonce. - */ - nonce?: null | number; - /** - * The gas limit. - */ - gasLimit?: null | string | number | bigint; - /** - * The gas price. - */ - gasPrice?: null | string | number | bigint; - /** - * The maximum priority fee per gas for london transactions. - */ - maxPriorityFeePerGas?: null | string | number | bigint; - /** - * The maximum total fee per gas for london transactions. - */ - maxFeePerGas?: null | string | number | bigint; - /** - * The data. - */ - data?: null | string; - /** - * The value (in wei) to send. - */ - value?: null | string | number | bigint; - /** - * The chain ID the transaction is valid on. - */ - chainId?: null | string | number | bigint; - /** - * The transaction hash. - */ - hash?: null | string; - /** - * The signature provided by the sender. - */ - signature?: null | Uint8Array; - /** - * The access list for berlin and london transactions. - */ - accessList?: null | Array<{ address: string; storageKeys: Array }>; - /** - * The transaction index. - */ - maxFeePerBlobGas?: null | string | number | bigint; - /** - * - */ - blobVersionedHashes?: null | Array; - /** - * Shard ID. - */ - shardId?: null | string; + index: number; + shardId: number; + from: string; + to: string; + value: number; + data: string; + seqno: number; + signature: string | null; + maxPriorityFeePerGas: number; + gasPrice: number; + maxFeePerGas: number; + chainId: number; } export type { ITransaction }; - -// need to elaborate on this more diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 00000000..5c687896 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,6 @@ +export * from "../clients/types/ClientConfigs.js"; +export * from "./ITransaction.js"; +export * from "../signers/types/ISigner.js"; +export * from "../signers/types/IPrivateKey.js"; +export * from "./IBlock.js"; +export * from "../signers/types/ILocalKeySignerConfig.js"; diff --git a/src/utils/assert.ts b/src/utils/assert.ts index 544dfe3d..39b11850 100644 --- a/src/utils/assert.ts +++ b/src/utils/assert.ts @@ -1,6 +1,6 @@ import type { Hex } from "@noble/curves/abstract/utils"; import invariant from "tiny-invariant"; -import type { IPrivateKey } from "../types/IPrivateKey.js"; +import type { IPrivateKey } from "../signers/index.js"; import type { ITransaction } from "../types/ITransaction.js"; import { isHexString } from "./hex.js"; import { isAddress } from "./transaction.js"; @@ -42,7 +42,7 @@ const assertIsValidPrivateKey = ( message?: string, ): void => { invariant( - isHexString(privateKey) && privateKey.length === 64 + 2, + isHexString(privateKey) && privateKey.length === 64, message ?? `Expected a valid private key, but got ${privateKey}`, ); }; @@ -53,14 +53,8 @@ const assertIsValidPrivateKey = ( * @param transaction - The transaction to validate. */ const assertIsValidTransaction = (transaction: ITransaction) => { - const { - chainId, - maxPriorityFeePerGas, - gasPrice, - maxFeePerGas, - to, - accessList, - } = transaction; + const { chainId, maxPriorityFeePerGas, gasPrice, maxFeePerGas, to } = + transaction; invariant( typeof to === "string" && isAddress(to), `Expected a valid 'to' address but got ${to}`, @@ -81,10 +75,6 @@ const assertIsValidTransaction = (transaction: ITransaction) => { typeof maxPriorityFeePerGas === "number" && maxPriorityFeePerGas > 0, `Expected a valid 'maxPriorityFeePerGas' but got ${maxPriorityFeePerGas}`, ); - invariant( - Array.isArray(accessList), - `Expected a valid 'accessList' but got ${accessList}`, - ); }; export { diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 00000000..8bb9654a --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,3 @@ +export * from "./assert.js"; +export * from "./hex.js"; +export * from "./transaction.js"; diff --git a/test/mocks/endpoint.ts b/test/mocks/endpoint.ts new file mode 100644 index 00000000..cc15d13d --- /dev/null +++ b/test/mocks/endpoint.ts @@ -0,0 +1,5 @@ +const defaultRpcEndpoint = "http://127.0.0.1:8529"; + +const endpoint = process.env.RPC_ENDPOINT ?? defaultRpcEndpoint; + +export { endpoint }; diff --git a/test/vitest.config.ts b/test/vitest.config.ts index f597e81b..15eb3d44 100644 --- a/test/vitest.config.ts +++ b/test/vitest.config.ts @@ -9,3 +9,7 @@ export default defineConfig({ globals: true, }, }); + +// start testing with local node +// we will have something public in the future. +// or we can mock api in the future. diff --git a/tsconfig.json b/tsconfig.json index c0d47ef7..dfa856fe 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,5 @@ { - "include": ["src/**/*.ts"], + "include": ["src/**/*.ts", "test/**/*.ts"], "exclude": ["node_modules", "dist"], "compilerOptions": { "strict": true,