Token Amount

A balance formatted from base units without ever touching a float. 1 ETH is 10¹⁸ wei, which exceeds Number.MAX_SAFE_INTEGER by two orders of magnitude — a balance that passes through a JavaScript number is silently wrong.

src/components/ui/token-amount.tsx

About

From the component's own source, where it was written.

A token balance, formatted from base units without ever touching a float.

This is the one that has to be right. On-chain amounts are integers in the token's smallest unit — 18 decimals for most ERC-20s — and 1 ETH is 1000000000000000000 wei. That exceeds Number.MAX_SAFE_INTEGER by two orders of magnitude, so the moment a balance passes through a JavaScript number it is silently wrong. Every value here stays a bigint or a decimal string, and the split into whole and fractional parts is done on the digit string.

Display is deliberately lossy in one direction only: it rounds *down*. A balance shown as more than the wallet holds produces a failed transaction, which is a worse outcome than showing slightly less.

Install

npx astralyx-ui add token-amount

Usage

import { TokenAmount } from '@/components/ui/token-amount'

<TokenAmount value="1234567890123456789" decimals={18} symbol="ETH" />
<TokenAmount value={usdcBalance} decimals={6} symbol="USDC" fiat={2500} />

Composer

Change the props and the source below follows.

1.2345ETH
Props
tsx
<TokenAmount
  value="1234567890123456789"
  decimals={18}
  precision={4}
  symbol="ETH"
/>

Examples

Precision at scale

API

Generated from the source, so it cannot fall behind the code. One table per exported component, then hooks, functions and types.

TokenAmount

A token balance, formatted from base units without ever touching a float.

Extends Omit<ComponentProps<'span'>, 'children'>.

PropTypeDefaultDescription
valuebigint | stringrequiredBase units. A string keeps it exact — never a number, which loses precision above 2^53.
decimalsnumber1818 for most ERC-20s, 8 for BTC, 6 for USDC.
symbolstring——
precisionnumber4Fraction digits shown. Truncated, never rounded up — displaying more than is held produces a failed transaction.
trimbooleantrueFraction digits shown. Truncated, never rounded up — displaying more than is held produces a failed transaction.
fiatnumber—Converted value, shown alongside.
fiatCurrencystring'USD'Converted value, shown alongside.
localestring'en-GB'Grouping and the decimal mark both follow it, so a German amount reads 1.234,5 rather than a mix of the two conventions.
signedbooleanfalseColour and sign by direction, for a delta rather than a balance.
size'sm' | 'default' | 'lg''default'—

Functions

FunctionSignatureDescription
splitUnits(value: bigint, decimals: number) => { negative: boolean; whole: string; fraction: string; }Exported. The split is done on the digit string — padding and slicing is exact at any magnitude.
parseUnits(value: string, decimals: number) => bigintA decimal string from an API — "12.5" — as base units, for value. Digits past decimals are cut, never rounded up; anything that is not a plain decimal throws.
formatUnits(value: bigint, decimals: number, precision: number, trim: boolean, locale: string) => stringExported. The split is done on the digit string — padding and slicing is exact at any magnitude.