User manual¶
This chapter is the user-facing journey: from "I have an unflashed SD card" to "I just signed and broadcast a transaction." It assumes you've already done the hardware bring-up in Getting started. The bonnet-driven UI for some of these steps is part of Phase 2 and is noted in each section; until it ships, the CLI on the Pi (and the companion web app on the phone) provides the same workflows.
Beta software
PiWalletSV is beta-quality. Until v1.0, exercise these flows with testnet-scale amounts only. The Disclaimer and Security policy describe the risks and your responsibilities.
Verify your SD card on arrival¶
Pre-flashed kits should be verified before you create a wallet or receive funds:
- Option B — Re-flash a verified image (recommended): download from Download, verify GPG + SHA-256, and flash the card yourself before first boot. Easiest way to trust the microSD.
- Option A — Light checks (optional, weaker): compare Image ID on the kit insert to piwalletsv.com (paperwork only), or hash the card in a computer reader without booting the Pi (advanced). The device cannot verify the card on-screen.
A printable version of this section ships in the kit:
docs/print/kit-insert.md.
Why verify?¶
PiWalletSV is a cold wallet. You should trust the microSD firmware only if it matches an official PiWalletSV release — not a card substituted in transit or at fulfillment.
If your kit shipped with a pre-flashed card, verify it before you create a wallet or receive funds.
| Option | Best for | |
|---|---|---|
| B | Re-flash from a verified image | Recommended — easiest real assurance (~15–30 min) |
| A | Light checks | Paperwork sanity check, or advanced SD dump (~5–60 min) |
Re-flash is the easiest path
Option B is what we recommend for cold storage. You download the signed release yourself, verify it, and write it to the card. That is simpler and stronger than trying to prove the factory-flashed card byte-for-byte without rewriting it.
Do not boot the Pi to verify
The device cannot show a trustworthy checksum of the whole microSD. The first power-on already changes the card (system logs, disclaimer file, and more). There is no on-screen Image ID check that proves the card matches the official image.
Honest limits
Raspberry Pi hardware has no secure boot. Option B removes supply-chain doubt because you supply the image from Download (GitHub Releases) after GPG verification.
Before you start¶
- Do this before funding the device. Option B erases the card; if you already created a wallet, back up first (Upgrade your device § Step 1).
- Check tamper-evident packaging on the case if included.
- For Option B you need a computer (internet once), GPG, a card
reader, and Raspberry Pi Imager
(or balenaEtcher /
dd).
Option B — Re-flash from a verified image (recommended)¶
Replace the included microSD contents with firmware you downloaded and verified. On a new kit this is the simplest way to know the card is correct.
1. Download¶
From Download:
piwalletsv-<VERSION>-<BOARD>[-maturity].img.xz(e.g.piwalletsv-0.1.0-r3-pi0-beta.img.xz)piwalletsv-<VERSION>-<BOARD>[-maturity].img.xz.ascSHA256SUMS(+.ascif provided)
Your kit paperwork states which <VERSION> applies (e.g. round-one beta
0.1.0-r3, board pi0, Image ID c05daabb).
2. Verify signature and checksum¶
Import the release key once (fingerprint in Security § Release key):
gpg --keyserver hkps://keys.openpgp.org --recv-keys 9E048B6E7F54C49DE2D5AEB5DA261F4F2B0CA281
gpg --verify piwalletsv-<VERSION>-<BOARD>[-maturity].img.xz.asc piwalletsv-<VERSION>-<BOARD>[-maturity].img.xz
shasum -a 256 piwalletsv-<VERSION>-<BOARD>[-maturity].img.xz # match SHA256SUMS
Stop if anything fails. Do not flash.
Step-by-step: Flash and first run § Verify the download.
3. Re-flash the microSD¶
- Do not power on the Pi yet (or power off if you already did).
- Remove the microSD and insert it into your computer (USB reader).
- Flash the verified
.img.xzwith Raspberry Pi Imager (Use custom → select file). Do not enable OS customisation (no SSH/Wi‑Fi setup). - Eject, reinsert in the Pi, power on.
Details: Flash and first run § Flash the image.
4. First-time setup¶
Continue from Flash and first run § Assemble: disclaimer → vault PIN → wallet → air-gap check.
Option A — Light checks (optional)¶
Use these only if you are not re-flashing yet. They are weaker than Option B.
A1. Image ID on the kit insert (paperwork only)¶
- Compare Image ID and firmware version printed on your kit insert (or packaging) to the verify list on piwalletsv.com (linked from Download).
- Match → paperwork matches that official batch. Mismatch → do not use; re-flash (Option B).
This does not prove the microSD in the box was flashed correctly — only that the paperwork matches a published batch.
A2. Hash the microSD on your computer (advanced)¶
To check the card itself without re-flashing:
- Do not boot the Pi. Remove the microSD and insert it into your computer (USB card reader).
- Copy or hash the entire card (example on Linux; adjust device name):
On macOS, use /dev/rdiskN and shasum -a 256 the same way.
- Compare the result to the published card hash for your Image ID on piwalletsv.com (when provided for your batch).
If no card hash is published, this step cannot confirm the card — use Option B instead. In practice re-flashing is easier than maintaining a full-card dump workflow.
A3. Verify the release file on the download page¶
Confirms the official .img.xz file on piwalletsv.com is intact.
Required before Option B; does not prove the microSD in your kit
matches that file.
- From Download, note the SHA-256 for
piwalletsv-<VERSION>.img.xz(also inSHA256SUMS). - Download the same
.img.xzand run:
- The output must exactly match the published hash.
Already flashed the card yourself?¶
If you downloaded, verified, and flashed before first boot, you already followed Option B. Proceed to First boot.
After verification¶
- Settings → Maintenance → Airgap status — confirm Wi‑Fi / Bluetooth / network indicators (User manual § Airgap status).
- Create or restore your wallet; test on testnet before mainnet.
If anything fails after a verified re-flash, stop and contact support before signing.
1. First boot¶
On a freshly flashed SD card, the Pi reboots once while the image expands to the card size. The panel may stay dark for up to ~1 minute until the logo splash appears. After that one-time expand reboot, every boot is single-stage.
When you first boot the Pi with PiWalletSV installed, the bonnet shows the PiWalletSV logo briefly, then walks you through a three-page disclaimer:
- Beta software. A short statement that this is pre-release code with no warranty.
- You are your own custodian. A reminder that nobody can recover funds for you if the seed is lost.
- No liability. A confirmation that operating this device is on your own responsibility.
You hold the A button on the third page to confirm. The acknowledgement is persisted (with the disclaimer version and timestamp) into the vault file's metadata so the device doesn't re-prompt on every boot. A version bump in the disclaimer text re-prompts on next boot.
Hold B for five seconds on the boot logo to open factory diagnostics instead of continuing setup.
Until Phase 2 ships, the equivalent acknowledgement happens in the companion's first-load modal (see §3 below).
2. Create your first wallet¶
A "wallet" in PiWalletSV is one BIP39 seed plus one BIP44 account
at m/44'/236'/0'. The device supports multiple wallets in the
same vault; each has its own seed, its own xpub, and its own pair
of receive / change branches.
# 1. Initialise the vault (asks for a PIN twice).
piwallet vault init
# 2. Generate a fresh mnemonic and write it down on paper / steel.
piwallet mnemonic new --words 12 > /dev/tty
# (or --words 24 for 256-bit entropy)
# 3. Add the mnemonic to the vault. The CLI prompts for the
# PIN and reads the mnemonic from stdin.
cat <<MNEMO | piwallet vault add --label "primary"
word1 word2 ... word12
MNEMO
The mnemonic never touches disk in cleartext: it's read, used to derive the account xprv, encrypted under the vault's PIN-derived KEK, and discarded. The encrypted xprv plus its fingerprint, label, and BIP44 path are what get persisted.
The bonnet will provide:
- A "New wallet" menu that generates a fresh mnemonic and walks you through reading each word aloud while a piece of paper is in front of you.
- A "Restore wallet" menu that lets you re-enter a mnemonic
via the joystick + A/B with BIP39 prefix autocomplete (the
word-entry-uitask on the roadmap).
The "label" is purely for display — it's what shows up on the
bonnet selector and in the companion's wallet list. Pick something
that distinguishes wallets to you ("savings", "lightning float",
etc.); the label is included in the xpub_export envelope but is
not authoritative metadata.
3. First-load companion¶
Open the companion in your browser. The URL is whatever you decided
during the setup (https://localhost:5173/ for development;
https://your-domain.example/ once you deploy the static build).
On the first visit you'll see a blocking modal:
- A short summary of the disclaimer's key points.
- A checkbox: "I have read and accept the full DISCLAIMER.md on my own responsibility."
- A Continue button that's disabled until the box is ticked.
This modal re-appears whenever the disclaimer version increases.
localStorage tracks the acknowledgement; clearing site data
re-prompts.
After accepting, you land on /#/wallets, which is empty until you
pair a wallet.
Add to Home Screen¶
On a phone, install the companion to your home screen so it opens full-screen like an app — without the browser address bar getting in the way during QR scans.
- Open the companion in Safari.
- Tap Share (square with ↑).
- Scroll and tap Add to Home Screen.
- Confirm the name PiWalletSV → Add.
Opens full-screen without the browser chrome — handy for camera pairing and sends.
- Open the companion in Chrome.
- Tap the ⋮ menu → Install app or Add to Home screen (wording varies by version). Or accept the in-app Install banner on the Wallets page if shown.
- Confirm Install.
Optional — mainly for development. Use the install icon in the address bar (⊕ or computer icon) after visiting the companion over HTTPS.
4. Pair the Pi with the companion¶
Pairing copies the public account xpub from the Pi onto the companion so the companion can watch the wallet (discover UTXOs, display the next receive address, build proposals). No private material crosses the gap.
Select Pair → Show pairing QR on the bonnet. The screen animates the multipart QR. The companion's Scan page reads it.
# On the Pi:
sudo apt install qrencode
piwallet xpub-export --wallet-id <id> -o /tmp/xpub.bin
piwallet qr split --chunk-chars 200 /tmp/xpub.bin |
while IFS= read -r line; do
clear
qrencode -t UTF8 -- "$line"
sleep 0.4
done
Then on the companion (/#/scan), tap Start camera and
point it at the terminal.
Once the assembler completes:
- The companion shows the parsed
xpub_exportenvelope inline: label, fingerprint (hex), derivation path, xpub. - A Save as paired wallet card appears. You can rename the wallet locally before saving.
- Click Save. The wallet shows up under
/#/wallets.
The companion verifies the 4-byte self-fingerprint of the xpub
matches the envelope's fp field. If it doesn't (corrupted scan,
malicious source), saving is refused.
5. Receive¶
On /#/wallets/<id> you'll see a Receive card.
- Address. The next unused address (derived at
m/0/<nextReceiveIndex>). - QR. A QR code of the address text for the sender.
- Previous / Next. Step through addresses without revealing the current one. The "next" pointer is persisted; clicking past it advances the counter.
The receive flow is purely client-side — no Pi involvement. The companion derives the address from the cached xpub. The Pi never knows or cares which addresses you've handed out.
When a payment arrives, you don't need to do anything on the Pi.
Hit Refresh balance on the companion's Balance card to
re-scan. The scanner walks both m/0/* and m/1/* with a default
gap-limit of 20 and reports total sats, total BSV, UTXO count, and
the per-UTXO details (txid, vout, amount, derivation).
Confirmed vs. pending (mempool) balance¶
The balance scan includes both confirmed on-chain UTXOs and
pending UTXOs still in the mempool (shown with height = 0 /
"mempool" in the UTXO list). Your total includes both.
Only confirmed coins can be used as inputs when you send. This is a deliberate SPV requirement, not a bug:
- Each spend input must carry a Merkle proof tying the funding transaction to a specific block header.
- Mempool transactions are not yet in a block, so no Merkle proof exists for them.
- The companion therefore refuses to build a proposal that spends unconfirmed UTXOs, and the Pi would reject one if it were supplied.
After you send, your change output often lands in the mempool first. It appears in Balance as pending but is not spendable until it confirms (typically one block). The Send tab shows Spendable: as confirmed-only and labels any pending amount separately.
See SPV verification for the full trust model and what the Pi re-checks before signing.
6. Send (Build a proposal)¶
On /#/wallets/<id> find the Send card.
The Spendable line at the top shows confirmed coins only. If you have mempool UTXOs, they are listed separately as pending and cannot be selected for the next send until they confirm.
- Enter the Recipient address. Any valid BSV mainnet P2PKH address is accepted. The companion validates the checksum client-side; bad addresses are rejected before anything is built.
- Enter the Amount (sats).
- Optional: under Advanced, change the fee rate (sats per kB). Default is 500 sats/kB; the Pi rejects rates above 10000 sats/kB by default.
- Click Build proposal.
What happens next, in order:
- Select confirmed inputs. Greedy coin selection picks from confirmed UTXOs only (largest-first) until the target + estimated fee is covered. Mempool UTXOs are skipped entirely.
- Fetch and verify SPV proofs. For each selected UTXO, the proof fetcher calls the block-explorer endpoint to get the prior tx hex, its TSC Merkle proof, and the containing block's header.
- Cross-check Merkle roots. The TSC proof is translated into the
@bsv/sdkMerklePathformat, then re-checked against the header's Merkle root. If the computed root doesn't match the header, the build aborts with a clear error — that's the companion's last-mile sanity check before it relies on the proof. - A BEEF blob is assembled from the prior tx + path.
- The change address is derived at
m/1/<lastChangeUsed + 1>. If the residue after fees is below the 546-sat dust threshold, change is folded into the fee instead. - An
unsigned_proposalenvelope is built per the Envelope spec §4. - The envelope is gzipped + CBORed + split into PW1 multipart frames and animated on the canvas.
While steps 1–3 run, the companion shows an SPV build progress indicator (Select inputs → Verify SPV proofs → Build proposal) with per-input status such as "verified at block N (Merkle root matches header)." When all checks pass, a green banner confirms that SPV verification completed and reminds you that the Pi will re-verify before signing.
The card now shows:
- The proposal frame counter (
Frame X / Y · Z bytes total). - A Pause / Resume button.
- A New send button to discard the proposal and start over.
7. Verify and sign (Pi)¶
Take the Pi to wherever the companion's screen is. Point the camera at the QR canvas.
From the wallet manage menu (the screen you reach by selecting a wallet from the list), pick "Sign transaction". The bonnet opens a live camera preview with a status line beneath it:
- Aiming... — the camera is settling and no
PW1|fragment has decoded yet. - frame N / M — fragments are arriving; the count climbs until N == M and the proposal is fully assembled.
Press B at any time to abort the scan and return to the wallet manage menu.
Once assembly completes, the bonnet shows a Verifying SPV screen with a progress bar and a live status line as each input's BEEF path, Merkle proof, and block anchor are checked. On success it advances automatically to the review screen, which includes a green SPV verified badge (input count and block height range) above:
- Send / Fee / Net (sats and network).
This is the Pi's second SPV gate: it re-parses each input's
BEEF, recomputes the Merkle root from the embedded BUMP path,
and checks it against the header_anchors map the companion
supplied. Signing only proceeds if every input passes.
If verification fails on the SPV screen, the rejection reason is shown there; press B to return to the manage menu.
If verification rejected the proposal on the review screen (legacy path when no pre-verify result is injected), the screen shows the rejection reason instead of the summary, and pressing A is a no-op.
On a clean proposal, press A to sign. The Pi derives the
per-input keys, builds the Atomic BEEF, wraps it in a
signed_tx envelope, and animates it back to the companion
as PW1| multipart QR frames at the same density used for
pairing. Press A or B when the companion confirms it
has the full set.
Until the bonnet flow ships, run the steps as discrete CLI invocations:
# 1. On the Pi, capture the animated QR with the camera:
piwallet qr scan-camera -o /tmp/proposal.bin
# 2. (Optional) print a human-readable summary first:
piwallet decode /tmp/proposal.bin
# 3. Sign. The CLI prompts for the PIN, runs verify_proposal()
# end to end, prints the verification result, and writes
# the signed_tx envelope:
piwallet sign \
--wallet-id <id> \
--max-fee-rate-satskb 10000 \
-o /tmp/signed.bin \
/tmp/proposal.bin
# 4. Display the signed envelope back as an animated terminal
# QR for the companion to scan:
piwallet qr split --chunk-chars 200 /tmp/signed.bin |
while IFS= read -r line; do
clear
qrencode -t UTF8 -- "$line"
sleep 0.4
done
The signer must display the recipient, amount, fee, and anchor pairs to the user before producing any signature. The signer must not display the seed, mnemonic, or any private key.
8. Broadcast¶
Back on the companion's /#/scan page, point the laptop/phone
camera at the Pi's signed-tx QR. When assembly completes, the
parsed signed_tx shows up inline:
- Wallet fingerprint (hex, matched against the proposal you sent).
- Txid (lowercase hex, 64 chars).
- Raw hex (collapsed; expandable for inspection).
A Broadcast signed transaction card appears with a button. Click
it. The companion POSTs the raw hex to the block-explorer's
broadcast endpoint and shows the returned txid plus a link to the
public explorer page.
If the returned txid differs from the one the Pi signed, the card warns you. This usually means the broadcaster found a malleable form of your signature or refused to relay it; treat as suspicious and re-investigate before assuming the transaction will confirm.
If the broadcast fails outright (network error, "too few fees",
"missing inputs"), the card shows the error inline and the
Broadcast button stays available so you can retry. The original
signed_tx envelope stays in memory until you navigate away or hit
Reset — so you can fail, fix the network, and retry without
asking the Pi to re-sign.
9. Restore from mnemonic¶
Lost a vault, replaced a SD card, switching devices? If you need to recover funds without access to the device at all, see Recover without device for step-by-step instructions using iancoleman.io/bip39/ or ElectrumSV.
Select Restore wallet on the bonnet's main menu. The joystick word-entry UI lets you type each word with prefix autocomplete. Both 12 and 24-word mnemonics are supported. The checksum is verified before the account is derived.
A restored wallet produces the same xpub and the same
fingerprint as the original device. If you had paired the
original with the companion, the new device will pair to the same
companion record — the companion verifies the
(walletFp, path, xpub) triple matches.
The companion's local state (receive index, scan cache, label) is not restored along with the wallet — that's companion-side metadata and is rebuildable from the chain.
10. Wipe a wallet / wipe the vault¶
If you want to retire a wallet:
piwallet vault list # find the wallet id
piwallet vault remove --wallet-id <id> # removes the encrypted xprv entry
To wipe the vault entirely, delete the vault file:
Or use Settings → Maintenance → Factory reset on the bonnet to securely overwrite the vault and clear settings and disclaimer state before handing the device to someone else — see §15 Settings.
The Pi will treat that as "no vault yet" on next launch. This is irreversible without your mnemonic — the encrypted xprv and any on-device-only state are gone.
If you've lost the PIN: there's no recovery mechanism. By design.
The vault will lock for an exponential delay after wrong-PIN
attempts (see piwallet/core/vault.py)
and wipe after a configurable threshold of consecutive failures.
You'll need to restore from the mnemonic.
11. Upgrade your device¶
PiWalletSV ships as a sealed SD-card image. There is no over-the-air or in-app update path — by design, a signer that can pull software updates over the network is not air-gapped. When a newer firmware release is published, you re-flash the microSD and restore your wallet from backup.
This section is the full end-user workflow. The companion PWA on your phone or laptop is updated separately (refresh the browser or re-install from the site); only the Pi device follows the steps below.
Re-flashing wipes the SD card
Flashing a new image erases everything on the card — vault, PIN verifier, disclaimer acceptance, and display settings. Back up before you flash. Your written-down mnemonic is always the canonical recovery path; the encrypted vault file is a convenience backup that still requires your PIN.
What you need¶
- Your mnemonic on paper (or steel), or a USB backup from Settings → Maintenance → USB backup → Backup to USB (recommended before any re-flash).
- Your PIN (required for USB or vault-file restore).
- A FAT32 or exFAT USB flash drive (factory formatting is fine) for Path A below, or a microSD card reader for Path C.
- The new
.img.xzand.ascsignature from Download, verified the same way as first setup (Flash and first run § Step 1).
Overview¶
| Step | What happens |
|---|---|
| 1 | Back up to USB (or confirm you have your mnemonic) |
| 2 | Download and verify the new image |
| 3 | Re-flash the microSD (full wipe) |
| 4 | Restore from USB, mnemonic, or vault file |
| 5 | Accept disclaimer, re-verify airgap, TESTNET smoke test |
Step 1 — Back up before you flash¶
Path A — USB backup (recommended)¶
On the current firmware, before you re-flash:
- Insert a FAT32 or exFAT USB stick into the Pi's data micro-USB port (the one closer to the SD slot; power stays on PWR IN).
- Press B → Settings → Maintenance → USB backup → Backup to USB.
- Pick the drive from the list, confirm your PIN, and wait for Backup saved. Press A or B to dismiss, then B to return to Settings.
Backups are stored under PiWalletSV/backups/<timestamp>/ on the stick
(vault.bin, optional settings.json, and a manifest). terms.json
is never exported — you will re-accept the disclaimer after upgrading.
Keep the stick offline with the device. Anyone with the stick and your PIN can sign.
Path B — Mnemonic (always works)¶
If you have the 12- or 24-word seed written down, you do not need a USB or SD backup. After re-flash, restore via the bonnet (§9).
Path C — Copy vault.bin off the SD card (fallback)¶
Copying vault.bin lets you skip re-typing every word, but you must
still know the same PIN as before. The file is encrypted at rest;
treat the backup like a second copy of the vault — store it securely.
On the sealed production image, PiWalletSV state lives in a single directory (nothing else on the card is user-writable):
/home/pwsv/.piwallet/
├── vault.bin ← required for vault restore
└── settings.json ← optional (brightness, sleep timeout)
Disclaimer (terms.json) is not backed up — a firmware upgrade always
re-prompts the disclaimer on first boot after re-flash.
Physical backup (no SSH on the sealed device):
- Power off the Pi and remove the microSD.
- Insert the card into a USB card reader on another computer.
- Mount the Linux root partition (ext4). On Linux this is usually automatic; on macOS or Windows you may need an ext4 driver or a Linux live USB — plan ahead if you do not have Linux handy.
- Copy the entire
.piwallet/folder (or at minimumvault.bin) to encrypted storage — a password-protected archive on a USB stick you control, not cloud sync.
Do not skip this step and assume you will copy files after flashing; the new image overwrites the card completely.
Step 2 — Download and verify the new image¶
Follow Download and Flash and first run § Step 1:
- Fetch
piwalletsv-<VERSION>.img.xzand its.ascsignature. - Verify the signature with the project release key.
- Decompress if your flashing tool requires a raw
.img.
Do not flash an unverified image.
Step 3 — Re-flash the microSD¶
Flash the verified image with Raspberry Pi Imager or your platform's equivalent (Flash and first run § Step 2).
This step destroys the old card contents. Confirm your backup from Step 1 is safe before you proceed.
Step 4 — Restore your wallet¶
Pick one path below. Import replaces all wallets on the device (and optionally display settings). There is no merge.
Path A — Restore from USB (recommended after Step 1 Path A)¶
- Power on the flashed Pi, accept the disclaimer (always shown after a firmware upgrade).
- On First setup, choose Restore from USB (or, if you already have a vault, press B → Settings → Maintenance → USB backup → Restore from USB).
- Insert the backup stick, pick the drive, then pick the backup timestamp.
- Review the wallet list — existing wallets on the device (if any) are shown as will be erased. Confirm twice if replacing a vault.
- Toggle Import settings with RIGHT if you want brightness / sleep timer restored (optional).
- Enter the backup vault PIN and unlock.
Path B — Restore from mnemonic¶
- Accept the disclaimer and choose New vault (set PIN) on first setup, or use an empty vault from first-boot PIN setup.
- On the wallet list, choose Restore wallet and enter your seed (§9).
Your xpub and fingerprint match the old device, so the companion wallet you already paired should continue to work without re-pairing.
Path C — Restore from vault.bin on the SD card (fallback)¶
Use this only if you copied vault.bin in Step 1 and remember the
original PIN.
- Before first boot on the new image (recommended): mount the freshly flashed card's root partition on your computer and copy your backup files into place:
- Ensure the directory is owned by the runtime user. On the sealed
image that user is
pwsv. After mounting the root partition on a Linux machine, look up its numeric id and fix ownership before booting the Pi: If you cannot set ownership correctly from your host OS, use Path B (mnemonic restore) instead. - Power on the Pi. If
vault.binis present and valid, the bonnet skips "choose a PIN" and goes straight to PIN unlock. - Enter your original PIN — not a new one from a aborted setup.
If you already completed first-boot PIN setup on an empty vault, you
have created a new vault that does not contain your wallets. Either
restore via Path B (mnemonic), or stop the Pi, mount the card
again, replace vault.bin with your backup, and boot once more.
Step 5 — Verify after upgrade¶
Treat the upgraded device like a new install until you have evidence it is still sealed:
- Press B → Settings → Maintenance → Airgap status → A. Every row
should read
OKand the header should say Air-gapped. See §14 Airgap status if anything shows!!. - Run a TESTNET send round-trip (Flash and first run § Step 9) before returning to mainnet amounts.
Companion app after a Pi upgrade¶
- Mnemonic or vault restore with the same seed → same xpub → your existing companion wallet record should work; hit Refresh balance to resync UTXOs.
- Companion version: if the release notes mention a wire-format change, update the PWA on your phone/laptop to match the new Pi firmware before signing.
Developer installs (SSH, not the sealed image)¶
If you built the signer yourself from source and still have SSH access,
day-to-day software updates are a git pull and service restart — see
Operate § Updating the software.
That path does not apply to the downloadable sealed image, which
has no network and no editable app tree under /opt/piwallet.
Common mistakes¶
| Mistake | Consequence |
|---|---|
| Flash before backing up | Vault gone unless you have the mnemonic |
Complete first-boot PIN setup, then copy old vault.bin |
Confusing state — replace vault.bin on SD while powered off, or use mnemonic |
| Restore vault file but forget original PIN | Vault file is useless without PIN; restore from mnemonic |
| Skip airgap check after re-flash | You may sign on a mis-provisioned image |
| Assume the companion auto-updates with the Pi | Update both independently when release notes say so |
12. USB backup and restore¶
PiWalletSV can export and import the encrypted vault (and optional display settings) to a FAT32 or exFAT USB stick. This is the recommended path before re-flashing the SD card (§11 Upgrade your device) and works any time from Settings.
Bonnet navigation¶
From the wallet list:
- Press B to open Settings.
- Select Maintenance → A.
- Select USB backup → A.
- Pick Backup to USB or Restore from USB.
B at the USB backup submenu returns to Maintenance. B from Maintenance returns to the Settings hub. B on the hub returns to the wallet list.
On first boot (empty vault), choose Restore from USB from the First setup screen instead.
USB port¶
Use the Pi's left micro-USB port with the included OTG adapter and a USB flash drive. Keep PWR IN (right port) connected for power.
Stick requirements¶
- FAT32 or exFAT only (typical factory formatting is fine).
- Any capacity; backups are small (encrypted vault + manifest).
On-stick layout¶
PiWalletSV/backups/<YYYYMMDD-HHMMSSZ>/
├── manifest.json wallet labels + fingerprints (public metadata)
├── vault.bin encrypted vault (same format as on the SD card)
└── settings.json optional (brightness, sleep timer, QR background)
terms.json is never exported — you re-accept the disclaimer
after a firmware upgrade.
Import replaces the entire vault on the device (all wallets). When replacing an existing vault, the bonnet shows a preview and asks for double confirmation before writing.
Hot-plug¶
Inserting or removing a stick is safe except during an active backup or restore (while data is being read or written). When waiting to pick a drive, the bonnet rescans about once per second; press A to rescan immediately.
Security¶
The stick holds an encrypted vault. Anyone with both the stick and your vault PIN can sign. Store the stick like a second copy of the vault file — offline, under your control. The mnemonic remains the canonical recovery path if you lose the stick or forget the PIN.
For shell access on a development Pi (or scripted backups), see
CLI § piwallet backup and
Operate § USB vault backup.
13. Troubleshooting¶
Accessing a local debug console (tty2) via HDMI + USB keyboard.
The sealed image keeps a second virtual terminal on tty2 for local troubleshooting — it is never accessible over the network. To use it:
- Plug a USB keyboard and micro-HDMI cable into the Pi.
- Power on (or reboot) the device.
- Once the bonnet shows the boot splash or disclaimer, press Ctrl + Alt + F2 on the keyboard. The HDMI output switches to tty2 and shows a login prompt.
- Log in as
pisvwith your device password. - To return to tty1 (bonnet display output) press Ctrl + Alt + F1.
This console has no network access and is intended for reading logs
(sudo journalctl -u piwallet-bonnet -f), running the factory smoke
test, or other local diagnostics. It does not interfere with the bonnet
UI running on tty1.
Send says "no spendable UTXOs" or "only confirmed coins can be sent" but Balance shows a non-zero total.
- Your wallet may hold only mempool (unconfirmed) UTXOs — for example change from a send you just broadcast. Refresh Balance, check the UTXO list for "mempool" tags, and wait for confirmation.
- SPV requires each input to be anchored in a mined block. Until pending coins confirm, they count toward your displayed total but cannot be spent. This is by design; see §5 "Confirmed vs. pending."
The Pi camera doesn't see the companion's animated QR.
- Confirm
rpicam-helloshows live preview. If not, the CSI cable is the usual culprit — re-seat both ends. - The kit OV5647 is fixed-focus (~30 cm / ~1 ft). Hold the bonnet at that distance from the companion screen.
- The companion's animation is too fast. Use the Pause button to hold a frame, then resume. The Pi assembler is happy with frames in any order.
- Ambient light. The bonnet's display reflects glare from overhead lights into the camera. Tilt one or the other.
The Pi's verify step fails with "merkle root mismatch."
- Confirm the companion's chosen block-explorer endpoint is the same chain you intend to operate on (mainnet, not testnet — v1 is mainnet-only).
- Re-run Refresh balance in the companion so the UTXO snapshot is current. A stale snapshot can point at UTXOs that have since been spent; their proofs won't recompute against the current chain headers.
- Display the on-bonnet anchor pair on the Pi (height + root) and compare against a public block explorer. If they match the explorer but the verify still fails, file a bug.
The Pi's verify step fails with "script does not match derivation."
- The companion claimed an input came from
m/0/i, but the prior tx output isn't a P2PKH spend to the address at that derivation. This almost always means the companion's UTXO scanner has a bug or the wallet was restored without resetting the scan state. Force a re-scan and try again.
The companion's broadcast returns a different txid than the Pi signed.
- The most likely cause is tx malleability in the signed transaction. This shouldn't happen for the canonical P2PKH spend path the signer uses, but it's worth filing a bug with the raw hex if you see it consistently.
The companion is stuck on the disclaimer modal.
- Check the checkbox. The Continue button is disabled until you tick it. This is by design — it forces you to actually acknowledge each version of the disclaimer.
Lost PIN, no mnemonic backup.
- You have lost your funds. We're sorry. This is the non-custodial promise: no party (the project, the device, the companion) can recover your wallet without the mnemonic.
“Airgap status” shows BREACH or a row with !!.
- See §14 Airgap status for what each indicator means and what to do. Do not sign until the report is all-green.
14. Airgap status¶
PiWalletSV's security model depends on the signing device having no network path. The Airgap status screen in Settings runs live checks so you can verify that claim on demand — at first setup, after a reflash, or any time before signing something sensitive.
From the wallet list:
- Press B to open Settings.
- Joystick down to “Airgap status”.
- Press A.
Header¶
The title band at the top is the overall verdict:
| Header | Meaning |
|---|---|
| Air-gapped (green) | Every check that could run passed. Safe to proceed. |
| BREACH (red) | At least one check failed conclusively. Do not sign until the leak is understood and fixed. |
Status column (bonnet)¶
Each row shows a friendly label on the left and a plain-English status on the right:
| Status | Meaning |
|---|---|
| Disabled (green) | That radio or network path looks off — no leak found. |
| Active (red) | Something is live — treat as BREACH. |
| Unknown (grey) | Could not verify from the bonnet app alone. Re-flash or run the shell diagnostic below. |
On a healthy sealed device you should see Disabled on all three rows and a green Air-gapped header.
The CLI still uses compact glyphs (OK, !!, --) in
piwallet diag airgap output.
Check rows (bonnet UI)¶
The Settings screen shows three summary rows. Each rolls up several technical checks so you do not need to read kernel module names:
| Row | What it covers |
|---|---|
| Wi-Fi | No Wi-Fi driver loaded; radios blocked; Wi-Fi services off; firmware disables Wi-Fi at boot; modules blacklisted. |
| Bluetooth | Same checks for Bluetooth (hciuart, bluetooth, etc.). |
| Network | Only the loopback interface is present inside the bonnet app's network sandbox (PrivateNetwork=yes). |
A green Network row confirms the bonnet sandbox is intact. It does not by itself prove the host Pi has no other interfaces — for that, run the full six-check report from a shell (below).
On some older images, Wi-Fi and Bluetooth could show Unknown when rfkill sysfs was unreadable inside the bonnet sandbox even though radios were actually disabled. Current firmware shows Disabled when every conclusive sub-check passes.
Full report from a shell¶
piwallet diag airgap lists six technical rows (modules, rfkill,
interfaces, services, boot_config, blacklist) and sees the host
directly. Use it periodically and whenever the on-screen result looks
wrong:
See Operate § Airgap diagnostic and
CLI § piwallet diag airgap.
Controls¶
| Button | Action |
|---|---|
| A | Refresh — re-run all checks. |
| B | Back to Maintenance. |
If you see BREACH¶
- Stop. Do not sign transactions on this device until the report is all-green.
- Note which rows show Active — Wi-Fi or Bluetooth failures map to drivers, radios, services, boot overlays, or blacklists; Network failures usually mean the bonnet sandbox is broken.
- If you flashed a prebuilt image, re-verify the download signature
and re-flash. If the second flash still fails, contact
@PiWalletSV on X or file an issue on
GitHub with the
failing rows (or
piwallet diag airgap --jsonoutput). - Re-check after any SD-card reflash, config edit, or software update, and before signing anything you would regret.
For the shell equivalent (recommended periodically, and required for full host interface verification), see Operate — Airgap diagnostic.
15. Settings¶
Global device options live under Settings on the bonnet (hub with Preferences and Maintenance). Open it with a short B press from the wallet list. Press B on the hub to return to wallets.
Opening Settings¶
From the wallet list, press B (short press) to open Settings. The hub offers Preferences and Maintenance.
Press B on the hub to return to the wallet list. That is the only way to leave Settings altogether.
Navigation: B always moves up one level:
| You are on | B goes to |
|---|---|
| Sub-flow (airgap, about, USB menu, change PIN, …) | Maintenance |
| Maintenance or Preferences | Settings hub |
| Settings hub | Wallet list |
There is no button or gesture to exit the bonnet app from the UI.
The signer runs under systemd (Restart=always); power off externally
when you need the device off.
Preferences¶
From the hub, select Preferences.
Use UP/DOWN to move the cursor. On Brightness and Sleep timer, use LEFT/RIGHT to cycle through the allowed values. Press A on a value row to save (you stay on Preferences). Press B to return to the Settings hub.
| Row | What it does |
|---|---|
| Brightness | Backlight level (preset percentages). |
| Sleep timer | Minutes until the panel blanks when idle (Off disables). |
Maintenance¶
From the hub, select Maintenance.
Press A on an action row to open that sub-screen. Press B to return to the Settings hub.
| Row | What it does |
|---|---|
| Change PIN | Re-enter the current PIN, then set a new vault PIN. |
| Airgap status | Live Wi-Fi / Bluetooth / Network check — see § Airgap status. |
| USB backup | Export or import the encrypted vault to a USB stick — see § USB backup. |
| About | Version, website, wallet count, Pi serial, and hostname. |
| Factory reset | Factory wipe for resale or hand-off — see below. |
Sub-screens use B to return to Maintenance (not the wallet list).
Factory reset¶
Maintenance → Factory reset erases all signer state on the device:
- encrypted vault (
vault.bin, securely overwritten), - display settings (
settings.json), - disclaimer acceptance (
terms.json).
Funds remain on the blockchain; only your seed phrase can recover them. The flow asks for double confirmation, then your vault PIN, then shows Factory reset complete and returns to first-setup (disclaimer → new PIN) on the next boot loop.
Use this when selling or gifting the hardware. To wipe only one wallet
while keeping others, use the CLI (piwallet vault remove) — see
§ Wipe a wallet.
For operator-facing USB steps, see §12 USB backup. For airgap interpretation, see §14 Airgap status.
16. Factory diagnostics¶
Support and factory workflows can open the Diagnostics menu from the boot splash without unlocking the vault.
Entering diagnostics¶
On every boot, the bonnet shows the PiWalletSV logo splash for a few seconds. From that screen:
| Input | Result |
|---|---|
| Hold B for ~5 seconds | Open the Diagnostics menu (factory / support entry). |
| A, timeout, or release B before 5 s | Continue normal boot (disclaimer, vault setup, PIN, wallet list). |
Diagnostics runs before the disclaimer and vault unlock, so you can inspect or test a device even when the vault is missing or locked.
Diagnostics menu¶
| Item | Purpose |
|---|---|
| Device info | Version, vault state, unlock tries left, terms version, Pi serial, hostname. |
| Run all checks | Automated software checks (vault file, paths, airgap helpers, etc.). |
| Test joystick | Interactive direction and press test. |
| Test buttons | Interactive A / B / SELECT test. |
| Test camera | Live camera preview. |
| Test screen | Fill patterns and colour bars. |
| Restart app | Exit cleanly so systemd restarts the bonnet service (~3 s). |
Press A to open the highlighted item. Press B (short press, release) to leave diagnostics and continue boot.
Sub-screens use A/B: back unless noted otherwise.
Restart app¶
Restart app is for recovering a stuck UI or applying a fresh process after config changes — it does not reboot the whole Raspberry Pi.
- Select Restart app → A.
- Confirm twice (A on each prompt; B cancels).
- The screen shows Continuing boot…, the backlight turns off, and the
bonnet process exits with code
0. - systemd restarts
piwallet-bonnet(Restart=always,RestartSec=3). - The splash runs again; unless you hold B, boot continues where it left off (disclaimer / unlock / wallet list as appropriate).
To restart from SSH instead:
For day-to-day service restarts from SSH, see Operate § Reading the bonnet log.
Help & support¶
General questions and usage help: @PiWalletSV on X.
Bug reports and feature requests: GitHub Issues.
Security vulnerabilities: use the private channel in Security briefing — not X or public issues.