A Solana wallet that cannot connect to anything.
Better Wallet Pi is a buildable reference design for signing Solana via Keystone-style UR requests over QR — never over the wire. The online wallet shows an animated ur:sol-sign-request; the Pi scans it, renders the transaction or message for review, signs offline at m/44'/501'/i', and returns ur:sol-signature by QR. No driver. No data cable. No background sync.
- Primary path
- ● Solana (Keystone UR)
- Network surface
- 0 / 0
- Standards
-
BC-UR · Keystone Solana
SLIP-10 · BIP-39
- A
- 3.5″ SPI display
- B
- UR-capable camera
- C
- Confirm · GPIO 17
- D
- Deny · GPIO 27
Most hardware wallets ship with closed firmware,
a proprietary chip,
and a USB cable. This one ships with none of those.
-
01
No path in.
WiFi and Bluetooth are disabled in firmware. There is no USB data path. The device cannot phone home — there is no home to call.
-
02
Light in. Light out.
Animated UR fountain-encoded QR carries the request in. A second QR carries the Ed25519 signature out. Nothing else crosses.
-
03
No mystery chips.
Every line of the signing path is open-source Python on commodity silicon. Read it, run it, fork it, build it.
§ 02 / Architecture
Two devices. One photon-thick channel.
The signing flow is a closed loop with exactly one boundary. Bytes never cross it. Only the camera and the screen do — the payload is always an animated BC-UR fountain QR with a sol-sign-request (or legacy-compatible UR types the wallet emits).
Your laptop, in the world.
- 01
A Solana wallet builds the unsigned transaction or message and wraps it in a Keystone Solana sign request for the Pi camera.
- 02
It is rendered as animated QR fragments that fountain-decode into a single CBOR payload on the Pi.
- 06
Camera scans the signed result and broadcasts via the chain's normal path.
The Pi, in a Faraday calm.
- 03
Camera decodes the QR. UR fragments fountain-reassemble into a structured sign request.
- 04
Human-readable summary on the 3.5″ screen. SIGN or DENY by hand.
- 05
wallet.sol.sign returns a SolSignResult (64-byte Ed25519). The firmware encodes ur:sol-signature for the return QR.
§ 03 / Chain
Solana first. Air gap always. Keystone UR in. Signature UR out. No network stack.
The firmware speaks Keystone-style Solana sign requests: scan an animated ur:sol-sign-request, confirm on-device, and show ur:sol-signature for the online wallet to finish broadcast.
Solana
- Ed25519
- SLIP-10 · m/44'/501'/{i}'
- versioned transaction or off-chain message (wallet-supplied bytes)
- Keystone Solana UR · BC-UR fountain QR (ur:sol-sign-request)
- Solflare (Keystone flow) · Phantom (if Keystone Solana is exposed)
- ur:sol-signature (animated QR)
Solflare supports this device as a Keystone-class Solana signer today. Phantom compatibility depends on the extension exposing the same Keystone Solana UR path — the firmware side is aligned with ur:sol-sign-request / ur:sol-signature. Start with devnet or a throwaway mainnet key until you trust your build.
Solana docs ↗§ 04 / Source of truth
One signer. One Solana surface.
Verbatim from wallet/__init__.py. Every SolSignRequest passes through this method: path checks, optional pubkey binding, then Ed25519 signing. Anything else is a bug.
35
def sign(self, request: SolSignRequest):
36
if not isinstance(request, SolSignRequest):
37
raise ValueError(f"unsupported sign request type: {type(request).__name__}")
38
39
account_index = _account_index_from_path(request.derivation_path)
40
account = next((entry for entry in self.sol_accounts if int(entry["index"]) == account_index), None)
41
if account is None:
42
raise ValueError("requested signer derivation path is not available on this device")
43
44
if request.address:
45
requested_address = _derive.sol_bytes_to_address(request.address)
46
if requested_address != account["public_key"]:
47
raise ValueError("requested signer pubkey does not match derivation path")
48
49
signing_key = _derive.derive_sol_signing_key(self._mnemonic, index=account_index)
50
return _sol.sign(signing_key, request)
§ 05 / Cornerstones
Claims are easy to write. These are the structural decisions that make them hold.
We don't ask you to trust the marketing. We ask you to read the file path next to each claim, and verify it for yourself.
-
No WiFi. No Bluetooth. No USB data.
Network silicon is disabled at the firmware level via the Raspberry Pi config overlay. There is no driver path to enable it from userspace. CM4 no-wireless variants remove the silicon entirely.
config.txt :: dtoverlay=disable-wifi,disable-bt -
PIN-stretched encrypted mnemonic.
The keystore stores the BIP-39 mnemonic, encrypted with AES-256-GCM keyed by a scrypt-stretched PIN. The mnemonic is the single source of truth — an Ed25519 signing key for Solana is re-derived from it on unlock via SLIP-10. No private key persists on disk.
wallet/keystore.py · AES-GCM -
BIP-39 recovery, day one.
A 12-word mnemonic is generated on first boot and shown once for offline backup. Solana accounts derive at m/44'/501'/i' (SLIP-10 Ed25519). One backup seeds every signer slot the device exposes.
wallet/derive.py · BIP-32 + SLIP-10 -
Hard module isolation.
Only state/machine.py is permitted to import wallet/. The boundary is checked architecturally. Adding networking, USB, or Bluetooth code anywhere is a project-level rule, not a convention.
state/machine.py · sole importer -
Fully auditable.
Every line of the signing path is open-source Python: derivation in wallet/derive.py, UR typing in ur/, and Ed25519 signatures via pynacl. No closed firmware. No secure element you have to take on faith. Read it before you trust it.
github.com/BetterWallet/solana-firmware
§ 06 / Hardware
A reference design. Not a product.
There is no SKU. There is no shipping date. There is a list of commodity parts, an SD-card image, and a repository whose every screw and every line is yours to inspect.
| REF | PART | DETAIL | QTY |
|---|---|---|---|
| PI | Raspberry Pi 4 | or CM4 (no-wireless) for production | ×1 |
| DSP | 3.5″ SPI touchscreen | 480 × 320 · ADS7846 · tft35a overlay | ×1 |
| CAM | Pi Camera Module | libcamera · ribbon variant recommended | ×1 |
| BTN | Momentary push buttons | GPIO 17 (sign) · GPIO 27 (deny) | ×2 |
| SD | microSD card | 8 GB · Class 10 minimum | ×1 |
| PSU | USB-C power supply | 5 V · 3 A · power-only, no data | ×1 |
Hardware whose every screw is yours to inspect.
Source the parts. Flash the SD card. Solder two buttons. You now hold a hardware wallet whose entire trust chain you can audit, fork, and rebuild from scratch.
A note, plainly
This is a Phase 1 prototype. The architecture is built for the right reasons, but no external audit has been performed and the code has not been hardened against side-channel attacks. Use a testnet account before anything else.
§ 07 / Datasheet
No mystery. No magic.
A small surface area of well-understood, off-the-shelf components.
- Python 3.11+ · asyncio
- pynacl · solders
- pyzbar · BC-UR (_bc_ur) · cbor2
- pygame · framebuffer (/dev/fb1)
- picamera2 (libcamera)
- Keystone Solana UR · BC-UR
- ur:sol-sign-request
- ur:sol-signature
- BIP-39 · 12 words
- SLIP-10 · m/44'/501'/{i}'
- Ed25519
- Keystone Solana UR
- depends on Keystone exposure in app
- mainnet-beta · devnet · testnet
- camera + display only
- open source · see repo
§ 08 / Questions
The uncomfortable answers, first.
Is this ready to hold real funds today?
No, and we will not pretend otherwise. Better Wallet Pi is a Phase 1 Python prototype. Use Solana devnet or a disposable key first. The architecture is built for the right reasons, but no external audit has been performed and the code has not been hardened against side-channel attacks.
Which Solana wallets does it work with?
Solflare supports the Keystone Solana UR flow this firmware implements — pair it as a Keystone-class hardware signer and use animated `ur:sol-sign-request` / `ur:sol-signature` QRs. Phantom may work once the extension exposes the same Keystone Solana path; until then, treat Phantom as experimental.
Why center Solana?
Solana already uses Ed25519 at standard BIP-44 paths, and Keystone's Solana UR encoding gives us an open, QR-native request/response framing that maps cleanly onto Pi camera + display. The air gap stays visible: the hot machine prepares and broadcasts; the cold Pi parses, verifies, and signs.
How is this different from a Ledger or Trezor?
Ledger and Trezor ship sealed devices with proprietary firmware and a USB cable into a host computer. Better Wallet Pi is open hardware you build yourself, has no USB data path at all, and has no closed components in the signing path.
Why a Raspberry Pi?
It is cheap, widely available, well documented, and auditable. The CM4 no-wireless variant is recommended for production builds because the network silicon is physically absent — not just disabled in software.
Has the code been audited?
Not externally. Open source means you can audit it yourself — and you should, before trusting it with non-trivial value. The codebase is small and the security boundaries are explicit.
Can I contribute?
Yes — issues and pull requests welcome on GitHub. The project follows one strict architectural rule: only state/machine.py may import wallet/. Read CONTEXT.md before sending a PR.
§ 09 / Coda
Don't trust.
Verify.
Then build it.
The repository is the source of truth. The website is the menu; the menu is not the meal. Star it, fork it, audit it, build it.