Why on-chain amounts are stored as base units
Blockchains don't store fractional numbers. An ERC-20 token contract — and every protocol built on top of one, including x402 — keeps balances and transfer amounts as plain integers measured in the token's smallest indivisible unit, the base unit. The human-friendly number you see in a wallet is just a display convenience produced by dividing by 10decimals.
So to put a price on-chain you do the reverse — multiply by 10decimals:
base_units = human_amount × 10^decimals
human_amount = base_units ÷ 10^decimals Doing this with floating-point numbers is a classic source of bugs: 0.1 + 0.2 isn't exactly 0.3 in IEEE-754, and at 18 decimals those tiny errors become real lost (or extra) tokens. That's why this converter uses exact string/integer math instead of JavaScript floats, and why on-chain code uses BigInt / uint256 everywhere.
Token decimals reference
"How many decimals does this token have?" decides every conversion. Here are the common ones. When in doubt, read the token contract's decimals() function on a block explorer — never assume.
| Token | Decimals | 1.0 in base units | Notes |
|---|---|---|---|
| USDC | 6 | 1000000 | Same on Base, Ethereum, Polygon, Arbitrum. |
| USDT | 6 | 1000000 | 6 on Ethereum/Base; can differ on other chains — verify. |
| ETH / wei | 18 | 1000000000000000000 | Native ETH; 1 ETH = 1018 wei. |
| DAI | 18 | 1000000000000000000 | Most stablecoins other than USDC/USDT use 18. |
| WETH | 18 | 1000000000000000000 | Wrapped ETH mirrors ETH's 18. |
| WBTC | 8 | 100000000 | Mirrors Bitcoin's 8 decimals (1 BTC = 108 sats). |
Base units in x402 maxAmountRequired
If you're building an HTTP 402 paywall, the maxAmountRequired field is exactly a base-unit value — as a string. Get it wrong and clients either overpay, underpay, or reject the challenge. Worked examples for USDC (6 decimals):
| Price | maxAmountRequired | Use case |
|---|---|---|
| $0.0001 | "100" | Per-token / per-call AI metering |
| $0.001 | "1000" | Micropayment per API request |
| $0.01 | "10000" | One cent — typical floor on cards, trivial here |
| $0.10 | "100000" | Single article / query |
| $1.00 | "1000000" | Report, file, or session |
Do it in code
For production, convert with big-integer math, not Number. JavaScript / TypeScript with viem:
import { parseUnits, formatUnits } from 'viem'
// human -> base units (returns a bigint)
const amount = parseUnits('0.01', 6) // 10000n (USDC)
// base units -> human string
const human = formatUnits(10000n, 6) // "0.01" Plain JS without a library:
// human -> base units, exact (no float drift)
function toBaseUnits(amount, decimals) {
const [int, frac = ''] = String(amount).split('.')
return BigInt(int + frac.padEnd(decimals, '0').slice(0, decimals))
}
toBaseUnits('1.5', 6) // 1500000n Python (use Decimal, never float):
from decimal import Decimal
base = int(Decimal("0.01") * (10 ** 6)) # 10000 Common pitfalls
- Assuming 18 decimals. USDC and USDT use 6, WBTC uses 8. Multiplying a USDC price by 1018 overcharges by a trillion ×.
- Using floats.
0.07 * 1e6can yield70000.00000000001. Always use integer/BigInt math or a decimal library. - Passing a number where a string is expected. JSON-based protocols like x402 want
maxAmountRequiredas a string to preserve precision for large values. - Trusting a hard-coded decimals value across chains. The same token symbol can have different decimals on different networks; read
decimals()from the contract. - Too many decimal places. You can't express $0.0000001 in USDC (only 6 decimals). This tool flags that instead of silently truncating.
Frequently asked questions
How many base units is 1 USDC?
USDC has 6 decimals, so 1 USDC = 1000000 base units, $0.01 = 10000, and $0.000001 (the smallest unit) = 1.
What's the difference between wei, gwei and ether?
They're units of the same token at different scales. 1 ether = 109 gwei = 1018 wei. "Wei" is ETH's base unit, so to convert ETH to wei set decimals to 18.
Why not just use a decimal number on-chain?
The EVM has no native fractional type. Integers in the smallest unit avoid rounding errors and make arithmetic deterministic across every node. Display formatting happens off-chain.
Does this converter round my numbers?
No. It uses exact string/integer math. If you enter more decimal places than the token supports, it tells you instead of silently dropping precision.
Is anything I type sent to a server?
No. The conversion runs entirely in your browser. Nothing is transmitted or stored.
Building a paywall? Use the x402 / HTTP 402 response builder, browse x402 libraries & SDKs, or compare payment protocols.