# Decimals

Every launch quotes in one of the approved pair tokens, and every quote-side amount is in
**that asset's own decimals**. Every launched memecoin is **18 decimals**. Almost every
arithmetic bug in an integration comes from mixing the two — or from assuming the quote is
always the 6-decimal one.

## The quote assets

| Symbol | Address | Decimals |
|---|---|---|
| USDC | `0x3600000000000000000000000000000000000000` | **6** |
| EURC | `0xbef5f6d51cb62b58e6a8f77868681825c6fe21c1` | **6** |
| cirBTC | `0x171a4217b86a807a64eb94757db6849fb4bdbaa0` | **8** |
| WETH | `0x128cc466b61f542da60c70e3aa11c10e19b84edb` | **18** |
| XAUM | `0x178b01f61cbea1d2a5581fe1621be607835ec349` | **18** |

The live list — with each asset's phantom reserve and graduation threshold — is
`GET /v1/protocol` → `approvedPairTokens[]`, and on chain `factory.pairTokenEconomics(token)`
plus the token's own `decimals()`. Read it per launch: `getLaunchedToken(token).pairToken` says
which asset a given curve trades in.

## Which side is which

| Quantity | Decimals |
|---|---|
| `quoteIn`, `minQuoteOut`, `phantomQuote`, `graduationThreshold`, escrow balances, distributor epochs | **the launch's quote asset** (6, 8 or 18 — table above) |
| `launchFee` | **the fee token's** — `launchFeeToken()` is USDC, so 6, whatever the launch quotes in |
| `minTokensOut`, `tokensIn`, `supply`, token balances, curve token reserves | **18** |

So on a USDC launch `5e6` is five dollars and `5e18` is five tokens; on a WETH launch a
`quoteIn` of `5e6` is 0.000000000005 ETH — dust. The 5 USDC launch fee reads back from
`launchFee()` as `5000000` for every launch.

```ts
const { pairToken } = await client.readContract({ address: FACTORY, abi: factoryAbi, functionName: "getLaunchedToken", args: [token] });
const quoteDecimals = await client.readContract({ address: pairToken, abi: erc20Abi, functionName: "decimals" });
const quoteIn = parseUnits("0.05", quoteDecimals);   // never a hard-coded 6
```

## 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` — and, for any other quote,
from that quote's own contract.

## 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:

```
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
decimal gap between the quote side and the 18-decimal memecoin — 10^12 against USDC or EURC,
10^10 against cirBTC, none against WETH or XAUM. It is not a human price. Adjust for the
launch's quote 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. The quote asset is `currency0`
only when the memecoin's address happens to sort above it — for USDC (`0x36…`) that is most
of the time, for WETH (`0x12…`) less often, and never reliably, because launch addresses are
CREATE2 and effectively random.

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

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