A minimal but real BDK-powered Bitcoin wallet: create/import a seed, derive a BIP84 SegWit wallet, show balance, generate a receive address + QR code. Runs on desktop, Android, and iOS from the same codebase via Dioxus.
Honesty check: this was written and reviewed against the documented
APIs of bdk_wallet, bdk_file_store, bdk_esplora, bip39, and
dioxus, but it has not been compiled — the environment that produced
it has no internet access to fetch crates. Treat this as a strong,
carefully-written first draft, not a verified build. The first time you
run cargo build, expect to fix a handful of small things (an import
path, a method that got renamed in a point release). That's normal Rust
development against fast-moving crates, not a sign something's
fundamentally wrong. Paste me the compiler error if you get stuck — that's
the fastest way for me to fix it exactly.
core/src/
lib.rs — module wiring
mnemonic.rs — generate/parse a 12-word seed, derive BIP84 descriptors
wallet.rs — open-or-create wallet, receive address, balance, persist
chain.rs — sync balance/history from an Esplora server (testnet)
ui/src/
main.rs — app state, screen routing
screens/welcome.rs — "Create New Wallet" / "Import Existing Wallet"
screens/seed_backup.rs — "write these words down" screen
screens/home.rs — balance + Receive button (Send is stubbed)
screens/receive.rs — address + QR code
What's not included yet, on purpose (see "Next steps" below): sending transactions, Lightning, biometric lock, encrypted-at-rest storage, and anything mainnet-related. Build and test the above first — it's the foundation everything else sits on.
mkdir rustwallet && cd rustwallet
# Core logic crate
cargo new --lib core
# UI crate — let Dioxus's own CLI generate this, don't hand-write it.
# When prompted, pick the blank/minimal template.
dx new ui
cd ui && dx new . --platform mobile 2>/dev/null; cd .. # if dx new asked for a platform separately, this covers it — otherwise ignore(If dx new ui already asked you to choose a platform interactively,
just pick "mobile" or "desktop" — you can run on any platform afterward
regardless of what's marked default.)
Replace whatever Cargo.toml exists at the rustwallet/ root with:
[workspace]
members = ["core", "ui"]
resolver = "2"(This file is also provided at the root of this download — just copy it in.)
cd core
cargo add bdk_wallet@2.3.0
cargo add bdk_file_store
cargo add bdk_esplora --features blocking
cargo add bip39
cargo add anyhow
cd ../ui
cargo add core --path ../core
cargo add qrcodecargo add resolves versions that are actually compatible with each
other today — typing version numbers into Cargo.toml by hand is how you
end up with a dependency graph that doesn't resolve.
Copy every file from this download's core/src/ over what cargo new
generated, and every file from ui/src/ (including the whole
screens/ folder) over what dx new generated. Overwrite freely — the
generated boilerplate isn't needed.
cd rustwallet
dx serve --platform desktop # fastest loop — start here
dx serve --platform android # once desktop works
dx serve --platform ios # macOS only- Launch the app → "Create New Wallet"
- Write down (mentally, this is testnet) the 12 words → confirm
- You land on Home with a
0sats balance — this is a brand-new wallet, nothing's synced yet - Tap Receive → you get a real testnet address + QR code
- Get testnet coins to that address from a faucet (search "bitcoin
testnet faucet") and you'll want to wire up
core::chain::full_scanfrommain.rsto actually see the balance update — that's wired as a library function but not yet called from a UI button. That's a good first thing to add yourself, or ask me to add a "Refresh" button that calls it.
- Wire up the sync button — call
core::chain::full_scanfrom a "Refresh" button on Home, usingcore::chain::endpoints::MEMPOOL_SPACE_TESTNET. - Build the Send screen —
bdk_wallet'sTxBuilder(viawallet.build_tx()) handles coin selection and PSBT construction; you'll sign withwallet.sign()and broadcast via the same Esplora client used for syncing. - Encrypt the seed at rest — right now nothing encrypts
wallet.dbor the mnemonic. Before this touches real money, addaes-gcm+argon2(derive a key from a user PIN) around however you persist the mnemonic, or better, push the mnemonic into the OS Keychain / Android Keystore via Dioxus's native FFI bridge instead of storing it yourself. - Biometric unlock — small native Swift/Kotlin snippet, called through Dioxus.
- Lightning — add
ldk-nodeas a new module incore, once on-chain send/receive is solid. Don't start here. - Only after all of the above, and ideally a security review — consider a mainnet build.
This handles real cryptographic key material once you give it a real
seed. Keep all development on testnet (already the default in
main.rs) until send, receive, and restore-from-seed have all been
exercised repeatedly and you understand exactly what every line of
wallet.rs does. Don't point this at mainnet funds without a security
review — losing a private key in a wallet app means losing money,
permanently, with no recourse.