# CLAUDE.md — Zyquo Wallet ## Project Overview **Zyquo Wallet** is a native **macOS** self-custody stablecoin wallet built in **Swift + SwiftUI**. It manages **real on-chain USDC** on **Base** (Ethereum L2 by Coinbase — chosen for sub-cent gas fees and native Circle-issued USDC). This is NOT a mock/demo app. Every feature must work against a real blockchain: 1. Real HD wallet creation (BIP-39 mnemonic → secp256k1 keys) 2. Receive USDC (real address + QR code) 3. Send USDC (real signed ERC-20 transfers broadcast via JSON-RPC) **Development rule: build and test everything on Base Sepolia testnet first. Mainnet support is enabled last, behind an explicit network switch.** --- ## Tech Stack | Layer | Choice | |---|---| | Language | Swift 5.10+ (strict concurrency where possible) | | UI | SwiftUI, macOS 14+ target | | Project | Xcode project, dependencies via Swift Package Manager only | | Web3 | `web3swift` (https://github.com/web3swift-team/web3swift) — actively maintained, macOS support, BIP-39/BIP-32, ERC-20, EIP-1559 | | QR codes | CoreImage (`CIQRCodeGenerator`) — no extra dependency | | Key storage | macOS Keychain (Security framework) | | Persistence | UserDefaults for settings; no database needed for v1 (history read from chain/explorer API) | If `web3swift` causes build issues on macOS, fallback is `argentlabs/web3.swift`. Never mix both. --- ## Network Configuration (source of truth) ```swift enum Network { case baseSepolia // DEFAULT during development case baseMainnet } ``` | | Base Sepolia (testnet) | Base Mainnet | |---|---|---| | Chain ID | 84532 | 8453 | | RPC (dev) | https://sepolia.base.org | https://mainnet.base.org | | USDC contract | `0x036CbD53842c5426634e7929541eC2318f3dCF7e` | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` | | Explorer | https://sepolia.basescan.org | https://basescan.org | | Faucets | Circle faucet (https://faucet.circle.com — 20 USDC / 2h) + Base Sepolia ETH faucet for gas | — | - **USDC has 6 decimals.** Always convert with `BigUInt` / decimal math — never `Double`. 1 USDC = 1_000_000 base units. - Public RPCs are fine for dev. For production, make the RPC URL configurable (Alchemy/QuickNode) via Settings — but never require an API key to run the app. - Gas is paid in **ETH**, not USDC. The UI must clearly warn when the ETH balance is too low to send. --- ## Architecture MVVM + service layer. Keep views dumb; all blockchain logic lives in services. ``` ZyquoWallet/ ├── App/ ZyquoWalletApp.swift, AppState (ObservableObject / @Observable) ├── Models/ Wallet, Transaction, Network, TokenAmount ├── Services/ │ ├── KeyManager.swift // mnemonic gen/import, keystore, Keychain I/O │ ├── WalletService.swift // address derivation, wallet lifecycle │ ├── RPCService.swift // web3 provider, network switching │ ├── BalanceService.swift // ETH + USDC balances, polling every 15s │ └── TransactionService.swift// build, estimate gas, sign, send, track receipt ├── ViewModels/ OnboardingVM, HomeVM, SendVM, ReceiveVM, SettingsVM ├── Views/ │ ├── Onboarding/ Welcome, CreateWallet (show + verify mnemonic), ImportWallet │ ├── Home/ Balance card, tx list, Send/Receive buttons │ ├── Send/ recipient + amount + gas preview + confirm sheet │ ├── Receive/ address display, QR, copy button │ └── Settings/ network switch, RPC override, export seed (auth-gated), reset └── Utils/ formatting, address validation (EIP-55), errors ``` - Use `async/await` everywhere; wrap web3swift callbacks if needed. - One `AppState` injected via `.environment`; services are protocol-backed for testability. - Every service method that touches the network returns typed errors (`WalletError` enum), never `fatalError`. --- ## Security Requirements (non-negotiable) 1. **Mnemonic & private key never leave the device.** No analytics, no telemetry, no network calls except JSON-RPC and explorer API. 2. Store the mnemonic (or derived keystore) in **Keychain** with `kSecAttrAccessibleWhenUnlockedThisDeviceOnly`. Never in UserDefaults, files, or iCloud. 3. Gate seed export and every send behind **LocalAuthentication** (Touch ID / password). 4. **Never log** mnemonics, private keys, or signatures. Never put them in error messages. 5. Never hardcode keys, mnemonics, or API keys anywhere in the repo — including tests. Test wallets are generated at runtime. 6. Onboarding must force a **mnemonic backup verification** step (user re-enters 3 random words) before showing the wallet. 7. Validate recipient addresses: hex format + EIP-55 checksum. Warn (don't block) on all-lowercase addresses. 8. Enable **App Sandbox** (with network client entitlement) and **Hardened Runtime**. 9. Confirm screen before send must show: recipient, amount, network, estimated gas, and total — no one-tap sends. 10. If the clipboard is used for addresses, never auto-paste into the recipient field without user action (clipboard-hijack protection). --- ## Feature Phases (implement in order, each must compile + pass tests) ### Phase 1 — Wallet core - Generate 12-word BIP-39 mnemonic, derive account at `m/44'/60'/0'/0/0` - Import from mnemonic - Persist via KeyManager/Keychain; app relaunch restores wallet - Onboarding flow with backup verification ### Phase 2 — Receive - Show checksummed address, copy button, QR code (address only, no `ethereum:` URI needed for v1) ### Phase 3 — Balances - ETH balance (`eth_getBalance`) + USDC balance (`balanceOf(address)`) - Poll every 15s while app is active; manual refresh; loading/error states ### Phase 4 — Send USDC - ERC-20 `transfer(to, amount)` with EIP-1559 fees - Gas estimation shown in ETH + rough USD - Sign locally, broadcast, then poll `eth_getTransactionReceipt` until confirmed - Pending/confirmed/failed states surfaced in UI, link to BaseScan ### Phase 5 — History & polish - Transaction list from BaseScan API (USDC transfers in/out) with graceful fallback if API unavailable - Network switcher (Sepolia ⇄ Mainnet) with a prominent "TESTNET" badge on Sepolia - Settings: custom RPC, export seed (auth-gated), delete wallet (typed confirmation) --- ## Build & Test Commands ```bash # Build xcodebuild -scheme ZyquoWallet -destination 'platform=macOS' build # Tests xcodebuild -scheme ZyquoWallet -destination 'platform=macOS' test ``` - Unit-test: mnemonic → address derivation (against known BIP-39 test vectors), USDC decimal conversion, address validation, amount formatting. - Never write tests that send real mainnet funds. Network-dependent tests run against Base Sepolia and must be skippable offline. ## Manual test checklist (Base Sepolia) 1. Create wallet → relaunch app → same address restored 2. Fund via Circle faucet (USDC) + Base faucet (ETH for gas) 3. Balance appears within one polling cycle 4. Send 1 USDC to a second test wallet → confirm on sepolia.basescan.org 5. Import that second wallet from mnemonic → balance matches --- ## Conventions & Pitfalls - All amounts are `BigUInt` in base units internally; convert to display strings only at the UI edge. - USDC = 6 decimals, ETH = 18 decimals. Mixing these up is the #1 bug source. - Chain ID must match the selected network in every signed transaction (replay protection). - Handle RPC failures with retry + user-visible error; the app must never crash on network errors. - Comments and UI strings in English; French localization can come later via String Catalog. - Small, focused commits per phase. Do not start a later phase before the earlier one builds and passes tests.