Skip to content

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).

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.asc
  • SHA256SUMS (+ .asc if 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

  1. Do not power on the Pi yet (or power off if you already did).
  2. Remove the microSD and insert it into your computer (USB reader).
  3. Flash the verified .img.xz with Raspberry Pi Imager (Use custom → select file). Do not enable OS customisation (no SSH/Wi‑Fi setup).
  4. 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)

  1. Compare Image ID and firmware version printed on your kit insert (or packaging) to the verify list on piwalletsv.com (linked from Download).
  2. 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:

  1. Do not boot the Pi. Remove the microSD and insert it into your computer (USB card reader).
  2. Copy or hash the entire card (example on Linux; adjust device name):
sudo dd if=/dev/sdX bs=4M status=progress | shasum -a 256

On macOS, use /dev/rdiskN and shasum -a 256 the same way.

  1. 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.

  1. From Download, note the SHA-256 for piwalletsv-<VERSION>.img.xz (also in SHA256SUMS).
  2. Download the same .img.xz and run:
shasum -a 256 piwalletsv-<VERSION>.img.xz
  1. 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:

  1. Beta software. A short statement that this is pre-release code with no warranty.
  2. You are your own custodian. A reminder that nobody can recover funds for you if the seed is lost.
  3. 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-ui task 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.

  1. Open the companion in Safari.
  2. Tap Share (square with ↑).
  3. Scroll and tap Add to Home Screen.
  4. Confirm the name PiWalletSVAdd.

Opens full-screen without the browser chrome — handy for camera pairing and sends.

  1. Open the companion in Chrome.
  2. 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.
  3. 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:

  1. The companion shows the parsed xpub_export envelope inline: label, fingerprint (hex), derivation path, xpub.
  2. A Save as paired wallet card appears. You can rename the wallet locally before saving.
  3. 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.

  1. 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.
  2. Enter the Amount (sats).
  3. 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.
  4. Click Build proposal.

What happens next, in order:

  1. 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.
  2. 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.
  3. Cross-check Merkle roots. The TSC proof is translated into the @bsv/sdk MerklePath format, 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.
  4. A BEEF blob is assembled from the prior tx + path.
  5. 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.
  6. An unsigned_proposal envelope is built per the Envelope spec §4.
  7. 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.

piwallet vault init                  # only if you don't have a vault yet
cat <<MNEMO | piwallet vault add --label "primary"
word1 word2 ... word12
MNEMO

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:

rm ~/.piwallet/vault.bin

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 backupBackup 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.xz and .asc signature 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

On the current firmware, before you re-flash:

  1. 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).
  2. Press BSettingsMaintenanceUSB backupBackup to USB.
  3. 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):

  1. Power off the Pi and remove the microSD.
  2. Insert the card into a USB card reader on another computer.
  3. 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.
  4. Copy the entire .piwallet/ folder (or at minimum vault.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:

  1. Fetch piwalletsv-<VERSION>.img.xz and its .asc signature.
  2. Verify the signature with the project release key.
  3. 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.

  1. Power on the flashed Pi, accept the disclaimer (always shown after a firmware upgrade).
  2. On First setup, choose Restore from USB (or, if you already have a vault, press BSettingsMaintenanceUSB backupRestore from USB).
  3. Insert the backup stick, pick the drive, then pick the backup timestamp.
  4. Review the wallet list — existing wallets on the device (if any) are shown as will be erased. Confirm twice if replacing a vault.
  5. Toggle Import settings with RIGHT if you want brightness / sleep timer restored (optional).
  6. Enter the backup vault PIN and unlock.

Path B — Restore from mnemonic

  1. Accept the disclaimer and choose New vault (set PIN) on first setup, or use an empty vault from first-boot PIN setup.
  2. 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.

  1. 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:
    /home/pwsv/.piwallet/vault.bin
    /home/pwsv/.piwallet/settings.json   (optional)
    
  2. 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:
    PWSV=$(grep '^pwsv:' /path/to/mounted/root/etc/passwd | cut -d: -f3-4)
    sudo chown -R "$PWSV" /path/to/mounted/root/home/pwsv/.piwallet
    sudo chmod 700 /path/to/mounted/root/home/pwsv/.piwallet
    sudo chmod 600 /path/to/mounted/root/home/pwsv/.piwallet/vault.bin
    
    If you cannot set ownership correctly from your host OS, use Path B (mnemonic restore) instead.
  3. Power on the Pi. If vault.bin is present and valid, the bonnet skips "choose a PIN" and goes straight to PIN unlock.
  4. 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:

  1. Press BSettingsMaintenanceAirgap statusA. Every row should read OK and the header should say Air-gapped. See §14 Airgap status if anything shows !!.
  2. 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:

  1. Press B to open Settings.
  2. Select MaintenanceA.
  3. Select USB backupA.
  4. 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:

  1. Plug a USB keyboard and micro-HDMI cable into the Pi.
  2. Power on (or reboot) the device.
  3. 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.
  4. Log in as pisv with your device password.
  5. 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-hello shows 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:

  1. Press B to open Settings.
  2. Joystick down to “Airgap status”.
  3. Press A.

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:

piwallet diag airgap              # table; exit 1 on BREACH
piwallet diag airgap --json       # machine-readable

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

  1. Stop. Do not sign transactions on this device until the report is all-green.
  2. Note which rows show ActiveWi-Fi or Bluetooth failures map to drivers, radios, services, boot overlays, or blacklists; Network failures usually mean the bonnet sandbox is broken.
  3. 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 --json output).
  4. 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.

  1. Select Restart appA.
  2. Confirm twice (A on each prompt; B cancels).
  3. The screen shows Continuing boot…, the backlight turns off, and the bonnet process exits with code 0.
  4. systemd restarts piwallet-bonnet (Restart=always, RestartSec=3).
  5. 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:

sudo systemctl restart piwallet-bonnet

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.