add_contact
Save a label → address binding to the address book. Production mode + Ledger paired: blob is signed with the user's paired Ledger key on that chain (BIP-137 for BTC, EIP-191 for EVM in v1.0; Solana / TRON support deferred to v1.5). Persisted to ~/.vaultpilot-mcp/contacts.json and verified on every read. Production mode + no Ledger paired (issue #428): writes to a process-local in-memory store and returns unsigned: true + anchorAddress: "UNSIGNED_NO_LEDGER" so first-run / accountant-share users can label addresses without entering demo mode (which intercepts broadcasts). The label is process-local — lost on restart — and resolves with a (unsigned) warning in send-flow verification blocks. Pair a Ledger and re-add to upgrade to a signed entry. Demo mode (VAULTPILOT_DEMO=true): same in-memory store, returns unsigned: true + anchorAddress: "DEMO_ANCHOR". All four chains usable from day one (btc/evm/solana/tron). v1.0 production chains: btc + evm. solana / tron return CONTACTS_CHAIN_NOT_YET_SUPPORTED. The notes and tags fields update the unsigned metadata sidecar (joined across chains by label) so editing them doesn't require a fresh device signature. Sends like prepare_native_send({ to: "Mom" }) resolve Mom against the signed blob first, then fall through to the unsigned overlay with a warning. Adding the same label twice on the same chain replaces the address (with a fresh signature in production-signed mode). Adding a different label that maps to an already-saved address rejects with CONTACTS_DUPLICATE_ADDRESS.