From 99fbe9d0183b7dceb244db32eb064c120ab4744b Mon Sep 17 00:00:00 2001 From: ukorvl Date: Wed, 3 Jul 2024 15:58:16 +0400 Subject: [PATCH] remove viem encoding #53 --- .changeset/fluffy-paws-float.md | 5 ++ .github/workflows/release.yaml | 1 + examples/asyncCall.ts | 2 +- examples/bounce.ts | 3 +- examples/deployInternalMessage.ts | 2 +- examples/deployWallet.ts | 2 +- examples/externalContractDeployment.ts | 2 +- examples/syncCall.ts | 2 +- examples/tokenMint.ts | 4 +- package-lock.json | 107 +++++------------------- package.json | 2 +- src/clients/PublicClient.ts | 12 ++- src/contracts/Faucet/Faucet.test.ts | 10 ++- src/contracts/Faucet/Faucet.ts | 4 +- src/contracts/WalletV1/WalletV1.test.ts | 4 +- src/contracts/WalletV1/WalletV1.ts | 9 +- src/encoding/deployPart.ts | 4 +- src/encoding/externalMessage.ts | 6 +- src/encoding/fromBytes.ts | 11 ++- src/encoding/fromHex.test.ts | 8 +- src/encoding/fromHex.ts | 45 +++++++++- src/encoding/toHex.test.ts | 3 + src/encoding/toHex.ts | 69 +++++++++++---- src/errors/encoding.ts | 33 ++++++++ src/errors/index.ts | 1 + src/integrations/calling.test.ts | 2 +- src/integrations/deploy.test.ts | 2 +- src/integrations/tokens.test.ts | 13 +-- src/message.ts | 4 +- src/signers/LocalECDSAKeySigner.test.ts | 2 +- src/signers/LocalECDSAKeySigner.ts | 2 +- src/types/IDeployData.ts | 2 +- src/utils/address.ts | 8 +- src/utils/hex.ts | 14 +++- src/utils/refiners.ts | 2 +- 35 files changed, 247 insertions(+), 155 deletions(-) create mode 100644 .changeset/fluffy-paws-float.md create mode 100644 src/errors/encoding.ts diff --git a/.changeset/fluffy-paws-float.md b/.changeset/fluffy-paws-float.md new file mode 100644 index 0000000..5f3a0df --- /dev/null +++ b/.changeset/fluffy-paws-float.md @@ -0,0 +1,5 @@ +--- +"@nilfoundation/niljs": minor +--- + +Remove viem encoding and replace with local implementation diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index d5bce71..57969d2 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -3,6 +3,7 @@ name: Release on: pull_request: types: [closed] + branches: [master] workflow_dispatch: concurrency: diff --git a/examples/asyncCall.ts b/examples/asyncCall.ts index ac66100..0806449 100644 --- a/examples/asyncCall.ts +++ b/examples/asyncCall.ts @@ -1,10 +1,10 @@ -import { bytesToHex } from "viem"; import { Faucet, HttpTransport, LocalECDSAKeySigner, PublicClient, WalletV1, + bytesToHex, generateRandomPrivateKey, waitTillCompleted, } from "../src"; diff --git a/examples/bounce.ts b/examples/bounce.ts index d2bf790..49694f0 100644 --- a/examples/bounce.ts +++ b/examples/bounce.ts @@ -1,10 +1,11 @@ -import { bytesToHex, encodeFunctionData } from "viem"; +import { encodeFunctionData } from "viem"; import { Faucet, HttpTransport, LocalECDSAKeySigner, PublicClient, WalletV1, + bytesToHex, generateRandomPrivateKey, waitTillCompleted, } from "../src"; diff --git a/examples/deployInternalMessage.ts b/examples/deployInternalMessage.ts index ea48a02..60ebfbd 100644 --- a/examples/deployInternalMessage.ts +++ b/examples/deployInternalMessage.ts @@ -1,11 +1,11 @@ import type { Abi } from "abitype"; -import { bytesToHex } from "viem"; import { Faucet, HttpTransport, LocalECDSAKeySigner, PublicClient, WalletV1, + bytesToHex, generateRandomPrivateKey, waitTillCompleted, } from "../src"; diff --git a/examples/deployWallet.ts b/examples/deployWallet.ts index 5ab71af..2f92c38 100644 --- a/examples/deployWallet.ts +++ b/examples/deployWallet.ts @@ -1,10 +1,10 @@ -import { bytesToHex } from "viem"; import { Faucet, HttpTransport, LocalECDSAKeySigner, PublicClient, WalletV1, + bytesToHex, generateRandomPrivateKey, } from "../src"; diff --git a/examples/externalContractDeployment.ts b/examples/externalContractDeployment.ts index eb72029..d37ebdb 100644 --- a/examples/externalContractDeployment.ts +++ b/examples/externalContractDeployment.ts @@ -1,10 +1,10 @@ -import { bytesToHex } from "viem"; import { Faucet, HttpTransport, LocalECDSAKeySigner, PublicClient, WalletV1, + bytesToHex, externalDeploymentMessage, generateRandomPrivateKey, } from "../src"; diff --git a/examples/syncCall.ts b/examples/syncCall.ts index 34fa754..d767bb5 100644 --- a/examples/syncCall.ts +++ b/examples/syncCall.ts @@ -1,10 +1,10 @@ -import { bytesToHex } from "viem"; import { Faucet, HttpTransport, LocalECDSAKeySigner, PublicClient, WalletV1, + bytesToHex, convertEthToWei, generateRandomPrivateKey, } from "../src"; diff --git a/examples/tokenMint.ts b/examples/tokenMint.ts index 470e921..1468e17 100644 --- a/examples/tokenMint.ts +++ b/examples/tokenMint.ts @@ -1,4 +1,4 @@ -import { bytesToHex, encodeFunctionData, hexToBigInt } from "viem"; +import { encodeFunctionData } from "viem"; import { Faucet, HttpTransport, @@ -7,7 +7,9 @@ import { MINTER_ADDRESS, PublicClient, WalletV1, + bytesToHex, generateRandomPrivateKey, + hexToBigInt, waitTillCompleted, } from "../src"; diff --git a/package-lock.json b/package-lock.json index 6bf7f35..13b89d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@nilfoundation/niljs", - "version": "0.5.0", + "version": "0.8.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@nilfoundation/niljs", - "version": "0.5.0", + "version": "0.8.0", "license": "MIT", "dependencies": { "@chainsafe/persistent-merkle-tree": "^0.7.2", @@ -18,7 +18,7 @@ "@scure/bip39": "^1.3.0", "abitype": "^1.0.2", "tiny-invariant": "^1.3.3", - "viem": "^2.16.3" + "viem": "^2.17.0" }, "devDependencies": { "@biomejs/biome": "^1.8.1", @@ -2100,46 +2100,13 @@ } }, "node_modules/@scure/bip32": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.2.tgz", - "integrity": "sha512-N1ZhksgwD3OBlwTv3R6KFEcPojl/W4ElJOeCZdi+vuI5QmTFwLq3OFf2zd2ROpKvxFdgZ6hUpb0dx9bVNEwYCA==", - "dependencies": { - "@noble/curves": "~1.2.0", - "@noble/hashes": "~1.3.2", - "@scure/base": "~1.1.2" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@scure/bip32/node_modules/@noble/curves": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", - "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.4.0.tgz", + "integrity": "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==", "dependencies": { - "@noble/hashes": "1.3.2" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@scure/bip32/node_modules/@noble/curves/node_modules/@noble/hashes": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", - "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@scure/bip32/node_modules/@noble/hashes": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", - "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", - "engines": { - "node": ">= 16" + "@noble/curves": "~1.4.0", + "@noble/hashes": "~1.4.0", + "@scure/base": "~1.1.6" }, "funding": { "url": "https://paulmillr.com/funding/" @@ -2563,9 +2530,9 @@ } }, "node_modules/abitype": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.4.tgz", - "integrity": "sha512-UivtYZOGJGE8rsrM/N5vdRkUpqEZVmuTumfTuolm7m/6O09wprd958rx8kUBwVAAAhQDveGAgD0GJdBuR8s6tw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.5.tgz", + "integrity": "sha512-YzDhti7cjlfaBhHutMaboYB21Ha3rXR9QTkNJFzYC4kC8YclaiwPBBBJY8ejFdu2wnJeZCVZSMlQJ7fi8S6hsw==", "funding": { "url": "https://github.com/sponsors/wevm" }, @@ -9949,9 +9916,9 @@ } }, "node_modules/viem": { - "version": "2.16.3", - "resolved": "https://registry.npmjs.org/viem/-/viem-2.16.3.tgz", - "integrity": "sha512-6ExbIpi77C1HzGSjx6W+fn39Cz1ULlVV74XHxi1kYpOAqY7/iAoynK5X2aQp2LvxstTqS1GIKEdpCAZ8dKi2Ag==", + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.17.0.tgz", + "integrity": "sha512-+gaVlsfDsHL1oYdjpatdRxW1WK/slLYVvpOws3fEdLfQFUToezKI6YLC9l1g2uKm4Hg3OdGX1KQy/G7/58tTKQ==", "funding": [ { "type": "github", @@ -9960,11 +9927,11 @@ ], "dependencies": { "@adraffy/ens-normalize": "1.10.0", - "@noble/curves": "1.2.0", - "@noble/hashes": "1.3.2", - "@scure/bip32": "1.3.2", - "@scure/bip39": "1.2.1", - "abitype": "1.0.4", + "@noble/curves": "1.4.0", + "@noble/hashes": "1.4.0", + "@scure/bip32": "1.4.0", + "@scure/bip39": "1.3.0", + "abitype": "1.0.5", "isows": "1.0.4", "ws": "8.17.1" }, @@ -9977,40 +9944,6 @@ } } }, - "node_modules/viem/node_modules/@noble/curves": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", - "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", - "dependencies": { - "@noble/hashes": "1.3.2" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/viem/node_modules/@noble/hashes": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", - "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/viem/node_modules/@scure/bip39": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.1.tgz", - "integrity": "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==", - "dependencies": { - "@noble/hashes": "~1.3.0", - "@scure/base": "~1.1.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/viem/node_modules/ws": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", diff --git a/package.json b/package.json index 5a1487a..9475e9f 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "@scure/bip39": "^1.3.0", "abitype": "^1.0.2", "tiny-invariant": "^1.3.3", - "viem": "^2.16.3" + "viem": "^2.17.0" }, "keywords": [ "nil", diff --git a/src/clients/PublicClient.ts b/src/clients/PublicClient.ts index 2156644..2af5301 100644 --- a/src/clients/PublicClient.ts +++ b/src/clients/PublicClient.ts @@ -1,6 +1,10 @@ -import { bytesToHex } from "@noble/curves/abstract/utils"; -import { hexToBytes, numberToHex } from "viem"; -import { hexToBigInt, hexToNumber } from "../encoding/index.js"; +import { + bytesToHex, + hexToBigInt, + hexToBytes, + hexToNumber, + toHex, +} from "../encoding/index.js"; import { BlockNotFoundError } from "../errors/block.js"; import { type Hex, assertIsValidShardId } from "../index.js"; import type { IAddress } from "../signers/types/IAddress.js"; @@ -410,7 +414,7 @@ class PublicClient extends BaseClient { typeof callArgs.data === "string" ? callArgs.data : addHexPrefix(bytesToHex(callArgs.data)), - value: numberToHex(callArgs.value || 0n), + value: toHex(callArgs.value || 0n), gasLimit: (callArgs.gasLimit || 5_000_000n).toString(10), }; diff --git a/src/contracts/Faucet/Faucet.test.ts b/src/contracts/Faucet/Faucet.test.ts index 141764c..128641e 100644 --- a/src/contracts/Faucet/Faucet.test.ts +++ b/src/contracts/Faucet/Faucet.test.ts @@ -1,13 +1,15 @@ -import { bytesToHex } from "viem"; -import { generatePrivateKey } from "viem/accounts"; import { PublicClient } from "../../clients/index.js"; -import { LocalECDSAKeySigner } from "../../signers/index.js"; +import { bytesToHex } from "../../index.js"; +import { + LocalECDSAKeySigner, + generateRandomPrivateKey, +} from "../../signers/index.js"; import { MockTransport } from "../../transport/MockTransport.js"; import { WalletV1 } from "../WalletV1/WalletV1.js"; import { Faucet } from "./Faucet.js"; const signer = new LocalECDSAKeySigner({ - privateKey: generatePrivateKey(), + privateKey: generateRandomPrivateKey(), }); test("Faucet with retry", async () => { diff --git a/src/contracts/Faucet/Faucet.ts b/src/contracts/Faucet/Faucet.ts index ded3125..356d0a5 100644 --- a/src/contracts/Faucet/Faucet.ts +++ b/src/contracts/Faucet/Faucet.ts @@ -1,6 +1,7 @@ -import { type Hex, bytesToHex, encodeFunctionData, hexToBytes } from "viem"; +import { type Hex, bytesToHex, encodeFunctionData } from "viem"; import type { PublicClient } from "../../clients/PublicClient.js"; import { ExternalMessageEnvelope } from "../../encoding/externalMessage.js"; +import { hexToBytes } from "../../index.js"; import type { IReceipt } from "../../types/IReceipt.js"; import { getShardIdFromAddress } from "../../utils/address.js"; import { waitTillCompleted } from "../../utils/receipt.js"; @@ -9,7 +10,6 @@ import FaucetAbi from "./Faucet.abi.json"; /** * Faucet is a special contract that is used to top up other contracts in the =nil; devnet. * - * @class Faucet * @typedef {Faucet} */ diff --git a/src/contracts/WalletV1/WalletV1.test.ts b/src/contracts/WalletV1/WalletV1.test.ts index dca6642..fe27b63 100644 --- a/src/contracts/WalletV1/WalletV1.test.ts +++ b/src/contracts/WalletV1/WalletV1.test.ts @@ -1,12 +1,12 @@ -import { generatePrivateKey } from "viem/accounts"; import { PublicClient } from "../../clients/index.js"; +import { generateRandomPrivateKey } from "../../index.js"; import { LocalECDSAKeySigner } from "../../signers/LocalECDSAKeySigner.js"; import { MockTransport } from "../../transport/MockTransport.js"; import { HttpTransport } from "../../transport/index.js"; import { WalletV1 } from "./WalletV1.js"; const signer = new LocalECDSAKeySigner({ - privateKey: generatePrivateKey(), + privateKey: generateRandomPrivateKey(), }); const pubkey = await signer.getPublicKey(); const client = new PublicClient({ diff --git a/src/contracts/WalletV1/WalletV1.ts b/src/contracts/WalletV1/WalletV1.ts index b2968b3..7865e04 100644 --- a/src/contracts/WalletV1/WalletV1.ts +++ b/src/contracts/WalletV1/WalletV1.ts @@ -1,9 +1,10 @@ import type { Abi } from "abitype"; import invariant from "tiny-invariant"; -import { bytesToHex, encodeFunctionData, hexToBytes } from "viem"; +import { bytesToHex, encodeFunctionData } from "viem"; import type { PublicClient } from "../../clients/PublicClient.js"; import { prepareDeployPart } from "../../encoding/deployPart.js"; import { externalMessageEncode } from "../../encoding/externalMessage.js"; +import { hexToBytes, toHex } from "../../index.js"; import type { ISigner } from "../../signers/index.js"; import type { IDeployData } from "../../types/IDeployData.js"; import { getShardIdFromAddress, refineAddress } from "../../utils/address.js"; @@ -19,9 +20,9 @@ import type { } from "./types/index.js"; /** - * WalletV1 is a class used for performing operations on the cluster that require authentication. + * WalletV1 is a class used for performing operations on the cluster that require authentication. * - + * @class WalletV1 * @typedef {WalletV1} */ @@ -166,7 +167,7 @@ export class WalletV1 { if (salt) { this.salt = refineSalt(salt); } - this.shardId = getShardIdFromAddress(this.address); + this.shardId = getShardIdFromAddress(toHex(this.address)); } /** diff --git a/src/encoding/deployPart.ts b/src/encoding/deployPart.ts index b6227cf..fece7c2 100644 --- a/src/encoding/deployPart.ts +++ b/src/encoding/deployPart.ts @@ -1,7 +1,9 @@ -import { bytesToHex, encodeDeployData, hexToBytes } from "viem"; +import { encodeDeployData } from "viem"; import type { IDeployData } from "../types/IDeployData.js"; import { calculateAddress } from "../utils/address.js"; import { refineSalt } from "../utils/refiners.js"; +import { bytesToHex } from "./fromBytes.js"; +import { hexToBytes } from "./fromHex.js"; /** * Refines the provided salt and generates the full bytecode for deployment. Returns the bytecode and the deployment address. diff --git a/src/encoding/externalMessage.ts b/src/encoding/externalMessage.ts index d707ac9..5de88f9 100644 --- a/src/encoding/externalMessage.ts +++ b/src/encoding/externalMessage.ts @@ -1,15 +1,15 @@ -import { bytesToHex } from "viem"; import type { PublicClient } from "../clients/PublicClient.js"; import type { ISigner } from "../signers/index.js"; import type { ExternalMessage } from "../types/ExternalMessage.js"; import type { IDeployData } from "../types/IDeployData.js"; import { prepareDeployPart } from "./deployPart.js"; +import { bytesToHex } from "./fromBytes.js"; import { SszMessageSchema, SszSignedMessageSchema } from "./ssz.js"; /** * The envelope for an external message (a message sent by a user, a dApp, etc.) * - + * @class ExternalMessageEnvelope * @typedef {ExternalMessageEnvelope} */ @@ -206,7 +206,7 @@ export class ExternalMessageEnvelope { /** * The envelope for an internal message (a message sent by a smart contract to another smart contract). * - + * @class InternalMessageEnvelope * @typedef {InternalMessageEnvelope} */ diff --git a/src/encoding/fromBytes.ts b/src/encoding/fromBytes.ts index c2fcd2a..ac2e5be 100644 --- a/src/encoding/fromBytes.ts +++ b/src/encoding/fromBytes.ts @@ -1,13 +1,20 @@ +import { type Hex, toHex } from "../index.js"; + +const decoder = new TextDecoder("utf8"); + /** * Converts bytes to a string. * @param bytes - The bytes to convert. * @returns The string representation of the input. */ const bytesToString = (bytes: Uint8Array): string => { - const decoder = new TextDecoder("utf8"); const str = decoder.decode(bytes); return str; }; -export { bytesToString }; +const bytesToHex = (bytes: Uint8Array): Hex => { + return toHex(bytes); +}; + +export { bytesToString, bytesToHex }; diff --git a/src/encoding/fromHex.test.ts b/src/encoding/fromHex.test.ts index b37a1a7..afa660e 100644 --- a/src/encoding/fromHex.test.ts +++ b/src/encoding/fromHex.test.ts @@ -1,11 +1,13 @@ -import { hexToBigInt, hexToNumber } from "./fromHex.js"; +import { hexToBigInt, hexToBytes, hexToNumber } from "./fromHex.js"; test("hexToBigInt", () => { expect(hexToBigInt("0x7b")).toBe(123n); - expect(hexToBigInt("7b")).toBe(123n); }); test("hexToNumber", () => { expect(hexToNumber("0x7b")).toBe(123); - expect(hexToNumber("7b")).toBe(123); +}); + +test("hexToBytes", () => { + expect(hexToBytes("0x7b")).toEqual(Uint8Array.from([123])); }); diff --git a/src/encoding/fromHex.ts b/src/encoding/fromHex.ts index 612f778..765f67a 100644 --- a/src/encoding/fromHex.ts +++ b/src/encoding/fromHex.ts @@ -1,6 +1,25 @@ +import { BaseError } from "../errors/BaseError.js"; import type { Hex } from "../index.js"; import { addHexPrefix, removeHexPrefix } from "../utils/hex.js"; +/** + * Convert a character code to a base16 number. + * @param charCode - The character code to convert. + * @returns The base16 representation of the input. + */ +const charCodeToBase16 = (charCode: number): number | undefined => { + if (charCode >= 48 && charCode <= 57) { + return charCode - 48; + } + if (charCode >= 65 && charCode <= 70) { + return charCode - 55; + } + if (charCode >= 97 && charCode <= 102) { + return charCode - 87; + } + return undefined; +}; + /** * Convert a hex string to a number. * @param hex - The hex string to convert. @@ -19,4 +38,28 @@ const hexToBigInt = (hex: Hex): bigint => { return BigInt(addHexPrefix(hex)); }; -export { hexToNumber, hexToBigInt }; +const hexToBytes = (hex: Hex): Uint8Array => { + let hexString = hex.slice(2); + if (hexString.length % 2) { + hexString = `0${hexString}`; + } + + const length = hexString.length / 2; + const bytes = new Uint8Array(length); + + for (let index = 0, j = 0; index < length; index++) { + const nibbleLeft = charCodeToBase16(hexString.charCodeAt(j++)); + const nibbleRight = charCodeToBase16(hexString.charCodeAt(j++)); + if (nibbleLeft === undefined || nibbleRight === undefined) { + throw new BaseError( + `Invalid byte sequence ("${hexString[j - 2]}${ + hexString[j - 1] + }" in "${hexString}").`, + ); + } + bytes[index] = nibbleLeft * 16 + nibbleRight; + } + return bytes; +}; + +export { hexToNumber, hexToBigInt, hexToBytes }; diff --git a/src/encoding/toHex.test.ts b/src/encoding/toHex.test.ts index c00372d..d936e5e 100644 --- a/src/encoding/toHex.test.ts +++ b/src/encoding/toHex.test.ts @@ -2,10 +2,13 @@ import { toHex } from "./toHex.js"; test("should convert a string to hex", () => { expect(toHex("hello")).toBe("0x68656c6c6f"); + expect(toHex("")).toBe("0x"); + expect(toHex("some string")).toBe("0x736f6d6520737472696e67"); }); test("should convert a number to hex", () => { expect(toHex(123)).toBe("0x7b"); + expect(toHex(0)).toBe("0x0"); }); test("should convert a bigint to hex", () => { diff --git a/src/encoding/toHex.ts b/src/encoding/toHex.ts index 419166b..759ef72 100644 --- a/src/encoding/toHex.ts +++ b/src/encoding/toHex.ts @@ -1,45 +1,82 @@ -import { bytesToHex, numberToHexUnpadded } from "@noble/curves/abstract/utils"; +import { type Hex, IntegerOutOfRangeError, addHexPrefix } from "../index.js"; + +// biome-ignore lint/style/useNamingConvention: +const hexes = Array.from({ length: 256 }, (_, i) => + i.toString(16).padStart(2, "0"), +); /** * Convert a string to a hex string. * @param str - The input string to convert. * @returns The hex string representation of the input. */ -const stringToHex = (str: string): string => { +const stringToHex = (str: string): Hex => { let hex = ""; for (let i = 0; i < str.length; i++) { hex += str.charCodeAt(i).toString(16); } - return hex; + return addHexPrefix(hex); +}; + +/** + * Convert bytes to a hex string. + * @param bytes - The bytes to convert. + * @returns The hex string representation of the input. + */ +const bytesToHex = (bytes: Uint8Array): Hex => { + let hex = ""; + + for (let i = 0; i < bytes.length; i++) { + hex += hexes[bytes[i]]; + } + + return addHexPrefix(hex); +}; + +/** + * Convert an unsigned number to a hex string. + * @param num - The number to convert. + * @returns The hex string representation of the input. + */ +const numberToHex = (num: number | bigint): Hex => { + const value = BigInt(num); + const maxValue = BigInt(Number.MAX_SAFE_INTEGER); + const minValue = 0; + + if ((maxValue && value > maxValue) || value < minValue) { + throw new IntegerOutOfRangeError({ + max: maxValue, + min: minValue, + value, + }); + } + + return addHexPrefix(value.toString(16)); }; /** * Convert a string, number, bigint, boolean, or ByteArrayType to a hex string. - * @param str - The input string to convert. + * @param value - The input to convert. * @returns The hex string representation of the input. */ -const toHex = ( +const toHex = ( value: T, -): `0x${string}` => { +): Hex => { if (typeof value === "string") { - return `0x${stringToHex(value)}`; - } - - if (typeof value === "number") { - return `0x${numberToHexUnpadded(value)}`; + return stringToHex(value); } - if (typeof value === "bigint") { - return `0x${numberToHexUnpadded(value)}`; + if (value instanceof Uint8Array) { + return bytesToHex(value); } - if (typeof value === "boolean") { - return `0x${(value ? 1 : 0).toString(16)}`; + if (typeof value === "number" || typeof value === "bigint") { + return numberToHex(value); } - return `0x${bytesToHex(value)}`; + return addHexPrefix((value ? 1 : 0).toString(16)); }; export { toHex }; diff --git a/src/errors/encoding.ts b/src/errors/encoding.ts new file mode 100644 index 0000000..c870c37 --- /dev/null +++ b/src/errors/encoding.ts @@ -0,0 +1,33 @@ +import { BaseError, type IBaseErrorParameters } from "./BaseError.js"; + +/** + * The interface for the parameters of the {@link BlockNotFoundError} constructor. + */ +type IntegerOutOfRangeErrorParameters = { + max?: number | bigint; + min: number | bigint; + value: number | bigint; +} & IBaseErrorParameters; + +/** + * The error class for 'integer out of range' errors. + * This error is thrown when the requested integer is out of range. + */ +class IntegerOutOfRangeError extends BaseError { + /** + * Creates an instance of IntegerOutOfRangeError. + * + * @constructor + * @param {IntegerOutOfRangeErrorParameters} param0 The error params. + * @param {*} param0.max The maximum value. + * @param {string} param0.min The minimum value. + */ + constructor({ max, min, value, ...rest }: IntegerOutOfRangeErrorParameters) { + super( + `Number "${value}" is not in safe integer range ${max ? `(${min} to ${max})` : `(above ${min})`}`, + { ...rest }, + ); + } +} + +export { IntegerOutOfRangeError }; diff --git a/src/errors/index.ts b/src/errors/index.ts index 00ab346..e10977c 100644 --- a/src/errors/index.ts +++ b/src/errors/index.ts @@ -1,2 +1,3 @@ export * from "./block.js"; export * from "./shardId.js"; +export * from "./encoding.js"; diff --git a/src/integrations/calling.test.ts b/src/integrations/calling.test.ts index 3363c59..5ba7760 100644 --- a/src/integrations/calling.test.ts +++ b/src/integrations/calling.test.ts @@ -1,4 +1,3 @@ -import { bytesToHex } from "viem"; import { testEnv } from "../../test/testEnv.js"; import { Faucet, @@ -6,6 +5,7 @@ import { LocalECDSAKeySigner, PublicClient, WalletV1, + bytesToHex, convertEthToWei, generateRandomPrivateKey, waitTillCompleted, diff --git a/src/integrations/deploy.test.ts b/src/integrations/deploy.test.ts index 19121c3..9357f6e 100644 --- a/src/integrations/deploy.test.ts +++ b/src/integrations/deploy.test.ts @@ -1,5 +1,4 @@ import type { Abi } from "abitype"; -import { bytesToHex } from "viem"; import { testEnv } from "../../test/testEnv.js"; import { Faucet, @@ -7,6 +6,7 @@ import { LocalECDSAKeySigner, PublicClient, WalletV1, + bytesToHex, convertEthToWei, externalDeploymentMessage, generateRandomPrivateKey, diff --git a/src/integrations/tokens.test.ts b/src/integrations/tokens.test.ts index 5ff340b..796039f 100644 --- a/src/integrations/tokens.test.ts +++ b/src/integrations/tokens.test.ts @@ -1,4 +1,4 @@ -import { bytesToHex, encodeFunctionData, hexToBigInt, numberToHex } from "viem"; +import { encodeFunctionData } from "viem"; import { testEnv } from "../../test/testEnv.js"; import { Faucet, @@ -8,8 +8,11 @@ import { MINTER_ADDRESS, PublicClient, WalletV1, + bytesToHex, convertEthToWei, generateRandomPrivateKey, + hexToBigInt, + toHex, waitTillCompleted, } from "../index.js"; const client = new PublicClient({ @@ -61,8 +64,8 @@ test("mint and transfer tokens", async () => { expect(tokens).toBeDefined(); expect(Object.keys(tokens).length).toBeGreaterThan(0); - expect(tokens[numberToHex(n)]).toBeDefined(); - expect(tokens[numberToHex(n)]).toBe(mintCount); + expect(tokens[toHex(n)]).toBeDefined(); + expect(tokens[toHex(n)]).toBe(mintCount); const anotherAddress = WalletV1.calculateWalletAddress({ pubKey: pubkey, @@ -93,6 +96,6 @@ test("mint and transfer tokens", async () => { expect(anotherTokens).toBeDefined(); expect(Object.keys(anotherTokens).length).toBeGreaterThan(0); - expect(anotherTokens[numberToHex(n)]).toBeDefined(); - expect(anotherTokens[numberToHex(n)]).toBe(transferCount); + expect(anotherTokens[toHex(n)]).toBeDefined(); + expect(anotherTokens[toHex(n)]).toBe(transferCount); }); diff --git a/src/message.ts b/src/message.ts index 261400f..c25b5cd 100644 --- a/src/message.ts +++ b/src/message.ts @@ -1,7 +1,7 @@ -import { bytesToHex } from "viem"; import type { PublicClient } from "./clients/PublicClient.js"; import { prepareDeployPart } from "./encoding/deployPart.js"; import { SszMessageSchema, SszSignedMessageSchema } from "./encoding/ssz.js"; +import { bytesToHex } from "./index.js"; import type { ISigner } from "./signers/index.js"; import type { ExternalMessage } from "./types/ExternalMessage.js"; import type { IDeployData } from "./types/IDeployData.js"; @@ -9,7 +9,7 @@ import type { IDeployData } from "./types/IDeployData.js"; /** * The envelope for an external message (a message sent by a user, a dApp, etc.) * - + * @class ExternalMessageEnvelope * @typedef {ExternalMessageEnvelope} */ diff --git a/src/signers/LocalECDSAKeySigner.test.ts b/src/signers/LocalECDSAKeySigner.test.ts index 0a80dd6..9c01e36 100644 --- a/src/signers/LocalECDSAKeySigner.test.ts +++ b/src/signers/LocalECDSAKeySigner.test.ts @@ -1,5 +1,5 @@ -import { bytesToHex } from "viem"; import { accounts } from "../../test/mocks/accounts.js"; +import { bytesToHex } from "../index.js"; import { LocalECDSAKeySigner } from "./LocalECDSAKeySigner.js"; test("getPublicKey", async () => { diff --git a/src/signers/LocalECDSAKeySigner.ts b/src/signers/LocalECDSAKeySigner.ts index 24c2cdc..2f0342d 100644 --- a/src/signers/LocalECDSAKeySigner.ts +++ b/src/signers/LocalECDSAKeySigner.ts @@ -1,7 +1,7 @@ import { concatBytes, numberToBytesBE } from "@noble/curves/abstract/utils"; import { secp256k1 } from "@noble/curves/secp256k1"; import invariant from "tiny-invariant"; -import { type Hex, bytesToHex, hexToBytes } from "viem"; +import { type Hex, bytesToHex, hexToBytes } from "../index.js"; import { assertIsValidPrivateKey } from "../utils/assert.js"; import { addHexPrefix, removeHexPrefix } from "../utils/hex.js"; import { privateKeyFromPhrase } from "./mnemonic.js"; diff --git a/src/types/IDeployData.ts b/src/types/IDeployData.ts index e22b66a..b2ecb5a 100644 --- a/src/types/IDeployData.ts +++ b/src/types/IDeployData.ts @@ -1,5 +1,5 @@ import type { Abi } from "abitype"; -import type { Hex } from "viem"; +import type { Hex } from "./Hex.js"; /** * IDeployData is a data structure that contains information to deploy a contract. diff --git a/src/utils/address.ts b/src/utils/address.ts index 17b0784..a4370a8 100644 --- a/src/utils/address.ts +++ b/src/utils/address.ts @@ -1,6 +1,6 @@ -import { type Hex, numberToBytesBE } from "@noble/curves/abstract/utils"; -import { hexToBytes } from "viem"; +import { numberToBytesBE } from "@noble/curves/abstract/utils"; import { poseidonHash } from "../encoding/poseidon.js"; +import { hexToBytes } from "../index.js"; import type { IAddress } from "../signers/types/IAddress.js"; /** @@ -14,7 +14,7 @@ const ADDRESS_REGEX = /^0x[0-9a-fA-F]{40}$/; * Otherwise, returns false. * @param value The value to check. */ -const isAddress = (value: Hex): value is IAddress => { +const isAddress = (value: string): value is IAddress => { return typeof value === "string" && ADDRESS_REGEX.test(value); }; @@ -22,7 +22,7 @@ const isAddress = (value: Hex): value is IAddress => { * Returns the ID of the shard containing the provided address. * @param address The address. */ -const getShardIdFromAddress = (address: Hex): number => { +const getShardIdFromAddress = (address: string): number => { if (typeof address === "string") { return Number.parseInt(address.slice(2, 6), 16); } diff --git a/src/utils/hex.ts b/src/utils/hex.ts index 2d3a351..628442b 100644 --- a/src/utils/hex.ts +++ b/src/utils/hex.ts @@ -33,4 +33,16 @@ const addHexPrefix = (str: Hex | string): Hex => { return `0x${removeHexPrefix(str)}`; }; -export { isHexString, removeHexPrefix, addHexPrefix }; +/** + * Concatenates an array of hex strings. The hex strings are concatenated without the "0x" prefix. + * The resulting hex string will have the "0x" prefix. + * @param values - An array of hex strings. + * @returns The concatenated hex string. + */ +const concatHex = (values: readonly Hex[]): Hex => { + return addHexPrefix( + (values as Hex[]).reduce((acc, x) => acc + x.replace("0x", ""), ""), + ); +}; + +export { isHexString, removeHexPrefix, addHexPrefix, concatHex }; diff --git a/src/utils/refiners.ts b/src/utils/refiners.ts index daa4602..cd53ce0 100644 --- a/src/utils/refiners.ts +++ b/src/utils/refiners.ts @@ -1,5 +1,5 @@ import invariant from "tiny-invariant"; -import { hexToBytes } from "viem"; +import { hexToBytes } from "../index.js"; import { addHexPrefix } from "./hex.js"; const refineSalt = (salt: Uint8Array | bigint): Uint8Array => {