← blog

mintrr notes

Why Arc gas is 18 decimals and the USDC token is 6

· technical

On Arc the gas token is USDC, but gas is counted in 18 decimals and the token in 6. That one fact breaks naive tooling by a factor of a trillion.

Arc, native gas USDC, 18 decimals The USDC token, same chain USDC, 6 decimals Same ticker. Same chain. Wrong by 10 to the 12.
Native gas is 18 decimals. The USDC token is 6.
ArcUSDCdecimalsgasprecisionchain identity

On most chains the gas token and the money are different things. On Arc they are the same asset, USDC, and that is where a subtle and expensive trap lives: the gas figure is counted in 18 decimals, and the USDC token is 6. A tool that assumes one decimals value for both is wrong by a factor of a trillion.

mintrr reads chain identity from one table, and that table carries this fact once, so the trap is closed for anything mintrr builds. But it is worth understanding, because it catches everyone the first time.

The two numbers

A token amount is an integer with an implied decimal point. USDC is defined with 6 decimals, so the integer 1,000,000 means one USDC. That is the token's own convention, set by the contract, and it is the number you see when you ask the token for decimals().

Gas is counted differently. The EVM's native value, the thing that pays for computation, is defined with 18 decimals, so the integer 1e18 means one unit of the native token. On Arc the native token happens to be USDC, but the gas accounting still uses the EVM's 18-decimal convention.

So the same asset shows up as two different numbers depending on which layer you are reading. The chain says: this transaction cost 0.0001 native units, in 18 decimals. The USDC contract says: this balance is 1.0 USDC, in 6 decimals. Both are USDC. The decimal points are not the same.

Why it breaks naive tooling

Write code that assumes one decimals value and you get one of two failures.

Assume 6 everywhere, and every gas figure is a trillion times too small, so you never think you can afford a transaction, or you fund an account with a dust amount and it cannot pay for anything. Assume 18 everywhere, and every token balance is a trillion times too large, so you think a user holds far more than they do.

Neither failure throws. The numbers are integers; they are just scaled from different conventions. So the bug is silent, and silent is what makes it dangerous. The fix is not a clever constant. The fix is to read each number from the layer that owns its convention, and never to carry one decimals value across both.

How mintrr handles it

Chain identity is one table in mintrr. Every chain is a row, and the row carries the facts that differ per chain: the native symbol, the native decimals, the fee model, the address codec. There is no second list of chains, and no page or module that re-declares a chain's decimals. If a chain's native decimals changed, one row changes and everything downstream reads the new value.

That single-source rule is why this trap is closed here. The defect this replaced was a tooling list that lived in several places and drifted, so Arc was in some lists and missing from others. One table, one fact, and the drift becomes impossible rather than unlikely.

The wider lesson

Every chain has a fact like this one: a decimals value, a fee quirk, an address format, a block-time assumption. They are not interesting individually, and that is exactly why they are missed. The value of a chain table is not that it stores them; it is that it stores each of them once, so a build reads the fact from the table rather than remembering it.

If you are wiring a product to more than one chain, do this on day one. One row per chain. Read every number from the layer that owns it. Never carry a convention across a boundary where it does not apply. The trillion-factor bug is not a story about USDC. It is a story about assumptions, and the table is how you stop making them.

If you are building across chains and want the chain facts handled once, mintrr already does that. The contact page is where to start.