wallet_manager

Ethereum Wallet Manager Module

Handles HD wallet creation, derivation, and encrypted storage in Redis. Supports any EVM-compatible network through configurable RPC endpoints. Uses BIP39 mnemonics and BIP44 derivation paths (m/44’/60’/0’/0/x).

class wallet_manager.WalletManager[source]

Bases: object

Manages Ethereum HD wallets with encrypted storage in Redis.

Features: - BIP39 mnemonic generation and import - BIP44 derivation (m/44’/60’/0’/0/x) - AES-256-GCM encryption for private keys at rest - Per-user wallet isolation - Address caching for derived addresses

Unlike the v2 singleton, this class accepts an async Redis client via its async methods so it can work with the v3 ToolContext.redis.

__init__()[source]

Create a wallet manager with no master key loaded yet.

Leaves _master_key as None; it is fetched lazily by _ensure_master_key() on the first operation that needs to encrypt or decrypt, so construction touches no Redis or environment. The module instantiates a process-wide singleton wallet_manager at import time (imported by tools/eth_wallet_tools and the test suite), so this constructor is effectively called once per process.

static generate_mnemonic(strength=128)[source]

Generate a fresh BIP39 English mnemonic seed phrase.

Uses the mnemonic library (imported lazily) to produce a new random recovery phrase whose word count follows the entropy strength (128 bits -> 12 words, 256 -> 24). Pure CPU helper with no I/O. Called by create_wallet() when no mnemonic is supplied, and directly by the wallet tool layers (tools/eth_wallet_tools, tools/btc_wallet_tools) when generating a new wallet for a user.

Parameters:

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

Returns:

A space-separated BIP39 mnemonic phrase.

Return type:

str

static validate_mnemonic(mnemonic)[source]

Check that a string is a valid BIP39 English mnemonic.

Verifies both the wordlist membership and the embedded checksum via the mnemonic library (imported lazily). Pure CPU helper with no I/O. Called by create_wallet() to reject a bad imported phrase, and by the tool layers (tools/eth_wallet_tools, tools/btc_wallet_tools) and the identity-registry tests to validate user- or seed-supplied phrases.

Parameters:

mnemonic (str) – The candidate mnemonic phrase.

Returns:

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

Return type:

bool

static derive_address_from_mnemonic(mnemonic, index=0)[source]

Derive the EVM address and private key at one BIP44 index.

Walks the BIP44 Ethereum derivation path m/44'/60'/0'/0/{index} from the given mnemonic using eth_account (enabling its unaudited HD wallet feature first), returning the checksummed address and its hex private key. Pure CPU helper with no I/O. Called by create_wallet() (index 0), derive_address() (caching newly derived addresses), and get_private_key() (to recover the key at a given index).

Parameters:
  • mnemonic (str) – A BIP39 seed phrase.

  • index (int) – The address index along the standard external chain.

Returns:

(checksummed_address, hex_private_key).

Return type:

Tuple[str, str]

static derive_address_from_private_key(private_key)[source]

Compute the EVM address for a raw private key.

Normalises the key to 0x-prefixed form and loads it through eth_account.Account.from_key to obtain the checksummed address. Pure CPU helper with no I/O. Called by import_private_key() when registering a simple (non-HD) wallet.

Parameters:

private_key (str) – A hex private key, with or without the 0x prefix.

Returns:

The checksummed EVM address for that key.

Return type:

str

static is_valid_private_key(private_key)[source]

Report whether a string is a usable EVM private key.

Attempts to load the (0x-normalised) key via eth_account.Account.from_key and returns False on any exception, making it a safe pre-flight check that never raises. Pure CPU helper with no I/O. Called by import_private_key() to reject malformed input before storing anything.

Parameters:

private_key (str) – A candidate hex private key, with or without 0x.

Returns:

True if the key parses, else False.

Return type:

bool

async create_wallet(user_id, wallet_name, redis_client, mnemonic=None, strength=128)[source]

Create and persist a new HD wallet for a user.

Validates and normalises wallet_name (1-32 alphanumeric chars plus -/_), ensures it does not already exist across the user’s aliases, then either validates the supplied mnemonic or generates a fresh one via generate_mnemonic(). It derives the index-0 address with derive_address_from_mnemonic(), encrypts the mnemonic under a per-wallet key (_derive_wallet_key() then _encrypt()), and writes the wallet record to the eth_wallet:{user_id}:{wallet_name} Redis key while adding the name to the eth_wallet_index:{user_id} set. Touches the master key (_ensure_master_key()) and Redis (SET plus SADD); logs the creation. Invoked by tools/eth_wallet_tools (the create/import flows) and the identity-registry tests.

Parameters:
  • user_id (str) – The user the wallet belongs to.

  • wallet_name (str) – Desired wallet name (case-folded, validated).

  • redis_client – The async Redis client for persistence.

  • mnemonic (Optional[str]) – An existing BIP39 phrase to import, or None to generate one.

  • strength (int) – Entropy in bits when generating a new mnemonic.

Returns:

wallet_name, type ("hd"), address, and created_at for the new wallet (the seed is never returned).

Return type:

Dict[str, Any]

Raises:

ValueError – If the name is invalid, the wallet already exists, or a supplied mnemonic fails validation.

async import_private_key(user_id, wallet_name, private_key, redis_client)[source]

Import a raw private key as a simple (non-HD) wallet.

Mirrors create_wallet() but for a single externally-supplied key: it validates the wallet name, ensures the wallet does not already exist, normalises and validates the key (is_valid_private_key()), derives its address (derive_address_from_private_key()), encrypts the key under a per-wallet key (_derive_wallet_key() then _encrypt()), and stores a type: simple record at eth_wallet:{user_id}:{wallet_name} while adding the name to the eth_wallet_index:{user_id} set. Touches the master key (_ensure_master_key()) and Redis (SET plus SADD); logs the import. Called by tools/eth_wallet_tools.

Parameters:
  • user_id (str) – The user the wallet belongs to.

  • wallet_name (str) – Desired wallet name (case-folded, validated).

  • private_key (str) – The hex private key to import (0x optional).

  • redis_client – The async Redis client for persistence.

Returns:

wallet_name, type ("simple"), address, and created_at (the key itself is never returned).

Return type:

Dict[str, Any]

Raises:

ValueError – If the name is invalid, the wallet already exists, or the private key is invalid.

async wallet_exists(user_id, wallet_name, redis_client, config=None)[source]

Report whether a named wallet exists for the user (any alias).

Expands the user into all linked identities via _get_user_aliases() and checks each alias’s eth_wallet:{alias}:{wallet_name} Redis key (EXISTS), returning True on the first hit so a wallet created on one platform is visible across them all. Reads Redis only. Called by create_wallet() and import_private_key() as a uniqueness guard, and directly by the identity-registry tests.

Parameters:
  • user_id (str) – The user (raw or platform:user_id).

  • wallet_name (str) – The wallet name to look up (case-folded).

  • redis_client – The async Redis client.

  • config (Any) – Optional config passed through to alias resolution.

Returns:

True if the wallet exists under any alias, else False.

Return type:

bool

async get_wallet(user_id, wallet_name, redis_client, config=None)[source]

Fetch a wallet’s public metadata (never its seed) for the user.

Resolves the user’s aliases via _get_user_aliases() and reads each eth_wallet:{alias}:{wallet_name} Redis key (GET) until one hits, then returns the non-secret fields — name, type, derived addresses, and creation time — deliberately omitting the encrypted_seed. Reads Redis only. Called by list_wallets() and get_private_key() internally, and directly by the wallet tool layers (tools/eth_wallet_tools, tools/btc_wallet_tools) and tests.

Parameters:
  • user_id (str) – The user (raw or platform:user_id).

  • wallet_name (str) – The wallet name to fetch (case-folded).

  • redis_client – The async Redis client.

  • config (Any) – Optional config passed through to alias resolution.

Returns:

wallet_name, type, addresses, and created_at, or None if no matching wallet exists.

Return type:

Optional[Dict[str, Any]]

async get_decrypted_seed(user_id, wallet_name, redis_client, config=None)[source]

Decrypt and return a wallet’s seed (mnemonic or private key).

Loads the master key (_ensure_master_key()), then for each of the user’s aliases (_get_user_aliases()) reads the eth_wallet:{alias}:{wallet_name} record from Redis, re-derives that wallet’s key with _derive_wallet_key(), and decrypts the stored encrypted_seed via _decrypt(). Because the per-wallet key is salted with the owning alias, it retries the next alias on an InvalidTag or ValueError (logging the failure) so the right identity’s key eventually matches. Returns the raw secret, so callers must treat it carefully. Reads Redis and performs decryption. Called internally by derive_address() and get_private_key(), and directly by tools/eth_wallet_tools, tools/btc_wallet_tools, and the tests.

Parameters:
  • user_id (str) – The user (raw or platform:user_id).

  • wallet_name (str) – The wallet name (case-folded).

  • redis_client – The async Redis client.

  • config (Any) – Optional config passed through to alias resolution.

Returns:

The decrypted mnemonic or private key, or None if the wallet is missing, has no seed, or cannot be decrypted under any alias.

Return type:

Optional[str]

async derive_address(user_id, wallet_name, index, redis_client, config=None)[source]

Return the EVM address at a given index, deriving and caching it.

Loads the master key, resolves aliases, and finds the wallet record in Redis. If the requested index is already in the cached addresses map it returns it directly. For a simple (non-HD) wallet only index 0 exists, so any other index raises. For an HD wallet it fetches the decrypted seed (get_decrypted_seed()), derives the address with derive_address_from_mnemonic(), writes the newly derived address back into the wallet record (a Redis SET) so future lookups are free, and returns it. Reads and writes Redis and performs decryption. Called by tools/eth_wallet_tools for address display, sends, and balance checks, and by the identity-registry tests.

Parameters:
  • user_id (str) – The user (raw or platform:user_id).

  • wallet_name (str) – The wallet name (case-folded).

  • index (int) – The BIP44 address index to resolve.

  • redis_client – The async Redis client.

  • config (Any) – Optional config passed through to alias resolution.

Returns:

The address at index, or None if the wallet or its seed cannot be found.

Return type:

Optional[str]

Raises:

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

async get_private_key(user_id, wallet_name, redis_client, index=0, config=None)[source]

Return the private key for a wallet address at a given index.

Loads the master key and, for each alias, looks the wallet up via get_wallet() and decrypts its seed via get_decrypted_seed(). For a simple wallet the stored seed is the private key (only index 0 is valid); for an HD wallet it derives the key at index with derive_address_from_mnemonic(). Reads Redis and performs decryption, and returns secret key material, so callers (e.g. transaction signing in tools/eth_wallet_tools) must handle it carefully. Also exercised by the identity-registry tests.

Parameters:
  • user_id (str) – The user (raw or platform:user_id).

  • wallet_name (str) – The wallet name (case-folded).

  • redis_client – The async Redis client.

  • index (int) – The BIP44 index for HD wallets (must be 0 for simple wallets).

  • config (Any) – Optional config passed through to alias resolution.

Returns:

The hex private key, or None if the wallet or its seed cannot be found.

Return type:

Optional[str]

Raises:

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

async list_wallets(user_id, redis_client, config=None)[source]

List all of a user’s wallets (public metadata) across aliases.

Resolves the user’s aliases (_get_user_aliases()) and unions the members of each eth_wallet_index:{alias} Redis set (SMEMBERS, decoding any bytes) into a de-duplicated set of names, then loads each one’s public metadata via get_wallet() and returns them sorted by creation time. Reads Redis only; never exposes seeds. Called by tools/eth_wallet_tools and tools/btc_wallet_tools to render a user’s wallet list, and by the tests.

Parameters:
  • user_id (str) – The user (raw or platform:user_id).

  • redis_client – The async Redis client.

  • config (Any) – Optional config passed through to alias resolution.

Returns:

Per-wallet metadata dicts (as returned by get_wallet()), ordered by created_at.

Return type:

List[Dict[str, Any]]

async delete_wallet(user_id, wallet_name, redis_client, config=None)[source]

Delete a named wallet for every alias of the user.

Resolves the user’s aliases (_get_user_aliases()) and, for each, deletes the eth_wallet:{alias}:{wallet_name} key (DEL); when a key was actually removed it also drops the name from the eth_wallet_index:{alias} set (SREM) and logs the deletion. Reports success if any alias’s wallet was removed. Writes to Redis. Called by tools/eth_wallet_tools and tools/btc_wallet_tools for the user-facing delete flow, and by the tests.

Parameters:
  • user_id (str) – The user (raw or platform:user_id).

  • wallet_name (str) – The wallet name to delete (case-folded).

  • redis_client – The async Redis client.

  • config (Any) – Optional config passed through to alias resolution.

Returns:

True if a wallet was deleted under any alias, else False.

Return type:

bool