Stable
Sourced from: docs/06-BACKUP-SPEC.md, docs/07-API-REFERENCE.md §5.7, docs/10-SECURITY-SPEC.md §9, docs/DECISIONS.md D4.1-D4.7

Backup and restore

Backups are how you get a wallet's spend key out of your node — for disaster recovery, for migrating to new infrastructure, or for adopting an already-funded Zucoins wallet as your treasury. This file format is a raw private key. Read the security section below before you export anything.

What a backup file is

A ZuPayments backup is byte-identical to the file the official Zucoins wallet (wallet.zucoins.com) itself produces when you tap its Export button. That's deliberate: a wallet you export from your node must import cleanly into the official wallet app, and vice versa (D4.5). Concretely, the file is:

  • A single minified JSON object — JSON.stringify with no spacing, no indentation, no reformatting.
  • Unencrypted. There is no password on this file. Anyone who has the file can move every coin in that wallet.
  • No file extension. The download will look like my-secret-zucoins-wallet--6-39pm-29-Mar-2026--address-sn8bXnyqmg with nothing after it — that's correct, not a broken download.
  • Three top-level fields: user_wallet (the actual keypair), app_state (the wallet's full transaction history and display state), and unix_time_secs (export timestamp).

Never open a backup file in a text editor and re-save it, "clean it up," or run it through a JSON formatter. The official wallet's import check is byte-strict about key padding and object shape; pretty-printing or re-encoding the file can silently corrupt the private key encoding and cause the file to be rejected on import — or worse, imported wrong. Treat the file as an opaque binary blob you move around, not as text you edit.

Exporting a wallet

Export is available for any treasury wallet (the primary use case — disaster recovery of real funds) and, for audit purposes, any sub-wallet.

  1. From the admin dashboard, go to the wallet you want to export (treasury or sub-wallet) and choose Export backup.

  2. Choose history scope: full history (default for treasury — you want the complete audit trail) or last 10 transactions only (useful for a sub-wallet with a long chain where you only care about current balance). A truncated history still renders the correct current balance in the official wallet — only older rows are hidden — because balance is always derived from the most recent entry, never accumulated.

  3. You'll be shown this warning before the download starts — read it, it is not boilerplate:

    This file contains the wallet's unencrypted private spend key. Anyone who obtains it can move all funds in this wallet. It is not password-protected. Store it offline, encrypted at rest, and never email, upload, or paste it into a shared location.

  4. Enter a fresh TOTP code (one you have not used for anything else, including logging in) to authorize the export. Every export is written to the audit log — who, when, which wallet, which scope — but never the key material itself.

  5. The file downloads directly from your node to your browser over HTTPS. It is never written to your node's disk unencrypted, and it never touches ZuPayments' servers — self-custody means the platform structurally cannot receive this file (D0.2).

Storing an exported backup

Treat every backup file exactly like cash sitting in a folder:

  • Store it offline. Not in your email, not in a shared drive, not in Slack. A USB drive in a safe, or an encrypted local volume, is the right mental model.
  • Encrypt it at rest yourself if it touches any online storage at all — the file itself carries no encryption, so the responsibility is on you. A password-protected archive (7-Zip AES-256, age, GPG) around the raw file is fine; just remember the file inside must stay byte-identical, so encrypt the file as a whole, don't touch its contents.
  • Never paste the contents anywhere — not into a support ticket, not into a chat app, not into an AI assistant. If you need help troubleshooting a backup, describe the problem; do not paste the file.
  • A stolen backup file is bounded damage: it is exactly one wallet's key, no more. It doesn't give access to your node, your admin account, or any other wallet.

Importing a wallet

Two situations call for import:

  • Adopting a funded wallet as your treasury — you already have Zucoins sitting in a wallet (e.g. you're bootstrapping with existing funds) and want your node to treat it as a treasury target for sweeps.
  • Node migration — moving to new infrastructure: export every wallet from the old node, import each into the new one.

Steps:

  1. From the admin dashboard, choose Import backup and select the file.
  2. Enter a fresh TOTP code — import is gated identically to export.
  3. The node validates the file: the three required top-level keys are present, and the key pair itself is a genuine matching Ed25519 pair (the same integrity check the official wallet runs). A tampered or corrupted file is rejected here with a clear error — no partial adoption.
  4. The node then re-pads the keys if needed (some tools, including the older treasury/admin export tool, emit unpadded keys — your node tolerates both) and re-syncs the wallet's real balance and chain position directly from the SplitChain gateway. The gateway is authoritative, not the file — if the file's history is stale or the wallet has moved since it was exported, the live chain state wins.
  5. On success you get back the wallet's public key, its role (treasury), its current balance, and how many history entries were adopted.

A wallet already known to your node cannot be re-imported as a duplicate. If you're trying to re-sync an existing wallet's history from a fresh backup rather than adopt a new one, use the explicit re-sync action rather than a plain import — plain import on a known public key is rejected by design, to prevent two chain-head trackers ever existing for the same key (which risks a double in-flight transaction).

A wallet with an unsettled transaction in flight cannot be imported. Let it settle (or fail) first — this mirrors the same "wallet must be quiescent" rule that applies to consolidation and outbound transfers.

Verifying a restore actually works

Don't trust an import until you've proven the wallet can transact. The acceptance bar this feature is built and tested against is: export a wallet → import it into a fresh wallet clone or node → confirm balance, confirm history, confirm it can send and receive — and the same in reverse (export from the official wallet, import into a node). If you're doing a full node migration, send a small test amount through the migrated treasury before you consider the migration complete.

Reference — API surface (admin dashboard uses this under the hood)

Endpoint Notes
POST /admin/v1/backups/export Body {pubkey, history: "full"|"last_10", totp, ack: true}. Streams the file, application/octet-stream.
POST /admin/v1/backups/import multipart/form-data, fields file, totp, optional resync. Returns {pubkey, role, balance, history_count}.

Both require an authenticated, TOTP-enrolled admin session, CSRF token, and a fresh single-use TOTP code at the moment of the call — a session being logged in is not sufficient on its own.