pyrxd (top-level)

pyrxd — Python SDK for the Radiant (RXD) blockchain.

Provides transaction building, HD wallet, Glyph token protocol (NFT/FT/dMint), Gravity cross-chain atomic swaps, SPV verification, and ElectrumX networking.

Quickstart:

from pyrxd import GlyphBuilder, GlyphMetadata, GlyphProtocol
from pyrxd import RxdSdkError, ValidationError
Subpackages:

pyrxd.glyph — Glyph token protocol (NFT, FT, dMint, mutable, V2) pyrxd.gravity — BTC↔RXD atomic swaps (Gravity protocol) pyrxd.security — Typed secrets, error hierarchy, secure RNG pyrxd.hd — BIP-32/39/44 HD wallet pyrxd.network — ElectrumX client, BTC data sources pyrxd.spv — SPV chain/payment verification pyrxd.transaction — Transaction building and serialization pyrxd.script — Script types and evaluation

class pyrxd.ActiveOffer[source]

Bases: object

State of a live Gravity MakerOffer on Radiant.

Returned by GravityMakerSession.create_offer() and required by all subsequent lifecycle methods.

offer

The original GravityOffer covenant parameters.

Type:

pyrxd.gravity.types.GravityOffer

maker_offer_result

Raw tx details from build_maker_offer_tx.

Type:

pyrxd.gravity.types.MakerOfferResult

offer_txid

Radiant txid of the confirmed MakerOffer funding output.

Type:

str

offer_vout

Output index of the MakerOffer P2SH UTXO (always 0).

Type:

int

offer_photons

Photons locked in the MakerOffer P2SH output.

Type:

int

__init__(offer, maker_offer_result, offer_txid, offer_vout, offer_photons)
Parameters:
Return type:

None

offer: GravityOffer
maker_offer_result: MakerOfferResult
offer_txid: str
offer_vout: int
offer_photons: int
class pyrxd.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.GlyphBuilder[source]

Bases: object

Build unsigned Glyph transactions.

Separate commit and reveal methods — caller is responsible for:

  1. Signing the commit tx and broadcasting it.

  2. Waiting for confirmation.

  3. Passing the confirmed commit txid to the reveal method.

  4. Signing the reveal tx (via Transaction + PrivateKey).

Method selection guide (N9 — surface grew to 12 methods across 5 protocols)

Minting (commit → reveal)

Goal

Protocol tag(s)

Reveal method

Mint a singleton NFT

[NFT]

prepare_reveal()

Mint a plain FT

[FT]

prepare_ft_deploy_reveal()

Mint a dMint FT

[FT, DMINT]

prepare_dmint_deploy() (3 txs)

Mint a mutable NFT

[NFT, MUT]

prepare_mutable_reveal()

Mint a collection

``[NFT,CONTAINER]`

prepare_container_reveal()

Mint a WAVE name

[NFT,MUT,WAVE]

prepare_wave_reveal()

For every token type the first step is the same: call prepare_commit() (which derives the commit script from the metadata protocol list automatically). Only the reveal step differs.

Transfers (no commit needed)

  • NFT transfer: build_nft_transfer_tx()

  • FT transfer: build_ft_transfer_tx() (or FtUtxoSet in glyph/ft.py)

Low-level (rarely called directly)

  • prepare_reveal() — generic reveal; is_nft picks singleton vs FT reftype

  • build_reveal_scripts() — alternate reveal entry that returns scripts, not params

  • build_transfer_locking_script() — bare FT lock without constructing a tx

  • build_contract_script() — MUT contract script for mutable NFT reveals

build_ft_transfer_tx(params)[source]

Build a signed FT transfer transaction enforcing conservation.

Thin delegator to FtUtxoSet.build_transfer_tx() — the real logic (selection, two-pass fee, conservation) lives there so the API surface is available both at the builder level and directly on a UTXO-set instance.

Parameters:

params (FtTransferParams) – FtTransferParams — see dataclass docstring.

Returns:

FtTransferResult — signed tx + scripts + fee.

Raises:

ValueError – same conditions as FtUtxoSet.build_transfer_tx() (insufficient FT balance, insufficient RXD for fee + dust).

Return type:

FtTransferResult

build_nft_transfer_tx(params)[source]

Build a signed NFT transfer transaction.

Spends an existing NFT UTXO (standard P2PKH scriptSig unlock: <sig> <pubkey>) and creates a new NFT output locked to new_owner_pkh. The 36-byte ref is preserved across the transfer — it’s extracted from the input’s NFT script and written into the new output’s NFT script unchanged.

Fee calculation is two-pass: build a trial tx, sign it to measure actual serialised size, then rebuild with the final value = input_value - size*fee_rate. The trial signature is discarded (reset unlocking_script = None before final sign) so the final tx carries a signature over the final outputs, not the trial ones.

Parameters:

params (TransferParams) – TransferParams — see dataclass docstring

Returns:

TransferResult — signed tx, new locking script, ref, fee

Raises:
Return type:

TransferResult

build_transfer_locking_script(ref, new_owner_pkh, is_nft)[source]

Build the locking script for a transfer output.

Parameters:
Return type:

bytes

prepare_commit(params)[source]

Prepare the commit transaction parameters.

Returns the commit locking script + CBOR bytes + estimated fee. Caller must build, sign, and broadcast the actual transaction.

The commit script’s OP_REFTYPE_OUTPUT check is derived from metadata.protocol: NFT (2 in protocol) produces an OP_2/SINGLETON-expecting commit; any other protocol mix (FT, dMint FT, data, etc.) produces an OP_1/NORMAL-expecting commit. This means the caller does not hand-pick refType — the metadata drives it. Prior versions forced every commit to NFT shape; see build_commit_locking_script for the fix note.

Parameters:

params (CommitParams)

Return type:

CommitResult

prepare_container_reveal(commit_txid, commit_vout, cbor_bytes, owner_pkh, child_ref=None)[source]

Prepare scripts for a CONTAINER reveal.

A container is an NFT with an additional OP_PUSHINPUTREF <child_ref> prefix that links it to a child token ref. When child_ref is None the container is created empty (no child ref in locking script).

Protocol field must include GlyphProtocol.CONTAINER (7).

Parameters:
Return type:

ContainerRevealScripts

prepare_dmint_deploy(params)[source]

Prepare a full dMint token deploy: commit + reveal + deploy scripts.

A dMint deploy requires three transactions in sequence:

  1. Commit tx — commits the token metadata payload hash on-chain (standard Glyph commit, same as prepare_commit()).

  2. Reveal tx — spends the commit, creates the token ref UTXO (a 75-byte FT locking script — same shape as prepare_ft_deploy_reveal()). The token ref outpoint is the permanent identifier of the FT token.

  3. Deploy tx — creates the singleton contract UTXO, funded with the initial reward pool. Its output script is a full build_dmint_contract_script() — state prefix + OP_STATESEPARATOR + covenant code.

Usage

builder = GlyphBuilder()
result = builder.prepare_dmint_deploy(DmintFullDeployParams(...))

# Step 1: build, sign, broadcast commit tx using result.commit_result
# Step 2: wait for confirmation, get commit_txid
# Step 3: build reveal tx using result.reveal_scripts
# Step 4: broadcast reveal, get reveal_txid + reveal_vout
# Step 5: build deploy tx using result.deploy_contract_script
#         (the contract input refs the token ref at reveal outpoint)

The caller is responsible for constructing and signing actual Transaction objects using the scripts returned here, following the same pattern as the integration test in tests/test_dmint_deploy_integration.py.

param params:

DmintFullDeployParams — all deploy configuration.

returns:

DmintDeployResult with commit, reveal, and deploy artefacts.

raises ValidationError:

params.premine_amount < 546 (dust limit); metadata protocol does not include FT; reward pool too small.

Parameters:

params (DmintFullDeployParams)

Return type:

DmintDeployResult

prepare_ft_deploy_reveal(commit_txid, commit_vout, commit_value, cbor_bytes, premine_pkh, premine_amount)[source]

Prepare reveal scripts + premine amount for an FT deploy.

Thin convenience wrapper around prepare_reveal() for the FT-deploy-with-premine flow: the reveal produces one FT output carrying the full issued supply to premine_pkh, and its outpoint becomes the permanent token ref.

Caller still constructs the actual transaction. The returned premine_amount is what vout[0].value must be on the reveal tx — typically the full supply for a premine-only deploy (no covenant UTXO). Radiant FT convention: 1 photon = 1 FT unit, so premine_amount is the supply in whole units.

No dMint-specific logic here. The cbor_bytes already encode whatever protocol markers the caller chose — dMint FT ([1,4]), plain FT ([1]), or any other combination — via GlyphMetadata. pyrxd treats the protocol markers as caller-owned; classification happens at the indexer layer.

Parameters:
  • commit_txid (str)

  • commit_vout (int)

  • commit_value (int)

  • cbor_bytes (bytes)

  • premine_pkh (Hex20)

  • premine_amount (int)

Return type:

FtDeployRevealScripts

prepare_mutable_reveal(commit_txid, commit_vout, cbor_bytes, owner_pkh)[source]

Prepare scripts for a MUT (mutable NFT) reveal.

Returns the two output locking scripts the caller must place in the reveal tx: - nft_script: 63-byte NFT singleton (token the owner holds) - contract_script: 174-byte mutable contract UTXO (holds state)

The reveal scriptSig suffix is also returned; the caller prepends <sig> <pubkey> to form the full scriptSig.

Protocol field in cbor_bytes must include GlyphProtocol.MUT (5). Use GlyphMetadata(protocol=[GlyphProtocol.NFT, GlyphProtocol.MUT]).

Parameters:
Return type:

MutableRevealScripts

prepare_reveal(params)[source]

Prepare the reveal transaction scripts.

Returns locking script + scriptSig suffix. Caller must build, sign, and broadcast the actual transaction.

Parameters:

params (RevealParams)

Return type:

RevealScripts

prepare_wave_reveal(commit_txid, commit_vout, cbor_bytes, owner_pkh, name)[source]

Prepare scripts for a WAVE (on-chain naming) reveal.

WAVE extends MUT with a name field in the CBOR payload. Protocol field must include GlyphProtocol.WAVE (11).

name must be non-empty printable ASCII, max 255 characters. The name is validated here but must already be embedded in cbor_bytes by the caller via GlyphMetadata(name=...).

Protocol requirement: [NFT(2), MUT(5), WAVE(11)].

Parameters:
Return type:

MutableRevealScripts

class pyrxd.GlyphInspector[source]

Bases: object

Parse raw transaction bytes to find Glyph outputs. Pure — no network access.

extract_reveal_metadata(scriptsig)[source]

Parse a reveal TX scriptSig to extract CBOR metadata.

scriptSig format: <sig> <pubkey> <”gly”> <CBOR> Returns None if this is not a reveal scriptSig.

Parameters:

scriptsig (bytes)

Return type:

GlyphMetadata | None

find_glyphs(tx_outputs)[source]

Given list of (satoshis, script_bytes) outputs, return detected Glyphs.

Parameters:

tx_outputs (list[tuple[int, bytes]])

Return type:

list[GlyphOutput]

class pyrxd.GlyphMetadata[source]

Bases: object

CBOR payload for a Glyph token.

__init__(protocol, name='', ticker='', description='', token_type='', main=None, attrs=<factory>, loc='', loc_hash='', decimals=0, image_url='', image_ipfs='', image_sha256='', v=None, dmint_params=None, creator=None, royalty=None, policy=None, rights=None, created='', commit_outpoint='')
Parameters:
Return type:

None

commit_outpoint: str = ''
created: str = ''
creator: GlyphCreator | None = None
decimals: int = 0
description: str = ''
dmint_params: DmintCborPayload | None = None
classmethod for_dmint_ft(ticker, name, decimals=0, description='', image_url='', image_ipfs='', image_sha256='', protocol=None, dmint_params=None)[source]

Construct GlyphMetadata for a dMint-marked FT deploy.

Pass dmint_params (a DmintCborPayload) to embed the dMint configuration object in the token metadata. Indexers and wallets use this to display mining parameters without parsing the contract script.

Sets v=2 automatically when dmint_params is provided.

Parameters:
Return type:

GlyphMetadata

image_ipfs: str = ''
image_sha256: str = ''
image_url: str = ''
loc: str = ''
loc_hash: str = ''
main: GlyphMedia | None = None
name: str = ''
policy: GlyphPolicy | None = None
rights: GlyphRights | None = None
royalty: GlyphRoyalty | None = None
ticker: str = ''
to_cbor_dict()[source]

Build the dict that gets CBOR-encoded (excluding ‘gly’ marker).

Return type:

dict

token_type: str = ''
v: int | None = None
protocol: list[int]
attrs: dict[str, str]
class pyrxd.GlyphProtocol[source]

Bases: IntEnum

__new__(value)
FT = 1
NFT = 2
DAT = 3
DMINT = 4
MUT = 5
BURN = 6
CONTAINER = 7
ENCRYPTED = 8
TIMELOCK = 9
AUTHORITY = 10
WAVE = 11
class pyrxd.GlyphRef[source]

Bases: object

36-byte Glyph reference: txid (reversed LE) + vout (4-byte LE).

__init__(txid, vout)
Parameters:
Return type:

None

classmethod from_bytes(data)[source]

Parse 36-byte wire format.

Parameters:

data (bytes)

Return type:

GlyphRef

to_bytes()[source]

Encode as 36-byte wire format: txid_reversed + vout_le.

Return type:

bytes

txid: Txid
vout: int
class pyrxd.GlyphScanner[source]

Bases: object

Scan a Radiant address or script_hash for Glyph outputs.

Parameters:

client – An already-connected ElectrumXClient. The scanner does not own the connection lifecycle; callers should use the client as a context manager and pass it in.

__init__(client)[source]
Parameters:

client (ElectrumXClient)

Return type:

None

async scan_address(address)[source]

Return all Glyph outputs currently owned at address.

Parameters:

address (str) – Base58Check-encoded P2PKH address.

Returns:

Typed Glyph objects. metadata is None for transfer outputs (no reveal scriptSig in the origin transaction).

Return type:

List[GlyphNft | GlyphFt]

async scan_script_hash(script_hash)[source]

Return all Glyph outputs for script_hash.

Fetches UTXOs, raw transactions, and (where available) reveal transaction metadata, then constructs typed GlyphNft / GlyphFt objects.

Concurrency: UTXO raw-tx fetches and reveal-metadata fetches both run in parallel via asyncio.gather. Pre-fix (closes ultrareview re-review N17) the reveal-metadata path was inside the per-utxo loop and serialised one round-trip per glyph; for a 100-glyph wallet that meant ~100x the latency of the now- batched version.

Parameters:

script_hash (Hex32 | bytes | str)

Return type:

list[GlyphNft | GlyphFt]

class pyrxd.GravityMakerSession[source]

Bases: object

Manage the full lifecycle of a Gravity BTC↔RXD atomic swap offer.

This class handles the Maker’s side of the swap:

  1. Build and broadcast the MakerOffer tx (create_offer).

  2. Poll for the Taker’s claim (wait_for_claim).

  3. Broadcast a cancel tx if the Taker never claims (cancel_offer).

  4. Query current state (check_status).

Parameters:
  • rxd_client – Connected ElectrumXClient for Radiant chain operations (broadcast, query UTXOs).

  • btc_source – A BtcDataSource — used only by subclasses / extensions that need BTC confirmation data. May be None for pure Radiant operations.

  • maker_priv – Maker’s secp256k1 private key wrapped in PrivateKeyMaterial.

  • poll_interval_seconds – Seconds between UTXO polls in wait_for_claim. Default 30.

Examples

Typical Maker flow:

async with ElectrumXClient(["wss://electrumx.example.com"]) as rxd:
    session = GravityMakerSession(rxd_client=rxd, maker_priv=priv)
    params = GravityOfferParams(
        offer=offer,
        funding_txid="...",
        funding_vout=0,
        funding_photons=5_100_000,
        fee_sats=100_000,
    )
    active = await session.create_offer(params)
    claim_txid = await session.wait_for_claim(active, timeout_seconds=3600)
    if claim_txid is None:
        cancel_txid = await session.cancel_offer(active)
__init__(rxd_client, maker_priv, btc_source=None, poll_interval_seconds=30)[source]
Parameters:
Return type:

None

async cancel_offer(offer, fee_sats=1000, maker_address='')[source]

Broadcast the cancel (MakerOffer.cancel()) transaction.

Reclaims the MakerOffer UTXO before the claim deadline using build_cancel_tx. This is only valid if the Taker has NOT yet claimed the UTXO.

Parameters:
  • offer (ActiveOffer) – The ActiveOffer to cancel.

  • fee_sats (int) – Miner fee in photons for the cancel tx. Default 1000.

  • maker_address (str) – Maker’s Radiant P2PKH address to receive the reclaimed photons. Required — must be a valid Radiant address.

Returns:

The cancel tx’s txid.

Return type:

str

Raises:
async check_status(offer)[source]

Return the current status of the offer UTXO.

Queries the Radiant ElectrumX server for the MakerOffer P2SH UTXO.

Returns one of:

  • "open" — UTXO is still unspent (offer not yet claimed).

  • "claimed" — UTXO no longer in unspent set (Taker has claimed).

  • "expired" — claim_deadline has passed and UTXO is unspent

    (Maker can now forfeit).

  • "unknown" — UTXO not found and not yet past deadline

    (may be unconfirmed or already finalized/cancelled).

Parameters:

offer (ActiveOffer) – The ActiveOffer to check.

Returns:

One of "open", "claimed", "expired", "unknown".

Return type:

str

Raises:

NetworkError – On ElectrumX query failure.

async create_offer(offer_params)[source]

Build and broadcast the MakerOffer funding tx.

The offer UTXO is a P2SH output locked to offer_params.offer’s MakerOffer covenant. Once broadcast, the Taker can claim it by spending it with build_claim_tx.

Parameters:

offer_params (GravityOfferParams) – Funding-UTXO details and the GravityOffer covenant.

Returns:

Populated with the resulting txid and UTXO details.

Return type:

ActiveOffer

Raises:
async wait_for_claim(offer, timeout_seconds=3600)[source]

Poll for the Taker’s claim transaction.

Polls get_utxos() on the MakerOffer P2SH script hash. When the UTXO disappears from the unspent set the Taker has claimed it.

This method cannot directly return the claim txid — ElectrumX’s listunspent API only reports which UTXOs are currently unspent. Once the offer UTXO is spent (claimed), we return the offer’s txid as a sentinel so the caller knows which offer was claimed. Callers that need the actual claim txid should fetch the spending tx separately (e.g. via get_transaction on the address history).

Parameters:
  • offer (ActiveOffer) – The ActiveOffer returned by create_offer.

  • timeout_seconds (int) – Maximum seconds to wait. Returns None on timeout.

Returns:

The offer txid (as a claimed-sentinel) on success, or None on timeout.

Return type:

str or None

class pyrxd.GravityOfferParams[source]

Bases: object

Parameters required to create a new Gravity MakerOffer.

These are the funding-UTXO details for the Maker’s side. The GravityOffer itself (covenant bytecode, BTC-side params, etc.) is built externally (e.g. via build_gravity_offer) and passed as offer.

offer

Fully populated GravityOffer with offer_redeem_hex set.

Type:

pyrxd.gravity.types.GravityOffer

funding_txid

Hex txid of the Maker’s P2PKH UTXO being spent to fund the offer.

Type:

str

funding_vout

Output index of the Maker’s funding UTXO.

Type:

int

funding_photons

Value of the Maker’s funding UTXO in photons.

Type:

int

fee_sats

Miner fee in photons for the MakerOffer funding tx.

Type:

int

change_address

Optional Radiant P2PKH address for change output. See build_maker_offer_tx for semantics.

Type:

str | None

__init__(offer, funding_txid, funding_vout, funding_photons, fee_sats, change_address=None)
Parameters:
Return type:

None

change_address: str | None = None
offer: GravityOffer
funding_txid: str
funding_vout: int
funding_photons: int
fee_sats: int
class pyrxd.GravityTrade[source]

Bases: object

Orchestrate a complete Gravity BTC↔RXD atomic swap.

Parameters:
  • radiant_network – Connected ElectrumXClient for Radiant chain operations (broadcast, fetch tx/block).

  • bitcoin_source – A BtcDataSource for Bitcoin chain data (tx fetch, Merkle proof, block headers).

  • config – Optional TradeConfig. Uses defaults if not provided.

Examples

Typical Taker flow:

async with ElectrumXClient(["wss://electrumx.example.com"]) as rxd:
    trade = GravityTrade(radiant_network=rxd, bitcoin_source=btc_src)
    claim = await trade.claim(
        offer=offer,
        offer_txid="...",
        offer_vout=0,
        offer_photons=10_000_000,
        fee_sats=1000,
        taker_privkey=privkey,
    )
    btc_txid = "..."  # broadcast BTC payment externally
    status = await trade.wait_confirmations(btc_txid)
    result = await trade.finalize(
        btc_txid=btc_txid,
        offer=offer,
        claimed_txid=claim.txid,
        claimed_vout=0,
        claimed_photons=claim.output_photons,
        taker_address="...",
        fee_sats=1000,
    )
__init__(*, radiant_network, bitcoin_source, config=None)[source]
Parameters:
Return type:

None

async claim(offer, offer_txid, offer_vout, offer_photons, fee_sats, taker_privkey)[source]

Spend the MakerOffer UTXO, creating a MakerClaimed UTXO.

Broadcasts the claim transaction to the Radiant network and returns a ClaimResult.

The claim transaction requires Taker’s signature (audit 04-S3). build_claim_tx independently verifies the code hash before signing (audit 05-F-13).

Parameters:
  • offer (GravityOffer) – The GravityOffer posted by the Maker.

  • offer_txid (str) – Radiant txid of the MakerOffer funding output.

  • offer_vout (int) – Output index of the MakerOffer UTXO.

  • offer_photons (int) – Value of the MakerOffer UTXO in photons.

  • fee_sats (int) – Radiant miner fee in photons.

  • taker_privkey (PrivateKeyMaterial) – Taker’s secp256k1 private key.

Return type:

ClaimResult

async finalize(btc_txid, offer, claimed_txid, claimed_vout, claimed_photons, taker_address, fee_sats, btc_tx_height=None)[source]

Fetch the BTC SPV proof, verify it, and broadcast the finalize tx.

This method always runs the full SpvProofBuilder verifier chain — there is no way to bypass verification at this level.

Parameters:
  • btc_txid (str) – Bitcoin transaction ID of the Taker’s BTC payment.

  • offer (GravityOffer) – The GravityOffer originally posted by the Maker. Used to construct CovenantParams for SPV proof verification.

  • claimed_txid (str) – Radiant txid of the MakerClaimed UTXO (output of claim()).

  • claimed_vout (int) – Output index of the MakerClaimed UTXO.

  • claimed_photons (int) – Value of the MakerClaimed UTXO in photons.

  • taker_address (str) – Taker’s Radiant P2PKH address to receive the photons.

  • fee_sats (int) – Radiant miner fee in photons.

  • btc_tx_height (int | None) – Optional: Bitcoin block height where btc_txid was confirmed. If not provided, the orchestrator will determine it automatically.

Raises:
Return type:

FinalizeResult

async wait_confirmations(btc_txid, min_confirmations=None)[source]

Poll Bitcoin until btc_txid reaches the required confirmations.

Parameters:
  • btc_txid (str) – Bitcoin transaction ID (64 hex chars, big-endian).

  • min_confirmations (int | None) – Override config.min_btc_confirmations for this call.

Returns:

Always has confirmed=True on return (raises on timeout).

Return type:

ConfirmationStatus

Raises:
class pyrxd.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.PrivateKey[source]

Bases: object

__init__(private_key=None, network=None)[source]

create private key from WIF (str), or int, or bytes, or CoinCurve private key random a new private key if None

Parameters:
  • private_key (str | int | bytes | PrivateKey | None)

  • network (Network | None)

address(compressed=None, network=None)[source]
Returns:

P2PKH address corresponding to this private key

Parameters:
  • compressed (bool | None)

  • network (Network | None)

Return type:

str

decrypt(message)[source]

Electrum ECIES (aka BIE1) decryption

Parameters:

message (bytes)

Return type:

bytes

decrypt_text(text)[source]

decrypt BIE1 encrypted, base64 encoded text

Parameters:

text (str)

Return type:

str

der()[source]
Return type:

bytes

derive_child(public_key, invoice_number)[source]

derive a child key with BRC-42 :param public_key: the public key of the other party :param invoice_number: the invoice number used to derive the child key :return: the derived child key

Parameters:
  • public_key (PublicKey)

  • invoice_number (str)

Return type:

PrivateKey

derive_shared_secret(key)[source]
Parameters:

key (PublicKey)

Return type:

bytes

encrypt(message)[source]

Electrum ECIES (aka BIE1) encryption

Parameters:

message (bytes)

Return type:

bytes

encrypt_text(text)[source]
Returns:

BIE1 encrypted text, base64 encoded

Parameters:

text (str)

Return type:

str

classmethod from_der(octets)[source]
Parameters:

octets (str | bytes)

Return type:

PrivateKey

classmethod from_hex(octets)[source]
Parameters:

octets (str | bytes)

Return type:

PrivateKey

classmethod from_pem(octets)[source]
Parameters:

octets (str | bytes)

Return type:

PrivateKey

hex()[source]
Return type:

str

int()[source]
Return type:

int

pem()[source]
Return type:

bytes

public_key()[source]
Return type:

PublicKey

serialize()[source]
Return type:

bytes

sign(message, hasher=<function double_sha256>, k=None)[source]
Returns:

ECDSA signature in bitcoin strict DER (low-s) format

Parameters:
Return type:

bytes

Low-s enforcement: coincurve’s sign() calls libsecp256k1 which normalises signatures to low-s (SECP256K1_EC_NORMALIZED) by default. For custom k, _sign_custom_k() explicitly enforces low-s.

sign_recoverable(message, hasher=<function double_sha256>)[source]
Returns:

serialized recoverable ECDSA signature (aka compact signature) in format r (32 bytes) + s (32 bytes) + recovery_id (1 byte)

Parameters:
Return type:

bytes

sign_text(text)[source]

sign arbitrary text with bitcoin private key :returns: (p2pkh_address, stringified_recoverable_ecdsa_signature) This function follows Bitcoin Signed Message Format. For BRC-77, use signed_message.py instead.

Parameters:

text (str)

Return type:

tuple[str, str]

verify(signature, message, hasher=<function double_sha256>)[source]

verify ECDSA signature in bitcoin strict DER (low-s) format

Parameters:
Return type:

bool

verify_recoverable(signature, message, hasher=<function double_sha256>)[source]

verify serialized recoverable ECDSA signature in format “r (32 bytes) + s (32 bytes) + recovery_id (1 byte)”

Parameters:
Return type:

bool

wif(compressed=None, network=None)[source]
Parameters:
  • compressed (bool | None)

  • network (Network | None)

Return type:

str

exception pyrxd.RxdSdkError[source]

Bases: Exception

Base class for every exception raised by pyrxd.

Applying redact to each positional arg on construction defends against accidental key-material leakage when callers pass user-supplied values straight into the exception.

__init__(*args)[source]
Parameters:

args (Any)

Return type:

None

class pyrxd.RxdWallet[source]

Bases: object

High-level wallet for plain RXD (photon) transfers on Radiant.

Parameters:
  • private_key – Wallet key. All UTXOs and the change output use the corresponding P2PKH address.

  • electrumx_url – ElectrumX WebSocket URL (wss://..). A single URL is accepted for ergonomic parity with ElectrumXClient([url]).

  • fee_rate – Miner fee in photons per byte. Defaults to 10_000 (the current mainnet relay minimum).

  • allow_insecure – Pass-through to ElectrumXClient. Only set for local dev.

__init__(private_key, electrumx_url, fee_rate=10000, *, allow_insecure=False)[source]
Parameters:
  • private_key (PrivateKey)

  • electrumx_url (str)

  • fee_rate (int)

  • allow_insecure (bool)

Return type:

None

property address: str

Return the P2PKH mainnet address of this wallet.

build_send_max_tx(utxos, to_address)[source]

Build and sign a tx sweeping all provided UTXOs to to_address.

No change output. Single output value = sum(utxos) - fee.

Parameters:
  • utxos (list[UtxoRecord])

  • to_address (str)

Return type:

Transaction

build_send_tx(utxos, to_address, photons)[source]

Build and sign a P2PKH transfer from utxos to to_address.

Pure offline operation: no network calls. Useful for unit tests and for callers who prefer to broadcast via their own client.

Rules

  • photons must be >= DUST_THRESHOLD (546).

  • UTXOs are greedily selected in descending order of value.

  • A change output back to self.address is added only if the remainder after paying the fee exceeds the dust threshold; otherwise the dust is burned as additional fee.

Parameters:
  • utxos (list[UtxoRecord])

  • to_address (str)

  • photons (int)

Return type:

Transaction

property fee_rate: int
async get_balance()[source]

Return (confirmed_photons, unconfirmed_photons) for this wallet.

Return type:

tuple[int, int]

async get_utxos()[source]

Return typed UtxoRecord list for this wallet.

Return type:

list[UtxoRecord]

property pkh: bytes

Return the raw 20-byte public-key hash.

async send(to_address, photons)[source]

Fetch UTXOs, build + sign + broadcast a P2PKH transfer.

Returns the transaction id on success. Raises ValidationError on bad inputs or insufficient funds, NetworkError on RPC failure.

Parameters:
  • to_address (str)

  • photons (int)

Return type:

str

async send_max(to_address)[source]

Sweep all confirmed UTXOs to to_address minus fee.

Returns the transaction id on success.

Parameters:

to_address (str)

Return type:

str

class pyrxd.UtxoRecord[source]

Bases: object

A single unspent transaction output as returned by ElectrumX.

tx_hash

Transaction id in hex (little-endian / display order).

Type:

str

tx_pos

Output index within the transaction.

Type:

int

value

Output value in satoshis.

Type:

int

height

Block height at which the output was confirmed (0 = unconfirmed).

Type:

int

__init__(tx_hash, tx_pos, value, height)
Parameters:
Return type:

None

tx_hash: str
tx_pos: int
value: int
height: int
exception pyrxd.ValidationError[source]

Bases: RxdSdkError

Raised when input fails a trust-boundary validation check.

class pyrxd.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.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.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.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.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.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.mnemonic_from_entropy(entropy=None, lang='en')[source]
Parameters:
Return type:

str

pyrxd.script_hash_for_address(address)[source]

Return the ElectrumX script_hash for a P2PKH address.

ElectrumX indexes addresses by sha256(locking_script) with the bytes reversed (little-endian display order). This public helper lets callers derive the script hash without constructing a full client.

Parameters:

address (str) – Base58Check-encoded P2PKH address.

Returns:

The 32-byte script hash suitable for ElectrumX RPC calls.

Return type:

Hex32

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

bytes