Building the ZERA Wallet for desktop, iOS, and Android
Three platforms, one shielded pool, one design system. The trade-offs of building a wallet that has to feel like cash on a phone, like a tool on a laptop, and the same on both.
- FROM
- Dax the Dev <[email protected]>
- SOURCE
- https://blog.skill-issue.dev/blog/zera_wallet_three_platforms/
- FILED
- 2026-03-25 05:13 UTC
- REVISED
- 2026-03-25 05:13 UTC
- TIME
- 6 min read
- SERIES
- Wallets I've Wired
- TAGS
Most wallet posts start with the cryptography. This one starts with the part that is harder.
The cryptography is solved. We have Pedersen commitments, nullifiers, Groth16 proofs that run in human-tolerable time, and a SDK with the right asymmetric MCP surface. The hard problem is the one that does not appear in any cryptography paper: how do you make a wallet that feels like cash on a phone, like a tool on a laptop, and the same product on both?
This is the part of Zera Wallet that nobody quotes the marketing copy of. It is also the part that takes the most code.
Three platforms is two too many — except it isn’t
The temptation when you launch a wallet in 2026 is to ship “mobile first” and let the desktop experience be a responsive cousin. There is a real argument for this: the median crypto user holds their assets on a phone, the offline-P2P story is a phone story (you tap two phones, you don’t tap two laptops), and the mobile design constraints force discipline.
We almost did that. The reason we didn’t is the user we kept seeing in customer development: the operator.
Operators are the people who run the treasury for a Zera-using business, who hold the cold-storage keys, who reconcile the books at end-of-quarter. They live on laptops. They want a wallet that gives them dense information — a real table of unspent notes, sortable, filterable, exportable to CSV. They are not an edge case; they are the customer who pays.
So we ended up with the same product on three platforms with deliberately different information density:
- Mobile — single-task, gesture-driven, big tap targets, NFC pairing for offline P2P, Face ID / biometrics gate on every send.
- Desktop — multi-pane, keyboard-first, dense tables, hardware-key signing, multi-account view, CSV export.
- iOS / Android — same Mobile UX, native share sheets, native NFC stack, platform-specific Secure Enclave integration.
The thing that makes this tractable is that the primitives underneath are identical. Same shielded pool. Same SDK. Same Merkle tree. The wallet is just three different lenses over the same state.
The reference UX lives in zera-wallet-demo
Before the production wallet got a single line of code, the zera-wallet-demo repo was running. It was — and still is — the canonical reference for what the wallet should feel like. From the v3 ZKP commit log it is clear we iterated on the mental model in the demo dozens of times before committing to the production shape.
The demo’s package.json is a fair list of the bets we made:
"dependencies": {
"@solana/wallet-adapter-react": "^0.15.35",
"@solana/wallet-adapter-react-ui": "^0.9.35",
"@solana/web3.js": "^1.98.0",
"framer-motion": "^11.11.17",
"lucide-react": "^0.468.0",
"react": "^19.0.0",
"react-router-dom": "^7.1.0",
"sonner": "^1.7.1",
"tailwind-merge": "^2.6.0",
"zeraswap-sdk": "workspace:*"
}
A few of those choices deserve specific defence.
React 19 was not the easy call
React 19 was barely a year old when we started, and the wallet ecosystem on Solana is full of libraries that were tested against React 17 and 18 and quietly assume hooks behave a specific way. We took the upgrade hit because the Server Components story changes how we think about this is sensitive data, do not render it client-side — even though we mostly use it from the client side, the discipline of marking which components touch keys and which do not made the security model cleaner.
framer-motion for trust signals
You do not normally find a high-end animation library in a wallet codebase. We use it for one specific thing: the “send confirmed” state.
The transition between “you have authorised this send” and “this send is final on chain” is a moment of maximum user anxiety. A jarring instant flip from a button to a checkmark looks like the app glitched. A 350ms eased fade-in with the prior state visible underneath, settling into a green check, looks like the app is doing something. The animation is the trust signal. framer-motion makes that easy to ship and impossible to do badly.
We honour prefers-reduced-motion everywhere. The animation is decoration, not load-bearing.
sonner for toasts
Most toast libraries on React are ugly or overengineered. sonner is what happens when someone with taste shipped a toast library and called it done. The fact that it stacks gracefully and gets out of the way is the entire pitch.
lucide-react for icons, no exceptions
Across the entire Zera codebase — wallet, SDK, zera-med, zeraswap, even this blog — every single icon is from Lucide. One pack, one stroke weight, one optical alignment. This is the kind of decision that costs nothing to make in week one and is impossible to retrofit in year two.
The mobile drawer, ported
You can see the design language travel between repos in the responsive HUD work on zera_med_demo — the mobile drawer that ships in zera-wallet-demo is the same component, give or take a tag, that we shipped in the medical-records demo two months earlier. That is what a design system is for. Not the Tailwind tokens, not the icon pack, but the muscle memory of “we have already solved ‘phone with a sidebar that needs to also work on desktop.’”
What the production wallet adds
The demo is the lab. The production wallet adds three things the demo deliberately does not:
- Hardware-key signing — Ledger and (TODO: Dax confirm — Trezor support is in progress) — for the operator desktop case. The demo signs entirely in the browser; the production app refuses to broadcast a transaction whose proof was constructed without a hardware-signed approval.
- Native iOS / Android shells — TODO: Dax confirm exact framework choice (Tauri vs. React Native vs. native). The demo runs in a browser; the production app needs Secure Enclave access and the platform NFC stack, which means a real native shell.
- Compliance hooks — for the venues that need them. ZERA is token-agnostic and venue-flexible. The wallet has a clean integration point for permissioned KYC layers without making them mandatory for the protocol. Reasonable people can disagree about how much compliance belongs at the wallet edge; we ship the surface and let the customer choose.
The question I get asked the most
Why a wallet at all? Isn’t ZERA an SDK story?
The SDK is for developers. The wallet is for everyone else. You cannot ship privacy as a primitive that only protocol engineers can integrate. If we want a unified shielded pool to be the default for stablecoin transfers in 2027, the on-ramp has to be a wallet you can hand to your accountant, your sister, and an autonomous AI agent — and it has to feel obvious to all three.
The wallet is the product. The SDK is the contract.
Further reading
- zera-wallet-demo on GitHub — the reference UX
- wallet.zeralabs.org — production landing
- Zera Wallet v3 ZKP — the commit log
- The MCP server inside zera-sdk
- A Privacy Demo That Works on a Phone — sibling design work
- Solana Foundation, Wallet Standard — the React-side wallet-adapter contract