# How USDC and other stablecoins work (on Base) Written before implementation, per project rule: *understand the asset before building the wallet.* Every address and decimal count below was **verified live on-chain on 2026-08-05** via `eth_call` against `https://mainnet.base.org` / `https://sepolia.base.org` and the Etherscan contract-source API — not taken from memory. ## 1. What a stablecoin is, mechanically A stablecoin like USDC is **not a native blockchain asset**. It is an entry in the storage of an **ERC-20 smart contract**. "Holding 10 USDC" means the USDC contract's internal `balances[yourAddress]` equals `10_000_000` (10 × 10⁶ base units). Consequences for a wallet: - **Balances** are read with `balanceOf(address)` (an `eth_call`, selector `0x70a08231`), not `eth_getBalance` (which returns native ETH). - **Transfers** are transactions **to the token contract**, with calldata `transfer(to, amount)` (selector `0xa9059cbb`), `value = 0`. The recipient address lives inside the calldata, not in the tx `to` field. - **Gas is always paid in ETH**, never in the stablecoin. A wallet full of USDC with 0 ETH cannot move — the UI must surface this before the user hits a failed send. - Incoming/outgoing movements are observed via the contract's `Transfer(from, to, value)` event logs, which is what explorers index for token history. ## 2. Issuance models (why "stable") | Model | Example | Peg mechanism | Trust assumption | |---|---|---|---| | Fiat-backed, centralized | **USDC, EURC** (Circle), **USDT** (Tether) | 1:1 reserves (cash + T-bills); mint/burn on deposit/redemption | Issuer solvency + banking rails | | Crypto-collateralized | **DAI** (MakerDAO/Sky) | Overcollateralized vaults + rates | Protocol governance + collateral quality | | Bridged representation | **USDbC** (Base) | Lock on L1, mint wrapped on L2 | The bridge, **plus** the original issuer | Key operational facts for fiat-backed coins (USDC family): - The contract is an **upgradeable proxy**. Verified: Base USDC is Circle's `FiatTokenProxy` (AdminUpgradeabilityProxy pattern, implementation at `0x2ce6311ddae708829bc0784c967b7d77d19fd779`, upgradeable by Circle's admin key). Behavior can change under the same address — one more reason a wallet should treat token metadata (decimals) as per-token config, verified on-chain, not assumptions. - The issuer can **blocklist addresses** (`blacklist(address)` in FiatToken). Funds at a blocklisted address are frozen at the contract level. Self-custody protects keys, not against issuer-level freezing — worth knowing, nothing for the wallet to implement. - Some support gasless-approval extensions (EIP-2612 `permit`, EIP-3009 `transferWithAuthorization`). v1 uses plain `transfer` only. ## 3. Decimals — the #1 bug source (verified live) **Decimals differ per token.** A wallet must never hardcode 6. | Token | Base mainnet address (chain 8453) | Decimals | Notes | |---|---|---|---| | USDC | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` | **6** | Native Circle issue — the default token | | EURC | `0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42` | **6** | Circle euro coin | | USDT | `0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2` | **6** | Tether on Base | | DAI | `0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb` | **18** | ⚠ 18, like ETH — not 6 | | USDbC | `0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA` | **6** | Legacy bridged USDC; receive/display only, discourage new use | Testnet (Base Sepolia, chain 84532): USDC `0x036CbD53842c5426634e7929541eC2318f3dCF7e`, decimals **6** (verified). Circle faucet: https://faucet.circle.com (20 USDC / 2 h); gas ETH from a Base Sepolia faucet. Rules the code enforces: - All amounts are `BigUInt` base units internally; `Double` is banned for money. - Conversion display ⇄ base units goes through per-token `decimals` from the registry (`TokenAmount`), unit-tested for 6- and 18-decimal tokens. - ETH (gas) is 18 decimals; wei ⇄ ETH uses the same integer math. ## 4. Chain facts (verified live) - Base mainnet `eth_chainId` → `0x2105` = **8453**; Base Sepolia → `0x14a34` = **84532**. - Base is an OP-stack L2: EIP-1559 fees (`maxFeePerGas` / `maxPriorityFeePerGas`), tips are tiny (fractions of a gwei), an ERC-20 transfer costs well under a cent. - The signed transaction embeds the chain ID (EIP-155/1559) — replay protection across Sepolia/mainnet is automatic as long as the wallet signs with the selected network's ID. ## 5. What this means for OS Vault's design - A **token registry** (`Token` model) instead of a hardcoded USDC constant: symbol, name, per-network contract address, decimals. v1 ships USDC as default with EURC, DAI, USDT registered; adding a stablecoin is a registry entry, not a code change. - Per-token balance = one `eth_call` each; ETH balance fetched alongside for the gas warning ("not enough ETH to send"). - Send flow is token-generic: encode `transfer(to, amount)` with the token's address and decimals; everything else (nonce, fees, signing, receipt polling) is identical across stablecoins.