Build a Fresh DeFi Wallet with Minimal Blast Radius
Why a clean wallet matters on day zero
Spinning up a brand-new wallet is the easiest win for controlling downside, but only if you treat it like production infra: isolate keys, minimize browser footprint, and cap spend under $1k until you prove the setup. This note covers how I stand up a fresh wallet with small, repeatable steps and what I check before the first approval.
Designing a minimal blast radius setup
- Wallet type: Start with a hardware wallet for signing and a separate hot wallet only for dApp approvals; keep balances on the hardware-controlled address. This keeps drained hot-wallet scenarios capped to hundreds of dollars instead of the full stack.
- Seed hygiene: Generate the mnemonic fully offline using a live OS image and avoid cloud backups. BIP-39 mnemonics are deterministic, so a single compromise leaks every child key derived from the seed. BIP-39 spec{target="_blank" rel=“noopener noreferrer”}
- Derivation path discipline: Stick to
m/44'/60'/0'/0/0unless you intentionally separate accounts; it’s the default Ethereum path defined by BIP-44. BIP-44{target="_blank" rel=“noopener noreferrer”} - Browser hygiene: Use a dedicated browser profile with only the wallet extension installed, disable auto-approve popups, and clear caches after any signing session.
- Network layout: Keep the funding source on a different wallet; bridge or transfer only what you intend to risk (e.g., $200 for early testing) and leave the rest cold.
Python script to generate and verify an offline wallet
I keep a tiny Python helper to create a mnemonic offline, derive the first address, and stash the public data in a text file. Run it from an air-gapped session (live USB or Tails), then move only the address and xpub to your online machine.
# pip install eth-account==0.11.3
from eth_account import Account
from pathlib import Path
import secrets
Account.enable_unaudited_hdwallet_features()
# Generate 128 bits of entropy for a 12-word seed; bump to 24 words by using 256 bits
entropy = secrets.token_bytes(16)
account, mnemonic, seed = Account.create_with_mnemonic(extra_entropy=entropy)
# Derive the default Ethereum account at m/44'/60'/0'/0/0
child = Account.from_mnemonic(mnemonic, account_path="m/44'/60'/0'/0/0")
output = Path("fresh-wallet-public.txt")
output.write_text(
f"address: {child.address}\n"
f"mnemonic_words: {len(mnemonic.split())}\n"
f"pubkey: {child.key_public.hex()}\n"
)
print(f"First address: {child.address}")
print(f"Wrote public details to {output}")
# Write the mnemonic by hand; do not store it in any file
eth-account exposes the BIP-32/44 helpers so the derived address matches what Ledger, Trezor, and most browser wallets expect. eth-account docs{target="_blank" rel=“noopener noreferrer”} I run the script once, write down the words, and destroy the USB session. On the online machine I only import the address for monitoring.
Where fresh wallets still fail
- Supply-chain risk: A tampered hardware wallet or USB image leaks the mnemonic during setup. Buy direct from vendors and verify firmware hashes when provided.
- Clipboard and screen leaks: Keyboard loggers and screenshot tools on the host OS can capture seed words; use an offline machine with no persistence to avoid latent malware.
- Extension sprawl: Installing unrelated extensions reintroduces injection risk. Keep the DeFi profile barren and revoke permissions regularly.
- QR and NFC transfers: Copying mnemonics via QR or NFC makes them scannable by cameras. Stick to pen-and-paper and never photograph the words.
Practical takeaways before funding
- Keep the working balance small (a few hundred dollars) until you’ve run an approval-revoke cycle and a test swap on an L2.
- Store the mnemonic physically, add a short passphrase if supported, and record the derivation path alongside it.
- Maintain a separate “cash-out” address that never touches dApps; route profits there after each batch of tests.
- Tag every transaction in a block explorer so you can audit which wallet did what without cross-contaminating keys.