Skip to content

Wallet Guide

Alessandro Siniscalchi edited this page Feb 2, 2026 · 15 revisions

Wallet Guide

Key Management Model

  • Generate a mnemonic: run wallet generate --short for 12 words or wallet generate for 24 words. The phrase is printed once. Write it down offline since the tool never stores it.
  • Initialize the wallet: run wallet init --mnemonic "<words>" [--passphrase "<pass>"]. The passphrase encrypts your private key on disk and is required every time you sign a transaction. If you omit --passphrase, the CLI prompts twice to confirm it.
  • Re-running wallet init: if the wallet already exists, the command simply reconnects to Bitcoin Core and ensures the watch-only wallet is present. It does not overwrite your stored key material.
  • Signing transactions: whenever you call tx send-amount, tx register-collection, tx register-ownership, tx send-assets, or tx mix, the CLI asks for the passphrase (unless you provided it via flag). The key is decrypted only long enough to sign and then cleared from memory.

Local Storage

  • Location: all wallet files live under <data-dir>/<network>/. With defaults this is .brc721/regtest/ (or /testnet, /bitcoin). Each network keeps its own copy so you can safely reuse the same mnemonic on multiple chains.
  • Important files:
    • wallet.sqlite - tracks addresses, change, and other wallet metadata.
    • master-key.age - encrypted private key; created once you supply a passphrase.
    • brc721.sqlite - broader application data (collections, scanner state).
  • Keep it private: set the directory permissions so only your user can read it (for example, chmod 700 .brc721). Include wallet.sqlite and master-key.age in backups if you want the option to restore without typing the mnemonic again.

Watch-Only Wallet in Core

  • During wallet init we create a matching watch-only wallet inside Bitcoin Core. It is named after a hash of your descriptors, so importing the same mnemonic on another machine recreates the same Core wallet.
  • Core only receives public information; private keys never leave your local disk. Still, protect RPC credentials because anyone with them could broadcast a transaction you already signed.
  • If you restore the wallet or move to a new node, run wallet rescan. This calls rescanblockchain 0 so Core replays the entire chain and finds older payments tied to your descriptors.

Operational commands

  • wallet info - show your local wallet id and which wallets Core currently has loaded.
  • wallet load - load the watch-only wallet in Core if it exists on disk but is not loaded.
  • wallet unload - unload the watch-only wallet from Core (does not delete it).
  • wallet assets [--json] [--asset-ids] - list indexed (unspent) BRC-721 ownership ranges owned by this wallet (requires the scanner to have ingested the relevant blocks).

For a deeper dive on info/load/unload behavior, see Wallet lifecycle: info, load, unload.

Building Transactions

  • Receive funds: run wallet address whenever you need a fresh receiving address. Each call advances the index and is saved to disk, so addresses never repeat after restarts.
  • List revealed addresses: run wallet addresses to see every receive address the wallet has already handed out.
  • Send bitcoin:
    brc721 tx send-amount <ADDRESS> --amount-sat <SATOSHI> [--fee-rate <FEE_RATE>] [--passphrase <pass>]
    Fee rate is forwarded to Bitcoin Core via the fee_rate option on walletcreatefundedpsbt. Check bitcoin-cli help walletcreatefundedpsbt for the units your node expects. If it reports BTC/kvB, divide a sat/vB target by 1000.
  • Register a collection:
    brc721 tx register-collection --evm-collection-address 0x... [--rebaseable] [--fee-rate <FEE_RATE>] [--passphrase ...]
  • Register ownership:
    brc721 tx register-ownership --collection-id <height:tx_index> --slots <ranges> [--init-owner <ADDR|ADDRESS_H160>] [--target-owner <ADDR|ADDRESS_H160>] [--fee-rate <FEE_RATE>] [--passphrase ...]
    The command creates an OP_RETURN output at vout=0 and an ownership output at vout=1 (546 sat), and may add a change output after that. If you set --init-owner, the transaction's input0 must spend a non-NFT UTXO from that address (scanner DB required), and its addressH160 becomes the initOwnerH160 for minted token ids. If you omit --target-owner, it defaults to --init-owner. If you omit both, the CLI uses a new wallet address.
  • Send NFTs via implicit transfer:
    brc721 tx send-assets <ADDRESS> \
      --outpoint <TXID:VOUT> [--outpoint <TXID:VOUT> ...] \
      [--dust-sat 546] [--fee-rate <FEE_RATE>] [--passphrase ...]
    This spends ownership UTXOs without an OP_RETURN payload. Each input maps to one output to the target address (one output per outpoint).
  • Mix NFTs across outputs (explicit mapping):
    brc721 tx mix \
      --outpoint <TXID:VOUT> [--outpoint <TXID:VOUT> ...] \
      --output <ADDR>:0..=0 \
      --output <ADDR>:complement \
      [--dust-sat 546] [--fee-rate <FEE_RATE>] [--passphrase ...]
    Mix outputs must include exactly one complement output. Mix ranges are end-inclusive in the CLI (0..=0 means index 0 only). See Mix Examples for more patterns and edge cases.

All sending commands print the transaction id after broadcast.

Broadcasting and Monitoring

  • Broadcast: the CLI hands signed transactions to your Bitcoin Core node via sendrawtransaction. No extra steps are needed after the command succeeds.
  • Check balances: run wallet balance to see the same trusted/untrusted/immature numbers that bitcoin-cli getbalances would show for the watch-only wallet. If you see error -18 (wallet not loaded), run wallet load.
  • Track confirmations: after sending funds or registering a collection, mine or add blocks on your node and re-run wallet balance. Trusted balance increases once the transaction receives enough confirmations.
  • Rescan when needed: if you suspect Core missed something (new node, restored backup, long downtime), run wallet rescan to force a chain-wide scan.

Security Considerations

  • Store the mnemonic offline and never inside the repository. Anyone with the words and your passphrase can control the wallet.
  • Choose a strong, unique passphrase. Avoid passing it on the command line when sharing shells or logs; rely on the interactive prompt where possible.
  • Lock down the entire .brc721/<network>/ directory (for example, chmod 700 .brc721) so no other account on the machine can inspect wallet files or backups.
  • Keep your Bitcoin Core RPC endpoint private (bind to localhost or use a firewall) and guard rpcuser/rpcpassword or cookie files carefully.
  • Do not copy wallet.sqlite between machines while both wallets are active; address indexes could diverge and cause missing funds or duplicate addresses.

Recovery Procedures

  • New machine: install the app, configure RPC, then run wallet init --mnemonic "<words>" --passphrase "<pass>". Follow up with wallet rescan so Core rediscovers past activity.
  • Have the files but not the mnemonic: copy wallet.sqlite and master-key.age into the new .brc721/<network>/ folder. Any wallet command will prompt for the passphrase and continue where you left off. Without the passphrase, the encrypted key cannot be recovered.
  • Verify after recovery: run wallet address a few times and confirm the addresses match what you expect. If they look unfamiliar, ensure you restored the correct network directory and ran a rescan.
  • Watch-only wallet missing in Core: run wallet load. If the wallet was never created on this node, run wallet init again with your mnemonic and passphrase, then run wallet rescan.
  • Need more than 1,000 addresses: by default we pre-import descriptors covering the first 1,000 addresses in each branch. If you exceed that, use bitcoin-cli -rpcwallet=<wallet_id> importdescriptors ... with a higher range, or manually edit the descriptor import to widen the gap limit.

Clone this wiki locally