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: object

Manages 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_key as None so 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_manager is instantiated at the bottom of this file and imported by tools/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.Mnemonic and 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 that create_wallet then encrypts and stores; this method itself performs no Redis or network I/O.

Called by the _create_btc_wallet tool handler in tools/btc_wallet_tools.py when a user asks to create a new HD wallet without supplying their own phrase.

Parameters:

strength (int) – Entropy in bits (e.g. 128 or 256). Defaults to 128.

Returns:

A space-separated BIP39 mnemonic phrase.

Return type:

str

validate_mnemonic(mnemonic)[source]

Check whether a string is a valid BIP39 mnemonic (wordlist plus checksum).

Lazily imports bitcoinlib.mnemonic.Mnemonic and runs its checksum/wordlist validation, catching any exception and returning False so 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_wallet and by the _create_btc_wallet tool handler in tools/btc_wallet_tools.py to reject bad phrases early.

Parameters:

mnemonic (str) – Candidate seed phrase to validate.

Returns:

True if the phrase is a valid BIP39 mnemonic, else False.

Return type:

bool

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 uses bitcoinlib.HDKey to derive the first Native SegWit (BIP84) bech32 receive address at m/84'/<coin_type>'/0'/0/0 (coin type 1' for testnets, 0' for mainnet). The mnemonic is encrypted with a per-wallet key via _encrypt and the wallet record is written to Redis under btc_wallet:<user>:<name> while the name is added to the per-user index set btc_wallet_index:<user>. The returned dict deliberately excludes any secret material.

Touches Redis (SET plus SADD) and depends on the loaded master key; the plaintext mnemonic exists only transiently in memory. Called by the _create_btc_wallet tool handler in tools/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 (default bitcoin).

Returns:

Public wallet info with wallet_name, first address, display network, and created_at.

Return type:

Dict[str, Any]

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 with bitcoinlib.Key to derive the corresponding bech32 address, encrypts the WIF with a per-wallet key via _encrypt, and stores a record of type simple (one fixed address at index 0) under btc_wallet:<user>:<name>, adding the name to the per-user index set btc_wallet_index:<user>. Unlike create_wallet this wallet cannot derive further addresses.

Touches Redis (SET plus SADD) and depends on the loaded master key. Called by the _import_btc_wallet tool handler in tools/btc_wallet_tools.py.

Parameters:
  • user_id (str) – Owning user’s id; namespaces the Redis keys.

  • wallet_name (str) – 1-32 char wallet name.

  • wif (str) – The Wallet Import Format private key to store.

  • redis_client – Async Redis client for the master key and wallet storage.

  • network (str) – Network name or alias (default bitcoin).

Returns:

Public wallet info with wallet_name, address, display network, and created_at.

Return type:

Dict[str, Any]

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 the encrypted_seed field so callers never see secret material through this path (the decrypted seed is only reachable via the dedicated get_decrypted_seed/get_private_key methods). Returns None when no such wallet key exists. Performs a single Redis GET.

Called internally as a duplicate-existence check by create_wallet and import_wif, by list_wallets to expand each indexed name, and by tool handlers (_derive_btc_address, _export_btc_wallet) in tools/btc_wallet_tools.py.

Parameters:
  • user_id (str) – Owning user’s id.

  • wallet_name (str) – Wallet name to look up.

  • redis_client – Async Redis client.

Returns:

The wallet record without encrypted_seed, or None if it does not exist.

Return type:

Optional[Dict[str, Any]]

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 calls get_wallet for each to load its secret-stripped record, skipping any that have gone missing. The combined list is sorted by created_at. Touches Redis with one SMEMBERS plus one GET per wallet.

Called by the _list_btc_wallets tool handler in tools/btc_wallet_tools.py.

Parameters:
  • user_id (str) – Owning user’s id.

  • redis_client – Async Redis client.

Returns:

Public wallet records (no secrets), oldest first.

Return type:

List[Dict[str, Any]]

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 the index:address_type cache key is already stored it is returned immediately. For simple (WIF-imported) wallets only index 0 is valid and the stored address is returned. Otherwise it decrypts the mnemonic via _decrypt, rebuilds the bitcoinlib.HDKey, and derives the BIP84 subkey at m/84'/<coin_type>'/0'/0/<index>, encoding as bech32, p2sh, or legacy per address_type. The newly derived address is written back into the wallet’s addresses cache in Redis so subsequent calls are cheap.

Reads and conditionally writes Redis (GET then SET) and depends on the loaded master key; the mnemonic exists only transiently in memory. Called by several tool handlers in tools/btc_wallet_tools.py (_derive_btc_address, _check_btc_balance, _list_btc_utxos, _send_btc).

Parameters:
  • user_id (str) – Owning user’s id.

  • wallet_name (str) – Wallet name to derive from.

  • index (int) – Address index along the external chain.

  • redis_client – Async Redis client.

  • address_type (str) – bech32 (default), p2sh, or legacy.

Returns:

The derived address, or None if the wallet is missing or the seed cannot be decrypted.

Return type:

Optional[str]

Raises:

ValueError – If a non-zero index is requested for a simple wallet.

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. For simple wallets the decrypted value is itself the WIF and is returned directly; for HD wallets it rebuilds the bitcoinlib.HDKey, derives the BIP84 subkey at m/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_btc tool handler in tools/btc_wallet_tools.py to sign transactions.

Parameters:
  • user_id (str) – Owning user’s id.

  • wallet_name (str) – Wallet name.

  • redis_client – Async Redis client.

  • index (int) – Address index whose key is wanted (default 0).

Returns:

The WIF private key, or None if the wallet is missing or the seed cannot be decrypted.

Return type:

Optional[str]

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 _decrypt over the stored encrypted_seed without 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_wallet tool handler in tools/btc_wallet_tools.py.

Parameters:
  • user_id (str) – Owning user’s id.

  • wallet_name (str) – Wallet name.

  • redis_client – Async Redis client.

Returns:

The decrypted seed/WIF, or None if the wallet is missing or decryption fails.

Return type:

Optional[str]

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 set btc_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 with EXISTS, DELETE, and SREM.

Called by the _delete_btc_wallet tool handler in tools/btc_wallet_tools.py.

Parameters:
  • user_id (str) – Owning user’s id.

  • wallet_name (str) – Wallet name to delete.

  • redis_client – Async Redis client.

Returns:

True if a wallet was found and removed, False if it did not exist.

Return type:

bool