From fedada49c9a0929695ac619569aac44f739bd520 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Such=C3=BD?= Date: Fri, 15 Aug 2025 00:38:40 +0200 Subject: [PATCH 1/6] feat: migrate units conversion utils --- package.json | 3 +- src/index.ts | 1 + src/unitsConversion.test.ts | 195 +++++++++++ src/unitsConversion.ts | 237 +++++++++++++ yarn.lock | 650 +++++++++++++++++++++++++++++++----- 5 files changed, 1007 insertions(+), 79 deletions(-) create mode 100644 src/unitsConversion.test.ts create mode 100644 src/unitsConversion.ts diff --git a/package.json b/package.json index 9c0ed7062..540fe165d 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,8 @@ "ts-node": "^10.7.0", "tsd": "^0.29.0", "typedoc": "^0.23.15", - "typescript": "~5.0.4" + "typescript": "~5.0.4", + "web3": "^4.16.0" }, "packageManager": "yarn@3.8.5", "engines": { diff --git a/src/index.ts b/src/index.ts index 9eae3660c..abeac3f8c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -36,3 +36,4 @@ export * from './superstruct'; export * from './time'; export * from './transaction-types'; export * from './versions'; +export * from './unitsConversion'; diff --git a/src/unitsConversion.test.ts b/src/unitsConversion.test.ts new file mode 100644 index 000000000..c97e58ded --- /dev/null +++ b/src/unitsConversion.test.ts @@ -0,0 +1,195 @@ +// web3 dependency is not used in the codebase, but only in tests +// eslint-disable-next-line import/no-extraneous-dependencies +import { utils as web3Utils } from 'web3'; + +import { toWei, fromWei, numberToString, unitMap } from './unitsConversion'; + +const totalTypes = Object.keys(unitMap).length; + +/** + * Test random value conversion to Wei against web3 implementation. + * + * @param negative - Whether to test negative values. + */ +function testRandomValueAgainstWeb3ToWei(negative: boolean) { + const stringTestValue = `${negative ? '-' : ''}${String( + Math.floor(Math.random() * 100000000000000000 + 1), + )}`; + const randomunitsType = Object.keys(unitMap)[ + Math.floor(Math.random() * (totalTypes - 1) + 1) + ] as keyof typeof unitMap; + const unitsValue = toWei(stringTestValue, randomunitsType); + const web3Value = BigInt(web3Utils.toWei(stringTestValue, randomunitsType)); + + expect(unitsValue).toStrictEqual(web3Value); +} + +/** + * Test random value conversion from Wei against web3 implementation. + * + * @param negative - Whether to test negative values. + */ +function testRandomValueAgainstWeb3FromWei(negative: boolean) { + const stringTestValue = `${negative ? '-' : ''}${String( + Math.floor(Math.random() * 100000000000000000 + 1), + )}`; + const randomunitsType = Object.keys(unitMap)[ + Math.floor(Math.random() * (totalTypes - 1) + 1) + ] as keyof typeof unitMap; + const unitsValue = fromWei(stringTestValue, randomunitsType); + const web3Value = web3Utils.fromWei(stringTestValue, randomunitsType); + + // it(`fromWei should work like web3 rounded val ${unitsValue.substr(0, web3Value.length - 1)} should equal ${web3Value.substr(0, web3Value.length - 1)} for unit type ${randomunitsType}`, () => { + + // Skip test cases where web3 has a formatting bug that makes the value unparseable + // Web3 formats negative decimals incorrectly as "0.000-123" instead of "-0.000123" + if (web3Value.includes('-') && !web3Value.startsWith('-')) { + // This is a known web3 bug, skip this test case + return; + } + + // Handle formatting differences between our implementation and web3 + // Our implementation: "-0.123" vs web3: "-.123" + // Use numerical comparison for accuracy while allowing for precision differences + const unitsValueAsNumber = parseFloat(unitsValue); + const web3ValueAsNumber = parseFloat(web3Value); + + // Allow for small floating point precision differences (up to 10 decimal places) + const tolerance = 1e-10; + expect(Math.abs(unitsValueAsNumber - web3ValueAsNumber)).toBeLessThan( + tolerance, + ); + // }); +} + +describe('getValueOfUnit', () => { + it('should throw when undefined or not string', () => { + /** + * Helper function to test invalid unit. + */ + function invalidFromWei() { + fromWei(1000000000000000000, 'something' as any); + } + expect(invalidFromWei).toThrow(Error); + }); +}); + +describe('toWei', () => { + it('should handle edge cases', () => { + expect(toWei(0, 'wei').toString(10)).toBe('0'); + expect(toWei('0.0', 'wei').toString(10)).toBe('0'); + expect(toWei('.3', 'ether').toString(10)).toBe('300000000000000000'); + expect(() => toWei('.', 'wei')).toThrow(Error); + expect(() => toWei('1.243842387924387924897423897423', 'ether')).toThrow( + Error, + ); + expect(() => toWei('8723.98234.98234', 'ether')).toThrow(Error); + }); + + it('should return the correct value', () => { + expect(toWei(1, 'wei').toString(10)).toBe('1'); + expect(toWei(1, 'kwei').toString(10)).toBe('1000'); + expect(toWei(1, 'Kwei').toString(10)).toBe('1000'); + expect(toWei(1, 'babbage').toString(10)).toBe('1000'); + expect(toWei(1, 'mwei').toString(10)).toBe('1000000'); + expect(toWei(1, 'Mwei').toString(10)).toBe('1000000'); + expect(toWei(1, 'lovelace').toString(10)).toBe('1000000'); + expect(toWei(1, 'gwei').toString(10)).toBe('1000000000'); + expect(toWei(1, 'Gwei').toString(10)).toBe('1000000000'); + expect(toWei(1, 'shannon').toString(10)).toBe('1000000000'); + expect(toWei(1, 'szabo').toString(10)).toBe('1000000000000'); + expect(toWei(1, 'finney').toString(10)).toBe('1000000000000000'); + expect(toWei(1, 'ether').toString(10)).toBe('1000000000000000000'); + expect(toWei(1, 'kether').toString(10)).toBe('1000000000000000000000'); + expect(toWei(1, 'grand').toString(10)).toBe('1000000000000000000000'); + expect(toWei(1, 'mether').toString(10)).toBe('1000000000000000000000000'); + expect(toWei(1, 'gether').toString(10)).toBe( + '1000000000000000000000000000', + ); + expect(toWei(1, 'tether').toString(10)).toBe( + '1000000000000000000000000000000', + ); + + expect(toWei(1, 'kwei').toString(10)).toBe( + toWei(1, 'femtoether').toString(10), + ); + expect(toWei(1, 'szabo').toString(10)).toBe( + toWei(1, 'microether').toString(10), + ); + expect(toWei(1, 'finney').toString(10)).toBe( + toWei(1, 'milliether').toString(10), + ); + expect(toWei(1, 'milli').toString(10)).toBe( + toWei(1, 'milliether').toString(10), + ); + expect(toWei(1, 'milli').toString(10)).toBe( + toWei(1000, 'micro').toString(10), + ); + + expect(() => { + toWei(1, 'wei1' as any); + }).toThrow(Error); + }); +}); + +describe('numberToString', () => { + it('should handle edge cases', () => { + // expect(() => numberToString(null)).toThrow(Error); + expect(() => numberToString(undefined as any)).toThrow(Error); + // expect(() => numberToString(NaN)).toThrow(Error); + expect(() => numberToString({} as any)).toThrow(Error); + expect(() => numberToString([] as any)).toThrow(Error); + expect(() => numberToString('-1sdffsdsdf')).toThrow(Error); + expect(() => numberToString('-0..-...9')).toThrow(Error); + expect(() => numberToString('fds')).toThrow(Error); + expect(() => numberToString('')).toThrow(Error); + expect(() => numberToString('#')).toThrow(Error); + expect(numberToString(55)).toBe('55'); + expect(numberToString(1)).toBe('1'); + expect(numberToString(-1)).toBe('-1'); + expect(numberToString(0)).toBe('0'); + expect(numberToString(-0)).toBe('0'); + expect(numberToString(10.1)).toBe('10.1'); + expect(numberToString(BigInt(10))).toBe('10'); + expect(numberToString(BigInt(10000))).toBe('10000'); + expect(numberToString(BigInt('-1'))).toBe('-1'); + expect(numberToString(BigInt('1'))).toBe('1'); + expect(numberToString(BigInt(0))).toBe('0'); + }); +}); + +describe('fromWei', () => { + it('should handle options', () => { + expect(fromWei(10000000, 'wei', { commify: true })).toBe('10,000,000'); + }); + + it('should return the correct value', () => { + expect(fromWei(1000000000000000000, 'wei')).toBe('1000000000000000000'); + expect(fromWei(1000000000000000000, 'kwei')).toBe('1000000000000000'); + expect(fromWei(1000000000000000000, 'mwei')).toBe('1000000000000'); + expect(fromWei(1000000000000000000, 'gwei')).toBe('1000000000'); + expect(fromWei(1000000000000000000, 'szabo')).toBe('1000000'); + expect(fromWei(1000000000000000000, 'finney')).toBe('1000'); + expect(fromWei(1000000000000000000, 'ether')).toBe('1'); + expect(fromWei(1000000000000000000, 'kether')).toBe('0.001'); + expect(fromWei(1000000000000000000, 'grand')).toBe('0.001'); + expect(fromWei(1000000000000000000, 'mether')).toBe('0.000001'); + expect(fromWei(1000000000000000000, 'gether')).toBe('0.000000001'); + expect(fromWei(1000000000000000000, 'tether')).toBe('0.000000000001'); + }); +}); + +describe('units', () => { + describe('normal functionality', () => { + it('should be the same as web3', () => { + for (let i = 0; i < 15000; i++) { + testRandomValueAgainstWeb3ToWei(false); + testRandomValueAgainstWeb3ToWei(true); + testRandomValueAgainstWeb3FromWei(false); + testRandomValueAgainstWeb3FromWei(true); + } + // Ensure we've run the test loop + expect(true).toBe(true); + }); + }); +}); diff --git a/src/unitsConversion.ts b/src/unitsConversion.ts new file mode 100644 index 000000000..b456a1fb6 --- /dev/null +++ b/src/unitsConversion.ts @@ -0,0 +1,237 @@ +/* eslint-disable operator-assignment */ +/* +Primary Attribution +Richard Moore +https://github.com/ethers-io + +Note, Richard is a god of ether gods. Follow and respect him, and use Ethers.io! +*/ + +const zero = BigInt(0); +const negative1 = BigInt(-1); + +/** + * Converts a string, number, or bigint to a bigint. + * + * @param arg - The value to convert to bigint. + * @returns The bigint representation of the input. + * @throws Error if the input type cannot be converted to bigint. + */ +function numberToBigInt(arg: string | number | bigint): bigint { + if (typeof arg === 'string') { + return BigInt(arg); + } + if (typeof arg === 'number') { + return BigInt(arg); + } + if (typeof arg === 'bigint') { + return arg; + } + + throw new Error(`Cannot convert ${typeof arg} to BigInt`); +} + +// complete ethereum unit map +export const unitMap = { + noether: '0', + wei: '1', + kwei: '1000', + Kwei: '1000', + babbage: '1000', + femtoether: '1000', + mwei: '1000000', + Mwei: '1000000', + lovelace: '1000000', + picoether: '1000000', + gwei: '1000000000', + Gwei: '1000000000', + shannon: '1000000000', + nanoether: '1000000000', + nano: '1000000000', + szabo: '1000000000000', + microether: '1000000000000', + micro: '1000000000000', + finney: '1000000000000000', + milliether: '1000000000000000', + milli: '1000000000000000', + ether: '1000000000000000000', + kether: '1000000000000000000000', + grand: '1000000000000000000000', + mether: '1000000000000000000000000', + gether: '1000000000000000000000000000', + tether: '1000000000000000000000000000000', +} as const; + +type EthereumUnit = keyof typeof unitMap; + +/** + * Returns value of unit in Wei. + * + * @param unitInput - The unit to convert to, default ether. + * @returns Value of the unit (in Wei). + * @throws Error if the unit is not correct. + */ +export function getValueOfUnit(unitInput: EthereumUnit = 'ether'): bigint { + const unit = unitInput.toLowerCase() as EthereumUnit; + const unitValue = unitMap[unit]; + + if (typeof unitValue !== 'string') { + throw new Error( + `[ethjs-unit] the unit provided ${unitInput} doesn't exists, please use the one of the following units ${JSON.stringify( + unitMap, + null, + 2, + )}`, + ); + } + + return BigInt(unitValue); +} + +/** + * Converts a number to a string. + * + * @param arg - The number to convert to a string. + * @returns The string representation of the number. + * @throws Error if the number is invalid. + */ +export function numberToString(arg: string | number | bigint) { + if (typeof arg === 'string') { + if (!arg.match(/^-?[0-9.]+$/u)) { + throw new Error( + `while converting number to string, invalid number value '${arg}', should be a number matching (^-?[0-9.]+).`, + ); + } + return arg; + } + if (typeof arg === 'number') { + return String(arg); + } + // eslint-disable-next-line valid-typeof + if (typeof arg === 'bigint') { + return arg.toString(); + } + throw new Error( + `while converting number to string, invalid number value '${String( + arg, + )}' type ${typeof arg}.`, + ); +} + +/** + * Converts a number from Wei to a string. + * + * @param weiInput - The number to convert from Wei. + * @param unit - The unit to convert to, default ether. + * @param optionsInput - The options to use for the conversion. + * @param optionsInput.pad - Whether to pad the fractional part with zeros. + * @param optionsInput.commify - Whether to add commas to separate thousands. + * @returns The string representation of the number. + * @throws Error if the number is invalid. + */ +export function fromWei( + weiInput: string | number | bigint, + unit: EthereumUnit, + optionsInput?: { pad?: boolean; commify?: boolean }, +) { + var wei = numberToBigInt(weiInput); // eslint-disable-line + var negative = wei < zero; // eslint-disable-line + const base = getValueOfUnit(unit); + const baseLength = unitMap[unit].length - 1 || 1; + const options = optionsInput ?? {}; + + if (negative) { + wei = wei * negative1; + } + + var fraction = (wei % base).toString(); // eslint-disable-line + + while (fraction.length < baseLength) { + fraction = `0${fraction}`; + } + + if (!options.pad) { + const fractionMatch = fraction.match(/^([0-9]*[1-9]|0)(0*)/u); + fraction = fractionMatch?.[1] ?? '0'; + } + + var whole = (wei / base).toString(); // eslint-disable-line + + if (options.commify) { + whole = whole.replace(/\B(?=(\d{3})+(?!\d))/gu, ','); + } + + var value = `${whole}${fraction == '0' ? '' : `.${fraction}`}`; // eslint-disable-line + + if (negative) { + value = `-${value}`; + } + + return value; +} + +/** + * Converts a number to Wei. + * + * @param etherInput - The number to convert to Wei. + * @param unit - The unit to convert to, default ether. + * @returns The number in Wei. + * @throws Error if the number is invalid. + */ +export function toWei( + etherInput: string | number | bigint, + unit: EthereumUnit, +): bigint { + var ether = numberToString(etherInput); // eslint-disable-line + const base = getValueOfUnit(unit); + const baseLength = unitMap[unit].length - 1 || 1; + + // Is it negative? + var negative = ether.substring(0, 1) === '-'; // eslint-disable-line + if (negative) { + ether = ether.substring(1); + } + + if (ether === '.') { + throw new Error( + `[ethjs-unit] while converting number ${etherInput} to wei, invalid value`, + ); + } + + // Split it into a whole and fractional part + var comps = ether.split('.'); // eslint-disable-line + if (comps.length > 2) { + throw new Error( + `[ethjs-unit] while converting number ${etherInput} to wei, too many decimal points`, + ); + } + + let whole = comps[0]; + let fraction = comps[1]; + + if (!whole) { + whole = '0'; + } + if (!fraction) { + fraction = '0'; + } + if (fraction.length > baseLength) { + throw new Error( + `[ethjs-unit] while converting number ${etherInput} to wei, too many decimal places`, + ); + } + + while (fraction.length < baseLength) { + fraction += '0'; + } + + const wholeBigInt = BigInt(whole); + const fractionBigInt = BigInt(fraction); + let wei = wholeBigInt * base + fractionBigInt; + + if (negative) { + wei = wei * negative1; + } + + return wei; +} diff --git a/yarn.lock b/yarn.lock index f4b55947d..ea7f84753 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,6 +12,13 @@ __metadata: languageName: node linkType: hard +"@adraffy/ens-normalize@npm:^1.8.8": + version: 1.11.0 + resolution: "@adraffy/ens-normalize@npm:1.11.0" + checksum: b2911269e3e0ec6396a2e5433a99e0e1f9726befc6c167994448cd0e53dbdd0be22b4835b4f619558b568ed9aa7312426b8fa6557a13999463489daa88169ee5 + languageName: node + linkType: hard + "@ampproject/remapping@npm:^2.1.0": version: 2.2.0 resolution: "@ampproject/remapping@npm:2.2.0" @@ -498,6 +505,15 @@ __metadata: languageName: node linkType: hard +"@ethereumjs/rlp@npm:^5.0.2": + version: 5.0.2 + resolution: "@ethereumjs/rlp@npm:5.0.2" + bin: + rlp: bin/rlp.cjs + checksum: b569061ddb1f4cf56a82f7a677c735ba37f9e94e2bbaf567404beb9e2da7aa1f595e72fc12a17c61f7aec67fd5448443efe542967c685a2fe0ffc435793dcbab + languageName: node + linkType: hard + "@ethereumjs/tx@npm:^4.2.0": version: 4.2.0 resolution: "@ethereumjs/tx@npm:4.2.0" @@ -1111,6 +1127,7 @@ __metadata: typedoc: ^0.23.15 typescript: ~5.0.4 uuid: ^9.0.1 + web3: ^4.16.0 languageName: unknown linkType: soft @@ -1632,6 +1649,15 @@ __metadata: languageName: node linkType: hard +"@types/ws@npm:8.5.3": + version: 8.5.3 + resolution: "@types/ws@npm:8.5.3" + dependencies: + "@types/node": "*" + checksum: 0ce46f850d41383fcdc2149bcacc86d7232fa7a233f903d2246dff86e31701a02f8566f40af5f8b56d1834779255c04ec6ec78660fe0f9b2a69cf3d71937e4ae + languageName: node + linkType: hard + "@types/yargs-parser@npm:*": version: 21.0.0 resolution: "@types/yargs-parser@npm:21.0.0" @@ -1853,6 +1879,19 @@ __metadata: languageName: node linkType: hard +"abitype@npm:0.7.1": + version: 0.7.1 + resolution: "abitype@npm:0.7.1" + peerDependencies: + typescript: ">=4.9.4" + zod: ^3 >=3.19.1 + peerDependenciesMeta: + zod: + optional: true + checksum: de0d7082d28a4835b3d8dc4d8c75e9222c95a1f9eed13d6b2381403b46f46b68ea7a281e8ba6628d259a98c54ea466ebc206eec21db6205fa1641c7393854f5e + languageName: node + linkType: hard + "acorn-jsx@npm:^5.3.2": version: 5.3.2 resolution: "acorn-jsx@npm:5.3.2" @@ -2137,10 +2176,12 @@ __metadata: languageName: node linkType: hard -"available-typed-arrays@npm:^1.0.5": - version: 1.0.5 - resolution: "available-typed-arrays@npm:1.0.5" - checksum: 20eb47b3cefd7db027b9bbb993c658abd36d4edd3fe1060e83699a03ee275b0c9b216cc076ff3f2db29073225fb70e7613987af14269ac1fe2a19803ccc97f1a +"available-typed-arrays@npm:^1.0.5, available-typed-arrays@npm:^1.0.7": + version: 1.0.7 + resolution: "available-typed-arrays@npm:1.0.7" + dependencies: + possible-typed-array-names: ^1.0.0 + checksum: 1aa3ffbfe6578276996de660848b6e95669d9a95ad149e3dd0c0cda77db6ee1dbd9d1dd723b65b6d277b882dd0c4b91a654ae9d3cf9e1254b7e93e4908d78fd3 languageName: node linkType: hard @@ -2361,16 +2402,35 @@ __metadata: languageName: node linkType: hard -"call-bind@npm:^1.0.0, call-bind@npm:^1.0.2, call-bind@npm:^1.0.7": - version: 1.0.7 - resolution: "call-bind@npm:1.0.7" +"call-bind-apply-helpers@npm:^1.0.0, call-bind-apply-helpers@npm:^1.0.1, call-bind-apply-helpers@npm:^1.0.2": + version: 1.0.2 + resolution: "call-bind-apply-helpers@npm:1.0.2" dependencies: - es-define-property: ^1.0.0 es-errors: ^1.3.0 function-bind: ^1.1.2 + checksum: b2863d74fcf2a6948221f65d95b91b4b2d90cfe8927650b506141e669f7d5de65cea191bf788838bc40d13846b7886c5bc5c84ab96c3adbcf88ad69a72fcdc6b + languageName: node + linkType: hard + +"call-bind@npm:^1.0.0, call-bind@npm:^1.0.2, call-bind@npm:^1.0.7, call-bind@npm:^1.0.8": + version: 1.0.8 + resolution: "call-bind@npm:1.0.8" + dependencies: + call-bind-apply-helpers: ^1.0.0 + es-define-property: ^1.0.0 get-intrinsic: ^1.2.4 - set-function-length: ^1.2.1 - checksum: 295c0c62b90dd6522e6db3b0ab1ce26bdf9e7404215bda13cfee25b626b5ff1a7761324d58d38b1ef1607fc65aca2d06e44d2e18d0dfc6c14b465b00d8660029 + set-function-length: ^1.2.2 + checksum: aa2899bce917a5392fd73bd32e71799c37c0b7ab454e0ed13af7f6727549091182aade8bbb7b55f304a5bc436d543241c14090fb8a3137e9875e23f444f4f5a9 + languageName: node + linkType: hard + +"call-bound@npm:^1.0.2, call-bound@npm:^1.0.3, call-bound@npm:^1.0.4": + version: 1.0.4 + resolution: "call-bound@npm:1.0.4" + dependencies: + call-bind-apply-helpers: ^1.0.2 + get-intrinsic: ^1.3.0 + checksum: 2f6399488d1c272f56306ca60ff696575e2b7f31daf23bc11574798c84d9f2759dceb0cb1f471a85b77f28962a7ac6411f51d283ea2e45319009a19b6ccab3b2 languageName: node linkType: hard @@ -2631,7 +2691,7 @@ __metadata: languageName: node linkType: hard -"crc-32@npm:^1.2.0": +"crc-32@npm:^1.2.0, crc-32@npm:^1.2.2": version: 1.2.2 resolution: "crc-32@npm:1.2.2" bin: @@ -2647,6 +2707,15 @@ __metadata: languageName: node linkType: hard +"cross-fetch@npm:^4.0.0": + version: 4.1.0 + resolution: "cross-fetch@npm:4.1.0" + dependencies: + node-fetch: ^2.7.0 + checksum: c02fa85d59f83e50dbd769ee472c9cc984060c403ee5ec8654659f61a525c1a655eef1c7a35e365c1a107b4e72d76e786718b673d1cb3c97f61d4644cb0a9f9d + languageName: node + linkType: hard + "cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3": version: 7.0.6 resolution: "cross-spawn@npm:7.0.6" @@ -2883,6 +2952,17 @@ __metadata: languageName: node linkType: hard +"dunder-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "dunder-proto@npm:1.0.1" + dependencies: + call-bind-apply-helpers: ^1.0.1 + es-errors: ^1.3.0 + gopd: ^1.2.0 + checksum: 149207e36f07bd4941921b0ca929e3a28f1da7bd6b6ff8ff7f4e2f2e460675af4576eeba359c635723dc189b64cdd4787e0255897d5b135ccc5d15cb8685fc90 + languageName: node + linkType: hard + "eastasianwidth@npm:^0.2.0": version: 0.2.0 resolution: "eastasianwidth@npm:0.2.0" @@ -2997,12 +3077,10 @@ __metadata: languageName: node linkType: hard -"es-define-property@npm:^1.0.0": - version: 1.0.0 - resolution: "es-define-property@npm:1.0.0" - dependencies: - get-intrinsic: ^1.2.4 - checksum: f66ece0a887b6dca71848fa71f70461357c0e4e7249696f81bad0a1f347eed7b31262af4a29f5d726dc026426f085483b6b90301855e647aa8e21936f07293c6 +"es-define-property@npm:^1.0.0, es-define-property@npm:^1.0.1": + version: 1.0.1 + resolution: "es-define-property@npm:1.0.1" + checksum: 0512f4e5d564021c9e3a644437b0155af2679d10d80f21adaf868e64d30efdfbd321631956f20f42d655fedb2e3a027da479fad3fa6048f768eb453a80a5f80a languageName: node linkType: hard @@ -3013,12 +3091,12 @@ __metadata: languageName: node linkType: hard -"es-object-atoms@npm:^1.0.0": - version: 1.0.0 - resolution: "es-object-atoms@npm:1.0.0" +"es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1": + version: 1.1.1 + resolution: "es-object-atoms@npm:1.1.1" dependencies: es-errors: ^1.3.0 - checksum: 26f0ff78ab93b63394e8403c353842b2272836968de4eafe97656adfb8a7c84b9099bf0fe96ed58f4a4cddc860f6e34c77f91649a58a5daa4a9c40b902744e3c + checksum: 214d3767287b12f36d3d7267ef342bbbe1e89f899cfd67040309fc65032372a8e60201410a99a1645f2f90c1912c8c49c8668066f6bdd954bcd614dda2e3da97 languageName: node linkType: hard @@ -3439,6 +3517,13 @@ __metadata: languageName: node linkType: hard +"eventemitter3@npm:^5.0.1": + version: 5.0.1 + resolution: "eventemitter3@npm:5.0.1" + checksum: 543d6c858ab699303c3c32e0f0f47fc64d360bf73c3daf0ac0b5079710e340d6fe9f15487f94e66c629f5f82cd1a8678d692f3dbb6f6fcd1190e1b97fcad36f8 + languageName: node + linkType: hard + "execa@npm:^5.0.0, execa@npm:^5.1.1": version: 5.1.1 resolution: "execa@npm:5.1.1" @@ -3631,12 +3716,12 @@ __metadata: languageName: node linkType: hard -"for-each@npm:^0.3.3": - version: 0.3.3 - resolution: "for-each@npm:0.3.3" +"for-each@npm:^0.3.3, for-each@npm:^0.3.5": + version: 0.3.5 + resolution: "for-each@npm:0.3.5" dependencies: - is-callable: ^1.1.3 - checksum: 6c48ff2bc63362319c65e2edca4a8e1e3483a2fabc72fbe7feaf8c73db94fc7861bd53bc02c8a66a0c1dd709da6b04eec42e0abdd6b40ce47305ae92a25e5d28 + is-callable: ^1.2.7 + checksum: 3c986d7e11f4381237cc98baa0a2f87eabe74719eee65ed7bed275163082b940ede19268c61d04c6260e0215983b12f8d885e3c8f9aa8c2113bf07c37051745c languageName: node linkType: hard @@ -3750,16 +3835,21 @@ __metadata: languageName: node linkType: hard -"get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.1, get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.0, get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.4": - version: 1.2.4 - resolution: "get-intrinsic@npm:1.2.4" +"get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.1, get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.0, get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.3.0": + version: 1.3.0 + resolution: "get-intrinsic@npm:1.3.0" dependencies: + call-bind-apply-helpers: ^1.0.2 + es-define-property: ^1.0.1 es-errors: ^1.3.0 + es-object-atoms: ^1.1.1 function-bind: ^1.1.2 - has-proto: ^1.0.1 - has-symbols: ^1.0.3 - hasown: ^2.0.0 - checksum: 414e3cdf2c203d1b9d7d33111df746a4512a1aa622770b361dadddf8ed0b5aeb26c560f49ca077e24bfafb0acb55ca908d1f709216ccba33ffc548ec8a79a951 + get-proto: ^1.0.1 + gopd: ^1.2.0 + has-symbols: ^1.1.0 + hasown: ^2.0.2 + math-intrinsics: ^1.1.0 + checksum: 301008e4482bb9a9cb49e132b88fee093bff373b4e6def8ba219b1e96b60158a6084f273ef5cafe832e42cd93462f4accb46a618d35fe59a2b507f2388c5b79d languageName: node linkType: hard @@ -3770,6 +3860,16 @@ __metadata: languageName: node linkType: hard +"get-proto@npm:^1.0.0, get-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "get-proto@npm:1.0.1" + dependencies: + dunder-proto: ^1.0.1 + es-object-atoms: ^1.0.0 + checksum: 4fc96afdb58ced9a67558698b91433e6b037aaa6f1493af77498d7c85b141382cf223c0e5946f334fb328ee85dfe6edd06d218eaf09556f4bc4ec6005d7f5f7b + languageName: node + linkType: hard + "get-stream@npm:^6.0.0": version: 6.0.1 resolution: "get-stream@npm:6.0.1" @@ -3944,12 +4044,10 @@ __metadata: languageName: node linkType: hard -"gopd@npm:^1.0.1": - version: 1.0.1 - resolution: "gopd@npm:1.0.1" - dependencies: - get-intrinsic: ^1.1.3 - checksum: a5ccfb8806e0917a94e0b3de2af2ea4979c1da920bc381667c260e00e7cafdbe844e2cb9c5bcfef4e5412e8bf73bab837285bc35c7ba73aaaf0134d4583393a6 +"gopd@npm:^1.0.1, gopd@npm:^1.2.0": + version: 1.2.0 + resolution: "gopd@npm:1.2.0" + checksum: cc6d8e655e360955bdccaca51a12a474268f95bb793fc3e1f2bdadb075f28bfd1fd988dab872daf77a61d78cbaf13744bc8727a17cfb1d150d76047d805375f3 languageName: node linkType: hard @@ -4018,19 +4116,19 @@ __metadata: languageName: node linkType: hard -"has-symbols@npm:^1.0.2, has-symbols@npm:^1.0.3": - version: 1.0.3 - resolution: "has-symbols@npm:1.0.3" - checksum: a054c40c631c0d5741a8285010a0777ea0c068f99ed43e5d6eb12972da223f8af553a455132fdb0801bdcfa0e0f443c0c03a68d8555aa529b3144b446c3f2410 +"has-symbols@npm:^1.0.2, has-symbols@npm:^1.0.3, has-symbols@npm:^1.1.0": + version: 1.1.0 + resolution: "has-symbols@npm:1.1.0" + checksum: b2316c7302a0e8ba3aaba215f834e96c22c86f192e7310bdf689dd0e6999510c89b00fbc5742571507cebf25764d68c988b3a0da217369a73596191ac0ce694b languageName: node linkType: hard -"has-tostringtag@npm:^1.0.0": - version: 1.0.0 - resolution: "has-tostringtag@npm:1.0.0" +"has-tostringtag@npm:^1.0.0, has-tostringtag@npm:^1.0.2": + version: 1.0.2 + resolution: "has-tostringtag@npm:1.0.2" dependencies: - has-symbols: ^1.0.2 - checksum: cc12eb28cb6ae22369ebaad3a8ab0799ed61270991be88f208d508076a1e99abe4198c965935ce85ea90b60c94ddda73693b0920b58e7ead048b4a391b502c1c + has-symbols: ^1.0.3 + checksum: 999d60bb753ad714356b2c6c87b7fb74f32463b8426e159397da4bde5bca7e598ab1073f4d8d4deafac297f2eb311484cd177af242776bf05f0d11565680468d languageName: node linkType: hard @@ -4050,7 +4148,7 @@ __metadata: languageName: node linkType: hard -"hasown@npm:^2.0.0": +"hasown@npm:^2.0.0, hasown@npm:^2.0.2": version: 2.0.2 resolution: "hasown@npm:2.0.2" dependencies: @@ -4275,6 +4373,16 @@ __metadata: languageName: node linkType: hard +"is-arguments@npm:^1.0.4": + version: 1.2.0 + resolution: "is-arguments@npm:1.2.0" + dependencies: + call-bound: ^1.0.2 + has-tostringtag: ^1.0.2 + checksum: aae9307fedfe2e5be14aebd0f48a9eeedf6b8c8f5a0b66257b965146d1e94abdc3f08e3dce3b1d908e1fa23c70039a88810ee1d753905758b9b6eebbab0bafeb + languageName: node + linkType: hard + "is-array-buffer@npm:^3.0.1, is-array-buffer@npm:^3.0.2": version: 3.0.2 resolution: "is-array-buffer@npm:3.0.2" @@ -4312,7 +4420,7 @@ __metadata: languageName: node linkType: hard -"is-callable@npm:^1.1.3, is-callable@npm:^1.1.4, is-callable@npm:^1.2.7": +"is-callable@npm:^1.1.4, is-callable@npm:^1.2.7": version: 1.2.7 resolution: "is-callable@npm:1.2.7" checksum: 61fd57d03b0d984e2ed3720fb1c7a897827ea174bd44402878e059542ea8c4aeedee0ea0985998aa5cc2736b2fa6e271c08587addb5b3959ac52cf665173d1ac @@ -4367,6 +4475,18 @@ __metadata: languageName: node linkType: hard +"is-generator-function@npm:^1.0.7": + version: 1.1.0 + resolution: "is-generator-function@npm:1.1.0" + dependencies: + call-bound: ^1.0.3 + get-proto: ^1.0.0 + has-tostringtag: ^1.0.2 + safe-regex-test: ^1.1.0 + checksum: f7f7276131bdf7e28169b86ac55a5b080012a597f9d85a0cbef6fe202a7133fa450a3b453e394870e3cb3685c5a764c64a9f12f614684b46969b1e6f297bed6b + languageName: node + linkType: hard + "is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3": version: 4.0.3 resolution: "is-glob@npm:4.0.3" @@ -4427,13 +4547,15 @@ __metadata: languageName: node linkType: hard -"is-regex@npm:^1.1.4": - version: 1.1.4 - resolution: "is-regex@npm:1.1.4" +"is-regex@npm:^1.1.4, is-regex@npm:^1.2.1": + version: 1.2.1 + resolution: "is-regex@npm:1.2.1" dependencies: - call-bind: ^1.0.2 - has-tostringtag: ^1.0.0 - checksum: 362399b33535bc8f386d96c45c9feb04cf7f8b41c182f54174c1a45c9abbbe5e31290bbad09a458583ff6bf3b2048672cdb1881b13289569a7c548370856a652 + call-bound: ^1.0.2 + gopd: ^1.2.0 + has-tostringtag: ^1.0.2 + hasown: ^2.0.2 + checksum: 99ee0b6d30ef1bb61fa4b22fae7056c6c9b3c693803c0c284ff7a8570f83075a7d38cda53b06b7996d441215c27895ea5d1af62124562e13d91b3dbec41a5e13 languageName: node linkType: hard @@ -4471,12 +4593,12 @@ __metadata: languageName: node linkType: hard -"is-typed-array@npm:^1.1.10, is-typed-array@npm:^1.1.9": - version: 1.1.12 - resolution: "is-typed-array@npm:1.1.12" +"is-typed-array@npm:^1.1.10, is-typed-array@npm:^1.1.3, is-typed-array@npm:^1.1.9": + version: 1.1.15 + resolution: "is-typed-array@npm:1.1.15" dependencies: - which-typed-array: ^1.1.11 - checksum: 4c89c4a3be07186caddadf92197b17fda663a9d259ea0d44a85f171558270d36059d1c386d34a12cba22dfade5aba497ce22778e866adc9406098c8fc4771796 + which-typed-array: ^1.1.16 + checksum: ea7cfc46c282f805d19a9ab2084fd4542fed99219ee9dbfbc26284728bd713a51eac66daa74eca00ae0a43b61322920ba334793607dc39907465913e921e0892 languageName: node linkType: hard @@ -4533,6 +4655,15 @@ __metadata: languageName: node linkType: hard +"isomorphic-ws@npm:^5.0.0": + version: 5.0.0 + resolution: "isomorphic-ws@npm:5.0.0" + peerDependencies: + ws: "*" + checksum: e20eb2aee09ba96247465fda40c6d22c1153394c0144fa34fe6609f341af4c8c564f60ea3ba762335a7a9c306809349f9b863c8beedf2beea09b299834ad5398 + languageName: node + linkType: hard + "istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0": version: 3.2.0 resolution: "istanbul-lib-coverage@npm:3.2.0" @@ -5450,6 +5581,13 @@ __metadata: languageName: node linkType: hard +"math-intrinsics@npm:^1.1.0": + version: 1.1.0 + resolution: "math-intrinsics@npm:1.1.0" + checksum: 0e513b29d120f478c85a70f49da0b8b19bc638975eca466f2eeae0071f3ad00454c621bf66e16dd435896c208e719fc91ad79bbfba4e400fe0b372e7c1c9c9a2 + languageName: node + linkType: hard + "meow@npm:^9.0.0": version: 9.0.0 resolution: "meow@npm:9.0.0" @@ -5750,6 +5888,20 @@ __metadata: languageName: node linkType: hard +"node-fetch@npm:^2.7.0": + version: 2.7.0 + resolution: "node-fetch@npm:2.7.0" + dependencies: + whatwg-url: ^5.0.0 + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + checksum: d76d2f5edb451a3f05b15115ec89fc6be39de37c6089f1b6368df03b91e1633fd379a7e01b7ab05089a25034b2023d959b47e59759cb38d88341b2459e89d6e5 + languageName: node + linkType: hard + "node-gyp@npm:^10.0.0": version: 10.1.0 resolution: "node-gyp@npm:10.1.0" @@ -6190,6 +6342,13 @@ __metadata: languageName: node linkType: hard +"possible-typed-array-names@npm:^1.0.0": + version: 1.1.0 + resolution: "possible-typed-array-names@npm:1.1.0" + checksum: cfcd4f05264eee8fd184cd4897a17890561d1d473434b43ab66ad3673d9c9128981ec01e0cb1d65a52cd6b1eebfb2eae1e53e39b2e0eca86afc823ede7a4f41b + languageName: node + linkType: hard + "postcss@npm:^8.1.10": version: 8.4.31 resolution: "postcss@npm:8.4.31" @@ -6565,14 +6724,14 @@ __metadata: languageName: node linkType: hard -"safe-regex-test@npm:^1.0.0": - version: 1.0.0 - resolution: "safe-regex-test@npm:1.0.0" +"safe-regex-test@npm:^1.0.0, safe-regex-test@npm:^1.1.0": + version: 1.1.0 + resolution: "safe-regex-test@npm:1.1.0" dependencies: - call-bind: ^1.0.2 - get-intrinsic: ^1.1.3 - is-regex: ^1.1.4 - checksum: bc566d8beb8b43c01b94e67de3f070fd2781685e835959bbbaaec91cc53381145ca91f69bd837ce6ec244817afa0a5e974fc4e40a2957f0aca68ac3add1ddd34 + call-bound: ^1.0.2 + es-errors: ^1.3.0 + is-regex: ^1.2.1 + checksum: 3c809abeb81977c9ed6c869c83aca6873ea0f3ab0f806b8edbba5582d51713f8a6e9757d24d2b4b088f563801475ea946c8e77e7713e8c65cdd02305b6caedab languageName: node linkType: hard @@ -6624,7 +6783,7 @@ __metadata: languageName: node linkType: hard -"set-function-length@npm:^1.2.1": +"set-function-length@npm:^1.2.2": version: 1.2.2 resolution: "set-function-length@npm:1.2.2" dependencies: @@ -6638,6 +6797,13 @@ __metadata: languageName: node linkType: hard +"setimmediate@npm:^1.0.5": + version: 1.0.5 + resolution: "setimmediate@npm:1.0.5" + checksum: c9a6f2c5b51a2dabdc0247db9c46460152ffc62ee139f3157440bd48e7c59425093f42719ac1d7931f054f153e2d26cf37dfeb8da17a794a58198a2705e527fd + languageName: node + linkType: hard + "shebang-command@npm:^2.0.0": version: 2.0.0 resolution: "shebang-command@npm:2.0.0" @@ -7128,6 +7294,13 @@ __metadata: languageName: node linkType: hard +"tr46@npm:~0.0.3": + version: 0.0.3 + resolution: "tr46@npm:0.0.3" + checksum: 726321c5eaf41b5002e17ffbd1fb7245999a073e8979085dacd47c4b4e8068ff5777142fc6726d6ca1fd2ff16921b48788b87225cbc57c72636f6efa8efbffe3 + languageName: node + linkType: hard + "trim-newlines@npm:^3.0.0": version: 3.0.1 resolution: "trim-newlines@npm:3.0.1" @@ -7479,6 +7652,19 @@ __metadata: languageName: node linkType: hard +"util@npm:^0.12.5": + version: 0.12.5 + resolution: "util@npm:0.12.5" + dependencies: + inherits: ^2.0.3 + is-arguments: ^1.0.4 + is-generator-function: ^1.0.7 + is-typed-array: ^1.1.3 + which-typed-array: ^1.1.2 + checksum: 705e51f0de5b446f4edec10739752ac25856541e0254ea1e7e45e5b9f9b0cb105bc4bd415736a6210edc68245a7f903bf085ffb08dd7deb8a0e847f60538a38a + languageName: node + linkType: hard + "uuid@npm:^9.0.1": version: 9.0.1 resolution: "uuid@npm:9.0.1" @@ -7546,6 +7732,290 @@ __metadata: languageName: node linkType: hard +"web3-core@npm:^4.4.0, web3-core@npm:^4.5.0, web3-core@npm:^4.6.0, web3-core@npm:^4.7.1": + version: 4.7.1 + resolution: "web3-core@npm:4.7.1" + dependencies: + web3-errors: ^1.3.1 + web3-eth-accounts: ^4.3.1 + web3-eth-iban: ^4.0.7 + web3-providers-http: ^4.2.0 + web3-providers-ipc: ^4.0.7 + web3-providers-ws: ^4.0.8 + web3-types: ^1.10.0 + web3-utils: ^4.3.3 + web3-validator: ^2.0.6 + dependenciesMeta: + web3-providers-ipc: + optional: true + checksum: 43b1c33fcbfda04f6e03625cce23286e6263f3f91aef3d0b3d133e126d2f15742093764523003076c388ce6b8336cd814d8d4e590c30fe419c3e3cc0bbe22f96 + languageName: node + linkType: hard + +"web3-errors@npm:^1.1.3, web3-errors@npm:^1.2.0, web3-errors@npm:^1.3.0, web3-errors@npm:^1.3.1": + version: 1.3.1 + resolution: "web3-errors@npm:1.3.1" + dependencies: + web3-types: ^1.10.0 + checksum: 74efaa571a26ca590a2d1eefa33e73fcb870cb465c40430620514d34624731c895718815b5190a8eb248a5f14452672d48131830f555b934ab12448ab19bd39c + languageName: node + linkType: hard + +"web3-eth-abi@npm:^4.4.1": + version: 4.4.1 + resolution: "web3-eth-abi@npm:4.4.1" + dependencies: + abitype: 0.7.1 + web3-errors: ^1.3.1 + web3-types: ^1.10.0 + web3-utils: ^4.3.3 + web3-validator: ^2.0.6 + checksum: 0519a351e60ce0d74e555af181eb15cb2585c19d7d8eb84c9f8f4f05ae8495e5a8ace62d0c239a69452ccd8be3f0bd2bccda91cc0f7f4ae97b7dfcc6a2a13b4d + languageName: node + linkType: hard + +"web3-eth-accounts@npm:^4.3.1": + version: 4.3.1 + resolution: "web3-eth-accounts@npm:4.3.1" + dependencies: + "@ethereumjs/rlp": ^4.0.1 + crc-32: ^1.2.2 + ethereum-cryptography: ^2.0.0 + web3-errors: ^1.3.1 + web3-types: ^1.10.0 + web3-utils: ^4.3.3 + web3-validator: ^2.0.6 + checksum: 0cafe490507fbef5624cc463c53fbc85b1943b595c7e7002f5e6b5dc73ec56fc22ba8e89771347c17ecf2d9acfd479825242c1c454a1d601bd3fec65c39b8b8d + languageName: node + linkType: hard + +"web3-eth-contract@npm:^4.5.0, web3-eth-contract@npm:^4.7.2": + version: 4.7.2 + resolution: "web3-eth-contract@npm:4.7.2" + dependencies: + "@ethereumjs/rlp": ^5.0.2 + web3-core: ^4.7.1 + web3-errors: ^1.3.1 + web3-eth: ^4.11.1 + web3-eth-abi: ^4.4.1 + web3-types: ^1.10.0 + web3-utils: ^4.3.3 + web3-validator: ^2.0.6 + checksum: 4ab10b3214156d2acbb860a5fef7b1ba44fc7a3d3ec0c2273e0128c088c54e57ca99d918697a7e84f2ca9bbf4238e79fca46825959f55c7d01df23ec095ca599 + languageName: node + linkType: hard + +"web3-eth-ens@npm:^4.4.0": + version: 4.4.0 + resolution: "web3-eth-ens@npm:4.4.0" + dependencies: + "@adraffy/ens-normalize": ^1.8.8 + web3-core: ^4.5.0 + web3-errors: ^1.2.0 + web3-eth: ^4.8.0 + web3-eth-contract: ^4.5.0 + web3-net: ^4.1.0 + web3-types: ^1.7.0 + web3-utils: ^4.3.0 + web3-validator: ^2.0.6 + checksum: f64dd27e679686993fbdbc9d6be22cc61afe0e63ce4bd2472b5812bec3c9c7cbdb1e1f2edfba9418694299f6603be062f8da834944f2cfe33f0b1b2497b412b5 + languageName: node + linkType: hard + +"web3-eth-iban@npm:^4.0.7": + version: 4.0.7 + resolution: "web3-eth-iban@npm:4.0.7" + dependencies: + web3-errors: ^1.1.3 + web3-types: ^1.3.0 + web3-utils: ^4.0.7 + web3-validator: ^2.0.3 + checksum: c21785ece6c69146a605f60ebdd530e8a3faeda4302cbecef4665639c297fc11edd2f0dc8a6f6ba50b3f32d2c252d106687c24e31af3d297d5365a90f9badae0 + languageName: node + linkType: hard + +"web3-eth-personal@npm:^4.1.0": + version: 4.1.0 + resolution: "web3-eth-personal@npm:4.1.0" + dependencies: + web3-core: ^4.6.0 + web3-eth: ^4.9.0 + web3-rpc-methods: ^1.3.0 + web3-types: ^1.8.0 + web3-utils: ^4.3.1 + web3-validator: ^2.0.6 + checksum: fc436e51641bdae4adc18dc41ad8a7359fbd91d3ed42416b095b4269257504e0296514c3ab67748b93a9a474c8d7e65e246eeebb7aab8a1f1a87baf64667406c + languageName: node + linkType: hard + +"web3-eth@npm:^4.11.1, web3-eth@npm:^4.8.0, web3-eth@npm:^4.9.0": + version: 4.11.1 + resolution: "web3-eth@npm:4.11.1" + dependencies: + setimmediate: ^1.0.5 + web3-core: ^4.7.1 + web3-errors: ^1.3.1 + web3-eth-abi: ^4.4.1 + web3-eth-accounts: ^4.3.1 + web3-net: ^4.1.0 + web3-providers-ws: ^4.0.8 + web3-rpc-methods: ^1.3.0 + web3-types: ^1.10.0 + web3-utils: ^4.3.3 + web3-validator: ^2.0.6 + checksum: 21b7e3e92499be72d92f594ccdffe44806231e2f50808d41b723e0a03617a2b31434d829465107cd74ac57fbf283c087d425a3423d5899ef128b90a8466a6d2b + languageName: node + linkType: hard + +"web3-net@npm:^4.1.0": + version: 4.1.0 + resolution: "web3-net@npm:4.1.0" + dependencies: + web3-core: ^4.4.0 + web3-rpc-methods: ^1.3.0 + web3-types: ^1.6.0 + web3-utils: ^4.3.0 + checksum: 8a257fbee5e73de20cf43a974be923f2d0eddcacd8bba39cc1426f97942d38c42816a3658ccf8db09920c8d99710e255c38ff9b84cdda011181c12b8c097a71d + languageName: node + linkType: hard + +"web3-providers-http@npm:^4.2.0": + version: 4.2.0 + resolution: "web3-providers-http@npm:4.2.0" + dependencies: + cross-fetch: ^4.0.0 + web3-errors: ^1.3.0 + web3-types: ^1.7.0 + web3-utils: ^4.3.1 + checksum: 8f65965979dc8a79720a52c9255acea5a564f6f6bd3d69eada60890e439bf71fd555ca3691a81543450f347652075e4a82adacb273b33e0444ec02b3ae6e34ce + languageName: node + linkType: hard + +"web3-providers-ipc@npm:^4.0.7": + version: 4.0.7 + resolution: "web3-providers-ipc@npm:4.0.7" + dependencies: + web3-errors: ^1.1.3 + web3-types: ^1.3.0 + web3-utils: ^4.0.7 + checksum: 83e734d833bd3663bc6d4a802c3eea83144a54244635d81d714913bd2f08a7463610fdb574bbbb1328c730340fea13730d4e33465fbf175d1c747170c142c7a7 + languageName: node + linkType: hard + +"web3-providers-ws@npm:^4.0.8": + version: 4.0.8 + resolution: "web3-providers-ws@npm:4.0.8" + dependencies: + "@types/ws": 8.5.3 + isomorphic-ws: ^5.0.0 + web3-errors: ^1.2.0 + web3-types: ^1.7.0 + web3-utils: ^4.3.1 + ws: ^8.17.1 + checksum: ecbc2324c4a5ae3cb8ad756cf2081b380dd12103f3fe4b451366fa29cd8b2db85f63ead97afe524420603c9afe0519797bab300d6e29b8a96eb3084494855c26 + languageName: node + linkType: hard + +"web3-rpc-methods@npm:^1.3.0": + version: 1.3.0 + resolution: "web3-rpc-methods@npm:1.3.0" + dependencies: + web3-core: ^4.4.0 + web3-types: ^1.6.0 + web3-validator: ^2.0.6 + checksum: 21673d6d2f539b0082a806bd0bf5d62882871584bf06337fc9b8399b6aeacd352c9fb19c6cae1cd25c145a60897c301fd12417848e34648bd5d5c7df7a76e095 + languageName: node + linkType: hard + +"web3-rpc-providers@npm:^1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "web3-rpc-providers@npm:1.0.0-rc.4" + dependencies: + web3-errors: ^1.3.1 + web3-providers-http: ^4.2.0 + web3-providers-ws: ^4.0.8 + web3-types: ^1.10.0 + web3-utils: ^4.3.3 + web3-validator: ^2.0.6 + checksum: bebb9cfaff5d179712af91a4da262b61ca69025234d1b35bf81487d49bab7fcb9a1327510d1ae51534a68be487263906c4d156a7e4f8b4dcda542925b2bf3411 + languageName: node + linkType: hard + +"web3-types@npm:^1.10.0, web3-types@npm:^1.3.0, web3-types@npm:^1.6.0, web3-types@npm:^1.7.0, web3-types@npm:^1.8.0": + version: 1.10.0 + resolution: "web3-types@npm:1.10.0" + checksum: a7e1a67dc0629073a55c096574cf60e4ba140a5b06558ed37b49403cf2100f973a389d2434ee0fbc510c0f874a69aca90c9c91cb98ee2c75927331f4c191c5bb + languageName: node + linkType: hard + +"web3-utils@npm:^4.0.7, web3-utils@npm:^4.3.0, web3-utils@npm:^4.3.1, web3-utils@npm:^4.3.3": + version: 4.3.3 + resolution: "web3-utils@npm:4.3.3" + dependencies: + ethereum-cryptography: ^2.0.0 + eventemitter3: ^5.0.1 + web3-errors: ^1.3.1 + web3-types: ^1.10.0 + web3-validator: ^2.0.6 + checksum: 7ba4fa6caae6e393e2ecbca7c7d36011c4a115658a4ab16a49cd52a5542f6dc2ad30766b8b8d908e1d68be800455554e8f87ae19c5e8ed8581987dc0fff55b7b + languageName: node + linkType: hard + +"web3-validator@npm:^2.0.3, web3-validator@npm:^2.0.6": + version: 2.0.6 + resolution: "web3-validator@npm:2.0.6" + dependencies: + ethereum-cryptography: ^2.0.0 + util: ^0.12.5 + web3-errors: ^1.2.0 + web3-types: ^1.6.0 + zod: ^3.21.4 + checksum: 15981ffce73cfa75c07f1ce0dbf65fe35fbdedc3ce19876e829b71a2a0e98aaf3aae90df764e6da7df3ff098d8fbf2ab37f58652fa93d7c3f8cb93b00f608c14 + languageName: node + linkType: hard + +"web3@npm:^4.16.0": + version: 4.16.0 + resolution: "web3@npm:4.16.0" + dependencies: + web3-core: ^4.7.1 + web3-errors: ^1.3.1 + web3-eth: ^4.11.1 + web3-eth-abi: ^4.4.1 + web3-eth-accounts: ^4.3.1 + web3-eth-contract: ^4.7.2 + web3-eth-ens: ^4.4.0 + web3-eth-iban: ^4.0.7 + web3-eth-personal: ^4.1.0 + web3-net: ^4.1.0 + web3-providers-http: ^4.2.0 + web3-providers-ws: ^4.0.8 + web3-rpc-methods: ^1.3.0 + web3-rpc-providers: ^1.0.0-rc.4 + web3-types: ^1.10.0 + web3-utils: ^4.3.3 + web3-validator: ^2.0.6 + checksum: 7164f1068a3c56f6a5c0560468d4aef5945a1a357d916c36f8c6f6a61f5fbd562658c4a17e620e0dad2edc32f8c1a577483849616c4f8721f137525c1c18f1a5 + languageName: node + linkType: hard + +"webidl-conversions@npm:^3.0.0": + version: 3.0.1 + resolution: "webidl-conversions@npm:3.0.1" + checksum: c92a0a6ab95314bde9c32e1d0a6dfac83b578f8fa5f21e675bc2706ed6981bc26b7eb7e6a1fab158e5ce4adf9caa4a0aee49a52505d4d13c7be545f15021b17c + languageName: node + linkType: hard + +"whatwg-url@npm:^5.0.0": + version: 5.0.0 + resolution: "whatwg-url@npm:5.0.0" + dependencies: + tr46: ~0.0.3 + webidl-conversions: ^3.0.0 + checksum: b8daed4ad3356cc4899048a15b2c143a9aed0dfae1f611ebd55073310c7b910f522ad75d727346ad64203d7e6c79ef25eafd465f4d12775ca44b90fa82ed9e2c + languageName: node + linkType: hard + "which-boxed-primitive@npm:^1.0.2": version: 1.0.2 resolution: "which-boxed-primitive@npm:1.0.2" @@ -7559,16 +8029,18 @@ __metadata: languageName: node linkType: hard -"which-typed-array@npm:^1.1.10, which-typed-array@npm:^1.1.11": - version: 1.1.11 - resolution: "which-typed-array@npm:1.1.11" +"which-typed-array@npm:^1.1.10, which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.2": + version: 1.1.19 + resolution: "which-typed-array@npm:1.1.19" dependencies: - available-typed-arrays: ^1.0.5 - call-bind: ^1.0.2 - for-each: ^0.3.3 - gopd: ^1.0.1 - has-tostringtag: ^1.0.0 - checksum: 711ffc8ef891ca6597b19539075ec3e08bb9b4c2ca1f78887e3c07a977ab91ac1421940505a197758fb5939aa9524976d0a5bbcac34d07ed6faa75cedbb17206 + available-typed-arrays: ^1.0.7 + call-bind: ^1.0.8 + call-bound: ^1.0.4 + for-each: ^0.3.5 + get-proto: ^1.0.1 + gopd: ^1.2.0 + has-tostringtag: ^1.0.2 + checksum: 162d2a07f68ea323f88ed9419861487ce5d02cb876f2cf9dd1e428d04a63133f93a54f89308f337b27cabd312ee3d027cae4a79002b2f0a85b79b9ef4c190670 languageName: node linkType: hard @@ -7663,6 +8135,21 @@ __metadata: languageName: node linkType: hard +"ws@npm:^8.17.1": + version: 8.18.3 + resolution: "ws@npm:8.18.3" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: d64ef1631227bd0c5fe21b3eb3646c9c91229402fb963d12d87b49af0a1ef757277083af23a5f85742bae1e520feddfb434cb882ea59249b15673c16dc3f36e0 + languageName: node + linkType: hard + "y18n@npm:^5.0.5": version: 5.0.8 resolution: "y18n@npm:5.0.8" @@ -7741,3 +8228,10 @@ __metadata: checksum: f77b3d8d00310def622123df93d4ee654fc6a0096182af8bd60679ddcdfb3474c56c6c7190817c84a2785648cdee9d721c0154eb45698c62176c322fb46fc700 languageName: node linkType: hard + +"zod@npm:^3.21.4": + version: 3.25.76 + resolution: "zod@npm:3.25.76" + checksum: c9a403a62b329188a5f6bd24d5d935d2bba345f7ab8151d1baa1505b5da9f227fb139354b043711490c798e91f3df75991395e40142e6510a4b16409f302b849 + languageName: node + linkType: hard From fe0174246851827bb62297d1c997c5ed1ed15c8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Such=C3=BD?= Date: Fri, 15 Aug 2025 01:14:15 +0200 Subject: [PATCH 2/6] chore: perf improvements of conversion utils --- src/unitsConversion.test.ts | 336 +++++++++++++++++++++++++++++++++++- src/unitsConversion.ts | 84 +++++++-- 2 files changed, 402 insertions(+), 18 deletions(-) diff --git a/src/unitsConversion.test.ts b/src/unitsConversion.test.ts index c97e58ded..589131ec4 100644 --- a/src/unitsConversion.test.ts +++ b/src/unitsConversion.test.ts @@ -4,6 +4,9 @@ import { utils as web3Utils } from 'web3'; import { toWei, fromWei, numberToString, unitMap } from './unitsConversion'; +// Import the internal function for testing (note: this would normally be exported for testing) +// For now we'll test it indirectly through the public functions + const totalTypes = Object.keys(unitMap).length; /** @@ -39,8 +42,6 @@ function testRandomValueAgainstWeb3FromWei(negative: boolean) { const unitsValue = fromWei(stringTestValue, randomunitsType); const web3Value = web3Utils.fromWei(stringTestValue, randomunitsType); - // it(`fromWei should work like web3 rounded val ${unitsValue.substr(0, web3Value.length - 1)} should equal ${web3Value.substr(0, web3Value.length - 1)} for unit type ${randomunitsType}`, () => { - // Skip test cases where web3 has a formatting bug that makes the value unparseable // Web3 formats negative decimals incorrectly as "0.000-123" instead of "-0.000123" if (web3Value.includes('-') && !web3Value.startsWith('-')) { @@ -72,6 +73,16 @@ describe('getValueOfUnit', () => { } expect(invalidFromWei).toThrow(Error); }); + + it('should handle optimized unit lookups correctly', () => { + // Test that invalid units throw errors with optimized lookups + expect(() => toWei(BigInt(1), 'invalidunit' as any)).toThrow(Error); + expect(() => fromWei(BigInt(1000), 'invalidunit' as any)).toThrow(Error); + + // Test case insensitive lookups work + expect(() => toWei(1, 'ETHER' as any)).not.toThrow(); + expect(() => fromWei(1000000000000000000, 'GWEI' as any)).not.toThrow(); + }); }); describe('toWei', () => { @@ -86,6 +97,96 @@ describe('toWei', () => { expect(() => toWei('8723.98234.98234', 'ether')).toThrow(Error); }); + it('should handle BigInt inputs with fast path optimizations', () => { + // Fast path: BigInt + 'wei' unit (should return input directly) + expect(toWei(BigInt(123), 'wei')).toBe(BigInt(123)); + expect(toWei(BigInt(0), 'wei')).toBe(BigInt(0)); + expect(toWei(BigInt(-456), 'wei')).toBe(BigInt(-456)); + + // Fast path: BigInt + other units (should multiply by base) + expect(toWei(BigInt(1), 'ether')).toBe(BigInt('1000000000000000000')); + expect(toWei(BigInt(2), 'gwei')).toBe(BigInt('2000000000')); + expect(toWei(BigInt(5), 'kwei')).toBe(BigInt('5000')); + expect(toWei(BigInt(-1), 'ether')).toBe(BigInt('-1000000000000000000')); + + // Test case sensitivity with BigInt (should work with optimized lookup) + expect(toWei(BigInt(1), 'Gwei')).toBe(BigInt('1000000000')); + expect(toWei(BigInt(1), 'ETHER' as any)).toBe( + BigInt('1000000000000000000'), + ); + }); + + it('should handle large BigInt values', () => { + const largeBigInt = BigInt('999999999999999999999999999999'); + expect(toWei(largeBigInt, 'wei')).toBe(largeBigInt); + expect(toWei(BigInt(1000), 'tether')).toBe( + BigInt('1000000000000000000000000000000000'), + ); + }); + + it('should handle fractional input edge cases', () => { + // Empty whole part (leading decimal) + expect(toWei('.5', 'ether')).toBe(BigInt('500000000000000000')); + expect(toWei('.123', 'ether')).toBe(BigInt('123000000000000000')); + expect(toWei('.000000000000000001', 'ether')).toBe(BigInt('1')); + + // Empty fractional part (trailing decimal) + expect(toWei('5.', 'ether')).toBe(BigInt('5000000000000000000')); + expect(toWei('123.', 'gwei')).toBe(BigInt('123000000000')); + + // Maximum decimal places for different units + expect(toWei('1.000000000000000000', 'ether')).toBe( + BigInt('1000000000000000000'), + ); // 18 decimals for ether + expect(toWei('1.000000000', 'gwei')).toBe(BigInt('1000000000')); // 9 decimals for gwei + expect(toWei('1.000', 'kwei')).toBe(BigInt('1000')); // 3 decimals for kwei + + // Negative fractional values + expect(toWei('-.5', 'ether')).toBe(BigInt('-500000000000000000')); + expect(toWei('-0.123', 'ether')).toBe(BigInt('-123000000000000000')); + expect(toWei('-5.', 'ether')).toBe(BigInt('-5000000000000000000')); + + // Edge case: just a decimal point should throw + expect(() => toWei('.', 'ether')).toThrow(Error); + expect(() => toWei('-.', 'ether')).toThrow(Error); + }); + + it('should handle comprehensive negative value scenarios', () => { + // Negative integers + expect(toWei(-1, 'ether')).toBe(BigInt('-1000000000000000000')); + expect(toWei('-1', 'ether')).toBe(BigInt('-1000000000000000000')); + + // Negative zero handling + expect(toWei(-0, 'ether')).toBe(BigInt('0')); + expect(toWei('-0', 'ether')).toBe(BigInt('0')); + expect(toWei('-0.0', 'ether')).toBe(BigInt('0')); + + // Negative fractional values + expect(toWei(-1.5, 'ether')).toBe(BigInt('-1500000000000000000')); + expect(toWei('-1.5', 'ether')).toBe(BigInt('-1500000000000000000')); + + // Negative BigInt values (should use fast path) + expect(toWei(BigInt(-123), 'wei')).toBe(BigInt(-123)); + expect(toWei(BigInt(-456), 'gwei')).toBe(BigInt('-456000000000')); + }); + + it('should handle decimal precision edge cases', () => { + // Test maximum precision for each major unit type + const maxEtherDecimals = '0.123456789012345678'; // 18 decimal places + expect(toWei(maxEtherDecimals, 'ether')).toBe(BigInt('123456789012345678')); + + const maxGweiDecimals = '0.123456789'; // 9 decimal places + expect(toWei(maxGweiDecimals, 'gwei')).toBe(BigInt('123456789')); + + // Too many decimal places should throw + expect(() => toWei('1.1234567890123456789', 'ether')).toThrow(Error); // 19 decimals + expect(() => toWei('1.1234567890', 'gwei')).toThrow(Error); // 10 decimals + + // Multiple decimal points should throw + expect(() => toWei('1.2.3', 'ether')).toThrow(Error); + expect(() => toWei('1..2', 'ether')).toThrow(Error); + }); + it('should return the correct value', () => { expect(toWei(1, 'wei').toString(10)).toBe('1'); expect(toWei(1, 'kwei').toString(10)).toBe('1000'); @@ -156,6 +257,31 @@ describe('numberToString', () => { expect(numberToString(BigInt('1'))).toBe('1'); expect(numberToString(BigInt(0))).toBe('0'); }); + + it('should handle regex edge cases for string validation', () => { + // Valid patterns that should pass (based on regex /^-?[0-9.]+$/u) + expect(numberToString('123')).toBe('123'); + expect(numberToString('-123')).toBe('-123'); + expect(numberToString('123.456')).toBe('123.456'); + expect(numberToString('-123.456')).toBe('-123.456'); + expect(numberToString('0')).toBe('0'); + expect(numberToString('0.0')).toBe('0.0'); + expect(numberToString('-0')).toBe('-0'); + expect(numberToString('123.')).toBe('123.'); // Trailing dot is actually valid per regex + expect(numberToString('.123')).toBe('.123'); // Leading dot is valid per regex + expect(numberToString('12.34.56')).toBe('12.34.56'); // Multiple dots are valid per regex + + // Invalid patterns that should throw + expect(() => numberToString('abc')).toThrow(Error); + expect(() => numberToString('123abc')).toThrow(Error); + expect(() => numberToString('.123abc')).toThrow(Error); + expect(() => numberToString('--123')).toThrow(Error); // Double negative + expect(() => numberToString('123-')).toThrow(Error); // Trailing negative + expect(() => numberToString(' 123 ')).toThrow(Error); // Whitespace + expect(() => numberToString('1e10')).toThrow(Error); // Scientific notation + expect(() => numberToString('123a')).toThrow(Error); // Letters + expect(() => numberToString('a123')).toThrow(Error); // Letters at start + }); }); describe('fromWei', () => { @@ -163,6 +289,109 @@ describe('fromWei', () => { expect(fromWei(10000000, 'wei', { commify: true })).toBe('10,000,000'); }); + it('should handle BigInt inputs with optimized lookups', () => { + // Test BigInt inputs with various units + expect(fromWei(BigInt('1000000000000000000'), 'ether')).toBe('1'); + expect(fromWei(BigInt('2000000000'), 'gwei')).toBe('2'); + expect(fromWei(BigInt('5000'), 'kwei')).toBe('5'); + expect(fromWei(BigInt(0), 'ether')).toBe('0'); + + // Test negative BigInt values + expect(fromWei(BigInt('-1000000000000000000'), 'ether')).toBe('-1'); + expect(fromWei(BigInt('-5000000000'), 'gwei')).toBe('-5'); + + // Test case sensitivity with BigInt + expect(fromWei(BigInt('1000000000'), 'Gwei')).toBe('1'); + expect(fromWei(BigInt('1000000000000000000'), 'ETHER' as any)).toBe('1'); + + // Test large BigInt values + expect(fromWei(BigInt('999000000000000000000'), 'ether')).toBe('999'); + expect( + fromWei(BigInt('1000000000000000000000000000000000'), 'tether'), + ).toBe('1000'); + }); + + it('should handle BigInt with padding and commify options', () => { + // Test pad option with BigInt + expect(fromWei(BigInt('1500000000000000000'), 'ether', { pad: true })).toBe( + '1.500000000000000000', + ); + expect( + fromWei(BigInt('1500000000000000000'), 'ether', { pad: false }), + ).toBe('1.5'); + + // Test commify option with BigInt + expect( + fromWei(BigInt('1000000000000000000000'), 'wei', { commify: true }), + ).toBe('1,000,000,000,000,000,000,000'); + expect( + fromWei(BigInt('123456789000000000000000'), 'ether', { commify: true }), + ).toBe('123,456.789'); + }); + + it('should handle fractional padding edge cases', () => { + // Test different padding scenarios + expect(fromWei('1500000000000000000', 'ether', { pad: true })).toBe( + '1.500000000000000000', + ); + expect(fromWei('1500000000000000000', 'ether', { pad: false })).toBe('1.5'); + expect(fromWei('1500000000000000000', 'ether')).toBe('1.5'); // Default is no pad + + // Test zero fractional parts + expect(fromWei('1000000000000000000', 'ether', { pad: true })).toBe( + '1.000000000000000000', + ); + expect(fromWei('1000000000000000000', 'ether', { pad: false })).toBe('1'); + expect(fromWei('1000000000000000000', 'ether')).toBe('1'); // Default + + // Test very small fractions + expect(fromWei('1', 'ether', { pad: true })).toBe('0.000000000000000001'); + expect(fromWei('1', 'ether', { pad: false })).toBe('0.000000000000000001'); + expect(fromWei('1', 'ether')).toBe('0.000000000000000001'); + + // Test trailing zeros removal + expect(fromWei('1230000000000000000', 'ether', { pad: false })).toBe( + '1.23', + ); + expect(fromWei('1230000000000000000', 'ether', { pad: true })).toBe( + '1.230000000000000000', + ); + }); + + it('should handle negative values with various formatting', () => { + // Negative values with padding + expect(fromWei('-1500000000000000000', 'ether', { pad: true })).toBe( + '-1.500000000000000000', + ); + expect(fromWei('-1500000000000000000', 'ether', { pad: false })).toBe( + '-1.5', + ); + + // Negative values with commify + expect(fromWei('-1000000000000000000000', 'wei', { commify: true })).toBe( + '-1,000,000,000,000,000,000,000', + ); + + // Negative zero (special case) + expect(fromWei('-0', 'ether')).toBe('0'); + expect(fromWei(BigInt(-0), 'ether')).toBe('0'); + }); + + it('should handle very large and very small values', () => { + // Very large values + const largeWei = '999999999999999999999999999999'; + expect(fromWei(largeWei, 'wei')).toBe(largeWei); + expect(fromWei(largeWei, 'ether')).toBe('999999999999.999999999999999999'); + + // Very small values + expect(fromWei('1', 'tether')).toBe('0.000000000000000000000000000001'); + expect(fromWei('999', 'tether')).toBe('0.000000000000000000000000000999'); + + // Edge case: zero + expect(fromWei('0', 'ether')).toBe('0'); + expect(fromWei(BigInt(0), 'ether')).toBe('0'); + }); + it('should return the correct value', () => { expect(fromWei(1000000000000000000, 'wei')).toBe('1000000000000000000'); expect(fromWei(1000000000000000000, 'kwei')).toBe('1000000000000000'); @@ -192,4 +421,107 @@ describe('units', () => { expect(true).toBe(true); }); }); + + describe('performance optimizations', () => { + it('should handle mixed input types efficiently', () => { + // Test that all optimizations work together without breaking functionality + const testCases = [ + { input: BigInt(1), unit: 'wei', expected: BigInt(1) }, + { + input: BigInt(1), + unit: 'ether', + expected: BigInt('1000000000000000000'), + }, + { input: '1', unit: 'ether', expected: BigInt('1000000000000000000') }, + { input: 1, unit: 'ether', expected: BigInt('1000000000000000000') }, + { input: BigInt(-1), unit: 'gwei', expected: BigInt('-1000000000') }, + ]; + + testCases.forEach(({ input, unit, expected }) => { + expect(toWei(input, unit as any)).toBe(expected); + }); + }); + + it('should handle all unit types with BigInt inputs', () => { + const units = Object.keys(unitMap) as (keyof typeof unitMap)[]; + + units.forEach((unit) => { + // Skip noether as it's a special case (base = 0) + if (unit === 'noether') { + // eslint-disable-next-line jest/no-conditional-expect + expect(toWei(BigInt(1), unit)).toBe(BigInt(0)); + // eslint-disable-next-line jest/no-conditional-expect + expect(fromWei(BigInt(1000), unit)).toBe('0'); + return; + } + + // Test that BigInt conversion works for all units + const result = toWei(BigInt(1), unit); + expect(typeof result).toBe('bigint'); + expect(result).toBeGreaterThanOrEqual(BigInt(0)); + + // Test round trip conversion + const backToWei = fromWei(result, unit); + expect(parseFloat(backToWei)).toBe(1); + }); + }); + + it('should maintain precision with large numbers', () => { + // Test that optimizations don't lose precision + const largeValue = BigInt('999999999999999999999999999999'); + expect(toWei(largeValue, 'wei')).toBe(largeValue); + + const largeEther = BigInt('999999999999999999999999999999'); + const largeWei = toWei(largeEther, 'ether'); + expect(fromWei(largeWei, 'ether')).toBe(largeEther.toString()); + }); + + it('should handle boundary conditions and edge cases', () => { + // Test switching between fast and slow paths + expect(toWei(BigInt(0), 'wei')).toBe(BigInt(0)); // Fast path + expect(toWei('0', 'wei')).toBe(BigInt(0)); // Slow path + expect(toWei(0, 'wei')).toBe(BigInt(0)); // Slow path + + // Test case sensitivity extensively + const testUnits = [ + 'wei', + 'Wei', + 'WEI', + 'gwei', + 'Gwei', + 'GWEI', + 'ether', + 'Ether', + 'ETHER', + ]; + testUnits.forEach((unit) => { + expect(() => toWei(BigInt(1), unit as any)).not.toThrow(); + expect(() => fromWei(BigInt(1000), unit as any)).not.toThrow(); + }); + + // Test that optimized paths produce identical results to original paths + const testValues = [BigInt(1), '1', 1]; + const testUnitsForComparison = ['wei', 'gwei', 'ether']; + + testUnitsForComparison.forEach((unit) => { + const results = testValues.map((value) => toWei(value, unit as any)); + // All results should be identical + expect(results[0]).toBe(results[1]); + expect(results[1]).toBe(results[2]); + }); + }); + + it('should handle internal function edge cases', () => { + // Test numberToBigInt indirectly through toWei with various input types + expect(typeof toWei(BigInt(123), 'wei')).toBe('bigint'); + expect(typeof toWei('123', 'wei')).toBe('bigint'); + expect(typeof toWei(123, 'wei')).toBe('bigint'); + + // Test that invalid types would throw (tested through public API) + expect(() => toWei({} as any, 'wei')).toThrow(Error); + expect(() => toWei([] as any, 'wei')).toThrow(Error); + expect(() => toWei(null as any, 'wei')).toThrow(Error); + expect(() => toWei(undefined as any, 'wei')).toThrow(Error); + }); + }); }); diff --git a/src/unitsConversion.ts b/src/unitsConversion.ts index b456a1fb6..325bc8558 100644 --- a/src/unitsConversion.ts +++ b/src/unitsConversion.ts @@ -62,6 +62,19 @@ export const unitMap = { tether: '1000000000000000000000000000000', } as const; +// Pre-computed unit values as BigInt for performance +const unitMapBigInt = Object.fromEntries( + Object.entries(unitMap).map(([key, value]) => [key, BigInt(value)]), +) as Record; + +const unitLengths = Object.fromEntries( + Object.entries(unitMap).map(([key, value]) => [key, value.length - 1 || 1]), +) as Record; + +const NUMBER_REGEX = /^-?[0-9.]+$/u; +const FRACTION_REGEX = /^([0-9]*[1-9]|0)(0*)/u; +const COMMIFY_REGEX = /\B(?=(\d{3})+(?!\d))/gu; + type EthereumUnit = keyof typeof unitMap; /** @@ -73,9 +86,9 @@ type EthereumUnit = keyof typeof unitMap; */ export function getValueOfUnit(unitInput: EthereumUnit = 'ether'): bigint { const unit = unitInput.toLowerCase() as EthereumUnit; - const unitValue = unitMap[unit]; + const unitValue = unitMapBigInt[unit]; - if (typeof unitValue !== 'string') { + if (unitValue === undefined) { throw new Error( `[ethjs-unit] the unit provided ${unitInput} doesn't exists, please use the one of the following units ${JSON.stringify( unitMap, @@ -85,7 +98,7 @@ export function getValueOfUnit(unitInput: EthereumUnit = 'ether'): bigint { ); } - return BigInt(unitValue); + return unitValue; } /** @@ -97,7 +110,7 @@ export function getValueOfUnit(unitInput: EthereumUnit = 'ether'): bigint { */ export function numberToString(arg: string | number | bigint) { if (typeof arg === 'string') { - if (!arg.match(/^-?[0-9.]+$/u)) { + if (!NUMBER_REGEX.test(arg)) { throw new Error( `while converting number to string, invalid number value '${arg}', should be a number matching (^-?[0-9.]+).`, ); @@ -136,29 +149,43 @@ export function fromWei( ) { var wei = numberToBigInt(weiInput); // eslint-disable-line var negative = wei < zero; // eslint-disable-line - const base = getValueOfUnit(unit); - const baseLength = unitMap[unit].length - 1 || 1; + const unitLower = unit.toLowerCase() as EthereumUnit; + const base = unitMapBigInt[unitLower]; + const baseLength = unitLengths[unitLower]; const options = optionsInput ?? {}; + if (base === undefined) { + throw new Error( + `[ethjs-unit] the unit provided ${unit} doesn't exists, please use the one of the following units ${JSON.stringify( + unitMap, + null, + 2, + )}`, + ); + } + + // Handle special case of noether (base = 0) + if (base === zero) { + return negative ? '-0' : '0'; + } + if (negative) { wei = wei * negative1; } var fraction = (wei % base).toString(); // eslint-disable-line - while (fraction.length < baseLength) { - fraction = `0${fraction}`; - } + fraction = fraction.padStart(baseLength, '0'); if (!options.pad) { - const fractionMatch = fraction.match(/^([0-9]*[1-9]|0)(0*)/u); + const fractionMatch = fraction.match(FRACTION_REGEX); fraction = fractionMatch?.[1] ?? '0'; } var whole = (wei / base).toString(); // eslint-disable-line if (options.commify) { - whole = whole.replace(/\B(?=(\d{3})+(?!\d))/gu, ','); + whole = whole.replace(COMMIFY_REGEX, ','); } var value = `${whole}${fraction == '0' ? '' : `.${fraction}`}`; // eslint-disable-line @@ -182,9 +209,36 @@ export function toWei( etherInput: string | number | bigint, unit: EthereumUnit, ): bigint { + const unitLower = unit.toLowerCase() as EthereumUnit; + const base = unitMapBigInt[unitLower]; + const baseLength = unitLengths[unitLower]; + + if (base === undefined) { + throw new Error( + `[ethjs-unit] the unit provided ${unit} doesn't exists, please use the one of the following units ${JSON.stringify( + unitMap, + null, + 2, + )}`, + ); + } + + // Handle special case of noether (base = 0) + if (base === zero) { + return zero; + } + + // Fast path for bigint inputs when unit is wei (no conversion needed) + if (typeof etherInput === 'bigint' && unitLower === 'wei') { + return etherInput; + } + + // Fast path for bigint inputs with whole units (no fractional part) + if (typeof etherInput === 'bigint') { + return etherInput * base; + } + var ether = numberToString(etherInput); // eslint-disable-line - const base = getValueOfUnit(unit); - const baseLength = unitMap[unit].length - 1 || 1; // Is it negative? var negative = ether.substring(0, 1) === '-'; // eslint-disable-line @@ -221,9 +275,7 @@ export function toWei( ); } - while (fraction.length < baseLength) { - fraction += '0'; - } + fraction = fraction.padEnd(baseLength, '0'); const wholeBigInt = BigInt(whole); const fractionBigInt = BigInt(fraction); From 87c0939aae1d05f5783dceec30cdf29317f9bda4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Such=C3=BD?= Date: Wed, 20 Aug 2025 20:55:13 +0200 Subject: [PATCH 3/6] fix: add missing exports to tests --- src/index.test.ts | 5 +++++ src/node.test.ts | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/index.test.ts b/src/index.test.ts index 1db2b4793..428dd4bee 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -90,12 +90,14 @@ describe('index', () => { "createProjectLogger", "definePattern", "exactOptional", + "fromWei", "getChecksumAddress", "getErrorMessage", "getJsonRpcIdValidator", "getJsonSize", "getKnownPropertyNames", "getSafeJson", + "getValueOfUnit", "gtRange", "gtVersion", "hasProperty", @@ -141,6 +143,7 @@ describe('index', () => { "jsonrpc2", "numberToBytes", "numberToHex", + "numberToString", "object", "parseCaipAccountId", "parseCaipAssetId", @@ -155,6 +158,8 @@ describe('index', () => { "toCaipAssetId", "toCaipAssetType", "toCaipChainId", + "toWei", + "unitMap", "valueToBytes", "wrapError", ] diff --git a/src/node.test.ts b/src/node.test.ts index 4e1e8727e..9879912f6 100644 --- a/src/node.test.ts +++ b/src/node.test.ts @@ -95,12 +95,14 @@ describe('node', () => { "exactOptional", "fileExists", "forceRemove", + "fromWei", "getChecksumAddress", "getErrorMessage", "getJsonRpcIdValidator", "getJsonSize", "getKnownPropertyNames", "getSafeJson", + "getValueOfUnit", "gtRange", "gtVersion", "hasProperty", @@ -146,6 +148,7 @@ describe('node', () => { "jsonrpc2", "numberToBytes", "numberToHex", + "numberToString", "object", "parseCaipAccountId", "parseCaipAssetId", @@ -162,6 +165,8 @@ describe('node', () => { "toCaipAssetId", "toCaipAssetType", "toCaipChainId", + "toWei", + "unitMap", "valueToBytes", "wrapError", "writeFile", From 777c69942dfdce53854053c39e1ffd7c42df796e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Such=C3=BD?= Date: Wed, 20 Aug 2025 21:44:54 +0200 Subject: [PATCH 4/6] fix: add tests to satisfy coverage --- src/index.ts | 8 +- src/unitsConversion.test.ts | 200 ++++++++++++++++++++++++++++++++++-- src/unitsConversion.ts | 3 +- 3 files changed, 200 insertions(+), 11 deletions(-) diff --git a/src/index.ts b/src/index.ts index abeac3f8c..f881536aa 100644 --- a/src/index.ts +++ b/src/index.ts @@ -36,4 +36,10 @@ export * from './superstruct'; export * from './time'; export * from './transaction-types'; export * from './versions'; -export * from './unitsConversion'; +export { + toWei, + fromWei, + numberToString, + getValueOfUnit, + unitMap, +} from './unitsConversion'; diff --git a/src/unitsConversion.test.ts b/src/unitsConversion.test.ts index 589131ec4..46b64a0a1 100644 --- a/src/unitsConversion.test.ts +++ b/src/unitsConversion.test.ts @@ -2,7 +2,14 @@ // eslint-disable-next-line import/no-extraneous-dependencies import { utils as web3Utils } from 'web3'; -import { toWei, fromWei, numberToString, unitMap } from './unitsConversion'; +import { + toWei, + fromWei, + numberToString, + numberToBigInt, + getValueOfUnit, + unitMap, +} from './unitsConversion'; // Import the internal function for testing (note: this would normally be exported for testing) // For now we'll test it indirectly through the public functions @@ -64,14 +71,72 @@ function testRandomValueAgainstWeb3FromWei(negative: boolean) { } describe('getValueOfUnit', () => { - it('should throw when undefined or not string', () => { - /** - * Helper function to test invalid unit. - */ - function invalidFromWei() { - fromWei(1000000000000000000, 'something' as any); - } - expect(invalidFromWei).toThrow(Error); + it('should return correct values for all units', () => { + expect(getValueOfUnit('noether')).toBe(BigInt('0')); + expect(getValueOfUnit('wei')).toBe(BigInt('1')); + expect(getValueOfUnit('kwei')).toBe(BigInt('1000')); + expect(getValueOfUnit('Kwei')).toBe(BigInt('1000')); + expect(getValueOfUnit('babbage')).toBe(BigInt('1000')); + expect(getValueOfUnit('femtoether')).toBe(BigInt('1000')); + expect(getValueOfUnit('mwei')).toBe(BigInt('1000000')); + expect(getValueOfUnit('Mwei')).toBe(BigInt('1000000')); + expect(getValueOfUnit('lovelace')).toBe(BigInt('1000000')); + expect(getValueOfUnit('picoether')).toBe(BigInt('1000000')); + expect(getValueOfUnit('gwei')).toBe(BigInt('1000000000')); + expect(getValueOfUnit('Gwei')).toBe(BigInt('1000000000')); + expect(getValueOfUnit('shannon')).toBe(BigInt('1000000000')); + expect(getValueOfUnit('nanoether')).toBe(BigInt('1000000000')); + expect(getValueOfUnit('nano')).toBe(BigInt('1000000000')); + expect(getValueOfUnit('szabo')).toBe(BigInt('1000000000000')); + expect(getValueOfUnit('microether')).toBe(BigInt('1000000000000')); + expect(getValueOfUnit('micro')).toBe(BigInt('1000000000000')); + expect(getValueOfUnit('finney')).toBe(BigInt('1000000000000000')); + expect(getValueOfUnit('milliether')).toBe(BigInt('1000000000000000')); + expect(getValueOfUnit('milli')).toBe(BigInt('1000000000000000')); + expect(getValueOfUnit('ether')).toBe(BigInt('1000000000000000000')); + expect(getValueOfUnit('kether')).toBe(BigInt('1000000000000000000000')); + expect(getValueOfUnit('grand')).toBe(BigInt('1000000000000000000000')); + expect(getValueOfUnit('mether')).toBe(BigInt('1000000000000000000000000')); + expect(getValueOfUnit('gether')).toBe( + BigInt('1000000000000000000000000000'), + ); + expect(getValueOfUnit('tether')).toBe( + BigInt('1000000000000000000000000000000'), + ); + }); + + it('should use ether as default unit', () => { + expect(getValueOfUnit()).toBe(BigInt('1000000000000000000')); + expect(getValueOfUnit()).toBe(getValueOfUnit('ether')); + }); + + it('should handle case insensitive input', () => { + expect(getValueOfUnit('ETHER' as any)).toBe(BigInt('1000000000000000000')); + expect(getValueOfUnit('Ether' as any)).toBe(BigInt('1000000000000000000')); + expect(getValueOfUnit('GWEI' as any)).toBe(BigInt('1000000000')); + expect(getValueOfUnit('Gwei')).toBe(BigInt('1000000000')); + }); + + it('should throw error for invalid units', () => { + expect(() => getValueOfUnit('invalidunit' as any)).toThrow( + "[ethjs-unit] the unit provided invalidunit doesn't exists", + ); + expect(() => getValueOfUnit('' as any)).toThrow( + "[ethjs-unit] the unit provided doesn't exists", + ); + }); + + it('should handle edge cases', () => { + // Test noether (special case with value 0) + expect(getValueOfUnit('noether')).toBe(BigInt(0)); + + // Test that all units from unitMap are supported + Object.keys(unitMap).forEach((unit) => { + expect(() => getValueOfUnit(unit as any)).not.toThrow(); + expect(getValueOfUnit(unit as any)).toBe( + BigInt(unitMap[unit as keyof typeof unitMap]), + ); + }); }); it('should handle optimized unit lookups correctly', () => { @@ -289,6 +354,60 @@ describe('fromWei', () => { expect(fromWei(10000000, 'wei', { commify: true })).toBe('10,000,000'); }); + it('should handle pad option true', () => { + expect(fromWei('1500000000000000000', 'ether', { pad: true })).toBe( + '1.500000000000000000', + ); + }); + + it('should handle commify option with large numbers', () => { + expect(fromWei('123456789000000000000000', 'wei', { commify: true })).toBe( + '123,456,789,000,000,000,000,000', + ); + }); + + it('should handle combined pad and commify options', () => { + expect( + fromWei('1234567890123456789000', 'ether', { pad: true, commify: true }), + ).toBe('1,234.567890123456789000'); + }); + + it('should handle options with different units', () => { + expect(fromWei('1234567890', 'gwei', { pad: true })).toBe('1.234567890'); + }); + + it('should handle zero values with padding', () => { + expect(fromWei('0', 'ether', { pad: true })).toBe('0.000000000000000000'); + }); + + it('should handle noether special case (base = 0)', () => { + // Test positive values with noether always return '0' + expect(fromWei(0, 'noether')).toBe('0'); + expect(fromWei(1, 'noether')).toBe('0'); + expect(fromWei(100, 'noether')).toBe('0'); + expect(fromWei(999999999, 'noether')).toBe('0'); + expect(fromWei('0', 'noether')).toBe('0'); + expect(fromWei('123', 'noether')).toBe('0'); + expect(fromWei('999999999999999999999', 'noether')).toBe('0'); + expect(fromWei(BigInt(0), 'noether')).toBe('0'); + expect(fromWei(BigInt(456), 'noether')).toBe('0'); + expect(fromWei(BigInt('999999999999999999999'), 'noether')).toBe('0'); + + // Test negative values with noether always return '-0' + expect(fromWei(-1, 'noether')).toBe('-0'); + expect(fromWei(-100, 'noether')).toBe('-0'); + expect(fromWei(-999999999, 'noether')).toBe('-0'); + expect(fromWei('-123', 'noether')).toBe('-0'); + expect(fromWei('-999999999999999999999', 'noether')).toBe('-0'); + expect(fromWei(BigInt(-456), 'noether')).toBe('-0'); + expect(fromWei(BigInt('-999999999999999999999'), 'noether')).toBe('-0'); + + // Test edge case: negative zero should return '0', not '-0' + expect(fromWei(-0, 'noether')).toBe('0'); + expect(fromWei('-0', 'noether')).toBe('0'); + expect(fromWei(BigInt(-0), 'noether')).toBe('0'); + }); + it('should handle BigInt inputs with optimized lookups', () => { // Test BigInt inputs with various units expect(fromWei(BigInt('1000000000000000000'), 'ether')).toBe('1'); @@ -408,6 +527,69 @@ describe('fromWei', () => { }); }); +describe('numberToBigInt', () => { + it('should convert string/numbers to BigInt', () => { + expect(numberToBigInt('123')).toBe(BigInt(123)); + expect(numberToBigInt('0')).toBe(BigInt(0)); + expect(numberToBigInt('-456')).toBe(BigInt(-456)); + expect(numberToBigInt('999999999999999999999')).toBe( + BigInt('999999999999999999999'), + ); + expect(numberToBigInt(123)).toBe(BigInt(123)); + expect(numberToBigInt(0)).toBe(BigInt(0)); + expect(numberToBigInt(-456)).toBe(BigInt(-456)); + expect(numberToBigInt(42.0)).toBe(BigInt(42)); + // special cases + expect(numberToBigInt('')).toBe(BigInt(0)); + expect(numberToBigInt(' 123 ')).toBe(BigInt(123)); + }); + + it('should return BigInt inputs unchanged', () => { + expect(numberToBigInt(BigInt(123))).toBe(BigInt(123)); + }); + + it('should handle edge cases with numbers', () => { + expect(numberToBigInt(-0)).toBe(BigInt(0)); + expect(numberToBigInt(Number.MAX_SAFE_INTEGER)).toBe( + BigInt(Number.MAX_SAFE_INTEGER), + ); + expect(numberToBigInt(Number.MIN_SAFE_INTEGER)).toBe( + BigInt(Number.MIN_SAFE_INTEGER), + ); + }); + + it('should throw error for invalid input types', () => { + expect(() => numberToBigInt(null as any)).toThrow( + 'Cannot convert object to BigInt', + ); + expect(() => numberToBigInt(undefined as any)).toThrow( + 'Cannot convert undefined to BigInt', + ); + expect(() => numberToBigInt({} as any)).toThrow( + 'Cannot convert object to BigInt', + ); + expect(() => numberToBigInt(true as any)).toThrow( + 'Cannot convert boolean to BigInt', + ); + }); + + it('should throw error for invalid string formats', () => { + expect(() => numberToBigInt('abc')).toThrow(SyntaxError); + expect(() => numberToBigInt('123abc')).toThrow(SyntaxError); + expect(() => numberToBigInt('12.34')).toThrow(SyntaxError); // Decimal strings not supported by BigInt + expect(() => numberToBigInt('1e10')).toThrow(SyntaxError); // Scientific notation not supported + }); + + it('should throw error for non-integer numbers', () => { + expect(() => numberToBigInt(12.34)).toThrow(RangeError); + expect(() => numberToBigInt(0.5)).toThrow(RangeError); + expect(() => numberToBigInt(-7.89)).toThrow(RangeError); + expect(() => numberToBigInt(NaN)).toThrow(RangeError); + expect(() => numberToBigInt(Infinity)).toThrow(RangeError); + expect(() => numberToBigInt(-Infinity)).toThrow(RangeError); + }); +}); + describe('units', () => { describe('normal functionality', () => { it('should be the same as web3', () => { diff --git a/src/unitsConversion.ts b/src/unitsConversion.ts index 325bc8558..cee572b09 100644 --- a/src/unitsConversion.ts +++ b/src/unitsConversion.ts @@ -17,7 +17,7 @@ const negative1 = BigInt(-1); * @returns The bigint representation of the input. * @throws Error if the input type cannot be converted to bigint. */ -function numberToBigInt(arg: string | number | bigint): bigint { +export function numberToBigInt(arg: string | number | bigint): bigint { if (typeof arg === 'string') { return BigInt(arg); } @@ -179,6 +179,7 @@ export function fromWei( if (!options.pad) { const fractionMatch = fraction.match(FRACTION_REGEX); + // istanbul ignore next: defensive fallback that's never reachable but necessary to satisfy TS fraction = fractionMatch?.[1] ?? '0'; } From 67db48615635c6f515f896e3c47966fb89e73979 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Such=C3=BD?= Date: Thu, 21 Aug 2025 16:11:33 +0200 Subject: [PATCH 5/6] chore: remove unused eslint disable --- src/unitsConversion.test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/unitsConversion.test.ts b/src/unitsConversion.test.ts index 46b64a0a1..169412b4a 100644 --- a/src/unitsConversion.test.ts +++ b/src/unitsConversion.test.ts @@ -1,5 +1,3 @@ -// web3 dependency is not used in the codebase, but only in tests -// eslint-disable-next-line import/no-extraneous-dependencies import { utils as web3Utils } from 'web3'; import { From d4892403dc1961b129af8705f413b1775b409f00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Such=C3=BD?= Date: Thu, 21 Aug 2025 23:51:46 +0200 Subject: [PATCH 6/6] chore: better error messages, use let + const --- src/unitsConversion.test.ts | 66 ++++++++++++++++++------------------- src/unitsConversion.ts | 31 +++++++++-------- 2 files changed, 48 insertions(+), 49 deletions(-) diff --git a/src/unitsConversion.test.ts b/src/unitsConversion.test.ts index 169412b4a..f8cd58b41 100644 --- a/src/unitsConversion.test.ts +++ b/src/unitsConversion.test.ts @@ -4,7 +4,7 @@ import { toWei, fromWei, numberToString, - numberToBigInt, + numericToBigInt, getValueOfUnit, unitMap, } from './unitsConversion'; @@ -117,10 +117,10 @@ describe('getValueOfUnit', () => { it('should throw error for invalid units', () => { expect(() => getValueOfUnit('invalidunit' as any)).toThrow( - "[ethjs-unit] the unit provided invalidunit doesn't exists", + "The unit provided invalidunit doesn't exist", ); expect(() => getValueOfUnit('' as any)).toThrow( - "[ethjs-unit] the unit provided doesn't exists", + "The unit provided doesn't exist", ); }); @@ -525,66 +525,66 @@ describe('fromWei', () => { }); }); -describe('numberToBigInt', () => { +describe('numericToBigInt', () => { it('should convert string/numbers to BigInt', () => { - expect(numberToBigInt('123')).toBe(BigInt(123)); - expect(numberToBigInt('0')).toBe(BigInt(0)); - expect(numberToBigInt('-456')).toBe(BigInt(-456)); - expect(numberToBigInt('999999999999999999999')).toBe( + expect(numericToBigInt('123')).toBe(BigInt(123)); + expect(numericToBigInt('0')).toBe(BigInt(0)); + expect(numericToBigInt('-456')).toBe(BigInt(-456)); + expect(numericToBigInt('999999999999999999999')).toBe( BigInt('999999999999999999999'), ); - expect(numberToBigInt(123)).toBe(BigInt(123)); - expect(numberToBigInt(0)).toBe(BigInt(0)); - expect(numberToBigInt(-456)).toBe(BigInt(-456)); - expect(numberToBigInt(42.0)).toBe(BigInt(42)); + expect(numericToBigInt(123)).toBe(BigInt(123)); + expect(numericToBigInt(0)).toBe(BigInt(0)); + expect(numericToBigInt(-456)).toBe(BigInt(-456)); + expect(numericToBigInt(42.0)).toBe(BigInt(42)); // special cases - expect(numberToBigInt('')).toBe(BigInt(0)); - expect(numberToBigInt(' 123 ')).toBe(BigInt(123)); + expect(numericToBigInt('')).toBe(BigInt(0)); + expect(numericToBigInt(' 123 ')).toBe(BigInt(123)); }); it('should return BigInt inputs unchanged', () => { - expect(numberToBigInt(BigInt(123))).toBe(BigInt(123)); + expect(numericToBigInt(BigInt(123))).toBe(BigInt(123)); }); it('should handle edge cases with numbers', () => { - expect(numberToBigInt(-0)).toBe(BigInt(0)); - expect(numberToBigInt(Number.MAX_SAFE_INTEGER)).toBe( + expect(numericToBigInt(-0)).toBe(BigInt(0)); + expect(numericToBigInt(Number.MAX_SAFE_INTEGER)).toBe( BigInt(Number.MAX_SAFE_INTEGER), ); - expect(numberToBigInt(Number.MIN_SAFE_INTEGER)).toBe( + expect(numericToBigInt(Number.MIN_SAFE_INTEGER)).toBe( BigInt(Number.MIN_SAFE_INTEGER), ); }); it('should throw error for invalid input types', () => { - expect(() => numberToBigInt(null as any)).toThrow( + expect(() => numericToBigInt(null as any)).toThrow( 'Cannot convert object to BigInt', ); - expect(() => numberToBigInt(undefined as any)).toThrow( + expect(() => numericToBigInt(undefined as any)).toThrow( 'Cannot convert undefined to BigInt', ); - expect(() => numberToBigInt({} as any)).toThrow( + expect(() => numericToBigInt({} as any)).toThrow( 'Cannot convert object to BigInt', ); - expect(() => numberToBigInt(true as any)).toThrow( + expect(() => numericToBigInt(true as any)).toThrow( 'Cannot convert boolean to BigInt', ); }); it('should throw error for invalid string formats', () => { - expect(() => numberToBigInt('abc')).toThrow(SyntaxError); - expect(() => numberToBigInt('123abc')).toThrow(SyntaxError); - expect(() => numberToBigInt('12.34')).toThrow(SyntaxError); // Decimal strings not supported by BigInt - expect(() => numberToBigInt('1e10')).toThrow(SyntaxError); // Scientific notation not supported + expect(() => numericToBigInt('abc')).toThrow(SyntaxError); + expect(() => numericToBigInt('123abc')).toThrow(SyntaxError); + expect(() => numericToBigInt('12.34')).toThrow(SyntaxError); // Decimal strings not supported by BigInt + expect(() => numericToBigInt('1e10')).toThrow(SyntaxError); // Scientific notation not supported }); it('should throw error for non-integer numbers', () => { - expect(() => numberToBigInt(12.34)).toThrow(RangeError); - expect(() => numberToBigInt(0.5)).toThrow(RangeError); - expect(() => numberToBigInt(-7.89)).toThrow(RangeError); - expect(() => numberToBigInt(NaN)).toThrow(RangeError); - expect(() => numberToBigInt(Infinity)).toThrow(RangeError); - expect(() => numberToBigInt(-Infinity)).toThrow(RangeError); + expect(() => numericToBigInt(12.34)).toThrow(RangeError); + expect(() => numericToBigInt(0.5)).toThrow(RangeError); + expect(() => numericToBigInt(-7.89)).toThrow(RangeError); + expect(() => numericToBigInt(NaN)).toThrow(RangeError); + expect(() => numericToBigInt(Infinity)).toThrow(RangeError); + expect(() => numericToBigInt(-Infinity)).toThrow(RangeError); }); }); @@ -692,7 +692,7 @@ describe('units', () => { }); it('should handle internal function edge cases', () => { - // Test numberToBigInt indirectly through toWei with various input types + // Test numericToBigInt indirectly through toWei with various input types expect(typeof toWei(BigInt(123), 'wei')).toBe('bigint'); expect(typeof toWei('123', 'wei')).toBe('bigint'); expect(typeof toWei(123, 'wei')).toBe('bigint'); diff --git a/src/unitsConversion.ts b/src/unitsConversion.ts index cee572b09..4f1b1b34c 100644 --- a/src/unitsConversion.ts +++ b/src/unitsConversion.ts @@ -17,7 +17,7 @@ const negative1 = BigInt(-1); * @returns The bigint representation of the input. * @throws Error if the input type cannot be converted to bigint. */ -export function numberToBigInt(arg: string | number | bigint): bigint { +export function numericToBigInt(arg: string | number | bigint): bigint { if (typeof arg === 'string') { return BigInt(arg); } @@ -90,7 +90,7 @@ export function getValueOfUnit(unitInput: EthereumUnit = 'ether'): bigint { if (unitValue === undefined) { throw new Error( - `[ethjs-unit] the unit provided ${unitInput} doesn't exists, please use the one of the following units ${JSON.stringify( + `The unit provided ${unitInput} doesn't exist, please use the one of the following units ${JSON.stringify( unitMap, null, 2, @@ -120,7 +120,6 @@ export function numberToString(arg: string | number | bigint) { if (typeof arg === 'number') { return String(arg); } - // eslint-disable-next-line valid-typeof if (typeof arg === 'bigint') { return arg.toString(); } @@ -147,8 +146,8 @@ export function fromWei( unit: EthereumUnit, optionsInput?: { pad?: boolean; commify?: boolean }, ) { - var wei = numberToBigInt(weiInput); // eslint-disable-line - var negative = wei < zero; // eslint-disable-line + let wei = numericToBigInt(weiInput); + const negative = wei < zero; const unitLower = unit.toLowerCase() as EthereumUnit; const base = unitMapBigInt[unitLower]; const baseLength = unitLengths[unitLower]; @@ -156,7 +155,7 @@ export function fromWei( if (base === undefined) { throw new Error( - `[ethjs-unit] the unit provided ${unit} doesn't exists, please use the one of the following units ${JSON.stringify( + `The unit provided ${unit} doesn't exist, please use the one of the following units ${JSON.stringify( unitMap, null, 2, @@ -173,7 +172,7 @@ export function fromWei( wei = wei * negative1; } - var fraction = (wei % base).toString(); // eslint-disable-line + let fraction = (wei % base).toString(); fraction = fraction.padStart(baseLength, '0'); @@ -183,13 +182,13 @@ export function fromWei( fraction = fractionMatch?.[1] ?? '0'; } - var whole = (wei / base).toString(); // eslint-disable-line + let whole = (wei / base).toString(); if (options.commify) { whole = whole.replace(COMMIFY_REGEX, ','); } - var value = `${whole}${fraction == '0' ? '' : `.${fraction}`}`; // eslint-disable-line + let value = `${whole}${fraction === '0' ? '' : `.${fraction}`}`; if (negative) { value = `-${value}`; @@ -216,7 +215,7 @@ export function toWei( if (base === undefined) { throw new Error( - `[ethjs-unit] the unit provided ${unit} doesn't exists, please use the one of the following units ${JSON.stringify( + `The unit provided ${unit} doesn't exist, please use the one of the following units ${JSON.stringify( unitMap, null, 2, @@ -239,25 +238,25 @@ export function toWei( return etherInput * base; } - var ether = numberToString(etherInput); // eslint-disable-line + let ether = numberToString(etherInput); // Is it negative? - var negative = ether.substring(0, 1) === '-'; // eslint-disable-line + const negative = ether.startsWith('-'); if (negative) { ether = ether.substring(1); } if (ether === '.') { throw new Error( - `[ethjs-unit] while converting number ${etherInput} to wei, invalid value`, + `While converting number ${etherInput} to wei, invalid value`, ); } // Split it into a whole and fractional part - var comps = ether.split('.'); // eslint-disable-line + const comps = ether.split('.'); if (comps.length > 2) { throw new Error( - `[ethjs-unit] while converting number ${etherInput} to wei, too many decimal points`, + `While converting number ${etherInput} to wei, too many decimal points`, ); } @@ -272,7 +271,7 @@ export function toWei( } if (fraction.length > baseLength) { throw new Error( - `[ethjs-unit] while converting number ${etherInput} to wei, too many decimal places`, + `While converting number ${etherInput} to wei, too many decimal places`, ); }