Project Deep-Dives
Three projects, built in sequence: a mainnet portfolio dashboard, a testnet swap interface, and a staking vault with its own ERC-20. This page covers the engineering decisions — what was chosen, what was deliberately not chosen, and why. Everything is live and the code is public.
The first decision was the one most demo dApps get wrong: this is a read-only mainnet lookup, not a wallet-connect app. Real portfolio tools — Zapper, DeBank — work exactly this way: you inspect any address without connecting anything. That choice avoids the empty-wallet demo problem (a testnet wallet-connect demo shows a reviewer their own empty wallet and asks them to fight a faucet first), and it forces the harder engineering problem: handling arbitrary addresses holding hundreds of unknown tokens, including the spam and dust every real wallet accumulates. Spam is filtered server-side; anything without a real price feed is segregated from the portfolio math instead of quietly inflating it, and when a whale wallet exceeds the scan cap, the API returns a truncated flag and the UI says the total may be incomplete rather than presenting a partial number as truth.
Every call to Alchemy (balances, token metadata) and CoinGecko (pricing) runs server-side only — the browser talks to one internal endpoint and the API keys never appear in a client bundle or network tab. Pricing is queried by contract address, not by symbol: ERC-20 symbols aren't unique, so symbol mapping silently mixes up duplicate tickers and invents prices for unlisted tokens. Contract-address lookups make identity unambiguous — a token CoinGecko doesn't track comes back empty and is treated as unpriced, never guessed.
Both providers run on free tiers, so a caching layer sits in front of them: an in-memory TTL cache with in-flight request deduplication, batched lookups so a 50-token wallet costs a handful of upstream calls rather than a hundred, a per-IP rate limiter, and a timeout on every upstream fetch so one hung socket can't stall a serverless instance. The same interface would take Redis for production scale — the point is that rate-limit awareness was designed in, not bolted on.
// src/lib/cache.ts — concurrent lookups share one upstream call
async getOrSet<T>(key: string, ttlMs: number, fn: () => Promise<T>) {
const cached = this.get<T>(key);
if (cached !== undefined) return cached;
const pending = this.inFlight.get(key);
if (pending) return pending as Promise<T>; // dedup the stampede
const promise = fn()
.then((value) => { this.set(key, value, ttlMs); return value; })
.finally(() => this.inFlight.delete(key));
this.inFlight.set(key, promise);
return promise;
}The swap integrates Uniswap v3's deployed contracts instead of a custom AMM, and that's the point. Writing a toy constant-product pool is a tutorial exercise; most real front-end and full-stack Web3 roles need the other skill — integrating battle-tested protocols correctly. That means the unglamorous specifics: SwapRouter02's parameter struct differs from the original router (no deadline field), quotes come from QuoterV2's revert-based simulation path, native ETH needs the router's payable wrap-in-flight path going in and a multicall that bundles the swap with unwrapWETH9 coming out, and slippage tolerance has to become an on-chain amountOutMinimum, not just a display number. Every contract address was verified on-chain before being committed, and the exact multicall calldata the UI produces was simulated against the live router before a user ever signed it.
The wallet layer is RainbowKit, Wagmi, and viem — and it was deliberately scoped as the second project, not the first. Wallet-connect is where a signature is actually required; leading with it would have made the flagship demo worse (see project one). Two implementation details carried over from the dashboard's discipline: price quotes are fetched server-side through the same keys-stay-on-the-server rule — the wallet only signs and submits — and displayed quotes auto-refresh every 15 seconds but freeze the moment the wallet prompt opens, so the numbers on screen always match the calldata being signed.
// src/lib/swap/quoter.ts — QuoterV2 runs server-side; keys stay here
const { result } = await client().simulateContract({
address: UNISWAP.QUOTER_V2,
abi: quoterV2Abi,
functionName: "quoteExactInputSingle",
args: [{ tokenIn, tokenOut, amountIn,
fee: POOL_FEE, sqrtPriceLimitX96: 0n }],
});
const [amountOut, , , gasEstimate] = result;Staking Vault & Bochi Credits
This is the end-to-end piece: a custom ERC-20 (Bochi Credits, BWC) and a staking vault, written in Solidity with Foundry, deployed to Sepolia, and verified on Etherscan so the source is publicly provable, not just claimed. The token ships with a public faucet — 100 BWC per address per day — because a staking demo a reviewer can't actually try is a screenshot. Anyone can mint tokens, stake, and watch 10% APR accrue per second in the UI, with nothing to ask for and nothing at stake: BWC is explicitly a valueless testnet token.
The core design trade-off is documented in the contract's own NatSpec: rewards are minted on claim (the vault holds a minter role on the token) rather than paid from a pre-funded reward pool. For a faucet-fed demo token this is the honest choice — staked principal is never touched to pay yield, and the vault can't run dry mid-demo. The production-scale alternative is schedule-funded distribution in the Synthetix style, where a finite reward budget is streamed over a period with global accumulator accounting; that's more machinery than a fixed-APR demo needs, and knowing which pattern fits which situation is the actual skill. Accrual here is per-position (staked × rate × elapsed), snapshotted on every interaction, with floor rounding always in the protocol's favor.
The review process is a better story told accurately than inflated: the contracts went through an adversarial review in which 22 independent agents attacked them — reentrancy, access-control chains, donation attacks, overflow at extreme values — and every claimed vulnerability was independently verified rather than taken at face value. No security defects survived that verification. What did survive were two real gaps in the test suite, found by mutation testing: reviewers deliberately broke the contract to see whether the tests noticed. A mutant that emitted wrong event amounts passed everything (there were no event assertions), and a mutant that broke exit()'s claim-after-full-withdraw branch also passed (that path was untested). Both gaps got targeted tests that now kill those exact mutants.
// the test that kills the mutant the review found
function test_ExitClaimsFrozenRewardsAfterFullWithdraw() public {
vm.prank(alice); vault.stake(100e18);
vm.warp(block.timestamp + YEAR);
vm.prank(alice); vault.withdraw(100e18); // rewards frozen, staked 0
vm.warp(block.timestamp + 30 days);
vm.prank(alice); vault.exit(); // must still claim
assertEq(token.balanceOf(alice), 110e18);
}