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)(aneth_call, selector0x70a08231), noteth_getBalance(which returns native ETH). - Transfers are transactions to the token contract, with calldata
transfer(to, amount)(selector0xa9059cbb),value = 0. The recipient address lives inside the calldata, not in the txtofield. - 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 at0x2ce6311ddae708829bc0784c967b7d77d19fd779, 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-3009transferWithAuthorization). v1 uses plaintransferonly.
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
BigUIntbase units internally;Doubleis banned for money. - Conversion display ⇄ base units goes through per-token
decimalsfrom 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 (
Tokenmodel) 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_calleach; 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.