All notable changes to pyrxd are documented here. Format based on Keep a Changelog; this project follows Semantic Versioning.
RxinDexerClientdiscovery wrappers (glyph_get_recent,glyph_get_tokens_by_type) — global newest-first asset lists over the RXinDexer v4 discovery indexes (Glyph DB schema 4, live onelectrumx.radiantcore.orgsince 2026-07-18). Cursor-paginated ({"tokens", "next_cursor"});order="recent"on the by-type call is newest-deployed-first, default"ref"keeps the legacy order. Enables incremental watermark sync: page newest-first, stop whendeploy_heightdrops below the last run's watermark.
- dMint ASERT difficulty adjustment rebuilt as fractional fixed-point (ASERT-v2).
The previous on-chain "ASERT" was an integer power-of-2 stepper
(
drift = trunc(excess / halfLife)clamped to[-4, +4],target *= 2^drift) with three structural defects: a dead zone (no adjustment while|excess| < halfLife), a one-sided ratchet (it could never raise difficulty whenhalfLife >= targetTime), and ≥2× lurches off a single nLockTime sample. ASERT-v2 replaces it with a fractional, symmetric, damped step (RADIX = 2^16;driftFp = (excess·RADIX)/halfLifeclamped to±RADIX/4⇒ the target moves at most ±25% per mint; difficulty floor 4 via the LWMA-styleMAX/4cap). Newdaa_mode=asertdeploys emit the v2 bytecode, which is byte-identical to canonical PhotonicbuildAsertDaaBytecodeand overflow-safe in int64 by construction (divide-first). Validated by golden byte-match against upstream and a 3 000-case differential that executes the actual bytecode under a faithfulCScriptNum/OP_MUL-abort evaluator; a regtest consensus test (-m integration) is included. - No brick for pre-upgrade ASERT contracts. Contracts deployed before this
change keep mining under the legacy formula: the miner detects the on-chain
format by codescript signature (
<RADIX push> OP_MULvs<halfLife push> OP_DIV) and dispatches to the matching off-chain target computation. The legacy bytecode is retained and frozen by test. - Low-level fee default raised to the min-relay floor.
TRANSACTION_FEE_RATE(the default for the no-argumentTransaction.fee()) was5photons/KB (0.005 photons/byte) — ~2,000,000× under Radiant's 10,000 photons/byte post-V2 floor, so a transaction built via that default was non-relayable. It is now10_000_000photons/KB (= 10,000 photons/byte). All high-level paths (RxdWallet,HdWallet, glyph builders, CLI) already used 10,000 photons/byte; this aligns the low-level default with them. Override viaRXD_PY_SDK_TRANSACTION_FEE_RATEis unchanged.
-
DmintCborPayloadnow mirrors PhotonicDmintPayloadfor all DAA modes. The token-metadata CBOR gained the optionaldaasub-keysasymptote(ASERT),epochLength+maxAdjustment(EPOCH), andschedule(SCHEDULE, as[{height, difficulty}]), so EPOCH/SCHEDULE V2 tokens emit and round-trip complete indexer-display metadata. Backward-compatible: FIXED / ASERT / LWMA payloads are byte-unchanged (the new keys are emitted only when set).max_adjustmentis the multiplier (2/4/8/16) andscheduleuses difficulty, both CBOR-native — convert fromDmintDeployParamsat the call site. -
Default ASERT
half_lifeis now 240 seconds (was 3600), ≈4× the default 60 starget_time, matching canonical PhotonicDEFAULT_ASERT_HALFLIFE. AffectsDmintDeployParams,build_dmint_mint_tx, and thepyrxd glyph deploy-dmint/claim-dmintCLI defaults. Explicithalf_lifevalues are unaffected; a contract must be re-mined with the samehalf_lifeit was deployed with (the mint builder byte-verifies this against the baked bytecode before grinding PoW).
Posture + documentation release. pyrxd's maturity framing is now consistent with the rest of the Radiant ecosystem: it is open-source software provided as-is, without warranty (Apache 2.0), like Radiant Core itself — rather than singling itself out as uniquely pre-audit. No new features; no breaking API changes.
- Audit gates are now advisory, not blocking. The code-enforced gates that
previously raised on value-bearing networks no longer hard-block mainnet /
real-value use — pyrxd does what you tell it, consistent with running a Radiant
node.
require_audit_clearedandrequire_spv_sole_authority_clearedare retained as no-ops for backward compatibility (callers passingaudit_cleared=are unaffected), and dMint V2 deploy no longer requires theallow_v2_deployopt-in. The cross-chain swap stack remains unaudited — verify it yourself before moving real value. - Maturity language aligned to the standard open-source posture across the README and docs (the Apache-2.0 "as-is, no warranty" disclaimer is the operative one).
- Tutorials refreshed and verified end-to-end on regtest. Fixes: a wallet-load
crash in "your first Radiant transaction" (
str→Path); theGlyphMediaimport path; the FT token ref is the commit outpoint (not the reveal txid); the bundled parallel miner ships and is the default forclaim-dmint(was documented as "bring your own"); stale0.5.0version pins →0.8.0; and the newpyrxd wallet sendCLI is surfaced. The "inspect a transaction" supply math and output-badge counts corrected.
Feature release on top of 0.7.0 — 56 commits. The headline is full, mainnet-proven dMint V2 (PoW distributed mint with adaptive difficulty). Alongside, the experimental, pre-audit cross-chain swap + watchtower stack advances (an autonomous asset-claim executor, more counter-chains, a CLI signing agent) and a CRITICAL HTLC preimage fix lands.
No breaking changes to the stable public API — everything is additive and existing import paths + CLI commands are unchanged. The cross-chain swap + watchtower stack and dMint V2 real-value use remain pre-external-audit and gated; this code is not for production until externally audited.
- dMint V2 (PoW distributed mint) — full support, mainnet-proven.
pyrxd glyph deploy-dmint --v2+claim-dmintdeploy and PoW-mine V2 contracts with all five difficulty-adjustment modes (FIXED, ASERT, LWMA, EPOCH, SCHEDULE), byte-matched to canonical PhotonicdMintScript. A first V2 deploy + PoW mint and an adaptive-difficulty retarget were confirmed on Radiant mainnet. V2 deploy is gated behind an explicit opt-in (pre-external-audit). (#206, #228, #232–#238) - Autonomous asset-claim executor for the swap watchtower — dormant-by-construction, capped, value-vs-reorg-gated; arms a keyed RXD covenant claim only on an audit-cleared network. (#239)
- More counter-chains: Base and Litecoin, plus the Optimism / Arbitrum / Linea EVM registry. (#198, #200, #216)
- CLI signing agent (sign-on-behalf) with transient account-key derivation (no long-lived key residency). (#190, #191, #201)
- RXD multi-source quorum for the watchtower, clearing the single-source
low_corroborationblocker. (#187) - Consensus-enforced soulbound covenants + credential-bound swap gating. (#186)
- High-level partial-transaction swap API for same-chain trades. (#177)
CappedFeeWalletSource— a structural spend ceiling for autonomous RXD fees. (#211)- Tier-1 developer on-ramp: SDK swap primitive, regtest tooling, quickstart, a flagship RXD↔ETH cross-chain-swap tutorial, and expanded API reference. (#177, #185, #195, #196, #214, #215, #220, #226, #227)
- HTLC preimage-length theft (CRITICAL). The BTC claim leaf and all three
Gravity HTLC covenants now consensus-pin the revealed preimage to exactly
32 bytes (
OP_SIZE); without it a non-32-bytep'could defeat the keyless secret-scrape and let a maker keep both legs. (#239) - dMint EPOCH/LWMA int64-overflow. Found via differential testing (the on-chain retarget could exceed int64 and brick the contract), fixed upstream in canonical Photonic, and re-enabled here byte-matched to the merged fix. (#234, #238)
- dMint V1 + FIXED V2 validated on real Radiant consensus
(
radiant-corev3.1.1). (#195, #228) - Cross-chain swap hardening across several audit rounds — value-scaled claim
burial (fail-closed, dust opt-out), reorg-value guard, maker-stall →
mutual_refundrouting, durable seen-store default. (#189, #192, #193, #194, #210) - CLI: a malformed path option now returns a clean usage error. (#188)
Feature release on top of 0.6.x — 15 commits. The user-facing headline is a
wallet sweep command and setup --coin-type. Alongside, the
experimental, pre-audit cross-chain swap + watchtower stack advances:
ETH counter-leg watching (alert-only), FT/NFT↔ETH swap coverage, and a
dormant, capped, keyless autonomous BTC refund with a Go-gated dust-run
harness — see the dedicated section below; this code is still not for
production use until externally audited.
No breaking changes — everything is additive; existing public import paths and CLI commands are unchanged.
pyrxd wallet sweep— move the full balance from any single derived path to a destination address, for consolidating funds a recovery scan turned up on a non-default derivation (#161).pyrxd setup --coin-type— choose the HD derivation coin type at wallet init (e.g.0for Photonic/Chainbow compatibility) instead of the default; andpyrxd inspectnow classifies rarer Glyph types (#174).
- Internal: split the monolithic
glyph_cmds.py(extractedinspectand shared helpers, de-duplicated_load_wallet). Public CLI unchanged (#167).
wallet sweepnow reports a clear, actionable error when the balance is dust (below the fee) instead of failing opaquely (#163).
- Added a fuzz suite over the user-facing CLI surface, hardening argument parsing and command dispatch against malformed input (#175).
These components ship for testing and integration only. The cross-chain atomic-swap and watchtower code has not cleared an external security audit. Do not use it to move real value beyond throwaway amounts.
- Watchtower — ETH counter-leg watching (alert-only v3) — the watchtower
now also watches RXD/Glyph↔ETH swaps via a production keyless
RpcEthChainSourceover a read-only ETH RPC, with a regtest end-to-end harness. It holds no keys and broadcasts nothing (#168, #170). - ETH↔RXD swap coverage — FT (fungible-token)↔ETH and Glyph(NFT)↔ETH atomic-swap harnesses, with a mainnet REST REF-authenticity gate (#166, #169).
- Watchtower v2 — dormant, capped, keyless autonomous BTC refund — the
first autonomous watchtower action: it broadcasts an operator-pre-signed
BTC CSV refund when one is due and the operator is offline. Keyless (the
daemon never holds a key — it re-sends pre-signed bytes), refund-only, and
dormant-by-construction on a value-bearing network (no autonomy without
an explicit, dust-capped opt-in). Adds a signet/testnet-capable runner and
a Go-gated dust-run harness whose
setuprefuses to emit a funding address unless the refund provably reconstructs from on-disk state. Exercised on regtest and a mainnet dust run (#171, #172, #173).
- Stuck-RXD recovery guide: pipx/venv install guidance (#162), an
Electron-Wallet move option (#164), and a note that
recover --scantakes a couple of minutes rather than hanging (#165).
Patch release. Fixes the package version reported by the CLI and the
pyrxd.__version__ attribute.
pyrxd --versionandpyrxd.__version__now report the actual installed version.__version__was a hardcoded string inpyrxd/__init__.pythat was separate frompyproject.toml; it went stale and the 0.6.0 wheel shipped reporting0.5.1.__version__is now derived from the installed package metadata (importlib.metadata.version), so it trackspyproject.tomlautomatically and cannot drift again.
First release since 0.5.1 — 108 commits. The headline is multi-path HD wallet recovery; alongside it ship WAVE + RXinDexer support, a Photonic-compatible TIMELOCK protocol, a dmint subpackage refactor, and broad SPV/parser hardening. This release also includes experimental, pre-audit cross-chain HTLC atomic-swap engines (BTC↔RXD, ETH↔RXD) and a swap watchtower — see the dedicated section below; these are not for production use until externally audited.
No breaking changes to the stable public API: everything is additive, and
the dmint subpackage split preserved existing pyrxd.glyph /
pyrxd.glyph.dmint import paths.
pyrxd wallet recover --scan— read a BIP39 mnemonic and scan everycoin_type × accountpair across both BIP44 chains, reporting which derived addresses actually hold on-chain history and balance. Solves the "balance shows on the explorer but my wallet says empty" problem that arises because different wallets derive different addresses from the same seed — coin type0(legacy / Photonic ≤ v2 / Chainbow),512(SLIP-0044),236(older pyrxd). Read-only by design: it never signs or broadcasts; the mnemonic is prompted with hidden input and is never echoed, stored, or transmitted — only derived addresses (as scripthashes) reach the network.--coin-typesand--accountswiden or narrow the search;--jsonfor machine output.pyrxd.hd.discovery— publicdiscover(),DiscoveryReport,DiscoveryHit, andcoin_type_label, withDEFAULT_COIN_TYPES = (0, 512, 236)andDEFAULT_ACCOUNTS = (0, 1, 2)as the single source of truth for the default search space.
pyrxd.glyph.wave— Photonic-compatible WAVE CBOR encoding plusRxinDexerClientfor indexer-backed queries (#102).
- TIMELOCK token-protocol support (
pyrxd.glyph.timelock,timelock_reveal_tx) compatible with Photonic's REP-3009 (#106).
- Wire-format builders pinned to real on-chain bytes: CBOR payload (#125), commit-script FT + NFT (#126), NFT locking script (#127).
pyrxd.glyph.dmintis now a four-submodule subpackage, split from the former monolithicdmint.py(#109). Public import paths are unchanged.- Glyph parsing: extracted a shared input-ref opcode walker and added
PolicyRejection(#107); de-duplicatedhashOutputHashesand hardened output parsing (#124).
- Miner: parallel-miner workers are now terminated on every exit path, fixing orphaned multiprocessing workers left behind on early exit (#116).
- SPV primitives: red-team hardening plus swap-coordinator / data-source follow-ups (#138); secure-by-default and further hardening covering findings F-02/F-09/F-12/F-16/F-17/F-26 (#142); live-regtest consensus validation of deployed covenant semantics across the V/NB/M/S matrix (#143).
- Fixed a transitive
idnaDoS via a batched dependency update (#144). - Defense-in-depth secret scanning (trufflehog, #118) and tightened CI token scopes + CodeQL security-extended (#157).
- ~52× faster SPV-related tests by pre-mining synthetic-block PoW headers (#108); halved the CI test job via header-grind memoization and dependency caching (#140).
These components ship for testing and integration only. The cross-chain atomic-swap and watchtower code has not cleared an external security audit. Do not use it to move real value beyond throwaway amounts.
- Gravity BTC↔RXD HTLC atomic-swap engine — async coordinator, BTC and Radiant legs, a five-binding REF gate, and a reorg-finality gate; exercised on regtest and a dust mainnet run (#137).
- ETH↔RXD HTLC atomic swap — ETH leg and coordinator integration; red-team-fixed but pre-audit (#155).
- HTLC swap watchtower v1 — alert-only, BTC-first; holds no keys and never broadcasts (#156).
- Batched Dependabot updates across the cycle (#144, #146, #150, #153, and
others); dev tooling moved to mypy
^2.1.0(#150) and sphinx^8(#115). Added a manual Combine-PRs workflow (#154). Python support is unchanged:>=3.10,<4.0.
- New tutorials (your first transaction #87, mint from a V1 dMint contract #85, inspect a transaction in the browser #82), how-to guides (broadcast a transaction #83, scan an address for Glyphs #84, BIP143 sighash quirks #86, verify an SPV proof #88, SPV verification pitfalls #139), and concept explainers (Glyph structures #121, V1 dMint mechanics #77, external miner protocol #78).
Audit follow-ups + new SDK primitives. No breaking changes; everything new is additive. Closes the four golden-vector recommendations from the 0.5.0 pattern-recognition audit and ships the time-lock builders
- parallel miner that were deferred at 0.5.0 cut.
pyrxd.script.timelock— new module with canonical Bitcoin/Radiant time-lock locking scripts:build_p2pkh_with_cltv_script(pkh, locktime)— P2PKH gated by absolute time-lock (BIP-65OP_CHECKLOCKTIMEVERIFY).build_p2pkh_with_csv_script(pkh, sequence)— P2PKH gated by relative time-lock (BIP-112OP_CHECKSEQUENCEVERIFY).build_csv_sequence(units, kind)— encodes a (blocks | 512-second intervals) pair into the BIP-112 stack/nSequenceinteger form.CsvKind.BLOCKS/CsvKind.TIME_512_SECONDS— kind enum.LOCKTIME_THRESHOLD = 500_000_000— height-vs-Unix-time boundary.
- Validation rejects out-of-range locktime/sequence, the BIP-112 disable bit (would silently make the lock a no-op), and wrong-size PKH. 20 tests cover minimal-int push behaviour, sign-pad edge cases, bit-22 encoding, and the cross-invariant that CLTV and CSV shapes differ only at the verify opcode.
- Scope: locking scripts only. Threading
nLockTime/nSequencethrough transaction construction (and the unlockingScriptTemplatethat consumes signatures) is deferred until a concrete pyrxd consumer needs it.
mine_solution_dispatch(preimage, target, *, miner_argv=None, ...)— single entrypoint that routes to in-processmine_solutionwhenminer_argv is None, otherwise tomine_solution_externalfor a subprocess miner. Callers no longer have to branch on miner availability themselves. Exported frompyrxd.glyph.
pyrxd.contrib.miner— multi-process parallel miner that scales the existing pure-Python mining loop across CPU cores using the JSON-over-stdio external-miner protocol. No compiled extensions; useful when a faster C/Rust miner isn't available.
- Four new test classes pin every wire-format builder against real
on-chain bytes — the strongest interop assertion an SDK can carry:
TestFtLockingScriptMainnetGolden(tests/test_dmint_module.py) — 75-byte FT script vs RBG transferac7f1f70…0ae4.TestNftLockingScriptMainnetGolden(tests/test_glyph.py) — 63-byte NFT singleton vs Glyph NFT27390efa…be7e.TestCommitLockingScriptMainnetGolden(tests/test_glyph_dmint.py) — 75-byte commit script, both FT and NFT branches, vs the GLYPH deploya443d9df…878b.TestCborPayloadMainnetGolden(tests/test_glyph.py) — full 65,569-byte CBOR reveal payload incl. embedded PNG, vs GLYPH revealb965b32d…9dd6(fixture:tests/fixtures/glyph_reveal_cbor.bin). Pinssha256d(payload) == commit_hashlinkage, decoder shape, andOP_PUSHDATA4framing for payloads >65,535 bytes.
V2UnvalidatedWarning— newUserWarningsubclass; emitted by every V2 dMint entry point. Silenceable withwarnings.simplefilter("ignore", V2UnvalidatedWarning); escalable to error withwarnings.simplefilter("error", V2UnvalidatedWarning)in CI. No V2 dMint contract has been deployed to Radiant mainnet as of 0.5.1; the V2 code paths are byte-equivalent-by-construction to V1 where they share bytecode but have never been exercised against live consensus.build_dmint_v2_mint_preimage(...)— V2 PoW preimage helper that mirrors the V1 helper. CarriesV2UnvalidatedWarning.
- Migration guide:
docs/how-to/migrate-0.4-to-0.5.md. - New tutorials: mint a Glyph NFT, mint a Glyph FT, mint from a V1 dMint contract on Radiant mainnet, inspect any Radiant transaction in the browser, your first Radiant transaction.
- New how-tos: scan an address for Glyphs, broadcast a Radiant transaction, handle Radiant's BIP143 sighash quirks, verify an SPV proof.
- New concepts pages: V1 dMint mint mechanics, V1 dMint deploy, external miner protocol (JSON-over-stdio subprocess contract), Glyph inspect tool (structural match, not semantic correctness).
- Design decision:
docs/solutions/design-decisions/wave-protocol-deferred-until-consumer.md— WAVE name-claim protocol deferred until a concrete pyrxd consumer needs it.
- CI: cancel in-flight runs on push to the same branch / PR. Roughly halves per-PR Actions minute spend.
- CI: hash-pin
poetry==2.3.4andruff==0.15.12viapip-compile --generate-hasheslock files underci/. Each workflow that previously ranpip install poetry==2.3.4now runspip install -r ci/poetry-pin.txt --require-hashes. Closes 3 of the 5 open OpenSSF Scorecard / CodeQLPinnedDependenciesIDalerts (the remaining 2 are on the docs build's editablepip install -e ., which physically cannot be hash-pinned). Seeci/README.mdfor the bump workflow.
- CodeQL note-severity alerts cleared: replaced the bare
importavailability probe indocs/inspect_static/inspect/glue.pywithimportlib.util.find_spec(closespy/unused-import); switched internal re-exports insrc/pyrxd/glyph/builder.pyandsrc/pyrxd/cli/glyph_cmds.pyto PEP 484 explicitX as Xform (CodeQL doesn't honour# noqa: F401on those — the explicit re-export form does close the alert); added unreachableraise AssertionError("unreachable: pytest.skip raises")afterpytest.skipintests/test_ripemd160_fallback.py(closespy/mixed-returns).
- The 0.5.0 pre-release multi-reviewer audit's deferred items (V2 quarantine, mainnet golden vectors for FT/NFT/commit/CBOR, CodeQL note cleanup) are closed by this release. No new formal audit was run for 0.5.1; changes are additive on top of audited 0.5.0 code and each touches a single area (script primitives, test fixtures, CI config) with high in-PR review coverage.
prepare_dmint_deploy_v1— full V1 dMint deploy support. Builds the commit + reveal scripts for the mainnet-canonical "one reveal, many parallel contract UTXOs" shape that every live dMint token (RBG, snk, etc.) uses. Pinned against Photonic-Wallet's reference layout (docs/DMINT_RESEARCH.md§2/§3).DmintV1DeployParams/DmintV1DeployResult/DmintV1ContractInitialState— typed inputs/outputs for the V1 deploy flow.find_dmint_contract_utxos— chain helper that walks ElectrumX to enumerate the N unspent contract UTXOs from a V1 deploy reveal txid.examples/dmint_v1_deploy_demo.py— end-to-end runnable V1 deploy demo with resume support, commit/reveal atomic signing, and param-drift defense on the resume file.- Live-validation: deploy reveal at
8eeb333943771991c2752abc78038365ecd76b1a24426f7a3212eea71b6a6564(2026-05-11) produced 4 unspent contracts and was classified correctly by the inspect tool.
PowPreimageResultdataclass — frozen record of(preimage, input_hash, output_hash)returned bybuild_pow_preimage. Forces miners + scriptSig assembly to feed from a single byte source, structurally preventing the recurring builder-vs-covenant divergence pattern.TestCovenantShaperegression suite — pins the V1 mint scriptSig convention against the mainnet snk token mint146a4d688ba3fc1ea9588e406cc6104be2c9321738ea093d6db8e1b83581af3cAND pyrxd's own first successful mintc9fdcd3488f3e396bec3ce0b766bb8070963e7e75bb513b8820b6663e469e530. Two independent mainnet golden vectors.TestFtLockingScriptBuilderCrossEquality— byte-equality test between the two FT-output builders inpyrxd.glyph.scriptandpyrxd.glyph.dmint(red-team finding R2 from the 0.5.0 pre-release audit).
build_reveal_scriptsig_suffixnow supportsOP_PUSHDATA4for payloads above 65,535 bytes (up to a 256 KB hard cap). The mainnet GLYPH reveal atb965b32d…9dd6used 65,569 bytes via PUSHDATA4 — pyrxd would have refused to build that shape under the previous PUSHDATA2-only cap. Red-team finding R3.
- V1 mint scriptSig parsing — decode + display the 4 canonical pushes (nonce, inputHash, outputHash, OP_0). V1 vs V2 distinguished by nonce width (4 vs 8 bytes).
- V1 deploy commit/reveal shape detection in the browser inspector.
PowPreimageResult,build_dmint_v1_mint_preimage,build_dmint_v1_ft_output_scriptnow exported frompyrxd.glyphfor direct import and type annotation.
build_pow_preimage(...)now returnsPowPreimageResult(preimage, input_hash, output_hash)instead of the raw 64-byte preimage. Migration:# before (0.4.0): preimage = build_pow_preimage(txid_le, ref, in_script, out_script) # after (0.5.0): result = build_pow_preimage(txid_le, ref, in_script, out_script) preimage = result.preimage # if you only need the bytes
build_mint_scriptsig(nonce, preimage, *, nonce_width)is nowbuild_mint_scriptsig(nonce, input_hash, output_hash, *, nonce_width). The two 32-byte hashes MUST come from the samebuild_pow_preimagecall that produced the mined preimage. Splitting the sources caused the M1 covenant-rejection incident — seedocs/solutions/logic-errors/dmint-v1-mint-scriptsig-divergence.md.build_dmint_v1_mint_preimage(...)now returnsPowPreimageResultinstead of bytes. Callers feed.preimageto the miner and.input_hash/.output_hashtobuild_mint_scriptsig.- No backward-compatible shim — the old signature could silently
produce on-chain-rejected transactions, so a hard break with loud
TypeError/ValidationErroris safer than a deprecation path.
- CRITICAL (latent): V2 mint reward output was emitting a 25-byte
plain P2PKH; the V2 covenant requires a 75-byte FT-wrapped reward.
Every V2 mint would have been rejected by the network with
mandatory-script-verify-flag-failedonce a V2 contract existed on chain. No V2 contracts exist yet, so the bug was caught pre-mainnet during the 0.5.0 red-team audit (finding R1). V2 reward now uses the samebuild_dmint_v1_ft_output_scriptas V1 — the FT-conservation fingerprintdec0e9aa76e378e4a269e69dis shared via_PART_Cbetween V1 and V2. - CRITICAL (M1 follow-up): V1 mint scriptSig pushed the wrong
values. The original M1 (shipped in 0.4.0 via PR #65) had
build_mint_scriptsigpushing the PoW preimage halves into the scriptSig instead of the rawSHA256d(funding_script)andSHA256d(OP_RETURN_script)the covenant expects. Every successful mine was rejected by the on-chain covenant; M1 had never successfully spent a contract. Fix verified on Radiant mainnet at txidc9fdcd3488f3e396bec3ce0b766bb8070963e7e75bb513b8820b6663e469e530. Seedocs/solutions/logic-errors/dmint-v1-mint-scriptsig-divergence.md.
- The 0.5.0 pre-release audit was run by 8 independent specialised reviewers (security, red-team chain-conformance, data-integrity, Python code-quality, simplicity, architecture, performance, pattern-recognition). Two CRITICAL findings (R1 + the M1 scriptSig bug) and two HIGH findings (R2 cross-builder drift risk, R3 PUSHDATA4 capability gap) were addressed pre-tag. Medium/low findings tracked for 0.5.1.
Public API consumers must update three call sites:
build_pow_preimage(...)— use.preimageattribute on the returnedPowPreimageResultif you only need the 64 bytes.build_mint_scriptsig(nonce, preimage, ...)→build_mint_scriptsig(nonce, result.input_hash, result.output_hash, ...).build_dmint_v1_mint_preimage(...)— same.preimagepattern.
The library raises loud TypeError / ValidationError immediately
on old-style calls — there is no silent-failure migration path.
pyrxd glyph inspect— offline classifier for any Glyph input (script hex, txid, outpoint, contract id). Always emits a "structural pattern match" qualifier so users understand the tool classifies on-chain shapes, not protocol-level semantic correctness.pyrxd glyph inspect --fetch— txid lookup via the configured ElectrumX server; full transaction structure with per-output classification.- V1 dMint contract parsing — the actual mainnet format observed during RBG live testing. V2 also supported for future-compat.
- Locked against a real RBG transfer fixture so the classifier is pinned to mainnet behaviour, not synthetic vectors.
- New static tool at
docs/inspect_static/inspect/(live at the Pages site under/inspect/). Loads pyrxd via Pyodide and runs the inspect classifier entirely in-browser — no server, no key material, no transaction broadcast. - Inputs: raw script hex, txid (auto-fetches via ElectrumX WebSocket), outpoint, contract id.
- Tx-shape banner explaining what kind of transaction the user is looking at: FT deploy, NFT deploy, dMint contract deploy, dMint claim (with height / max_height), Glyph burn, mutable contract update. Plain RXD sends and ordinary transfers render with no banner.
- Per-output structural-match qualifier on every classified script type (ft, nft, mut, dmint, commit-ft, commit-nft, op_return) spelling out exactly what the pattern match does not verify — never claims semantic correctness.
- OP_RETURN data carriers classified explicitly with
data_hexsplit out from the leading opcode.
GlyphRef.from_contract_hex— parse explorer-style contract ids in the standard hex form.is_dmint_script/extract_*_from_dmint_script— first-class dMint contract recognition alongside the existing FT/NFT/MUT helpers.- TR39 confusables / homoglyph detector
(
pyrxd.glyph.confusables) — flags Latin-spoofed token names and symbols against the Unicode TR39 confusables data. Skeleton +is_latin_lookalikehelpers for inspecting hostile glyph metadata.
- Pure-Python RIPEMD160 fallback. OpenSSL 3 distros (and Pyodide) ship without a built-in RIPEMD160 provider; the fallback keeps pyrxd working out of the box on those environments. Selected at import time; OpenSSL is preferred when available.
- All browser-hosted inspect install artifacts (Pyodide loader,
pyrxd wheel, micropip wheels, vendored cbor2 wheel) verified by
SHA-256 before
micropip.install. Loader uses Subresource Integrity. Mismatch aborts install loudly rather than falling through. - Vendored
cbor2==5.4.6wheel served same-origin. cbor2 6.x ships C-only; pinning to the last pure-Python release closes a Pyodide install path that depended on PyPI staying reachable and unchanged. micropip.install(..., deps=False)for pyrxd to avoid transitive metadata fetches during browser bootstrap.pyrxd/__init__.py,pyrxd/glyph/__init__.py, andpyrxd/curve.pyrewritten to use lazy PEP 562__getattr__re-exports. Importingpyrxd.glyph.inspectno longer drags incoincurve,aiohttp, orwebsockets— both a Pyodide enabler and a startup-cost win for narrow callers.- Manifest filename validated as a bare basename (rejects path traversal, dot-only names, and URL-encoded separators) before use. CSP no longer allows PyPI as a script source. CLI outpoint rendering sanitized against terminal control-character injection. Manifest emit hardened against shell heredoc injection.
- CBOR
mime_typefield capped at 256 chars at parse time — bounds an otherwise-unbounded user-controlled string before it reaches metadata renderers.
pyrxd glyph inspect transfer-ftpreviously passed bytes where hex string was expected; corrected.- Python 3.10 compatibility for the CLI:
tomlifallback fortomllib(3.11+). _select_ripemd160exception handling widened so OpenSSL variants raisingValueError(not just the documentedUnsupportedDigestmodError) fall through to the pure-Python implementation cleanly.
docs/solutions/runtime-errors/dmint-v1-classifier-gap.mdwritten from the live RBG test that surfaced the V1/V2 split.docs/research/glyphs-on-radiant.md— explains why Radiant FTs are on-chain (not just metadata), with fuzzing strategy.
- Poetry version pinned in CI workflows.
- OSSF Scorecard residual-risk decisions documented.
- PyPI publishing automated via Trusted Publishing (no long-lived tokens).
0.3.0 — 2026-05-04
- Default BIP44 coin type is now 512 (Radiant per SLIP-0044), not 236
(Bitcoin SV). Wallets created with 0.2.0 derive at
m/44'/236'/0'/...; the same mnemonic in 0.3.0 derives atm/44'/512'/0'/...and produces different addresses. To recover funds from a 0.2.0 install, setRXD_PY_SDK_BIP44_DERIVATION_PATH="m/44'/236'/0'", or passcoin_type=236toHdWallet(see new per-instance kwarg below). Seedocs/research/wallet-derivation-paths.mdfor the full migration story.
- New
pyrxdconsole script (pip install pyrxdregisters it on PATH). pyrxd wallet new | load | info | export-xpub— create, validate, and inspect HD wallets; account-level xpub export for watch-only use.pyrxd address/pyrxd balance/pyrxd utxos— bare query commands for address derivation, balance, and UTXO listing.pyrxd glyphsubcommand group — Glyph protocol operations.pyrxd setup— onboarding walkthrough; probes node + ElectrumX reachability and wallet presence, writes default config.- Global flags:
--network,--electrumx,--wallet,--config,--json,--quiet,--no-color,--yes,--debug. - Typed CLI errors (
UserError,NetworkBoundaryError,WalletDecryptError) with stable exit codes and a static decrypt message that never echoes user input.
HdWallet(coin_type=...)per-instance kwarg overrides the default derivation path without touching env state.HdWallet.send/HdWallet.send_max— key-aware UTXO collection and signed-transaction construction.- Load-time path validation against the wallet record's stored derivation path.
⚠️ Downgrade hazard introduced here. Once 0.3.0 writes a wallet file with acoin_typeannotation, downgrading to a pre-0.3.0 version and re-saving the wallet corrupts the storedcoin_typewhile leaving derived keys unchanged. A subsequent upgrade will failload()validation. Funds are recoverable from the mnemonic but require manual re-creation of the wallet file. Pin all machines to the same pyrxd version. See the README "Upgrading" section for details.
docs/research/wallet-derivation-paths.md— public research doc on the five-way derivation path fragmentation across the Radiant wallet ecosystem with verified source links.docs/solutions/convention established for searchable problem/solution documentation.- README user-risk disclaimer above Status section.
- Documentation moved from Read the Docs to GitHub Pages (https://mudwoodlabs.github.io/pyrxd/).
HdWalletpreviously ignored theRXD_PY_SDK_BIP44_DERIVATION_PATHenv override. Now respected.- Cyclic imports between
cli.mainand the four CLI subcommand modules resolved by registering subcommands explicitly viacli.add_command(). pyrxd glyphbroadcast summary now surfaces metadata fields.- BIP39 empty-passphrase defaults annotated to silence false-positive bandit findings.
- All 16 GitHub Actions pinned to commit SHAs (no floating tags).
- Explicit minimum
permissionsdeclared in CI and lint workflows. - OSSF Scorecard and OSV Scanner workflows added.
- CodeQL static analysis workflow added.
- Threat model + red-team checklist documented.
--jsonmnemonic exposure warning documented.- bandit added to
task lintso security findings fail locally before CI.
task ciaggregate task + versioned pre-push git hook (scripts/git-hooks/pre-push) + installer for local CI parity.scripts/check-no-private-links.py— link checker that prevents tracked docs from referencing gitignored design docs.- ruff replaces flake8 + black for lint and format.
- Dependabot version updates landed:
actions/checkout→ 6.0.2,actions/deploy-pages→ 5.0.0,actions/upload-pages-artifact→ 5.0.0,github/codeql-action→ 4.35.3,click→ ^8.3,bandit→ ^1.9.4,pre-commit→ 4.6.0,myst-parserconstraint refresh. websocketsconstraint widened to>=15.0.1, <17.0.0(was^16.0.0). pyrxd uses only stable websockets API (connect/send/recv/close/WebSocketException) common to versions 13 through 16, so the upper-bound floor was unnecessarily strict and locked out coexistence with libraries pinned towebsockets <=15.0.1(e.g.,solana-py 0.36.x).
0.2.0 — 2026-04-29
Initial public release.
- Typed primitives at all SDK boundaries:
Hex32,Hex20,Txid,Satoshis,SecretBytes,RawTx. Strings and untyped bytes are rejected at the constructor. pyrxd.curve— secp256k1 withcoincurve, RFC 6979 deterministic signing, low-s normalization, DER encoding.pyrxd.security— typed errors, RNG, secret-bytes (libsodium-backedSecretBytesfor memory hygiene).pyrxd.crypto— symmetric primitives.
PrivateKey/PublicKeywith WIF encoding/decoding and address derivation (P2PKH mainnet).- BIP32 extended keys (
Xprv/Xpub) with hardened/non-hardened derivation. - BIP39 mnemonic generation and seed derivation.
- BIP44 derivation paths (
m/44'/236'/0'/...for Radiant). HdWalletwith persistent encrypted save/load (AES-CBC keyed by hash of the BIP39 seed) and BIP44 gap-limit address scanning.
Transaction/TransactionInput/TransactionOutput— Radiant tx construction, serialization, and txid computation.- BIP143-style sighash with Radiant's additional
hashOutputHashesfield; literal-zero zero-refs in the refsHash component. P2PKHscript template +unlock(private_key)for standard signing.- Script primitives in
pyrxd.scriptfor custom locking/unlocking patterns. SatoshisPerKilobytefee model.
GlyphBuilderwithprepare_commit,prepare_reveal,prepare_ft_deploy_reveal,prepare_dmint_deploy,prepare_mutable_reveal,prepare_container_reveal,prepare_wave_reveal.GlyphMetadatawith V1 and V2 sub-objects (creator, royalty, policy, rights, image+image_ipfs+image_sha256). Canonical CBOR encoding.GlyphInspector— parse Glyph tokens from a transaction's outputs.GlyphScanner— query an address's UTXOs and return Glyph tokens with metadata.FtUtxoSet+build_transfer_tx— conservation-enforcing FT transfers; refuses to build a tx that would create or destroy fungible units.DmintState.from_script— parse a live dMint contract UTXO into a typed state object.verify_sha256d_solution— off-chain PoW verifier matching on-chain semantics.
ElectrumXClient— async WebSocket client for ElectrumX servers. Multi-URL failover, transparent reconnect, per-request id correlation.get_balance,get_utxos,get_history,get_transaction,broadcast,get_merkle_proof.script_hash_for_address— derive the ElectrumX script hash from a Radiant address.BtcDataSource— Bitcoin chain reader for cross-chain Gravity flows.
GravityMakerSession— maker side of a sentinel-artifact-shaped atomic swap.- Covenant artifacts in
pyrxd.gravity.artifacts(sentinel and legacy variants). - SPV-anchored claim and forfeit flows.
Status: mainnet-proven for the sentinel-artifact path. Other covenant variants in this module are experimental.
- Block-header verification, merkle-proof verification, partial-merkle parsing.
- Header chain tip tracking.
examples/glyph_mint_demo.py— end-to-end Glyph NFT mint.examples/ft_deploy_premine.py— FT deploy with full premine to one address.examples/gravity_*.py— Gravity Protocol cross-chain demos.
- 2,000+ tests across unit, property-based (hypothesis), and integration suites.
- CBOR cross-decoder tests against an independent reference decoder (RXinDexer).
- Frozen golden vectors for CBOR encoding determinism and ECDSA RFC 6979 signing.
mypy --strictclean onsrc/.ruffclean on the codebase.
docs/DMINT_RESEARCH.md— premine vs PoW dMint scope.docs/DMINT_RESEARCH.md— Photonic Wallet TS reverse engineering.docs/DMINT_RESEARCH.md— decoded live mainnet dMint contracts.
- dMint PoW-based distributed FT mint not implemented. Premine-at-deploy works via
prepare_ft_deploy_reveal. PoW commit/reveal + ASERT/LWMA difficulty adjustment is documented as future work indocs/DMINT_RESEARCH.md. Premine-only consumers do not need it. - Gravity covenant variants beyond sentinel-artifact are experimental and have not been audited.
- No third-party security audit yet. Use at your own risk in production.