# Aether: Building a Wallet I'd Actually Trust

> Build log · Month 2 of 12

self-custody engineering. Keys, cost-basis P&L, RBF, curve-only pricing, the DeFi knowledge graph — and the paranoia of holding someone's money.

Day 31–60 · 30 posts · Building in public as @caelum0x4

**Day 31** — Starting the Aether deep-dive. It's a self-custody, multi-chain wallet. Rule zero: the keys never leave the device and I can *never* see them. If my server can recover your funds, so can whoever breaches my server. Non-custodial is the architecture, not a feature.

**Day 32** — Keys live in the secure enclave / keystore, encrypted at rest, unlocked by biometrics. The app holds a handle, not the raw key. I wrote a test that greps the whole codebase for the seed phrase crossing that boundary. It fails the build if it does.

**Day 33** — Multi-chain means every chain has its own rules for addresses, fees, and nonces, and they all disagree. I hid that behind a `ChainAdapter` interface: `getBalance`, `buildTx`, `estimateFee`, `broadcast`. Add a chain = write one adapter, touch nothing else.

**Day 34** — Cost-basis P&L is the feature I'm proudest of and it's brutal. "How much did I make" needs every historical buy, the price then, fees, and a lot-management method. I went FIFO by default. Getting the *acquisition price* right is 90% of the work.

**Day 35** — Bug: my P&L counted a wallet-to-wallet self-transfer as a taxable sell + rebuy, inventing fake gains. Fix: detect transfers between the user's own addresses and net them out. On-chain, "a transaction moved" and "you sold something" are very different claims.

**Day 36** — Pricing call that surprised people: Aether uses curve-only pricing for historical P&L marks, no centralized feed. The on-chain liquidity curve is the price your trade actually executed against. A CEX ticker is a different market than the one you traded in.

**Day 37** — RBF (replace-by-fee) shipped: your tx is stuck, you bump the fee to replace the pending one. The trap: you must rebuild the *exact same* tx at the same nonce with a higher fee, or you've broadcast a second spend. Got the nonce reuse wrong once. Caught it in test.

**Day 38** — Also shipped cancel: replace the stuck tx with a higher-fee 0-value send to yourself at the same nonce. "Cancel" on a blockchain isn't delete — it's outrace. I label it "attempt to cancel," because a miner might still grab the original first.

**Day 39** — Thread: RBF speed-up and cancel, the parts that bite you.
  - A pending tx isn't gone, it's just unconfirmed. You can't delete it, only outbid it.
  - Speed-up = rebroadcast the SAME tx, same nonce, higher fee. Different nonce = double spend.
  - Cancel = replace it with a higher-fee 0-value self-send at that nonce.
  - Neither is guaranteed. The original can still confirm first. UI must say "attempt," not "done."
  - Test with a local node and low fees so txs get stuck on purpose.

**Day 40** — The scariest wallet bug: silent success. A broadcast fails but the UI says "sent," so the user thinks funds moved and they didn't. I treat "confirmed on-chain" as the only truth. Optimistic UI is fine for a like button, not for money.

**Day 41** — Started the DeFi token knowledge graph. Nodes = tokens, pools, protocols. Edges = "paired with," "wrapped version of," "deployed by." A wallet that only shows balances is a spreadsheet. One that knows relationships can warn you *before* you approve a rug.

**Day 42** — Why a graph, not a table, for token data: the interesting questions are relational. "What pools hold this?" "Is this real USDC or a lookalike one byte off?" Those are traversals, not row lookups. The shape of the data should match the shape of the questions.

**Day 43** — Token-spoofing defense: scammers deploy a token with the same name/symbol as a real one. The graph knows the canonical contract address per chain. If an incoming token's address doesn't match the known-good node, it gets flagged, not shown as the real thing.

**Day 44** — Fee estimation is a UX minefield. Too low, tx hangs for hours. Too high, you overpay. I show three tiers (slow/normal/fast) from the live mempool, plus a plain-language ETA, not just gwei. Most people don't know what gwei is and shouldn't have to.

**Day 45** — Approvals are where wallets betray people. An unlimited approval to a sketchy contract can drain a token forever. Aether defaults to *exact-amount* approvals and shows existing unlimited ones with a one-tap revoke. The safe default is the one you get without thinking.

**Day 46** — Hard-won nonce lesson: they're per-account, strictly sequential, and skip one and everything after it stalls. I keep a local nonce tracker but reconcile against the chain before every send. The chain is truth; my local guess is just a guess.

**Day 47** — Thread: the poison inputs every wallet has to survive.
  - Malformed / non-checksummed addresses. Reject, don't guess.
  - Dust attacks: tiny unsolicited tokens meant to trick or track you.
  - Tokens with 0 decimals and tokens with 30. Never assume 18.
  - Balances that look negative from overflow. Big integers, always.
  - Symbols spoofing real tokens. Trust the contract address, not the label.
  - Wallets crash on the weird 1% — which is exactly what an attacker sends on purpose.

**Day 48** — Decimals bug of the week: displayed a balance 10^6 too large because I assumed 18 decimals and this token used 6. Now decimals come from the token contract, never a default. On-chain, assuming is how you tell someone they're a millionaire by accident.

**Day 49** — Aether shows pending, confirmed, and *dropped* transactions as distinct states. Most wallets hide "dropped" and users assume the money's in limbo. Showing the ugly state is more honest and generates far fewer "where is my money" panics.

**Day 50** — Halfway-ish and a confession: I trust this wallet more than my ability to market it. It's the best-engineered thing in the fleet and possibly the hardest to get installed, because "download a new wallet" is a huge ask. Distribution, again.

**Day 51** — Added transaction simulation before signing: Aether dry-runs the tx against a forked state and shows what will actually change — which tokens leave, which arrive. Signing blind is how people get drained. See the outcome, *then* approve.

**Day 52** — The knowledge graph earned its keep: flagged a token whose contract was deployed by the same address as three known scams — a "deployer reputation" edge. No price data needed. The relationship was the signal. Balances-only wallets miss this attack.

**Day 53** — Backup UX is the hardest honest conversation in self-custody: lose your recovery phrase and I *cannot* help you, by design. I made the backup flow slow and deliberate on purpose. The one screen where friction is a feature, not a bug.

**Day 54** — Multi-chain balance refresh was hammering RPC endpoints into rate limits. Fix: batch calls per chain, cache aggressively, refresh on a backoff not on every screen focus. A wallet that DDoSes its own data provider gets its API key banned. Ask me how I know.

**Day 55** — Opinion: a wallet's job is to make dangerous actions feel dangerous and safe ones feel effortless. Most wallets do the opposite — signing a drain and sending $5 look identical. Aether's whole UX thesis is making risk *legible*.

**Day 56** — Cost-basis edge case: airdrops. You didn't buy the token, so basis is... what? I mark airdropped tokens with a $0 basis and a flag, so P&L doesn't pretend you spent money you didn't. Honest accounting beats flattering accounting.

**Day 57** — Wrote the account-model docs so future-me (and any auditor) can follow the key handling without reading every file. For a wallet, "trust me, it's secure" is worthless. The design has to be legible enough to be *checked*.

**Day 58** — Month 2 reality: Aether is the most capable app in the fleet and has approximately no users. A trustworthy wallet is a huge technical win and a brutal go-to-market. "It's more secure" doesn't move people who already tolerate a wallet they have.

**Day 59** — What Aether taught me that transfers to every app: define your source of truth and never let the UI lie about it. On-chain confirmation for the wallet, the test exit code for the build, the real metric for a milestone tweet. Optimism is not a data source.

**Day 60** — Wrapping the wallet month. Next: the thing that makes all of this safe to ship fast — verification. An agent that writes code and grades its own homework will hand you green checks over a burning building. Month 3 is about not getting fooled.

← [Month 1: The Machine That Builds the Apps](https://ereboslab.com/blog/month-1-the-machine-that-builds-the-apps/) · [Month 3: Verification Is the Product](https://ereboslab.com/blog/month-3-verification-is-the-product/) →

[← All build-log months](https://ereboslab.com/blog/)
