Case study
Hoogii Wallet
Frontend Engineer · Hashgreen Labs · 2022 – Present
A browser-extension wallet for the Chia blockchain — send, receive and manage XCH and CATs, and review transaction history.
- React
- Tailwind CSS
- Mobx
- react-i18next
- Ably

Landing page
Typing a wallet into existence
A 12-cell backup-phrase grid that validates each word as you type and accepts a whole pasted phrase.
I built the custom phrase input, and made the call to open create and import in a full browser tab instead of the 360-pixel popup — key material deserves room to breathe.
The grid is a react-hook-form useFieldArray. A cell turns red only when it is non-empty and missing from the BIP39 wordlist_en.json set — a membership test, never a network call. Paste splits on spaces and distributes across the remaining cells.
Mirrors src/components/Mnemonic.tsx · src/store/MnemonicStore.ts
Finding a token before you finish typing it
A typo-tolerant fuzzy filter over the CAT list, tuned strict so it behaves like asset selection, not web search.
I owned the search surface and the tuning call: threshold 0.1 on purpose, plus a multi-token $and query so "usd coin" narrows instead of widening.
Search is fuse.js configured in utils/fuse.ts with threshold: 0.1 and includeScore. The query is rebuilt as an $and of tokens, each an OR across the keys, then results sort by score. It runs over an already-live array — never a fetch per keystroke.
- XCXCHChia
- SBSBXSpacebucks
- DBDBXdexie bucks
- MRMRMTMarmot
- WOWOLFChia Wolf
- USUSDSStably USD
- TITIBETTibetSwap
- WUWUSDCWrapped USDC
- HOHOAHoge Ants
- CACATCCool Cats
- GOGOATChia Goat
- PEPENGPenguin
Mirrors src/utils/fuse.ts · src/components/SearchBar.tsx
The ledger that updates itself
History and balances that update themselves — an incoming coin appears on its own, no polling.
This was the realtime layer I built — a channel per wallet, refetch on message, and enough defensiveness that a flaky socket degrades quietly instead of taking the screen down.
The channel name is literally '0x' + puzzleHash, so each wallet listens only on its own hash. Ably.tsx is a try/catch around useChannel that returns an empty fragment — a dropped socket logs and the UI stays up.
- +2.0 XCHReceivedfrom xch1c8d…44fa
- -0.75 XCHSentto xch1a91…09be
- +5.0 XCHReceivedfrom xch1f30…7c2d
Mirrors src/components/Ably.tsx
What locking actually does
Encrypt the phrase at rest, require the password to unlock, auto-lock when the machine goes idle.
I owned the lock lifecycle: the seed is never decrypted at rest, the password lives only in ephemeral session memory, and auto-lock leans on the browser's own idle signal rather than a timer I'd have to babysit.
encryption.ts derives an AES-GCM key with PBKDF2 · SHA-512 · 100,000 iterations · 32-byte salt. Auto-lock hooks chrome.idle.setDetectionInterval, not setInterval — real input resets the countdown.
Mirrors src/utils/encryption.ts
When a website asks the wallet to sign
A website asks the wallet to sign; the request crosses three script contexts and comes back a signature.
This is the core of what I owned — built with one other engineer: inject the provider, relay every request across page, content and background, gate by lock and connection state, then show a signature a human can actually read.
A request crosses three contexts. The injected script exposes window.chia.hoogii; the background enforces a fixed gate order — IS_VALID_WALLET → IS_LOCK → IS_CONNECTED — before forwarding. signCoinSpends raises the popup, which renders the spend bundle as an expandable tree via react-json-view-lite.
- IS_VALID_WALLET
- IS_LOCK
- IS_CONNECTED
- idle — trigger a signing request to trace it.
Mirrors src/injected-scripts/index.ts · src/background/index.ts · src/popup/


