spb/os-vault Public
Self-custody, multi-chain crypto wallet for macOS. One recovery phrase, six chain families, zero API keys — nothing leaves your Mac.
Swift 96%
Shell 3.4%
Makefile 0.6%
1<!--2 STABLECOINS.md3 OS Vault45 Author: Simon-Pierre Boucher6 Mail: contact@spboucher.ai7-->89# How USDC and other stablecoins work (on Base)1011Written before implementation, per project rule: *understand the asset before building the wallet.*12Every address and decimal count below was **verified live on-chain on 2026-08-05** via13`eth_call` against `https://mainnet.base.org` / `https://sepolia.base.org` and the14Etherscan contract-source API — not taken from memory.1516## 1. What a stablecoin is, mechanically1718A stablecoin like USDC is **not a native blockchain asset**. It is an entry in the19storage of an **ERC-20 smart contract**. "Holding 10 USDC" means the USDC contract's20internal `balances[yourAddress]` equals `10_000_000` (10 × 10⁶ base units).2122Consequences for a wallet:2324- **Balances** are read with `balanceOf(address)` (an `eth_call`, selector `0x70a08231`),25 not `eth_getBalance` (which returns native ETH).26- **Transfers** are transactions **to the token contract**, with calldata27 `transfer(to, amount)` (selector `0xa9059cbb`), `value = 0`. The recipient address28 lives inside the calldata, not in the tx `to` field.29- **Gas is always paid in ETH**, never in the stablecoin. A wallet full of USDC with30 0 ETH cannot move — the UI must surface this before the user hits a failed send.31- Incoming/outgoing movements are observed via the contract's `Transfer(from, to, value)`32 event logs, which is what explorers index for token history.3334## 2. Issuance models (why "stable")3536| Model | Example | Peg mechanism | Trust assumption |37|---|---|---|---|38| Fiat-backed, centralized | **USDC, EURC** (Circle), **USDT** (Tether) | 1:1 reserves (cash + T-bills); mint/burn on deposit/redemption | Issuer solvency + banking rails |39| Crypto-collateralized | **DAI** (MakerDAO/Sky) | Overcollateralized vaults + rates | Protocol governance + collateral quality |40| Bridged representation | **USDbC** (Base) | Lock on L1, mint wrapped on L2 | The bridge, **plus** the original issuer |4142Key operational facts for fiat-backed coins (USDC family):4344- The contract is an **upgradeable proxy**. Verified: Base USDC is Circle's45 `FiatTokenProxy` (AdminUpgradeabilityProxy pattern, implementation at46 `0x2ce6311ddae708829bc0784c967b7d77d19fd779`, upgradeable by Circle's admin key).47 Behavior can change under the same address — one more reason a wallet should treat48 token metadata (decimals) as per-token config, verified on-chain, not assumptions.49- The issuer can **blocklist addresses** (`blacklist(address)` in FiatToken). Funds at a50 blocklisted address are frozen at the contract level. Self-custody protects keys, not51 against issuer-level freezing — worth knowing, nothing for the wallet to implement.52- Some support gasless-approval extensions (EIP-2612 `permit`, EIP-300953 `transferWithAuthorization`). v1 uses plain `transfer` only.5455## 3. Decimals — the #1 bug source (verified live)5657**Decimals differ per token.** A wallet must never hardcode 6.5859| Token | Base mainnet address (chain 8453) | Decimals | Notes |60|---|---|---|---|61| USDC | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` | **6** | Native Circle issue — the default token |62| EURC | `0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42` | **6** | Circle euro coin |63| USDT | `0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2` | **6** | Tether on Base |64| DAI | `0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb` | **18** | ⚠ 18, like ETH — not 6 |65| USDbC | `0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA` | **6** | Legacy bridged USDC; receive/display only, discourage new use |6667Testnet (Base Sepolia, chain 84532): USDC `0x036CbD53842c5426634e7929541eC2318f3dCF7e`,68decimals **6** (verified). Circle faucet: https://faucet.circle.com (20 USDC / 2 h);69gas ETH from a Base Sepolia faucet.7071Rules the code enforces:72- All amounts are `BigUInt` base units internally; `Double` is banned for money.73- Conversion display ⇄ base units goes through per-token `decimals` from the registry74 (`TokenAmount`), unit-tested for 6- and 18-decimal tokens.75- ETH (gas) is 18 decimals; wei ⇄ ETH uses the same integer math.7677## 4. Chain facts (verified live)7879- Base mainnet `eth_chainId` → `0x2105` = **8453**; Base Sepolia → `0x14a34` = **84532**.80- Base is an OP-stack L2: EIP-1559 fees (`maxFeePerGas` / `maxPriorityFeePerGas`),81 tips are tiny (fractions of a gwei), an ERC-20 transfer costs well under a cent.82- The signed transaction embeds the chain ID (EIP-155/1559) — replay protection across83 Sepolia/mainnet is automatic as long as the wallet signs with the selected network's ID.8485## 5. What this means for OS Vault's design8687- A **token registry** (`Token` model) instead of a hardcoded USDC constant: symbol,88 name, per-network contract address, decimals. v1 ships USDC as default with EURC,89 DAI, USDT registered; adding a stablecoin is a registry entry, not a code change.90- Per-token balance = one `eth_call` each; ETH balance fetched alongside for the gas91 warning ("not enough ETH to send").92- Send flow is token-generic: encode `transfer(to, amount)` with the token's address93 and decimals; everything else (nonce, fees, signing, receipt polling) is identical94 across stablecoins.95