pyrxd.hd — BIP-32/39/44 HD wallets

class pyrxd.hd.AddressRecord[source]

Bases: object

AddressRecord(address: ‘str’, change: ‘int’, index: ‘int’, used: ‘bool’)

__init__(address, change, index, used)
Parameters:
Return type:

None

address: str
change: int
index: int
used: bool
class pyrxd.hd.HdWallet[source]

Bases: object

BIP44 HD wallet for Radiant with gap-limit discovery and encrypted persistence.

account

BIP44 account index (usually 0).

Type:

int

external_tip

Highest derived index on external chain (change=0).

Type:

int

internal_tip

Highest derived index on internal chain (change=1).

Type:

int

addresses

{path_key: AddressRecord} where path_key is f"{change}/{index}".

Type:

dict[str, pyrxd.hd.wallet.AddressRecord]

__init__(_xprv, _seed, account=0, external_tip=0, internal_tip=0, addresses=<factory>)
Parameters:
Return type:

None

account: int = 0
build_send_max_tx(triples, to_address, *, fee_rate=10000)[source]

Sweep all triples to to_address minus fee. No change output.

Parameters:
Return type:

Transaction

build_send_tx(triples, to_address, photons, *, fee_rate=10000, change_address=None)[source]

Build and sign a P2PKH transfer from HD UTXOs to to_address.

Pure offline operation. Mirrors RxdWallet.build_send_tx() but accepts (utxo, address, privkey) triples so each input is signed by the correct HD-derived key.

change_address defaults to the next unused internal index; callers can override (e.g. to keep change on the external chain for a single-address-style wallet).

Parameters:
  • triples (list[tuple[UtxoRecord, str, PrivateKey]])

  • to_address (str)

  • photons (int)

  • fee_rate (int)

  • change_address (str | None)

Return type:

Transaction

async collect_spendable(client)[source]

Return (utxo, address, privkey) triples for every UTXO across known addresses.

Address→key mapping is preserved so signing works correctly per UTXO. Falls back gracefully if any per-address fetch fails (the failed address contributes nothing rather than crashing the whole collection — the caller decides whether the resulting balance is enough).

Parameters:

client (ElectrumXClient)

Return type:

list[tuple[UtxoRecord, str, PrivateKey]]

external_tip: int = 0
classmethod from_mnemonic(mnemonic, passphrase='', account=0)[source]

Create a fresh wallet from a BIP39 mnemonic.

Parameters:
  • mnemonic (str)

  • passphrase (str)

  • account (int)

Return type:

HdWallet

async get_balance(client)[source]

Return total confirmed + unconfirmed satoshis across all known addresses.

Uses ElectrumXClient.get_balance per address. Call refresh() first to ensure the address set is current.

Parameters:

client (ElectrumXClient)

Return type:

int

async get_utxos(client)[source]

Return all UTXOs across all known addresses.

Parameters:

client (ElectrumXClient)

Return type:

list[UtxoRecord]

internal_tip: int = 0
known_addresses(*, change=None)[source]

Return all known address records, optionally filtered by chain.

Parameters:

change (int | None)

Return type:

list[AddressRecord]

classmethod load(path, mnemonic, passphrase='')[source]

Load a previously saved wallet from path.

The mnemonic is needed to derive the decryption key. Raises FileNotFoundError if path does not exist — a typo’d path will not silently produce an empty wallet that subsequently overwrites a real wallet on save. Callers that explicitly want the create-on-missing behavior should use load_or_create().

Parameters:
Return type:

HdWallet

classmethod load_or_create(path, mnemonic, passphrase='', account=0)[source]

Load a wallet from path, or build a fresh one if the file is missing.

Spelled separately from load() so the create-on-missing intent is explicit at the call site. A common safety failure with the old single-load API was that a typo in path would produce an empty wallet that subsequently overwrote the real wallet on save.

Parameters:
Return type:

HdWallet

next_receive_address()[source]

Return the first external (change=0) address with no recorded history.

Return type:

str

async refresh(client)[source]

Run BIP44 gap-limit scan on both external and internal chains.

Discovers which derived addresses have on-chain history. Stops after _GAP_LIMIT (20) consecutive unused addresses per chain.

Network errors (a transient ElectrumX outage, a server hangup mid-scan) propagate to the caller as NetworkError — previously they were silently treated as “address unused”, which made a funded wallet look empty after a flaky lookup.

Returns the count of newly discovered used addresses.

Parameters:

client (ElectrumXClient)

Return type:

int

save(path)[source]

Encrypt and atomically save wallet state to path.

Atomicity & permissions

Writes via mkstemp + fchmod(0o600) + fsync + os.replace, so:
  • The file is never visible at a wider mode than 0o600 — the mode is set on the fd before any bytes are written.

  • A crash mid-write cannot leave a half-encrypted blob in place — either the old file remains, or the new fully-fsynced file does.

Encryption

AES-256-GCM under a key derived from the BIP39 seed via scrypt with a per-file random salt. Tampering with the ciphertext breaks the GCM tag — load() raises rather than returning attacker-shaped JSON.

Parameters:

path (Path)

Return type:

None

async send(client, to_address, photons, *, fee_rate=10000, change_address=None)[source]

Fetch UTXOs, build, sign, broadcast. Returns broadcast txid.

Raises ValidationError on bad inputs or insufficient funds, NetworkError on RPC failure.

Parameters:
Return type:

str

async send_max(client, to_address, *, fee_rate=10000)[source]

Sweep all UTXOs to to_address minus fee. Returns broadcast txid.

Parameters:
Return type:

str

addresses: dict[str, AddressRecord]
class pyrxd.hd.WordList[source]

Bases: object

BIP39 word list

LIST_WORDS_COUNT: int = 2048
files: dict[str, str] = {'en': '/home/runner/work/pyrxd/pyrxd/src/pyrxd/hd/wordlist/english.txt', 'zh-cn': '/home/runner/work/pyrxd/pyrxd/src/pyrxd/hd/wordlist/chinese_simplified.txt'}
classmethod get_word(index, lang='en')[source]
Parameters:
Return type:

str

classmethod index_word(word, lang='en')[source]
Parameters:
Return type:

int

classmethod load()[source]
Return type:

None

classmethod load_wordlist(lang='en')[source]
Parameters:

lang (str)

Return type:

list[str]

path = '/home/runner/work/pyrxd/pyrxd/src/pyrxd/hd/wordlist'
wordlist: dict[str, list[str]] = {}
class pyrxd.hd.Xkey[source]

Bases: object

[ : 4] prefix [ 4: 5] depth [ 5: 9] parent public key fingerprint [ 9:13] child index [13:45] chain code [45:78] key (private/public)

__init__(xkey)[source]
Parameters:

xkey (str | bytes)

class pyrxd.hd.Xprv[source]

Bases: Xkey

__init__(xprv)[source]
Parameters:

xprv (str | bytes)

address()[source]
Return type:

str

ckd(index)[source]
Parameters:

index (int | str | bytes)

Return type:

Xprv

classmethod from_seed(seed, network=Network.MAINNET)[source]

derive master extended private key from seed

Parameters:
private_key()[source]
Return type:

PrivateKey

public_key()[source]
Return type:

PublicKey

serialize()[source]

Return the base58check-encoded xprv string. Named explicitly to make audit grep easy.

Return type:

str

xpub()[source]
Return type:

Xpub

class pyrxd.hd.Xpub[source]

Bases: Xkey

__init__(xpub)[source]
Parameters:

xpub (str | bytes)

address()[source]
Return type:

str

ckd(index)[source]
Parameters:

index (int | str | bytes)

Return type:

Xpub

classmethod from_xprv(xprv)[source]
Parameters:

xprv (str | bytes | Xprv)

Return type:

Xpub

public_key()[source]
Return type:

PublicKey

pyrxd.hd.bip32_derive_xkeys_from_xkey(xkey, index_start, index_end, path='m/', change=0)[source]

Derive a range of extended keys from Xprv and Xpub keys using BIP32 path structure.

Parameters:
  • xkey (Xprv | Xpub) – Parent extended key (Xprv or Xpub)

  • index_start (str | int) – Starting index for derivation

  • index_end (str | int) – Ending index for derivation (exclusive)

  • path (str) – Base derivation path (default: BIP32_DERIVATION_PATH)

  • change (str | int) – Change level (0 for receiving addresses, 1 for change addresses)

Returns:

List of derived extended keys

Return type:

List[Union[Xprv, Xpub]]

pyrxd.hd.bip32_derive_xprv_from_mnemonic(mnemonic, lang='en', passphrase='', prefix='mnemonic', path='m/', network=Network.MAINNET)[source]

Derive the subtree root extended private key from mnemonic and path.

Parameters:
  • mnemonic (str)

  • lang (str)

  • passphrase (str)

  • prefix (str)

  • path (str)

  • network (Network)

Return type:

Xprv

pyrxd.hd.bip32_derive_xprvs_from_mnemonic(mnemonic, index_start, index_end, lang='en', passphrase='', prefix='mnemonic', path='m/', change=0, network=Network.MAINNET)[source]

Derive a range of extended keys from a nmemonic using BIP32 format

Parameters:
Return type:

list[Xprv]

pyrxd.hd.bip44_derive_xprv_from_mnemonic(mnemonic, lang='en', passphrase='', prefix='mnemonic', path="m/44'/236'/0'", network=Network.MAINNET)[source]

Derives extended private key using BIP44 format- it is a subset of BIP32. Inherits from BIP32, only changing the default path value.

Parameters:
  • mnemonic (str)

  • lang (str)

  • passphrase (str)

  • prefix (str)

  • path (str)

  • network (Network)

Return type:

Xprv

pyrxd.hd.bip44_derive_xprvs_from_mnemonic(mnemonic, index_start, index_end, lang='en', passphrase='', prefix='mnemonic', path="m/44'/236'/0'", change=0, network=Network.MAINNET)[source]

Derive a range of extended keys from a nmemonic using BIP44 format

Parameters:
Return type:

list[Xprv]

pyrxd.hd.ckd(xkey, path)[source]

ckd = “Child Key Derivation” derive an extended key according to path like “m/44’/0’/1’/0/10” (absolute) or “./0/10” (relative)

Parameters:
Return type:

Xprv | Xpub

pyrxd.hd.derive_xkeys_from_xkey(xkey, index_start, index_end, change=0)[source]
[DEPRECATED] Use bip32_derive_xkeys_from_xkey instead.

This function name is kept for backward compatibility.

Parameters:
Return type:

list[Xprv | Xpub]

pyrxd.hd.derive_xprv_from_mnemonic(mnemonic, lang='en', passphrase='', prefix='mnemonic', path="m/44'/236'/0'", network=Network.MAINNET)[source]
[DEPRECATED] Use bip44_derive_xprv_from_mnemonic instead.

This function name is kept for backward compatibility.

Parameters:
  • mnemonic (str)

  • lang (str)

  • passphrase (str)

  • prefix (str)

  • path (str)

  • network (Network)

Return type:

Xprv

pyrxd.hd.derive_xprvs_from_mnemonic(mnemonic, index_start, index_end, lang='en', passphrase='', prefix='mnemonic', path="m/44'/236'/0'", change=0, network=Network.MAINNET)[source]
[DEPRECATED] Use bip44_derive_xprvs_from_mnemonic instead.

This function name is kept for backward compatibility.

Parameters:
Return type:

list[Xprv]

pyrxd.hd.master_xprv_from_seed(seed, network=Network.MAINNET)[source]
Parameters:
Return type:

Xprv

pyrxd.hd.mnemonic_from_entropy(entropy=None, lang='en')[source]
Parameters:
Return type:

str

pyrxd.hd.seed_from_mnemonic(mnemonic, lang='en', passphrase='', prefix='mnemonic')[source]
Parameters:
Return type:

bytes

pyrxd.hd.step_to_index(step)[source]

convert step (sub path) normal derivation or hardened derivation into child index

Parameters:

step (str | int)

Return type:

int

pyrxd.hd.validate_mnemonic(mnemonic, lang='en')[source]
Parameters: