Architecture & Module Map¶
Orientation¶
pyrxd is a Python SDK + Click CLI for the Radiant (RXD) UTXO blockchain: transaction building with Radiant’s BIP143/FORKID sighash, BIP-32/39/44 HD wallets, the on-chain Glyph token protocol (NFT / FT / dMint / mutable / WAVE), Gravity cross-chain HTLC atomic swaps (BTC-Taproot and ETH-Solidity counter-legs), same-chain partial-tx swaps, and a fail-closed SPV verifier — all over an untrusted ElectrumX / Esplora I/O layer. The codebase is layered: pure data + crypto primitives at the bottom, then chain I/O, then key custody, then the token / swap protocols, then the CLI. A cross-cutting pyrxd.security package (typed errors + secret wrappers + secure RNG) underpins every layer.
┌──────────────────────────────────────────────────────────────┐
│ L4 CLI + tooling cli/ contrib/miner/ devnet.py │
├──────────────────────────────────────────────────────────────┤
│ L3 protocols glyph/ (+dmint/) gravity/ (+watch/) │
│ swap/ btc_wallet/ eth_wallet/ │
├──────────────────────────────────────────────────────────────┤
│ L2 wallets+signing hd/ wallet.py agent/ │
├──────────────────────────────────────────────────────────────┤
│ L1 core tx + I/O transaction/ script/ keys.py spv/ │
│ network/ fee_models/ merkle_path.py │
├──────────────────────────────────────────────────────────────┤
│ L0 foundation security/ constants.py hash.py curve.py │
│ (cross-cutting) utils.py base58.py aes_cbc.py crypto/ │
└──────────────────────────────────────────────────────────────┘
lower layers never import higher ones (one-way)
See also the deeper concept pages: gravity.md, partial-tx-swaps.md, radiant-fts-are-on-chain.md, dmint-v1-deploy.md, external-miner-protocol.md.
The dependency rule¶
Lower layers never import higher ones. A primitive (hash, constants, curve) knows nothing about wallets; a wallet knows nothing about the CLI. Within and across subpackages this is enforced by two conventions:
PEP 562 lazy
__getattr__exports. Package__init__.pyfiles (pyrxd/__init__.py,pyrxd/glyph/__init__.py,pyrxd/glyph/dmint/__init__.py,pyrxd/script/__init__.py) map public names to(module, attr)and resolve them on first access. This keepsimport pyrxdcheap and the import graph minimal — critically, the browser-hosted inspect tool (pyrxd.glyph.inspect) can run under Pyodide without transitively dragging incoincurve(no WASM wheel),aiohttp, orwebsockets. The same motive drives lazy internal imports, e.g.curve.pyimportscoincurveinsidecurve_add/curve_multiply, andnetwork/*importsbtc_txid_from_rawfunction-locally.One-way internal dependency graphs. Subpackage
__init__docstrings declare their layering. The canonical example is the dMint subpackage:pyrxd.glyph.dmint: types ← builders ← chain ← miner (strictly one-way)
Two symbols were deliberately relocated to keep this acyclic:
_OP_STATESEPARATORlives intypes.py(becausebuildersneeds it andbuilders → chainwould cycle), and the_V1_EPILOGUE_*constants live inbuilders.py—chain.pyre-exports both under their original names. Other examples:script.py(leaf)←type.py/timelock.py;spv:pow←{chain, merkle, payment, witness}←proof;security:errors←types/secrets←rng.One intentional inversion exists: the
CounterChainLegABC +CounterClaimFinalityverdict live inpyrxd.gravity, andpyrxd.eth_wallet.htlc_legimports up into them — whilegravityimports onlyeth_wallet’s pure leaves (locator,secret). This is documented in both packages.
Subsystem map¶
Layer 0 — Foundation (cross-cutting, pure data + crypto leaves)¶
Subsystem |
Path |
Purpose |
Public entrypoints |
Depends on |
|---|---|---|---|---|
|
|
Exception hierarchy, secret-material wrappers, secure RNG, trust-boundary newtypes. Nothing here ever logs/prints raw key material. |
|
none (base layer) |
|
|
Radiant |
|
stdlib only |
|
|
All hash primitives + self-contained pure-Python RIPEMD160 fallback (OpenSSL legacy-provider-unloaded / Pyodide) |
|
stdlib only |
|
|
secp256k1 params + EC group arithmetic; re-validates |
|
|
|
|
Byte-serialization toolbox: |
|
|
|
|
Two leaf codecs: Base58Check (addresses/WIF/xkeys) and AES-256-CBC + PKCS#7 (used by ECIES in |
|
|
|
|
Photonic-compatible AEAD/KEM for Glyph v2: XChaCha20-Poly1305 (single + chunked-v1) and X25519+HKDF CEK wrapping. No internal pyrxd deps — pure leaf over external libs; |
|
Cryptodome, |
Note: a second
list[int]-based base58 impl mirroring the TS@bsvAPI also lives inutils.py; and two fee-import paths exist (fee_model.pyABC vsfee_models/package) — minor naming warts.
Layer 1 — Core tx model, scripts, keys, fees, SPV, chain I/O¶
Subsystem |
Path |
Purpose |
Public entrypoints |
Depends on |
|---|---|---|---|---|
|
|
Mutable UTXO tx model: serialize, sign orchestration, fee/change, txid/preimage, BEEF/EF codecs. Radiant sighash inserts field 8 |
|
|
|
|
|
|
|
|
|
secp256k1 key pairs over coincurve: address/WIF, ECDSA (low-s/recoverable), BIE1/ECIES, BRC-42 child derivation. Hardened: unhashable, no pickle/copy/repr of key bytes, |
|
|
|
|
Pluggable fee computation; |
|
consumed by |
|
|
BSV/BRC-74 BUMP inclusion proof (top-level module) used by |
|
|
|
|
Pure-CPU, fail-closed Bitcoin SPV: Merkle/PoW/payment, mirrors the on-chain covenant byte-for-byte. Highest-risk layer. Self-contained: depends only on |
|
|
|
|
Untrusted chain-I/O boundary: ElectrumX WS (Radiant) + Esplora/Core HTTP (BTC), broadcast, confirmation depth, Merkle fetch, RXinDexer extension RPCs. Validates + fail-closes all responses; does not import |
|
|
spv/payment.py(verify_payment) is deliberately omitted fromspv/__init__.__all__(audit F-09): it validates bytes at an offset, not a real output boundary — not a standalone value gate.
Layer 2 — Wallets + signing¶
Subsystem |
Path |
Purpose |
Public entrypoints |
Depends on |
|---|---|---|---|---|
|
|
BIP-39/32/44 HD wallet — the key-custody core. The only long-lived secret is the scrubbable 64-byte BIP39 seed; the account xprv is re-derived transiently per access ( |
|
|
|
|
|
|
|
|
|
Sign-on-behalf local daemon: holds the unlocked |
|
|
Layer 3 — Protocols (glyph, gravity, swap, counter-chain legs)¶
Subsystem |
Path |
Purpose |
Public entrypoints |
Depends on |
|---|---|---|---|---|
|
|
Single PEP 562 lazy surface (~70 names) for the whole Glyph API; import-light for the Pyodide inspect tool |
|
all glyph submodules (lazy), |
glyph types + payload |
|
Pure data: |
|
|
glyph script |
|
Build/classify consensus locking scripts: 63-byte NFT singleton ( |
|
|
|
|
Primary user-facing unsigned-tx orchestrator: commit→reveal mint, FT/NFT transfer, 2-tx/3-tx dMint deploy (V2 is the default as of 0.9.0; the |
|
|
glyph FT |
|
Conservation-aware FT coin selection + transfer build; |
|
|
glyph read path |
|
|
|
|
dMint |
|
Deterministic PoW mint contract. |
|
|
glyph aux |
|
Optional extensions: V2 creator sigs, WAVE naming |
|
|
|
|
Chain-neutral HTLC swap engine: pure 13-state FSM + live |
|
|
|
|
Persistent reconciliation/alerting loop. v1 alert-only (holds no key); pure |
|
|
|
|
Same-chain RXD/FT trades via |
|
|
|
|
Bitcoin-family counter-leg: single P2TR HTLC ( |
|
|
|
|
EVM counter-leg: hashlock+timelock Solidity HTLC, sha256 |
|
|
recover_secret(both btc/eth) scans every 32-byte calldata/log window bysha256==H(never by offset — the C-PARSER discipline) and treats a reverted-but-mined claim as not-claimed.
Layer 4 — CLI + tooling¶
Subsystem |
Path |
Purpose |
Public entrypoints |
Depends on |
|---|---|---|---|---|
|
|
Click command tree ( |
|
|
|
|
Multiprocessing pure-Python SHA256d reference miner satisfying the dMint external-miner protocol; ships as |
console script |
|
|
|
One-command regtest node over docker (mine mature coinbase, fund address). Regtest-only, 127.0.0.1-bound, fixed non-secret creds. Backs |
|
stdlib only (fixed |
Trust boundaries / what is pre-audit¶
Read this before depending on anything that moves real value.
Boundary |
Status |
Honest caveat |
|---|---|---|
Gravity swaps ( |
Pre-external-audit, dust-only |
Every value-bearing network sits behind |
SPV proof + REF reads |
Highest-risk; single-source |
|
Key material |
Scrubbable seed only |
The one long-lived secret is the 64-byte BIP39 seed in |
Untrusted I/O ( |
Validated, fail-closed |
|
|
Generic errors by design |
Decrypt error messages never reveal which check failed or any input. Post-quantum ML-KEM is out of scope. |
|
No API stability |
Ships in the wheel but carries no semver promise on its import surface. |
There is no assert in src/ for invariants — every safety check raises a typed ValidationError/CovenantError so python -O can’t strip it.
Where to add things¶
I want to… |
Touch |
Notes |
|---|---|---|
Add a CLI command |
a |
Don’t import |
Add / classify a token script type |
|
Keep length invariants (63/75-byte) and the input/output ref conservation rule. |
Add a swap counter-chain |
implement the |
New legs are duck-typed; import only |
Add a covenant / HTLC SPK |
|
The SPV verifier must stay byte-for-byte with the on-chain script. See |
Add a fee model |
subclass |
|
Add a dMint DAA mode or mining algo |
|
Only |
Add an indexer / ElectrumX RPC |
|
Validate + fail-close every response before it crosses into the SDK. |
Add a hash / codec primitive |
|
Keep these dependency-free (or hash-only) so higher layers can depend on them freely. |
Wrap a new trust-boundary value |
add a newtype in |
Subclass an immutable builtin; wrap external input ASAP, then treat as trusted downstream. |