The ERC-20 decimals value tells software how to display a raw token amount. With d decimals, divide the raw integer by 10 to the power d to obtain the human-readable quantity; multiply an exactly representable quantity by that scale to prepare a transfer. Decimals is optional metadata, so verify the token's scale rather than assuming 18. Keep display rounding separate from the integer authorized for spending.
The decimal point belongs to the interpretation of the amount. A misplaced point can make an exact integer describe the wrong transfer. Record the token identity, raw integer and scale together so either conversion can be reconstructed.
The number the contract receives
The ERC-20 specification defines balanceOf as returning a uint256 and transfer as accepting a uint256 amount. Its decimals method describes how to turn that amount into a user representation. For a declared scale of six, one displayed token corresponds to 1,000,000 raw units.
Keep two relationships explicit:
- Human-readable quantity = raw amount divided by
10^d. - Raw amount = human-readable quantity multiplied by
10^d, provided the result is an integer.
In the OpenZeppelin Contracts 5.x ERC-20 guide, decimals affects presentation while token arithmetic operates on integers. Supplying an integer to a transfer function does not ask that function to move a decimal point for you. The application preparing the call must already have chosen the correct unit.
"Base unit" here means one indivisible unit of the token's raw amount. Keep the token identity attached: a token base unit and a wei of native ETH are different accounting units. Neither the decimal count nor the raw balance supplies a market price.
One integer under two declared scales
Consider two fictional tokens on one network: Token A declares six decimals and Token B declares eighteen. In this hypothetical ledger, each wallet has a raw balance of 123456789. Assume ordinary integer balance accounting, with no rebase, transfer charge or wrapper conversion.
| Interpretation | Token A: 6 decimals | Token B: 18 decimals |
|---|---|---|
| Raw wallet balance | 123456789 | 123456789 |
| Raw units in one whole token | 1000000 | 1000000000000000000 |
| Exact displayed balance | 123.456789 | 0.000000000123456789 |
| Quantity represented by one raw unit | 0.000001 | 0.000000000000000001 |
The integers match, but the quantities use different denominators. Applying eighteen decimals to Token A would make its displayed balance one trillion times smaller than its six-decimal interpretation. The balance query could be perfectly correct while the interface remained wrong.
This comparison does not imply that Token A is worth more. Tokens A and B are different assets with no stated prices or exchange relationship. Decimal places specify how finely the displayed unit is divided; economic value needs separate evidence.
Now prepare the same human-readable amount, 12.345678, for each fictional token. Token A requires 12345678 raw units. Token B requires 12345678000000000000 raw units. Formatting either integer with its own declared scale returns exactly 12.345678. That round trip is the essential conversion check.
The amount also needs a balance check. Token A's candidate fits within its 123456789 raw balance. Token B's much larger candidate exceeds its identical raw balance and must be rejected. Exact conversion and sufficient holdings are separate checks; compare candidate and balance as integers of the same token.
Passing Token A's integer to Token B's transfer would request only 0.000000000012345678 B. A valid integer and a valid contract call can still express a different amount from the one a person authorized.
Convert the input text before constructing the call
Retain the original amount as decimal text. Convert it directly into an exact integer using the token's verified scale, then format that integer back with the same scale for review. The API decimal precision guide covers the wider parsing and transport problem; the additional requirement here is binding every conversion to the correct token.
A round trip can pass when the same wrong decimal count is used in both directions. It checks consistency with the chosen scale. Establishing that the scale belongs to the token requires independent evidence.
The ethers v6 unit-conversion documentation describes parseUnits as taking decimal text and a decimal count, returning a bigint; formatUnits performs the display conversion. Pass the token's decimal count explicitly. The ethers v6.17.0 implementation defaults these helpers to eighteen places when the unit is omitted. It does not discover the token contract's metadata for that conversion.
Keep the raw integer exact through storage and request preparation as well. Passing a correct integer through an imprecise numeric type can lose digits. A short display string can be useful for a screen, but retain the full value for the round trip and spending decision.
Rounding creates a revised amount
Suppose the requested amount changes to 12.3456789. At six decimals, multiplying by 1,000,000 gives 12,345,678.9 raw units. That fractional raw unit cannot be encoded as the transfer amount. At eighteen decimals, the same text is exactly representable as 12345678900000000000 raw units.
For Token A, the application must decide what to do with the unrepresentable request. The following alternatives use explicitly chosen policies; ERC-20 does not select one for the application.
| Token A policy | Raw amount | Resulting token quantity | Change from request |
|---|---|---|---|
| Reject excess precision | No transfer prepared | Request remains unresolved | No revised amount authorized |
| Round down | 12345678 | 12.345678 | 0.0000009 less |
| Round up | 12345679 | 12.345679 | 0.0000001 more |
Round down may be appropriate for a particular budget constraint, but it leaves part of the requested payment unsatisfied. Round up spends more than requested. Record the revised amount and its reason, and use that amount in the review step. Silent truncation hides a decision the decimal scale cannot make.
Count numerical precision rather than rejecting text solely because it contains many digits. At six decimals, 12.34567800 is exactly representable because the extra places are zero. By contrast, 12.34567801 contains a nonzero fraction smaller than one raw unit. A token declaring zero decimals likewise permits whole-token quantities; an input of 12.5 would need an explicit resolution.
Keep a rounded screen value out of “send max”
Return to Token A's exact balance of 123.456789. A screen rounding to two places might show 123.46. Parsing that text as a fresh six-decimal request produces 123460000 raw units, which exceeds the actual 123456789 balance by 3,211 raw units, or 0.003211 A.
Truncating the screen to 123.45 avoids that excess in this fixture, but reparsing it produces 123450000 raw units and leaves 6,789 raw units, or 0.006789 A. Both results come from editing the amount for presentation and then treating the edited text as the source of truth.
A full-balance action should start from the raw balance and apply any separately defined reserve or spending rules there. Its review display can expose the complete amount. For Token B's tiny balance in the first table, a two-place display could show 0.00 while 123456789 raw units remain. Show more detail or label the balance as a small positive amount.
When the decimal count cannot be read
The ERC-20 standard marks decimals as optional. A failed metadata read therefore leaves the scale unresolved; it does not establish eighteen decimals. Distinguish an absent method from a failed provider request or an unexpected response before deciding how to resolve it.
OpenZeppelin's ERC20 implementation at v5.4.0 returns eighteen by default and allows an override. That is evidence about the named implementation, not a universal fallback for every token. A valid zero-decimal response must also remain distinct from missing metadata.
For an integration, bind an approved decimal count to the chain and token contract, with its provenance and observation block where applicable. An explicit asset registry can carry that evidence when on-chain metadata is unavailable. Do not infer the scale from a familiar symbol, the length of a balance or a pleasing displayed result.
If inspecting the contract leads to proxy code, the CALL versus DELEGATECALL guide explains the relevant code and storage distinction. Retain the externally used token address alongside implementation evidence so later investigation can reproduce what was queried. If two sources disagree about the scale, preserve the raw amount and resolve the disagreement before converting a requested token quantity.
A scale check has a defined boundary
Correct decimals conversion establishes the numerical amount requested. Delivery needs its own evidence. Fee-on-transfer and rebasing token accounting explains why sender debit, recipient credit and a later balance can differ. Those mechanics require separate treatment even when every value uses the right scale.
Likewise, decimal adjustment does not convert a vault share into its underlying asset. The ERC-4626 shares versus assets guide follows that exchange relationship and its rounding. Giving two tokens the same decimal count would not make one share equal one underlying token.
Use this worksheet at the amount boundary:
| Record | Validation question |
|---|---|
| Chain and token contract | Which asset does this integer measure? |
| Raw balance and observation reference | What amount was actually read, and at which state? |
| Decimal count and evidence | Is the scale verified, explicitly configured or unresolved? |
| Original input text | What exact quantity was requested? |
| Raw candidate and any remainder | Is the request integral and within the call's uint256 range? |
| Rounding decision, if needed | Which revised amount is being authorized? |
| Full round-trip display | Does the integer reconstruct the reviewed quantity? |
Exercise the worksheet with one raw unit, a whole token, the largest amount the application supports and a request just below one raw unit. Include a missing metadata response and a valid zero-decimal token. These fixtures reveal different failures: lost digits, an assumed scale and an unrecorded rounding decision.
Before signing, keep three values together: the original decimal text, the verified decimal count and the exact raw integer. Format that integer back with the recorded scale. If the full display differs from the amount being authorized, resolve the scale or rounding decision before creating the transfer.
