btc_wallet_manager
Bitcoin Wallet Manager Module
Handles HD wallet creation, derivation, and encrypted storage in Redis. Supports BIP39 mnemonics and BIP84 derivation paths (m/84’/0’/0’/0/x) for Native SegWit.
- class btc_wallet_manager.BTCWalletManager[source]
Bases:
objectManages Bitcoin HD wallets with encrypted storage in Redis.
Features: - BIP39 mnemonic generation and import - BIP84 derivation (m/84’/0’/0’/0/x) for Native SegWit - AES-256-GCM encryption for seeds at rest - Per-user wallet isolation - Address caching for derived addresses
Accepts an async Redis client via method parameters for v3 compatibility.
- __init__()[source]
Construct a wallet manager with no master key loaded yet.
Leaves
_master_keyasNoneso the AES-256-GCM master key is fetched or generated lazily on first use via_ensure_master_key(which needs an async Redis client that is not available at construction time). Performs no I/O.A single module-level singleton
btc_wallet_manageris instantiated at the bottom of this file and imported bytools/btc_wallet_tools.py; this method is invoked once at import time.
- generate_mnemonic(strength=128)[source]
Generate a fresh BIP39 mnemonic seed phrase.
Lazily imports
bitcoinlib.mnemonic.Mnemonicand produces a new random phrase at the requested entropy strength (128 bits yields a 12-word phrase, 256 bits a 24-word phrase). The result is the human-recoverable backup thatcreate_walletthen encrypts and stores; this method itself performs no Redis or network I/O.Called by the
_create_btc_wallettool handler intools/btc_wallet_tools.pywhen a user asks to create a new HD wallet without supplying their own phrase.
- validate_mnemonic(mnemonic)[source]
Check whether a string is a valid BIP39 mnemonic (wordlist plus checksum).
Lazily imports
bitcoinlib.mnemonic.Mnemonicand runs its checksum/wordlist validation, catching any exception and returningFalseso malformed input is reported as invalid rather than raising. Used as a guard before a user-supplied phrase is accepted and encrypted. Performs no Redis or network I/O.Called internally by
create_walletand by the_create_btc_wallettool handler intools/btc_wallet_tools.pyto reject bad phrases early.
- async create_wallet(user_id, wallet_name, mnemonic, redis_client, network='bitcoin')[source]
Create and persist a new HD wallet from a BIP39 mnemonic.
Validates and normalizes the wallet name, ensures the master key is loaded, and rejects duplicates by checking for an existing wallet first. It then validates the mnemonic, resolves the network via
get_btc_network, and usesbitcoinlib.HDKeyto derive the first Native SegWit (BIP84) bech32 receive address atm/84'/<coin_type>'/0'/0/0(coin type1'for testnets,0'for mainnet). The mnemonic is encrypted with a per-wallet key via_encryptand the wallet record is written to Redis underbtc_wallet:<user>:<name>while the name is added to the per-user index setbtc_wallet_index:<user>. The returned dict deliberately excludes any secret material.Touches Redis (
SETplusSADD) and depends on the loaded master key; the plaintext mnemonic exists only transiently in memory. Called by the_create_btc_wallettool handler intools/btc_wallet_tools.py.- Parameters:
user_id (
str) – Owning user’s id; namespaces the Redis keys.wallet_name (
str) – 1-32 char alphanumeric name (_and-allowed).mnemonic (
str) – A valid BIP39 mnemonic to import as the wallet seed.redis_client – Async Redis client for the master key and wallet storage.
network (
str) – Network name or alias (defaultbitcoin).
- Returns:
Public wallet info with
wallet_name, firstaddress, displaynetwork, andcreated_at.- Return type:
- Raises:
ValueError – If the name is invalid, the wallet already exists, the mnemonic is invalid, or the network is unknown.
- async import_wif(user_id, wallet_name, wif, redis_client, network='bitcoin')[source]
Import a single private key (WIF) as a simple, non-HD wallet.
Validates and de-duplicates the wallet name, ensures the master key is loaded, and resolves the network via
get_btc_network. It then parses the WIF withbitcoinlib.Keyto derive the corresponding bech32 address, encrypts the WIF with a per-wallet key via_encrypt, and stores a record oftypesimple(one fixed address at index 0) underbtc_wallet:<user>:<name>, adding the name to the per-user index setbtc_wallet_index:<user>. Unlikecreate_walletthis wallet cannot derive further addresses.Touches Redis (
SETplusSADD) and depends on the loaded master key. Called by the_import_btc_wallettool handler intools/btc_wallet_tools.py.- Parameters:
- Returns:
Public wallet info with
wallet_name,address, displaynetwork, andcreated_at.- Return type:
- Raises:
ValueError – If the name is invalid, the wallet already exists, the network is unknown, or the WIF key cannot be parsed.
- async get_wallet(user_id, wallet_name, redis_client)[source]
Fetch a wallet’s public metadata from Redis, with the secret stripped out.
Reads the JSON record at
btc_wallet:<user>:<name>and, before returning, removes theencrypted_seedfield so callers never see secret material through this path (the decrypted seed is only reachable via the dedicatedget_decrypted_seed/get_private_keymethods). ReturnsNonewhen no such wallet key exists. Performs a single RedisGET.Called internally as a duplicate-existence check by
create_walletandimport_wif, bylist_walletsto expand each indexed name, and by tool handlers (_derive_btc_address,_export_btc_wallet) intools/btc_wallet_tools.py.
- async list_wallets(user_id, redis_client)[source]
List all of a user’s wallets as public metadata, sorted by creation time.
Reads the per-user index set
btc_wallet_index:<user>to get the wallet names, decoding any bytes members, then callsget_walletfor each to load its secret-stripped record, skipping any that have gone missing. The combined list is sorted bycreated_at. Touches Redis with oneSMEMBERSplus oneGETper wallet.Called by the
_list_btc_walletstool handler intools/btc_wallet_tools.py.
- async derive_address(user_id, wallet_name, index, redis_client, address_type='bech32')[source]
Derive (and cache) a receive address at a given index for an HD wallet.
Ensures the master key is loaded, then reads the raw wallet record from
btc_wallet:<user>:<name>. If the address for theindex:address_typecache key is already stored it is returned immediately. Forsimple(WIF-imported) wallets only index 0 is valid and the stored address is returned. Otherwise it decrypts the mnemonic via_decrypt, rebuilds thebitcoinlib.HDKey, and derives the BIP84 subkey atm/84'/<coin_type>'/0'/0/<index>, encoding as bech32, p2sh, or legacy peraddress_type. The newly derived address is written back into the wallet’saddressescache in Redis so subsequent calls are cheap.Reads and conditionally writes Redis (
GETthenSET) and depends on the loaded master key; the mnemonic exists only transiently in memory. Called by several tool handlers intools/btc_wallet_tools.py(_derive_btc_address,_check_btc_balance,_list_btc_utxos,_send_btc).- Parameters:
- Returns:
The derived address, or
Noneif the wallet is missing or the seed cannot be decrypted.- Return type:
- Raises:
ValueError – If a non-zero index is requested for a
simplewallet.
- async get_private_key(user_id, wallet_name, redis_client, index=0)[source]
Recover the WIF private key for a wallet address, decrypting the stored seed.
Ensures the master key is loaded, reads the raw record from
btc_wallet:<user>:<name>, and decrypts the stored secret via_decrypt. Forsimplewallets the decrypted value is itself the WIF and is returned directly; for HD wallets it rebuilds thebitcoinlib.HDKey, derives the BIP84 subkey atm/84'/<coin_type>'/0'/0/<index>, and returns that subkey’s WIF. This is the signing-key path, so its output must never be logged or persisted by callers.Reads Redis (
GET) and depends on the loaded master key. Called by the_send_btctool handler intools/btc_wallet_tools.pyto sign transactions.
- async get_decrypted_seed(user_id, wallet_name, redis_client)[source]
Decrypt and return a wallet’s raw seed material (mnemonic or WIF).
Ensures the master key is loaded, reads the raw record from
btc_wallet:<user>:<name>, and returns the result of_decryptover the storedencrypted_seedwithout any further derivation, so the caller gets the original backup secret (a BIP39 mnemonic for HD wallets or the WIF for simple ones). This is the most sensitive accessor and exists for explicit user-initiated export only.Reads Redis (
GET) and depends on the loaded master key. Called by the_export_btc_wallettool handler intools/btc_wallet_tools.py.
- async delete_wallet(user_id, wallet_name, redis_client)[source]
Permanently delete a wallet and its index entry from Redis.
Checks whether the wallet key
btc_wallet:<user>:<name>exists and, if so, deletes it and removes the name from the per-user index setbtc_wallet_index:<user>. If the wallet was never present it short-circuits and reports failure without touching the index. This destroys the only copy of the encrypted seed, so the underlying funds become unrecoverable unless the user has backed up the phrase. Touches Redis withEXISTS,DELETE, andSREM.Called by the
_delete_btc_wallettool handler intools/btc_wallet_tools.py.