fix: Move BigInt formatting functions from helper.ts to kraiken-lib/format (#246)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
openhands 2026-02-24 23:00:58 +00:00
parent 0f74cc8276
commit 8c43d3890c
16 changed files with 377 additions and 102 deletions

View file

@ -1,16 +1,17 @@
{
"name": "kraiken-lib",
"version": "0.2.0",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "kraiken-lib",
"version": "0.2.0",
"version": "1.0.0",
"dependencies": {
"@apollo/client": "^3.9.10",
"graphql": "^16.8.1",
"graphql-tag": "^2.12.6"
"graphql-tag": "^2.12.6",
"viem": "^2.22.13"
},
"devDependencies": {
"@graphql-codegen/cli": "^5.0.2",
@ -31,6 +32,12 @@
"typescript": "^5.4.3"
}
},
"node_modules/@adraffy/ens-normalize": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz",
"integrity": "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==",
"license": "MIT"
},
"node_modules/@ampproject/remapping": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
@ -219,6 +226,7 @@
"integrity": "sha512-5FcvN1JHw2sHJChotgx8Ek0lyuh4kCKelgMTTqhYJJtloNvUfpAFMeNQUtdlIaktwrSV9LtCdqwk48wL2wBacQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.24.2",
@ -2865,6 +2873,45 @@
"dev": true,
"license": "MIT"
},
"node_modules/@noble/ciphers": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz",
"integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==",
"license": "MIT",
"engines": {
"node": "^14.21.3 || >=16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@noble/curves": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz",
"integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==",
"license": "MIT",
"dependencies": {
"@noble/hashes": "1.8.0"
},
"engines": {
"node": "^14.21.3 || >=16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@noble/hashes": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
"integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
"license": "MIT",
"engines": {
"node": "^14.21.3 || >=16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@ -2952,6 +2999,42 @@
"dev": true,
"license": "MIT"
},
"node_modules/@scure/base": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz",
"integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==",
"license": "MIT",
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@scure/bip32": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz",
"integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==",
"license": "MIT",
"dependencies": {
"@noble/curves": "~1.9.0",
"@noble/hashes": "~1.8.0",
"@scure/base": "~1.2.5"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@scure/bip39": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz",
"integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==",
"license": "MIT",
"dependencies": {
"@noble/hashes": "~1.8.0",
"@scure/base": "~1.2.5"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@sinclair/typebox": {
"version": "0.27.8",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
@ -3106,6 +3189,7 @@
"integrity": "sha512-F1CBxgqwOMc4GKJ7eY22hWhBVQuMYTtqI8L0FcszYcpYX0fzfDGpez22Xau8Mgm7O9fI+zA/TYIdq3tGWfweBA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"undici-types": "~7.13.0"
}
@ -3190,6 +3274,7 @@
"integrity": "sha512-TGf22kon8KW+DeKaUmOibKWktRY8b2NSAZNdtWh798COm1NWx8+xJ6iFBtk3IvLdv6+LGLJLRlyhrhEDZWargQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.45.0",
"@typescript-eslint/types": "8.45.0",
@ -3511,12 +3596,34 @@
"node": ">=8"
}
},
"node_modules/abitype": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/abitype/-/abitype-1.2.3.tgz",
"integrity": "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/wevm"
},
"peerDependencies": {
"typescript": ">=5.0.4",
"zod": "^3.22.0 || ^4.0.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
},
"zod": {
"optional": true
}
}
},
"node_modules/acorn": {
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@ -3939,6 +4046,7 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"caniuse-lite": "^1.0.30001587",
"electron-to-chromium": "^1.4.668",
@ -4775,6 +4883,7 @@
"integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@ -5043,7 +5152,6 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
"dev": true,
"license": "MIT"
},
"node_modules/execa": {
@ -5515,6 +5623,7 @@
"resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.1.tgz",
"integrity": "sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==",
"license": "MIT",
"peer": true,
"engines": {
"node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0"
}
@ -5599,6 +5708,7 @@
"integrity": "sha512-Ju2RCU2dQMgSKtArPbEtsK5gNLnsQyTNIo/T7cZNp96niC1x0KdJNZV0TIoilceBPQwfb5itrGl8pkFeOUMl4A==",
"devOptional": true,
"license": "MIT",
"peer": true,
"workspaces": [
"website"
],
@ -6145,6 +6255,21 @@
"ws": "*"
}
},
"node_modules/isows": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz",
"integrity": "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/wevm"
}
],
"license": "MIT",
"peerDependencies": {
"ws": "*"
}
},
"node_modules/istanbul-lib-coverage": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
@ -6258,6 +6383,7 @@
"integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@jest/core": "^29.7.0",
"@jest/types": "^29.6.3",
@ -7929,6 +8055,36 @@
"node": ">=0.10.0"
}
},
"node_modules/ox": {
"version": "0.12.4",
"resolved": "https://registry.npmjs.org/ox/-/ox-0.12.4.tgz",
"integrity": "sha512-+P+C7QzuwPV8lu79dOwjBKfB2CbnbEXe/hfyyrff1drrO1nOOj3Hc87svHfcW1yneRr3WXaKr6nz11nq+/DF9Q==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/wevm"
}
],
"license": "MIT",
"dependencies": {
"@adraffy/ens-normalize": "^1.11.0",
"@noble/ciphers": "^1.3.0",
"@noble/curves": "1.9.1",
"@noble/hashes": "^1.8.0",
"@scure/bip32": "^1.7.0",
"@scure/bip39": "^1.6.0",
"abitype": "^1.2.3",
"eventemitter3": "5.0.1"
},
"peerDependencies": {
"typescript": ">=5.4.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/p-limit": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
@ -9238,8 +9394,9 @@
"version": "5.4.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz",
"integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==",
"dev": true,
"devOptional": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@ -9425,6 +9582,36 @@
"node": ">=12"
}
},
"node_modules/viem": {
"version": "2.46.3",
"resolved": "https://registry.npmjs.org/viem/-/viem-2.46.3.tgz",
"integrity": "sha512-2LJS+Hyh2sYjHXQtzfv1kU9pZx9dxFzvoU/ZKIcn0FNtOU0HQuIICuYdWtUDFHaGXbAdVo8J1eCvmjkL9JVGwg==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/wevm"
}
],
"license": "MIT",
"dependencies": {
"@noble/curves": "1.9.1",
"@noble/hashes": "1.8.0",
"@scure/bip32": "1.7.0",
"@scure/bip39": "1.6.0",
"abitype": "1.2.3",
"isows": "1.0.7",
"ox": "0.12.4",
"ws": "8.18.3"
},
"peerDependencies": {
"typescript": ">=5.0.4"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/walker": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
@ -9557,11 +9744,11 @@
}
},
"node_modules/ws": {
"version": "8.16.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
"integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
"dev": true,
"version": "8.18.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=10.0.0"
},

View file

@ -50,6 +50,11 @@
"types": "./dist/position.d.ts",
"require": "./dist/position.js",
"import": "./dist/position.js"
},
"./format": {
"types": "./dist/format.d.ts",
"require": "./dist/format.js",
"import": "./dist/format.js"
}
},
"files": [
@ -74,7 +79,8 @@
"dependencies": {
"@apollo/client": "^3.9.10",
"graphql": "^16.8.1",
"graphql-tag": "^2.12.6"
"graphql-tag": "^2.12.6",
"viem": "^2.22.13"
},
"devDependencies": {
"@graphql-codegen/cli": "^5.0.2",

26
kraiken-lib/src/format.ts Normal file
View file

@ -0,0 +1,26 @@
import { formatUnits } from 'viem';
/** Convert wei (bigint or string) to a JS number. Core primitive. */
export function weiToNumber(value: bigint | string, decimals = 18): number {
const bi = typeof value === 'string' ? BigInt(value || '0') : (value ?? 0n);
return Number(formatUnits(bi, decimals));
}
/** Format wei to fixed decimal string (e.g. "0.00123") */
export function formatWei(value: bigint | string, decimals = 18, digits = 5): string {
const num = weiToNumber(value, decimals);
return num === 0 ? '0' : num.toFixed(digits);
}
/** Format number to compact display (e.g. "1.23K", "4.56M") */
export function compactNumber(value: number): string {
return Intl.NumberFormat('en-US', {
notation: 'compact',
maximumFractionDigits: 2,
}).format(value);
}
/** Format number with commas (e.g. "1,234,567") */
export function commaNumber(value: number): string {
return value ? value.toLocaleString('en-US') : '0';
}

View file

@ -0,0 +1,112 @@
import { describe, expect, test } from '@jest/globals';
import { weiToNumber, formatWei, compactNumber, commaNumber } from '../format.js';
describe('weiToNumber', () => {
test('returns 0 for zero bigint', () => {
expect(weiToNumber(0n)).toBe(0);
});
test('returns 0 for empty string', () => {
expect(weiToNumber('')).toBe(0);
});
test('converts 1 ether bigint to 1', () => {
expect(weiToNumber(1_000_000_000_000_000_000n)).toBe(1);
});
test('converts string wei to number', () => {
expect(weiToNumber('1000000000000000000')).toBe(1);
});
test('handles small values correctly', () => {
expect(weiToNumber(1n)).toBeCloseTo(1e-18);
});
test('handles large values', () => {
expect(weiToNumber(1_000_000_000_000_000_000_000n)).toBe(1000);
});
test('respects custom decimals', () => {
expect(weiToNumber(1_000_000n, 6)).toBe(1);
});
test('handles string with custom decimals', () => {
expect(weiToNumber('1000000', 6)).toBe(1);
});
});
describe('formatWei', () => {
test('returns "0" for zero bigint', () => {
expect(formatWei(0n)).toBe('0');
});
test('returns "0" for zero string', () => {
expect(formatWei('0')).toBe('0');
});
test('formats 1 ether with default 5 digits', () => {
expect(formatWei(1_000_000_000_000_000_000n)).toBe('1.00000');
});
test('formats with custom digits', () => {
expect(formatWei(1_000_000_000_000_000_000n, 18, 2)).toBe('1.00');
});
test('formats small value', () => {
const result = formatWei(1_230_000_000_000_000n, 18, 5);
expect(result).toBe('0.00123');
});
test('formats string input', () => {
expect(formatWei('2000000000000000000', 18, 4)).toBe('2.0000');
});
test('formats large values', () => {
const wei = 1_000_000n * 10n ** 18n;
expect(formatWei(wei, 18, 2)).toBe('1000000.00');
});
});
describe('compactNumber', () => {
test('formats thousands', () => {
expect(compactNumber(1234)).toBe('1.23K');
});
test('formats millions', () => {
expect(compactNumber(4_560_000)).toBe('4.56M');
});
test('formats zero', () => {
expect(compactNumber(0)).toBe('0');
});
test('formats small number without suffix', () => {
expect(compactNumber(123)).toBe('123');
});
test('formats billions', () => {
expect(compactNumber(1_000_000_000)).toBe('1B');
});
});
describe('commaNumber', () => {
test('returns "0" for falsy input', () => {
expect(commaNumber(0)).toBe('0');
});
test('formats thousands with commas', () => {
expect(commaNumber(1234)).toBe('1,234');
});
test('formats millions with commas', () => {
expect(commaNumber(1_234_567)).toBe('1,234,567');
});
test('formats small number without commas', () => {
expect(commaNumber(99)).toBe('99');
});
test('formats negative number', () => {
expect(commaNumber(-1234)).toBe('-1,234');
});
});