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:
objectState of a live Gravity MakerOffer on Radiant.
Returned by
GravityMakerSession.create_offer()and required by all subsequent lifecycle methods.- offer
The original
GravityOffercovenant parameters.
- maker_offer_result
Raw tx details from
build_maker_offer_tx.
- offer_txid
Radiant txid of the confirmed MakerOffer funding output.
- Type:
- offer_vout
Output index of the MakerOffer P2SH UTXO (always 0).
- Type:
- offer_photons
Photons locked in the MakerOffer P2SH output.
- Type:
- __init__(offer, maker_offer_result, offer_txid, offer_vout, offer_photons)
- Parameters:
offer (GravityOffer)
maker_offer_result (MakerOfferResult)
offer_txid (str)
offer_vout (int)
offer_photons (int)
- Return type:
None
- offer: GravityOffer
- maker_offer_result: MakerOfferResult
- offer_txid: str
- offer_vout: int
- offer_photons: int
- class pyrxd.AddressRecord[source]
Bases:
objectAddressRecord(address: ‘str’, change: ‘int’, index: ‘int’, used: ‘bool’)
- __init__(address, change, index, used)
- address: str
- change: int
- index: int
- used: bool
- class pyrxd.GlyphBuilder[source]
Bases:
objectBuild unsigned Glyph transactions.
Separate commit and reveal methods — caller is responsible for:
Signing the commit tx and broadcasting it.
Waiting for confirmation.
Passing the confirmed commit txid to the reveal method.
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()(orFtUtxoSetinglyph/ft.py)
Low-level (rarely called directly)
prepare_reveal()— generic reveal;is_nftpicks singleton vs FT reftypebuild_reveal_scripts()— alternate reveal entry that returns scripts, not paramsbuild_transfer_locking_script()— bare FT lock without constructing a txbuild_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:
- 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:
ValidationError – nft_script is not a valid 63-byte NFT script
ValueError – nft_utxo_value - fee < 546 (dust limit)
- Return type:
TransferResult
- build_transfer_locking_script(ref, new_owner_pkh, is_nft)[source]
Build the locking script for a transfer output.
- 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_OUTPUTcheck is derived frommetadata.protocol: NFT (2in protocol) produces anOP_2/SINGLETON-expecting commit; any other protocol mix (FT, dMint FT, data, etc.) produces anOP_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; seebuild_commit_locking_scriptfor 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. Whenchild_refisNonethe container is created empty (no child ref in locking script).Protocol field must include
GlyphProtocol.CONTAINER(7).
- prepare_dmint_deploy(params)[source]
Prepare a full dMint token deploy: commit + reveal + deploy scripts.
A dMint deploy requires three transactions in sequence:
Commit tx — commits the token metadata payload hash on-chain (standard Glyph commit, same as
prepare_commit()).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.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
Transactionobjects using the scripts returned here, following the same pattern as the integration test intests/test_dmint_deploy_integration.py.- param params:
DmintFullDeployParams— all deploy configuration.- returns:
DmintDeployResultwith 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 topremine_pkh, and its outpoint becomes the permanent token ref.Caller still constructs the actual transaction. The returned
premine_amountis whatvout[0].valuemust 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, sopremine_amountis the supply in whole units.No dMint-specific logic here. The
cbor_bytesalready encode whatever protocol markers the caller chose — dMint FT ([1,4]), plain FT ([1]), or any other combination — viaGlyphMetadata. pyrxd treats the protocol markers as caller-owned; classification happens at the indexer layer.
- 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_bytesmust includeGlyphProtocol.MUT(5). UseGlyphMetadata(protocol=[GlyphProtocol.NFT, GlyphProtocol.MUT]).- Parameters:
- Return type:
- 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
namefield in the CBOR payload. Protocol field must includeGlyphProtocol.WAVE(11).namemust be non-empty printable ASCII, max 255 characters. The name is validated here but must already be embedded incbor_bytesby the caller viaGlyphMetadata(name=...).Protocol requirement:
[NFT(2), MUT(5), WAVE(11)].
- class pyrxd.GlyphInspector[source]
Bases:
objectParse 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
- class pyrxd.GlyphMetadata[source]
Bases:
objectCBOR 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:
name (str)
ticker (str)
description (str)
token_type (str)
main (GlyphMedia | None)
loc (str)
loc_hash (str)
decimals (int)
image_url (str)
image_ipfs (str)
image_sha256 (str)
v (int | None)
dmint_params (DmintCborPayload | None)
creator (GlyphCreator | None)
royalty (GlyphRoyalty | None)
policy (GlyphPolicy | None)
rights (GlyphRights | None)
created (str)
commit_outpoint (str)
- 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(aDmintCborPayload) 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=2automatically whendmint_paramsis provided.
- 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:
- token_type: 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:
object36-byte Glyph reference: txid (reversed LE) + vout (4-byte LE).
- classmethod from_bytes(data)[source]
Parse 36-byte wire format.
- txid: Txid
- vout: int
- class pyrxd.GlyphScanner[source]
Bases:
objectScan 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.
- 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.
- class pyrxd.GravityMakerSession[source]
Bases:
objectManage the full lifecycle of a Gravity BTC↔RXD atomic swap offer.
This class handles the Maker’s side of the swap:
Build and broadcast the MakerOffer tx (
create_offer).Poll for the Taker’s claim (
wait_for_claim).Broadcast a cancel tx if the Taker never claims (
cancel_offer).Query current state (
check_status).
- Parameters:
rxd_client – Connected
ElectrumXClientfor Radiant chain operations (broadcast, query UTXOs).btc_source – A
BtcDataSource— used only by subclasses / extensions that need BTC confirmation data. May beNonefor 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:
rxd_client (ElectrumXClient)
maker_priv (PrivateKeyMaterial)
btc_source (BtcDataSource | None)
poll_interval_seconds (int)
- 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
ActiveOfferto 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:
- Raises:
ValidationError – If
maker_addressis empty or the offer redeem is invalid.NetworkError – On broadcast failure.
- 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
ActiveOfferto check.- Returns:
One of
"open","claimed","expired","unknown".- Return type:
- 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 withbuild_claim_tx.- Parameters:
offer_params (GravityOfferParams) – Funding-UTXO details and the
GravityOffercovenant.- Returns:
Populated with the resulting txid and UTXO details.
- Return type:
- Raises:
ValidationError – On any parameter format or covenant validation error.
NetworkError – On broadcast failure.
- 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
listunspentAPI 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. viaget_transactionon the address history).- Parameters:
offer (ActiveOffer) – The
ActiveOfferreturned bycreate_offer.timeout_seconds (int) – Maximum seconds to wait. Returns
Noneon timeout.
- Returns:
The offer txid (as a claimed-sentinel) on success, or
Noneon timeout.- Return type:
str or None
- class pyrxd.GravityOfferParams[source]
Bases:
objectParameters required to create a new Gravity MakerOffer.
These are the funding-UTXO details for the Maker’s side. The
GravityOfferitself (covenant bytecode, BTC-side params, etc.) is built externally (e.g. viabuild_gravity_offer) and passed asoffer.- offer
Fully populated
GravityOfferwithoffer_redeem_hexset.
- funding_txid
Hex txid of the Maker’s P2PKH UTXO being spent to fund the offer.
- Type:
- funding_vout
Output index of the Maker’s funding UTXO.
- Type:
- funding_photons
Value of the Maker’s funding UTXO in photons.
- Type:
- fee_sats
Miner fee in photons for the MakerOffer funding tx.
- Type:
- change_address
Optional Radiant P2PKH address for change output. See
build_maker_offer_txfor semantics.- Type:
str | None
- __init__(offer, funding_txid, funding_vout, funding_photons, fee_sats, change_address=None)
- offer: GravityOffer
- funding_txid: str
- funding_vout: int
- funding_photons: int
- fee_sats: int
- class pyrxd.GravityTrade[source]
Bases:
objectOrchestrate a complete Gravity BTC↔RXD atomic swap.
- Parameters:
radiant_network – Connected
ElectrumXClientfor Radiant chain operations (broadcast, fetch tx/block).bitcoin_source – A
BtcDataSourcefor 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:
radiant_network (ElectrumXClient)
bitcoin_source (BtcDataSource)
config (TradeConfig | None)
- 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_txindependently verifies the code hash before signing (audit 05-F-13).- Parameters:
offer (GravityOffer) – The
GravityOfferposted 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:
- 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
SpvProofBuilderverifier 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
GravityOfferoriginally posted by the Maker. Used to constructCovenantParamsfor 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:
SpvVerificationError – If any SPV verifier rejects the proof.
NetworkError – On any network failure fetching BTC data.
ValidationError – On any parameter format error.
- Return type:
- async wait_confirmations(btc_txid, min_confirmations=None)[source]
Poll Bitcoin until btc_txid reaches the required confirmations.
- Parameters:
- Returns:
Always has
confirmed=Trueon return (raises on timeout).- Return type:
- Raises:
NetworkError – If polling exceeds
config.max_poll_attempts.ValidationError – If btc_txid is not a valid 64-char hex string.
- class pyrxd.HdWallet[source]
Bases:
objectBIP44 HD wallet for Radiant with gap-limit discovery and encrypted persistence.
- account
BIP44 account index (usually 0).
- Type:
- external_tip
Highest derived index on external chain (change=0).
- Type:
- internal_tip
Highest derived index on internal chain (change=1).
- Type:
- addresses
{path_key: AddressRecord}where path_key isf"{change}/{index}".- Type:
- __init__(_xprv, _seed, account=0, external_tip=0, internal_tip=0, addresses=<factory>)
- Parameters:
_xprv (Xprv)
_seed (SecretBytes)
account (int)
external_tip (int)
internal_tip (int)
addresses (dict[str, AddressRecord])
- 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.
- 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_addressdefaults to the next unused internal index; callers can override (e.g. to keep change on the external chain for a single-address-style wallet).
- 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:
- external_tip: int = 0
- classmethod from_mnemonic(mnemonic, passphrase='', account=0)[source]
Create a fresh wallet from a BIP39 mnemonic.
- async get_balance(client)[source]
Return total confirmed + unconfirmed satoshis across all known addresses.
Uses
ElectrumXClient.get_balanceper address. Callrefresh()first to ensure the address set is current.- Parameters:
client (ElectrumXClient)
- Return type:
- 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:
- classmethod load(path, mnemonic, passphrase='')[source]
Load a previously saved wallet from path.
The mnemonic is needed to derive the decryption key. Raises
FileNotFoundErrorif 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 useload_or_create().
- 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.
- next_receive_address()[source]
Return the first external (change=0) address with no recorded history.
- Return type:
- 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:
- 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
ValidationErroron bad inputs or insufficient funds,NetworkErroron RPC failure.
- async send_max(client, to_address, *, fee_rate=10000)[source]
Sweep all UTXOs to to_address minus fee. Returns broadcast txid.
- Parameters:
client (ElectrumXClient)
to_address (str)
fee_rate (int)
- Return type:
- 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
- address(compressed=None, network=None)[source]
- decrypt(message)[source]
Electrum ECIES (aka BIE1) decryption
- decrypt_text(text)[source]
decrypt BIE1 encrypted, base64 encoded text
- 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
- encrypt(message)[source]
Electrum ECIES (aka BIE1) encryption
- encrypt_text(text)[source]
- public_key()[source]
- Return type:
PublicKey
- sign(message, hasher=<function double_sha256>, k=None)[source]
- Returns:
ECDSA signature in bitcoin strict DER (low-s) format
- Parameters:
- Return type:
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]
- 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.
- verify(signature, message, hasher=<function double_sha256>)[source]
verify ECDSA signature in bitcoin strict DER (low-s) format
- 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)”
- exception pyrxd.RxdSdkError[source]
Bases:
ExceptionBase class for every exception raised by pyrxd.
Applying
redactto each positional arg on construction defends against accidental key-material leakage when callers pass user-supplied values straight into the exception.
- class pyrxd.RxdWallet[source]
Bases:
objectHigh-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 withElectrumXClient([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]
- 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:
- Return type:
- 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¶
photonsmust be >=DUST_THRESHOLD(546).UTXOs are greedily selected in descending order of value.
A change output back to
self.addressis added only if the remainder after paying the fee exceeds the dust threshold; otherwise the dust is burned as additional fee.
- Parameters:
- Return type:
- property fee_rate: int
- async get_balance()[source]
Return
(confirmed_photons, unconfirmed_photons)for this wallet.
- async get_utxos()[source]
Return typed
UtxoRecordlist 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
ValidationErroron bad inputs or insufficient funds,NetworkErroron RPC failure.
- class pyrxd.UtxoRecord[source]
Bases:
objectA single unspent transaction output as returned by ElectrumX.
- tx_hash
Transaction id in hex (little-endian / display order).
- Type:
- tx_pos
Output index within the transaction.
- Type:
- value
Output value in satoshis.
- Type:
- height
Block height at which the output was confirmed (0 = unconfirmed).
- Type:
- __init__(tx_hash, tx_pos, value, height)
- tx_hash: str
- tx_pos: int
- value: int
- height: int
- exception pyrxd.ValidationError[source]
Bases:
RxdSdkErrorRaised when input fails a trust-boundary validation check.
- class pyrxd.Xprv[source]
Bases:
Xkey- classmethod from_seed(seed, network=Network.MAINNET)[source]
derive master extended private key from seed
- 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:
- 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.
- 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.
- 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.
- 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)
- pyrxd.mnemonic_from_entropy(entropy=None, lang='en')[source]
- pyrxd.script_hash_for_address(address)[source]
Return the ElectrumX
script_hashfor 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.