Create
Documentation
Guides

Decimals

Arc's quote asset is a 6-decimal ERC-20 USDC at 0x3600000000000000000000000000000000000000. Every launched memecoin is 18 decimals. Almost every arithmetic bug in an integration comes from mixing the two.

Which side is which#

QuantityDecimals
quoteIn, minQuoteOut, launchFee, phantomQuote, graduationThreshold, escrow balances6
minTokensOut, tokensIn, supply, token balances, curve token reserves18

So 5e6 is five dollars, and 5e18 is five tokens. A launch fee of 500000 is 0.50 USDC.

The native asset is a different thing#

Arc uses USDC for gas, and viem's chain definition describes that native asset as { symbol: "USDC", decimals: 18 }. That is not the ERC-20 you trade with. They report the same balance through two different interfaces at two different scales. Reading the native balance and passing it as quoteIn is a 10^12 error.

Always read the traded balance from the ERC-20 at 0x3600…0000.

Never round-trip through a float#

Number(raw) / 1e18 loses precision above about 2^53. On a real balance the loss is large enough to matter:

code
balance          714275814275814275814275815
via a float      714275814275814175605773926
lost                         100208501889 wei

That is a hundred billion wei of dust that a "sell max" can never reach, because the amount submitted was never quite the amount held. Use formatUnits and parseUnits, which are exact inverses, and keep bigints end to end.

sqrtPriceX96 carries the skew#

The graduated pool's sqrtPriceX96 is derived from raw amount ratios, so it embeds the 10^12 difference between a 6-decimal and an 18-decimal side. It is not a human price. Adjust for decimals before displaying anything derived from it.

Currency ordering is by address, not by role#

A V4 PoolKey sorts its two currencies by raw address. USDC (0x36…) is currency0 only when the memecoin's address happens to sort above it — which is most of the time, but not always, because launch addresses are CREATE2 and effectively random.

Compute the ordering per launch. Assuming USDC is always currency0 will silently invert zeroForOne on some tokens and swap the wrong direction.

ts
const memecoinIsCurrency0 = token.toLowerCase() < usdc.toLowerCase();
const currency0 = memecoinIsCurrency0 ? token : usdc;
const currency1 = memecoinIsCurrency0 ? usdc : token;