pyrxd.glyph — Glyph token protocol

Glyph protocol — NFT singletons, FT tokens, dMint contracts, mutable refs.

Re-exports the public Glyph API from the submodules. Lazy via PEP 562 __getattr__ so import pyrxd.glyph.X paths that don’t need the full builder/signing chain (e.g. pyrxd.glyph.inspect from the browser-hosted inspect tool) avoid pulling in coincurve, aiohttp, Cryptodome.Cipher, etc. transitively.

See pyrxd for the broader rationale on lazy public re-exports.

class pyrxd.glyph.ContainerRevealScripts[source]

Bases: object

Scripts for a CONTAINER reveal.

__init__(ref, locking_script, scriptsig_suffix, child_ref)
Parameters:
Return type:

None

ref: GlyphRef
locking_script: bytes
scriptsig_suffix: bytes
child_ref: GlyphRef | None
class pyrxd.glyph.DaaMode[source]

Bases: IntEnum

__new__(value)
FIXED = 0
EPOCH = 1
ASERT = 2
LWMA = 3
SCHEDULE = 4
class pyrxd.glyph.DmintAlgo[source]

Bases: IntEnum

__new__(value)
SHA256D = 0
BLAKE3 = 1
K12 = 2
class pyrxd.glyph.DmintCborPayload[source]

Bases: object

The dmint object embedded in Glyph V2 token metadata CBOR.

Indexers read this to discover dMint contracts and display mining parameters in wallets/explorers without parsing the contract script.

Field names mirror Photonic Wallet DmintPayload type in types.ts.

__init__(algo, num_contracts, max_height, reward, premine, diff, daa_mode=DaaMode.FIXED, target_block_time=60, half_life=0, window_size=0)
Parameters:
Return type:

None

daa_mode: DaaMode = 0
classmethod from_cbor_dict(d)[source]

Parse the dmint CBOR value from an on-chain payload.

Parameters:

d (dict)

Return type:

DmintCborPayload

half_life: int = 0
target_block_time: int = 60
to_cbor_dict()[source]

Encode to the dict that becomes the dmint CBOR value.

Return type:

dict

window_size: int = 0
algo: DmintAlgo
num_contracts: int
max_height: int
reward: int
premine: int
diff: int
class pyrxd.glyph.DmintDeployParams[source]

Bases: object

Parameters for deploying a V2 dMint contract.

__init__(contract_ref, token_ref, max_height, reward, difficulty, algo=DmintAlgo.SHA256D, daa_mode=DaaMode.FIXED, target_time=60, half_life=3600, height=0, last_time=0, epoch_length=2016, max_adjustment_log2=2, schedule=())
Parameters:
Return type:

None

algo: DmintAlgo = 0
daa_mode: DaaMode = 0
epoch_length: int = 2016
half_life: int = 3600
height: int = 0
property initial_target: int

Compute initial target from difficulty using the SHA256d formula.

last_time: int = 0
max_adjustment_log2: int = 2
schedule: tuple[tuple[int, int], ...] = ()
target_time: int = 60
contract_ref: GlyphRef
token_ref: GlyphRef
max_height: int
reward: int
difficulty: int
class pyrxd.glyph.DmintMineResult[source]

Bases: object

The output of a successful mine_solution() call.

Parameters:
  • nonce – The nonce bytes (4B for V1, 8B for V2) that satisfy the target.

  • attempts – Number of nonce candidates tried before finding the solution.

  • elapsed_s – Wall-clock seconds spent searching.

__init__(nonce, attempts, elapsed_s)
Parameters:
Return type:

None

nonce: bytes
attempts: int
elapsed_s: float
class pyrxd.glyph.DmintMintResult[source]

Bases: object

Output of build_dmint_mint_tx().

Parameters:
  • tx – Unsigned transaction (caller must sign).

  • updated_state – New DmintState written into the contract output (height incremented, target updated if DAA is active).

  • contract_script – New contract output script (state + separator + code).

  • reward_script – P2PKH locking script of the miner reward output.

  • fee – Transaction fee in photons.

Note

The transaction returned here is unsigned — it uses raw script bytes for the contract input’s unlocking script (nonce + preimage halves) built by build_mint_scriptsig(). The contract script is a covenant, not a P2PKH, so standard Transaction.sign() is not appropriate. The caller must either set the unlocking script directly or use a custom signing path. See docstring of build_dmint_mint_tx() for details.

__init__(tx, updated_state, contract_script, reward_script, fee)
Parameters:
Return type:

None

tx: Any
updated_state: Any
contract_script: bytes
reward_script: bytes
fee: int
class pyrxd.glyph.DmintState[source]

Bases: object

Parsed dMint contract state (from on-chain UTXO script).

Supports both V1 (the current Radiant mainnet format) and V2 (Photonic Wallet’s HEAD spec, not yet seen on mainnet). V1 has 6 state items; V2 has 10. is_v1 is True iff this state was parsed from V1 layout — in which case target_time and last_time are not meaningful on-chain values and are set to 0; daa_mode is always FIXED for V1 (the V1 contract template has no DAA bytecode).

__init__(height, contract_ref, token_ref, max_height, reward, algo, daa_mode, target_time, last_time, target, is_v1=False)
Parameters:
Return type:

None

classmethod from_script(script_bytes)[source]

Parse a dMint contract UTXO script into a DmintState.

Tries V2 layout first (10 state items), falls back to V1 (6 items + fingerprinted code epilogue). Raises ValidationError if the script matches neither.

Parameters:

script_bytes (bytes) – Raw script bytes from a dMint contract UTXO output.

Raises:

ValidationError – Script is malformed or matches neither V1 nor V2 layout.

Return type:

DmintState

property is_exhausted: bool
is_v1: bool = False
height: int
contract_ref: GlyphRef
token_ref: GlyphRef
max_height: int
reward: int
algo: DmintAlgo
daa_mode: DaaMode
target_time: int
last_time: int
target: int
class pyrxd.glyph.DmintV1ContractInitialState[source]

Bases: object

Just-deployed state of a V1 dMint contract template.

Carries exactly the parameters needed to reconstruct the initial (height=0) contract codescript for every contract of a given deploy. Used by find_dmint_contract_utxos()’s fast path, where the caller already knows the deploy params.

Parameters:
  • num_contracts – Count of parallel contracts the deploy created (1..255 for V1; mainnet GLYPH used 32).

  • reward_sats – Photons emitted per successful mint (must fit in 3 bytes — V1 protocol constant).

  • max_height – Maximum mints per contract (3-byte ceiling).

  • target – 8-byte SHA256d PoW target.

  • algo – PoW algorithm. Defaults to DmintAlgo.SHA256D, which is the only algorithm seen on V1 mainnet.

__init__(num_contracts, reward_sats, max_height, target, algo=DmintAlgo.SHA256D)
Parameters:
Return type:

None

algo: DmintAlgo = 0
num_contracts: int
reward_sats: int
max_height: int
target: int
class pyrxd.glyph.DmintV1DeployParams[source]

Bases: object

Parameters for a V1 dMint deploy (2-tx: commit + reveal).

V1 is the only dMint format on Radiant mainnet today. Unlike V2 (which uses a separate deploy tx with a reward pool), V1 emits num_contracts parallel singleton contract UTXOs directly in the reveal — each is the full state+epilogue codescript at height=0. Mining works by spending a contract UTXO and re-creating it at height+1 with the same script template; the reward is paid from a miner-supplied funding input.

See docs/dmint-research-photonic-deploy.md for the byte-by-byte chain shape this dataclass drives. Live mainnet example: Radiant Glyph Protocol (GLYPH) at commit a443d9df…878b → reveal b965b32d…9dd6.

Parameters:
  • metadataGlyphMetadata for the token. Must include protocol [GlyphProtocol.FT, GlyphProtocol.DMINT] ([1, 4]) and NOT include a v version field (V2 uses v; V1 omits it).

  • owner_pkh – 20-byte PKH of the key that signs commit and all ref-seed P2PKH inputs in the reveal.

  • num_contracts – Count of parallel V1 dMint contract UTXOs to emit. Total supply = reward_photons * max_height * num_contracts. Validated to [1, 250] at construction. The 250 ceiling is the standardness limit for tx size at typical V1 contract bytes (≈ 241 bytes/contract output + overhead → 250 contracts fits in a ~64 KB reveal before the embedded media body).

  • max_height – Maximum mints per contract (3-byte ceiling).

  • reward_photons – Photons paid per successful mint (3-byte ceiling — see V1 contract state layout).

  • difficulty – Initial PoW difficulty (1 = easiest). Translated to 8-byte target via difficulty_to_target().

  • premine_amount – Photons to send to owner_pkh on the reveal tx as an optional premine FT output. None = no premine. Filed as deferred work in M2 (docs/dmint-research-photonic-deploy.md §7.2); accepted in the dataclass but rejected at build time for now.

  • op_return_msg – Optional OP_RETURN data carrier (raw bytes after the 0x6a prefix). None = no OP_RETURN output.

  • algo – PoW algorithm. Defaults to DmintAlgo.SHA256D (the only algorithm on V1 mainnet today).

__init__(metadata, owner_pkh, num_contracts, max_height, reward_photons, difficulty, premine_amount=None, op_return_msg=None, algo=DmintAlgo.SHA256D)
Parameters:
Return type:

None

algo: DmintAlgo = 0
op_return_msg: bytes | None = None
premine_amount: int | None = None
metadata: GlyphMetadata
owner_pkh: Hex20
num_contracts: int
max_height: int
reward_photons: int
difficulty: int
class pyrxd.glyph.DmintV1DeployResult[source]

Bases: object

Output of GlyphBuilder.prepare_dmint_deploy() for V1 deploys.

Carries everything the caller needs to broadcast a V1 deploy: the commit-tx script + CBOR body, plus a deferred-builder method that produces the reveal-tx outputs once the commit confirms.

V1 differs from V2 in that there is no separate deploy tx — the reveal directly creates the parallel contract UTXOs. So this result has no deploy_params_template / initial_pool_photons / placeholder_contract_script fields; instead it carries placeholder_contract_scripts (one per parallel contract) for fee estimation before the commit txid is known.

Parameters:
  • commit_resultCommitResult — commit-tx script + fee. Same shape as the V2 result’s field.

  • cbor_bytes – Encoded CBOR token body.

  • owner_pkh – 20-byte PKH of the deploy key.

  • premine_amount – Photons for optional premine output, or None. Deferred work in M2 — must be None.

  • num_contracts – Count of parallel V1 contracts.

  • placeholder_contract_scripts – Tuple of N contract scripts built with the placeholder commit txid (00…00). Each is the same byte length as the final contract script — the only difference is the contractRef / tokenRef txid component. Use the length for fee estimation.

  • max_height – Echoed from params for build_reveal_outputs access.

  • reward_photons – Echoed from params.

  • difficulty – Echoed from params.

  • algo – Echoed from params.

  • op_return_msg – Echoed from params.

__init__(commit_result, cbor_bytes, owner_pkh, premine_amount, num_contracts, placeholder_contract_scripts, max_height, reward_photons, difficulty, algo, op_return_msg)
Parameters:
  • commit_result (CommitResult)

  • cbor_bytes (bytes)

  • owner_pkh (Hex20)

  • premine_amount (int | None)

  • num_contracts (int)

  • placeholder_contract_scripts (tuple[bytes, ...])

  • max_height (int)

  • reward_photons (int)

  • difficulty (int)

  • algo (DmintAlgo)

  • op_return_msg (bytes | None)

Return type:

None

build_reveal_outputs(commit_txid)[source]

Build reveal-tx output scripts given the confirmed commit txid.

The V1 reveal: * spends commit vouts 0 + 1..N + (N+1 NFT-commit) + (N+2 change) * emits N parallel dMint contract UTXOs at vouts 0..N-1 * emits the FT NFT singleton + auth NFT singleton + change

The method name is build_reveal_outputs (not build_reveal_scripts as in V2) because V1’s reveal directly creates the output contract UTXOs — there is no separate deploy tx. The arity also differs from V2’s (no commit_vout / commit_value needed: V1 input values are protocol constants). Distinct names prevent silent polymorphic-call TypeErrors.

Parameters:

commit_txid (str) – txid of the confirmed commit tx.

Returns:

DmintV1RevealScripts ready to be placed into the reveal tx’s outputs.

Return type:

DmintV1RevealScripts

commit_result: CommitResult
cbor_bytes: bytes
owner_pkh: Hex20
premine_amount: int | None
num_contracts: int
placeholder_contract_scripts: tuple[bytes, ...]
max_height: int
reward_photons: int
difficulty: int
algo: DmintAlgo
op_return_msg: bytes | None
class pyrxd.glyph.DmintV1RevealScripts[source]

Bases: object

Output scripts for the V1 dMint deploy reveal tx.

Mirrors the shape of FtDeployRevealScripts (a flat locking-script + scriptsig-suffix bag), but with V1’s distinctive multi-output structure: N contract scripts + optional premine FT + optional OP_RETURN. The caller composes these into a transaction in declared order, signs each input, and broadcasts.

Parameters:
  • contract_scripts – Tuple of full V1 dMint contract output scripts (state + epilogue), one per parallel contract. Length equals the deploy’s num_contracts. Each is the 241-byte layout at height=0 with contractRef[i] = (commit_txid, i+1) and tokenRef = (commit_txid, 0).

  • contract_value – Photons per contract output. Always 1 (V1 contracts are singletons — the photon value stays at 1 as the contract advances).

  • cbor_bytes – Encoded CBOR token body. Caller pushes this in the reveal’s vin[0] scriptSig (after sig + pubkey), preceded by the gly magic bytes push.

  • scriptsig_suffix – The push sequence <gly> <CBOR> ready to append after <sig> <pubkey> for vin[0]. Mirrors the FtDeployRevealScripts.scriptsig_suffix convention.

  • premine_script – Locking script for an optional premine FT output (None = no premine). Deferred work in M2 — the builder currently raises if premine_amount is set.

  • premine_amount – Photons for the premine output (None if no premine).

  • op_return_script – Locking script for an optional OP_RETURN data carrier (None if no OP_RETURN).

__init__(contract_scripts, contract_value, cbor_bytes, scriptsig_suffix, premine_script, premine_amount, op_return_script)
Parameters:
  • contract_scripts (tuple[bytes, ...])

  • contract_value (int)

  • cbor_bytes (bytes)

  • scriptsig_suffix (bytes)

  • premine_script (bytes | None)

  • premine_amount (int | None)

  • op_return_script (bytes | None)

Return type:

None

contract_scripts: tuple[bytes, ...]
contract_value: int
cbor_bytes: bytes
scriptsig_suffix: bytes
premine_script: bytes | None
premine_amount: int | None
op_return_script: bytes | None
class pyrxd.glyph.DmintV2DeployParams[source]

Bases: object

Parameters for a V2 dMint token deploy (2-tx: commit + reveal).

Mirrors DmintV1DeployParams. V2 emits num_contracts parallel 1-photon singleton contract UTXOs directly in the reveal — contractRef[i] = commit:(i+1), tokenRef = commit:0 — exactly like V1. The only consensus differences are the V2 contract bytecode (10-item state + the V2 covenant) and the 8-byte mint nonce; the reward + tx fee for each mint come from a miner-supplied funding input, not a pool.

All five DaaMode values are supported — FIXED, ASERT, LWMA, EPOCH, and SCHEDULE — and the redesigned covenant advances target/last_time on-chain, byte-matched to canonical Photonic dMintScript (incl. the EPOCH/LWMA int64-overflow fix, Radiant-Core/Photonic-Wallet#2). See #219.

V2 is consensus-proven on regtest + mainnet but still pre-external-audit; prepare_dmint_deploy deploys V2 by default as of 0.9.0 (allow_v2_deploy defaults to True and is retained only for backward-compatibility).

Parameters:
  • metadataGlyphMetadata (must include GlyphProtocol.FT and GlyphProtocol.DMINT; set version=2 so indexers classify it as V2).

  • owner_pkh – 20-byte PKH of the key that signs commit + the ref-seed reveal inputs.

  • num_contracts – Count of parallel V2 contract UTXOs ([1, 250]).

  • max_height – Maximum mints per contract.

  • reward_photons – Photons paid per successful mint.

  • difficulty – Initial PoW difficulty (1 = easiest).

  • premine_amount – Deferred (mirrors V1) — must be None.

  • op_return_msg – Optional OP_RETURN data carrier (raw bytes after 0x6a).

  • algo – PoW algorithm (default SHA256d; only SHA256D is mined).

  • daa_mode – Must be DaaMode.FIXED (the only mintable mode).

  • target_time – Echoed into the state (DAA-only; vestigial for FIXED).

  • half_life – Echoed into the code (DAA-only; vestigial for FIXED).

__init__(metadata, owner_pkh, num_contracts, max_height, reward_photons, difficulty, premine_amount=None, op_return_msg=None, algo=DmintAlgo.SHA256D, daa_mode=DaaMode.FIXED, target_time=60, half_life=3600, epoch_length=2016, max_adjustment_log2=2, schedule=())
Parameters:
Return type:

None

algo: DmintAlgo = 0
daa_mode: DaaMode = 0
epoch_length: int = 2016
half_life: int = 3600
max_adjustment_log2: int = 2
op_return_msg: bytes | None = None
premine_amount: int | None = None
schedule: tuple[tuple[int, int], ...] = ()
target_time: int = 60
metadata: GlyphMetadata
owner_pkh: Hex20
num_contracts: int
max_height: int
reward_photons: int
difficulty: int
class pyrxd.glyph.DmintV2DeployResult[source]

Bases: object

Output of GlyphBuilder.prepare_dmint_deploy() for V2 deploys.

Mirrors DmintV1DeployResult: V2 emits num_contracts parallel 1-photon singleton contract UTXOs directly in the reveal (no separate deploy tx, no reward pool). Call build_reveal_outputs() once the commit confirms to get the reveal-tx output scripts.

Parameters:
  • commit_resultCommitResult — commit-tx script + fee.

  • cbor_bytes – Encoded CBOR token body.

  • owner_pkh – 20-byte PKH of the deploy key.

  • premine_amount – Deferred — must be None (mirrors V1).

  • num_contracts – Count of parallel V2 contracts.

  • placeholder_contract_scripts – Tuple of N V2 contract scripts built with the placeholder commit txid (00…00) — same byte length as the final scripts, for fee estimation before the commit txid is known.

:param max_height, reward_photons, difficulty, algo, op_return_msg, daa_mode,

target_time, half_life: Echoed from params for build_reveal_outputs().

__init__(commit_result, cbor_bytes, owner_pkh, premine_amount, num_contracts, placeholder_contract_scripts, max_height, reward_photons, difficulty, algo, op_return_msg, daa_mode, target_time, half_life, epoch_length=2016, max_adjustment_log2=2, schedule=())
Parameters:
Return type:

None

build_reveal_outputs(commit_txid)[source]

Build reveal-tx output scripts given the confirmed commit txid.

Mirrors DmintV1DeployResult.build_reveal_outputs(): emits num_contracts parallel 1-photon V2 contract UTXOs (contractRef[i] = commit:(i+1), tokenRef = commit:0) + the gly/CBOR reveal scriptSig suffix + optional OP_RETURN. The returned DmintV1RevealScripts bag has the same shape for V1 and V2.

Parameters:

commit_txid (str)

Return type:

DmintV1RevealScripts

epoch_length: int = 2016
max_adjustment_log2: int = 2
schedule: tuple[tuple[int, int], ...] = ()
commit_result: CommitResult
cbor_bytes: bytes
owner_pkh: Hex20
premine_amount: int | None
num_contracts: int
placeholder_contract_scripts: tuple[bytes, ...]
max_height: int
reward_photons: int
difficulty: int
algo: DmintAlgo
op_return_msg: bytes | None
daa_mode: DaaMode
target_time: int
half_life: int
class pyrxd.glyph.FtTransferParams[source]

Bases: object

Parameters for an FT transfer transaction.

Parameters:
  • ref – the GlyphRef identifying the token

  • utxos – list of FtUtxo available to spend

  • amount – FT units to send to new_owner_pkh

  • new_owner_pkh – recipient’s 20-byte PKH

  • private_key – sender’s pyrxd.keys.PrivateKey

  • fee_rate – photons/byte (Radiant post-V2 minimum is 10_000)

  • change_pkh – FT-change recipient PKH. Defaults to the sender’s PKH when None.

__init__(ref, utxos, amount, new_owner_pkh, private_key, fee_rate=10000, change_pkh=None)
Parameters:
Return type:

None

change_pkh: Hex20 | None = None
fee_rate: int = 10000
ref: GlyphRef
utxos: list
amount: int
new_owner_pkh: Hex20
private_key: Any
class pyrxd.glyph.FtTransferResult[source]

Bases: object

Output of FtUtxoSet.build_transfer_tx().

Parameters:
  • tx – signed Transaction, ready to broadcast

  • new_ft_script – locking script of the transfer (recipient) output

  • change_ft_script – locking script of the change output, or None if the transfer was an exact match

  • ref – the FT’s GlyphRef

  • fee – fee paid in photons

__init__(tx, new_ft_script, change_ft_script, ref, fee)
Parameters:
Return type:

None

tx: Any
new_ft_script: bytes
change_ft_script: bytes | None
ref: GlyphRef
fee: int
class pyrxd.glyph.FtUtxo[source]

Bases: object

A single UTXO holding some quantity of one FT.

Parameters:
  • txid – txid of the UTXO

  • vout – output index within that tx

  • value – RXD value (photons) on the output

  • ft_amount – token units held on the output

  • ft_script – full FT locking script (75 bytes, see pyrxd.glyph.script.build_ft_locking_script())

__init__(txid, vout, value, ft_amount, ft_script)
Parameters:
Return type:

None

txid: str
vout: int
value: int
ft_amount: int
ft_script: bytes
class pyrxd.glyph.FtUtxoSet[source]

Bases: object

Manages a set of FT UTXOs for a single token ref.

Responsibilities:

  • Total the FT amount across the set.

  • Select a minimum set of UTXOs to cover a requested transfer amount.

  • Build + sign a transfer tx (two-pass fee calculation) that respects conservation.

__init__(ref, utxos)[source]
Parameters:
Return type:

None

build_transfer_tx(amount, new_owner_pkh, private_key, fee_rate=10000, change_pkh=None, dust_limit=546)[source]

Build a signed FT transfer transaction enforcing conservation.

The selected UTXOs are spent with a standard P2PKH scriptSig (same unlock as an NFT transfer — the FT script embeds a full P2PKH prefix before the OP_PUSHINPUTREF / conservation epilogue, so <sig> <pubkey> satisfies it). A transfer output locked to new_owner_pkh is created for amount token units; any leftover token units flow to a change output locked to change_pkh (or the sender’s PKH if omitted).

Fee calculation uses the same two-pass pattern as GlyphBuilder.build_nft_transfer_tx(): build a trial tx → sign → measure bytes → rebuild fresh (so the final signature commits to the final outputs, not the trial ones).

Parameters:
  • amount (int) – FT units to transfer to new_owner_pkh

  • new_owner_pkh (Hex20) – recipient’s 20-byte PKH

  • private_key (Any) – pyrxd.keys.PrivateKey owning the inputs

  • fee_rate (int) – photons/byte (default 10_000, the Radiant minimum)

  • change_pkh (Hex20 | None) – FT-change recipient PKH. Defaults to the sender’s PKH derived from private_key.

  • dust_limit (int) – minimum photon value per output (default 546)

Raises:

ValueErroramount <= 0; total FT < amount; total RXD from the selected inputs cannot cover dust_limit * n_outputs + fee.

Returns:

FtTransferResult (signed tx, scripts, fee, ref).

Return type:

FtTransferResult

select(amount)[source]

Greedily select the minimum number of UTXOs covering amount.

Strategy: sort by ft_amount descending, take until covered.

Raises:

ValueErroramount exceeds total() (including the empty-set case, where total == 0).

Parameters:

amount (int)

Return type:

list[FtUtxo]

total()[source]

Return the sum of ft_amount across all UTXOs in the set.

Return type:

int

class pyrxd.glyph.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)

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, *, allow_v2_deploy=True)[source]

Prepare a dMint token deploy.

Dispatches on the type of params:

  • DmintV1DeployParams → returns DmintV1DeployResult. V1 is the only format on Radiant mainnet today (see GLYPH at a443d9df…878b). Two-tx deploy: commit + reveal (the reveal directly creates params.num_contracts parallel contract UTXOs).

  • DmintV2DeployParams → returns DmintV2DeployResult. V2 is consensus-proven on regtest + mainnet (#219) and now deploys by default (allow_v2_deploy=True). A soft UserWarning is emitted if the caller explicitly passes allow_v2_deploy=False so the historical opt-out path stays observable without blocking.

Parameters:
Returns:

V1 or V2 result, matching the param type via @overload.

Raises:

ValidationError – Various per-version invariants — see _prepare_dmint_v1_deploy() and the V2 implementation below for specifics.

Return type:

DmintV1DeployResult | DmintV2DeployResult

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 either attrs["name"] (the Photonic-compatible canonical shape — required for resolution against RXinDexer and other indexers) or top-level name (legacy pyrxd shape, accepted for backwards compatibility but not indexer-visible).

Photonic-compatible CBOR shape (canonical, see Photonic Wallet packages/lib/src/wave.ts):

{
    "p": [2, 5, 11],
    "attrs": {
        "name": "alice.rxd",
        "domain": "rxd",
        "target": "<radiant_address>",
        "target_type": "address"
    }
}

Use build_wave_attrs() (or pyrxd.glyph.wave.build_wave_metadata()) to construct the canonical shape; passing a top-level name field still works but emits a token RXinDexer will not index.

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

Parameters:
Return type:

MutableRevealScripts

class pyrxd.glyph.GlyphCreator[source]

Bases: object

Creator identity and optional ECDSA signature over the metadata commit hash.

pubkey: 33-byte compressed secp256k1 pubkey, hex-encoded. sig: DER-encoded ECDSA signature, hex-encoded (empty string = unsigned). algo: Signing algorithm identifier string.

__init__(pubkey, sig='', algo='ecdsa-secp256k1')
Parameters:
Return type:

None

algo: str = 'ecdsa-secp256k1'
classmethod from_cbor_dict(d)[source]
Parameters:

d (dict)

Return type:

GlyphCreator

sig: str = ''
to_cbor_dict()[source]
Return type:

dict

pubkey: str
class pyrxd.glyph.GlyphFt[source]

Bases: object

A minted or transferable FT Glyph.

__init__(ref, owner_pkh, amount, metadata)
Parameters:
Return type:

None

ref: GlyphRef
owner_pkh: Hex20
amount: int
metadata: GlyphMetadata
class pyrxd.glyph.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 (or if the CBOR is malformed / unrecognised).

Catches Exception broadly because every call site here crosses a trust boundary: scriptSigs from network-fetched txs are attacker- controlled, and the CBOR decoder + push-data walker may raise anything from ValidationError to cbor2.CBORDecodeError to IndexError on truncated input. Returning None is the contract callers expect.

Parameters:

scriptsig (bytes)

Return type:

GlyphMetadata | None

find_glyphs(tx_outputs)[source]

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

Detects NFT singletons, FT locks, mutable NFTs, and dMint contract outputs. Plain P2PKH and unrecognised scripts are silently skipped. Commit-output classification lives outside find_glyphs because a commit has no meaningful ref until its reveal lands.

Parameters:

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

Return type:

list[GlyphOutput]

find_reveal_metadata(scriptsigs)[source]

Walk every input scriptSig and return the first reveal metadata found.

Returns (input_index, metadata) for the first input whose scriptSig embeds a gly marker followed by parseable CBOR; None if no input does. Distinct from extract_reveal_metadata() (which checks a single scriptSig) — diagnostic callers want to know which input carried the metadata, and that the inspector looked beyond input 0.

Parameters:

scriptsigs (list[bytes])

Return type:

tuple[int, GlyphMetadata] | None

parse_mint_scriptsig(scriptsig)[source]

Decode a dMint mint-claim scriptSig into its 4 canonical pushes.

A V1/V2 dMint mint claim spends the contract UTXO with a scriptSig of the form:

V1 (nonce_width=4): <0x04 nonce(4)> <0x20 inputHash(32)> <0x20 outputHash(32)> <OP_0>  → 72 bytes
V2 (nonce_width=8): <0x08 nonce(8)> <0x20 inputHash(32)> <0x20 outputHash(32)> <OP_0>  → 76 bytes

Where:

  • nonce — little-endian PoW nonce found by the miner.

  • inputHashSHA256d(funding_input_locking_script). NOT a preimage half; the on-chain covenant recomputes SHA256(inputHash || outputHash) from these literal pushes.

  • outputHashSHA256d(OP_RETURN_msg_script at vout[2]).

  • OP_0 — the sentinel push the V1/V2 covenant requires.

Verified against mainnet V1 mint 146a4d68…f3c and the V1 mint c9fdcd34…e530.

Returns a dict with nonce_hex, input_hash, output_hash, version_hint ("v1" | "v2" | None), and scriptsig_length — or None if the scriptSig doesn’t match the canonical 4-push shape.

Catches Exception broadly because every call site crosses a trust boundary: scriptSigs from network-fetched txs are attacker- controlled. Non-mint inputs (P2PKH funding inputs, plain RXD spends, etc.) return None.

Parameters:

scriptsig (bytes)

Return type:

dict | None

class pyrxd.glyph.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.glyph.GlyphNft[source]

Bases: object

A minted or transferable NFT Glyph.

__init__(ref, owner_pkh, metadata)
Parameters:
Return type:

None

ref: GlyphRef
owner_pkh: Hex20
metadata: GlyphMetadata
class pyrxd.glyph.GlyphPolicy[source]

Bases: object

Token behaviour policy flags.

__init__(renderable=None, executable=None, nsfw=None, transferable=None)
Parameters:
  • renderable (bool | None)

  • executable (bool | None)

  • nsfw (bool | None)

  • transferable (bool | None)

Return type:

None

executable: bool | None = None
classmethod from_cbor_dict(d)[source]
Parameters:

d (dict)

Return type:

GlyphPolicy

nsfw: bool | None = None
renderable: bool | None = None
to_cbor_dict()[source]
Return type:

dict

transferable: bool | None = None
class pyrxd.glyph.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.glyph.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

classmethod from_contract_hex(contract_hex)[source]

Parse a 72-char contract id string as displayed in Radiant explorers.

The Glyph contract id concatenates the display-order txid (64 hex chars) with the big-endian-encoded vout (8 hex chars). Both halves are written in human-readable order so the whole string reads naturally — the trailing 00000004 decodes to 4:

b45dc453befb589a...c380eb31deaf96a2a8 00000004
└────────── txid (display order) ───┘ └─ vout BE ─┘  (= 4)

Equivalent forms:

  • from_contract_hex("b45dc4...a2a800000004")

  • GlyphRef(txid=Txid("b45dc4...a2a8"), vout=4)

Warning

This is the explorer / UI display form, not the on-chain wire form. from_bytes() parses the wire form used inside locking scripts, where the txid bytes are reversed and the vout is encoded little-endian. If you have raw bytes pulled out of a script, use from_bytes(). Use this method only when you have a contract id in the form a Radiant explorer or wallet UI shows it. Mixing them will silently produce a wrong-vout ref.

Parameters:

contract_hex (str)

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.glyph.GlyphRights[source]

Bases: object

Licensing and attribution information.

__init__(license='', terms='', attribution='')
Parameters:
  • license (str)

  • terms (str)

  • attribution (str)

Return type:

None

attribution: str = ''
classmethod from_cbor_dict(d)[source]
Parameters:

d (dict)

Return type:

GlyphRights

license: str = ''
terms: str = ''
to_cbor_dict()[source]
Return type:

dict

class pyrxd.glyph.GlyphRoyalty[source]

Bases: object

On-chain royalty hint for secondary-market wallets.

bps: Basis points (100 = 1%, 500 = 5%, max 10000 = 100%). address: Radiant address to receive royalty payments. enforced: Whether wallets should enforce this royalty. minimum: Minimum royalty amount in photons (0 = no minimum). splits: Optional list of (address, bps) pairs for royalty splitting.

The sum of split bps should equal the top-level bps.

__init__(bps, address, enforced=False, minimum=0, splits=<factory>)
Parameters:
Return type:

None

enforced: bool = False
classmethod from_cbor_dict(d)[source]
Parameters:

d (dict)

Return type:

GlyphRoyalty

minimum: int = 0
to_cbor_dict()[source]
Return type:

dict

bps: int
address: str
splits: tuple[tuple[str, int], ...]
class pyrxd.glyph.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.glyph.MutableRevealScripts[source]

Bases: object

Scripts for a MUT reveal — two outputs required.

__init__(ref, nft_script, contract_script, scriptsig_suffix, payload_hash)
Parameters:
Return type:

None

ref: GlyphRef
nft_script: bytes
contract_script: bytes
scriptsig_suffix: bytes
payload_hash: bytes
class pyrxd.glyph.PowPreimageResult[source]

Bases: object

The 64-byte PoW preimage plus the two script hashes a miner must push.

The covenant binds the PoW hash AND the scriptSig pushes together: it recomputes H2 = SHA256(scriptSig_inputHash || scriptSig_outputHash) and folds that into the same hash the miner solved. Diverging the preimage from the scriptSig pushes is a silent on-chain rejection — see docs/solutions/runtime-errors/dmint-v1-mint-scriptsig-shape.md for the prior incident that motivated returning all three values from a single helper.

Parameters:
  • preimage – 64-byte SHA256d PoW preimage; feeds mine_solution.

  • input_hashSHA256d(input_script) — push as scriptSig_inputHash.

  • output_hashSHA256d(output_script) — push as scriptSig_outputHash.

__init__(preimage, input_hash, output_hash)
Parameters:
Return type:

None

preimage: bytes
input_hash: bytes
output_hash: bytes
class pyrxd.glyph.RxinDexerClient[source]

Bases: object

Thin wrapper over ElectrumXClient for RXinDexer extension RPCs.

Methods are grouped by RPC namespace (wave_*, glyph_*, swap_*). Each wraps a single RPC call, parses the response into a typed result, and converts transport / parse failures into RxinDexerError subclasses.

The pyrxd.glyph.wave.WaveResolver is built on top of this client and is the canonical entry-point for WAVE name resolution in higher-level applications.

__init__(client)[source]
Parameters:

client (ElectrumXClient)

async glyph_get_balance(address, token_ref=None)[source]

glyph.get_balance — fungible-token balance for an address.

Pass token_ref to scope the query to a specific token; without it, the indexer returns all FT balances the address holds.

Parameters:
  • address (str)

  • token_ref (str | None)

Return type:

Any

async glyph_get_metadata(ref)[source]

glyph.get_metadata — decoded CBOR metadata for a token.

Parameters:

ref (str)

Return type:

dict[str, Any] | None

async glyph_get_token(ref)[source]

glyph.get_token — fetch a token by its txid:vout ref.

Parameters:

ref (str)

Return type:

dict[str, Any] | None

async wave_check_available(name)[source]

True if name is not yet registered on-chain.

Parameters:

name (str)

Return type:

bool

async wave_get_subdomains(name)[source]

Subdomains of name. Returns empty list if none.

Parameters:

name (str)

Return type:

list[str]

async wave_resolve(name)[source]

Raw wave.resolve call. Returns the indexer’s dict response, or None if the name is not registered. Higher-level callers should usually use pyrxd.glyph.wave.WaveResolver.

Parameters:

name (str)

Return type:

dict[str, Any]

async wave_reverse_lookup(address)[source]

All WAVE names that resolve to address.

Parameters:

address (str)

Return type:

list[str]

async wave_stats()[source]

Indexer-level WAVE stats — useful for health checks.

Return type:

IndexerStats

exception pyrxd.glyph.RxinDexerError[source]

Bases: Exception

Base class for RXinDexer-specific errors.

exception pyrxd.glyph.RxinDexerNotFound[source]

Bases: RxinDexerError

A lookup returned no result (name not registered, token unknown, etc.).

exception pyrxd.glyph.V2UnvalidatedWarning[source]

Bases: UserWarning

Retained warning category for V2 dMint code paths.

HISTORY: V2 dMint was once quarantined behind this warning because it had never been exercised against live consensus. That is no longer true — the canonical-Photonic V2 redesign is byte-matched to upstream and consensus- validated on radiant-core v3.1.1 regtest AND Radiant mainnet (3.1.2): the first V2 dMint deploy + PoW mint confirmed on mainnet (deploy 95335028…bb16fb09, mint 1239f64a…e0cd6c67; #219). The per-call warning is therefore no longer emitted.

The class is kept (not deleted) so any downstream warnings.simplefilter(…, V2UnvalidatedWarning) filters remain importable. V2 is still pre-external- audit — that caveat lives in the README / threat-model, the same level as V1, not in a per-call warning.

class pyrxd.glyph.WaveAttrs[source]

Bases: object

Parsed WAVE attrs dict, mirroring the on-chain Photonic shape.

__init__(name, domain, target, target_type='address')
Parameters:
Return type:

None

classmethod from_dict(d)[source]

Parse from a CBOR attrs dict; rejects missing required fields.

Parameters:

d (dict)

Return type:

WaveAttrs

target_type: str = 'address'
to_dict()[source]

Serialize as the CBOR attrs dict.

Return type:

dict[str, str]

name: str
domain: str
target: str
exception pyrxd.glyph.WaveNameNotFound[source]

Bases: WaveResolverError

Raised when the requested name does not exist in the indexer.

class pyrxd.glyph.WaveRecord[source]

Bases: object

A full WAVE registration, as returned by wave.resolve.

The exact response shape from RXinDexer is documented at https://github.com/Radiant-Core/RXinDexer; this class normalizes the minimum fields a swap coordinator needs.

__init__(name, target, target_type, claim_txid, block_height)
Parameters:
  • name (str)

  • target (str)

  • target_type (str)

  • claim_txid (str)

  • block_height (int)

Return type:

None

classmethod from_indexer_response(data)[source]

Build a WaveRecord from the JSON-RPC response.

Tolerant of field naming — RXinDexer’s response wraps things in attrs or surfaces them top-level depending on version. Tries both shapes before erroring.

Parameters:

data (dict[str, Any])

Return type:

WaveRecord

name: str
target: str
target_type: str
claim_txid: str
block_height: int
class pyrxd.glyph.WaveResolver[source]

Bases: object

High-level WAVE name resolver — composes RxinDexerClient.

Accepts either an ElectrumXClient (auto-wraps in RxinDexerClient) or an existing RxinDexerClient. The latter is preferred when you have other indexer use cases (Glyph metadata lookups, Swap state, etc.) so the same client is shared.

All methods raise WaveResolverError (a subclass of RxinDexerError) on transport / parse failures. Name-not-found raises WaveNameNotFound so callers can distinguish “does not exist” from “indexer is down”.

__init__(client)[source]
Parameters:

client (ElectrumXClient | RxinDexerClient)

async check_available(name)[source]

Return True if name is not yet registered.

Parameters:

name (str)

Return type:

bool

async resolve(name)[source]

Look up a qualified WAVE name (e.g. "alice.rxd").

Raises WaveNameNotFound if the name is not registered. Raises WaveResolverError on transport / parse failures.

Parameters:

name (str)

Return type:

WaveRecord

async reverse_lookup(address)[source]

Return the list of WAVE names that resolve to address.

Parameters:

address (str)

Return type:

list[str]

async stats()[source]

Return indexer-level stats — useful for health checks.

Return type:

dict[str, Any]

exception pyrxd.glyph.WaveResolverError

Bases: RxinDexerError

Raised when a WAVE name resolution call fails for any reason. Subclass of RxinDexerError — catch either to handle indexer failures.

pyrxd.glyph.build_dmint_code_script(params)[source]

Build the V2 dMint code bytecode (Part A + powHashOp + Part B + Part C).

Parameters:

params (DmintDeployParams)

Return type:

bytes

pyrxd.glyph.build_dmint_contract_script(params)[source]

Build the full V2 dMint output script: state + OP_STATESEPARATOR + code.

Byte-identical to the canonical Photonic dMintScript for the same parameters (validated against golden vectors in tests/test_dmint_v2_canonical.py, and consensus-proven on regtest + mainnet).

Parameters:

params (DmintDeployParams)

Return type:

bytes

pyrxd.glyph.build_dmint_state_script(params)[source]

Build the 10-item V2 dMint state script (before OP_STATESEPARATOR).

Layout (canonical redesign §4.2):

height(minimal) | d8:contractRef(36B) | d0:tokenRef(36B) |
maxHeight | reward | algoId | daaMode | targetTime |
lastTime(4B LE) | target(minimal)

height and target use minimal pushes (variable width) so the state script is MINIMALDATA-compliant from height 0 / target MAX onward — the old fixed 04 [LE4] height push was rejected by radiantd’s MINIMALDATA mempool policy on mainnet. lastTime stays a 4-byte push (Unix timestamps are always 4-byte minimal), which simplifies Part C’s 04 || NUM2BIN(4, locktime) reconstruction.

Parameters:

params (DmintDeployParams)

Return type:

bytes

pyrxd.glyph.build_dmint_v1_ft_output_script(miner_pkh, token_ref)[source]

Build the 75-byte P2PKH-wrapped FT output that a V1 mint produces.

Layout (docs/dmint-research-mainnet.md §4 vout[1]):

76 a9 14 <pkh:20>     OP_DUP OP_HASH160 PUSH20 pkh
88 ac                 OP_EQUALVERIFY OP_CHECKSIG    (25-byte P2PKH prologue)
bd                    OP_STATESEPARATOR
d0 <tokenRef:36>      OP_PUSHINPUTREF tokenRef       (37 bytes)
de c0 e9 aa 76 e3     12-byte covenant fingerprint   (`_V1_FT_OUTPUT_EPILOGUE`)
78 e4 a2 69 e6 9d
──────────────────────
Total: 75 bytes

This is the FT-bearing reward output — the V1 contract’s OP_CODESCRIPTHASHVALUESUM_OUTPUTS OP_NUMEQUALVERIFY at epilogue offset 168 sums photons under this codescript and requires the total to equal the contract’s reward field. Producing a plain P2PKH instead breaks FT conservation and the network rejects the mint.

Raises:

ValidationErrorminer_pkh is not 20 bytes.

Parameters:
Return type:

bytes

pyrxd.glyph.build_dmint_v1_mint_preimage(contract_utxo, funding_utxo, unsigned_tx)[source]

Build the V1 mining preimage AND scriptSig hashes for an unsigned mint tx.

The V1 covenant binds the PoW preimage to:

  1. The contract input’s outpoint txid + the contract ref (so a nonce mined for one contract slot can’t be replayed against another)

  2. The miner’s funding-input locking script (so the miner cannot substitute a different funding source after finding a nonce)

  3. The OP_RETURN msg output script at vout[2] (Photonic’s mainnet-canonical layout; the covenant computes outputHash = SHA256d(this script))

Layout (matches build_pow_preimage()):

preimage    = SHA256(txid_LE || contractRef) ||
              SHA256(SHA256d(input_script) || SHA256d(output_script))
input_hash  = SHA256d(input_script)    ← scriptSig push
output_hash = SHA256d(output_script)   ← scriptSig push

Callers feed preimage to mine_solution() and pass input_hash + output_hash to build_mint_scriptsig().

Parameters:
  • contract_utxo (DmintContractUtxo) – The V1 contract UTXO being spent.

  • funding_utxo (DmintMinerFundingUtxo) – The plain-RXD UTXO providing reward + fee.

  • unsigned_tx (Any) – The unsigned Transaction from build_dmint_mint_tx() — vout[2] is required to be the OP_RETURN msg output (mainnet-canonical 4-output shape).

Returns:

PowPreimageResult carrying the preimage and the two script hashes that the scriptSig must push for the covenant to accept the mint.

Raises:

ValidationErrorunsigned_tx has fewer than 4 outputs (no OP_RETURN at vout[2]) OR vout[2] is not actually an OP_RETURN script. Build the tx via build_dmint_mint_tx() with a non-empty op_return_msg; skipping that produces a 3-output tx, and hand-building a 4-output tx with a different vout[2] would silently bind the preimage to wrong bytes (the on-chain covenant would then reject after a successful mine — wasting the mining work).

Return type:

PowPreimageResult

pyrxd.glyph.build_dmint_v2_mint_preimage(contract_utxo, funding_utxo, output_script)[source]

Build the V2 mining preimage AND scriptSig hashes.

V2 analog of build_dmint_v1_mint_preimage(). The preimage shape (and the on-chain covenant’s H1/H2 binding logic) is identical to V1 — V2 inherits the output-validation block via _PART_C = _V1_EPILOGUE_SUFFIX[18:]. The only V1/V2 differences at the mint-tx level are the nonce width (8 bytes for V2 vs 4 for V1, a parameter of build_mint_scriptsig()) and the absence of the Photonic-Wallet op_return_msg convention in V2.

Layout (matches build_pow_preimage()):

preimage    = SHA256(txid_LE || contractRef) ||
              SHA256(SHA256d(input_script) || SHA256d(output_script))
input_hash  = SHA256d(input_script)    ← scriptSig push
output_hash = SHA256d(output_script)   ← scriptSig push

Unlike the V1 helper, this function takes output_script as an explicit argument. V2 has no canonical “OP_RETURN msg at vout[2]” convention (that’s Photonic-Wallet’s V1 layout); the V2 covenant binds outputHash to whatever bytes the caller chooses to push. Callers selecting output_script should pick one of the actual transaction outputs and document the binding in their own code.

Note

This helper closes the audit’s security-H1 finding (no V2 analog of build_dmint_v1_mint_preimage left V2 callers one careless script-mismatch away from reproducing the M1 bug pattern). V2 is consensus-proven on regtest + mainnet (#219).

Parameters:
  • contract_utxo (DmintContractUtxo) – The V2 contract UTXO being spent. Its state.is_v1 MUST be False — passing a V1 UTXO is a bug.

  • funding_utxo (DmintMinerFundingUtxo) – The plain-RXD UTXO providing reward + fee.

  • output_script (bytes) – The output-script bytes to bind into the preimage. V2 has no canonical convention; pick a transaction output the caller cares about (e.g. an OP_RETURN identifier, or the reward output’s locking script).

Returns:

PowPreimageResult with the preimage and the two script hashes the scriptSig must push.

Raises:

ValidationError – V1 contract UTXO passed by mistake, or an empty output_script.

Return type:

PowPreimageResult

pyrxd.glyph.build_mint_scriptsig(nonce, input_hash, output_hash, *, nonce_width=8)[source]

Build the scriptSig a miner includes in the contract-spend input.

Format (SHA256d):

V2 (nonce_width=8): <0x08> <nonce:8B> <0x20> <inputHash:32B> <0x20> <outputHash:32B> <0x00> → 76 bytes V1 (nonce_width=4): <0x04> <nonce:4B> <0x20> <inputHash:32B> <0x20> <outputHash:32B> <0x00> → 72 bytes

The V1 layout is documented in docs/dmint-research-mainnet.md §4 (vin[0] of the mainnet mint trace at 146a4d68…f3c). Same shape as V2, differing only in nonce width and corresponding push opcode.

The hashes pushed here MUST equal PowPreimageResult.input_hash and output_hash from the same build_pow_preimage() call that produced the preimage the miner solved. The on-chain covenant recomputes SHA256(input_hash || output_hash) from these pushes and folds that into the PoW hash — diverging them silently produces a mandatory-script-verify-flag-failed rejection after a successful mine.

Parameters:
  • nonce (bytes) – nonce_width-bytes nonce (found during mining).

  • input_hash (bytes) – 32-byte SHA256d(input_script) from PowPreimageResult.

  • output_hash (bytes) – 32-byte SHA256d(output_script) from PowPreimageResult.

  • nonce_width (Literal[4, 8]) – 4 for V1 contracts, 8 for V2. Keyword-only and Literal[4, 8] so a stray positional value is a type error rather than a silent V1/V2 confusion. Default 8 preserves pre-V1-support behavior.

Return type:

bytes

pyrxd.glyph.build_mutable_nft_script(mutable_ref, payload_hash)[source]

Build the 175-byte mutable NFT output script.

Layout: PUSH32 <payload_hash> OP_DROP OP_STATESEPARATOR

OP_PUSHINPUTREFSINGLETON <mutable_ref:36> <102-byte body>

Parameters:
  • mutable_ref (GlyphRef) – The singleton ref that identifies the mutable contract.

  • payload_hash (bytes) – 32-byte SHA256d of the CBOR metadata payload.

Return type:

bytes

pyrxd.glyph.build_mutable_scriptsig(operation, cbor_bytes, contract_output_index, ref_hash_index, ref_index, token_output_index)[source]

Build the scriptSig for spending a mutable NFT contract input.

The mutable NFT script expects the scriptSig stack (bottom→top):

gly_marker | cbor_payload | operation | contract_output_index | ref_hash_index | ref_index | token_output_index

Parameters:
  • operation (Literal['mod', 'sl']) – "mod" (modify — update payload hash) or "sl" (seal — burn the mutable contract).

  • cbor_bytes (bytes) – CBOR-encoded metadata for the new state.

  • contract_output_index (int) – Output index of the mutable contract in the tx.

  • ref_hash_index (int) – Index into the refdatasummary for this token.

  • ref_index (int) – Index of the singleton ref in token output data.

  • token_output_index (int) – Output index of the token in the tx.

Return type:

bytes

pyrxd.glyph.build_pow_preimage(txid_le, contract_ref_bytes, input_script, output_script)[source]

Build the PoW preimage AND the two script hashes the scriptSig must push.

preimage[0..32] = SHA256(txid_LE || contractRef) preimage[32..64] = SHA256(SHA256d(inputScript) || SHA256d(outputScript))

The covenant pulls inputHash and outputHash from the scriptSig pushes (not from the preimage halves) and recomputes the second SHA256 on-chain. Returning all three values here forces callers to feed both sites from the same source — splitting the helper into “preimage builder” and “scriptSig builder” with independently-recomputed hashes is what produced the M1 covenant-rejection bug.

Parameters:
  • txid_le (bytes) – 32-byte txid in little-endian (internal byte order)

  • contract_ref_bytes (bytes) – 36-byte contract ref (wire format)

  • input_script (bytes) – miner’s input locking script (e.g. P2PKH)

  • output_script (bytes) – miner’s output script (e.g. OP_RETURN message)

Returns:

PowPreimageResult with preimage, input_hash, output_hash.

Return type:

PowPreimageResult

pyrxd.glyph.build_wave_metadata(*, qualified_name, target, target_type='address', description='')[source]

Construct a Photonic-compatible WAVE GlyphMetadata.

Parameters:
  • qualified_name (str) – e.g. "alice.rxd" — split into name + domain.

  • target (str) – the address (or other identifier) the name resolves to.

  • target_type (str) – "address" by default; other values are reserved for future schemas (e.g. "cross_chain").

  • description (str) – optional human-readable description; stored as top-level desc in CBOR (NOT inside attrs).

Return type:

GlyphMetadata

The returned metadata has protocol [NFT, MUT, WAVE] and an attrs dict matching the Photonic on-chain shape — pass it through encode_payload() and then GlyphBuilder.prepare_wave_reveal() to construct the actual reveal transaction.

The top-level name field on GlyphMetadata is intentionally left empty: validation in prepare_wave_reveal prefers attrs.name, and emitting both would create ambiguity if they ever disagree.

pyrxd.glyph.classify_glyph_metadata(metadata)[source]

Return the highest-specificity protocol classification for a metadata payload.

Examples

[NFT, MUT, WAVE]"wave" (when attrs.name present) [NFT, MUT, WAVE] without attrs.name → "mut" (legacy, won’t resolve) [NFT, MUT, CONTAINER]"container" [NFT, MUT]"mut" [NFT, AUTHORITY]"authority" [NFT, ENCRYPTED, TIMELOCK]"timelock" [NFT, ENCRYPTED]"encrypted" [NFT]"nft" [FT, DMINT]"dmint" [FT]"ft" [DAT]"dat"

The string mirrors GlyphOutput.glyph_type values where applicable, with extensions for the metadata-only types that scripts alone can’t distinguish (WAVE/CONTAINER/ENCRYPTED/TIMELOCK/AUTHORITY share script templates with MUT/NFT, and DAT is data-only).

Ordering is highest-specificity-first: TIMELOCK is checked before ENCRYPTED (TIMELOCK requires ENCRYPTED per the protocol rules in types, so a timelocked token always carries both).

Parameters:

metadata (GlyphMetadata)

Return type:

str

pyrxd.glyph.compute_next_target_asert(current_target, last_time, current_time, target_time, half_life)[source]

Compute next ASERT-lite target (mirrors the redesigned on-chain bytecode).

The redesign replaced OP_LSHIFT/OP_RSHIFT (which Radiant evaluates as a big-endian bit-string shift — wrong on the LE target encoding) with an unrolled 4-step OP_2MUL/OP_2DIV loop with a per-step overflow cap:

drift = trunc((current_time - last_time - target_time) / half_life)  # clamp [-4,+4]
drift > 0:  repeat |drift|x:  target = MAX_TARGET if target > MAX/2 else target*2
drift < 0:  repeat |drift|x:  target = target // 2
minimum target is 1

The per-step cap matches the miner’s newTarget = min(MAX, oldTarget<<drift) clamp-at-MAX semantics (a naive target << drift would overshoot MAX).

Note

V2-only DAA. V1 has no DAA (fixed difficulty).

Parameters:
  • current_target (int)

  • last_time (int)

  • current_time (int)

  • target_time (int)

  • half_life (int)

Return type:

int

pyrxd.glyph.compute_next_target_linear(current_target, last_time, current_time, target_time)[source]

Compute next linear/LWMA target (mirrors the redesigned on-chain bytecode).

Divide-first with caps so the on-chain OP_MUL never overflows int64:

timeDelta_capped = max(0, min(current_time - last_time, 4 * target_time))
target_capped    = min(current_target, MAX_TARGET // 4)
new_target       = min(MAX_TARGET, (target_capped // target_time) * timeDelta_capped)
minimum target is 1

The MAX/4 target cap means LWMA contracts cannot have a difficulty floor below 4 (target <= MAX_TARGET/4). The 0-floor on timeDelta mirrors the on-chain OP_0 OP_MAX (Radiant-Core/Photonic-Wallet#2): a backwards-clock block (locktime earlier than the previous mint) gives a negative delta that would otherwise underflow the on-chain int64 multiply.

Note

V2-only DAA.

Parameters:
  • current_target (int)

  • last_time (int)

  • current_time (int)

  • target_time (int)

Return type:

int

pyrxd.glyph.difficulty_to_target(difficulty, algo=DmintAlgo.SHA256D)[source]

Convert difficulty to PoW target.

Parameters:
Return type:

int

pyrxd.glyph.extract_wave_attrs(cbor_data)[source]

Pull WaveAttrs out of a decoded CBOR payload, if present.

Returns None for non-WAVE payloads or WAVE payloads using only the legacy top-level name shape (those exist on-chain but RXinDexer won’t index them).

Parameters:

cbor_data (dict)

Return type:

WaveAttrs | None

async pyrxd.glyph.find_dmint_contract_utxos(client, *, token_ref, initial_state=None, limit=None, min_confirmations=1)[source]

Discover live V1 dMint contract UTXOs for a given token_ref.

Two call shapes:

  • Fast path — pass initial_state. The function rebuilds each contract’s expected initial codescript locally (contractRef[i] = (commit_txid, i+1), tokenRef = token_ref), computes its scripthash inline, and asks the server for the UTXO at that scripthash. One get_utxos call per contract. Use this shape immediately after deploy to verify all N contracts went live, or any time the caller has the deploy params handy.

  • Walk-from-reveal fallback — omit initial_state. The function fetches the deploy commit, derives the FT-commit hashlock’s scripthash, queries history for the reveal txid, then fetches the reveal and extracts every fresh V1 contract output whose tokenRef matches. Slower (3+ extra round-trips) but works on any live token where you only know the token_ref.

Both shapes apply the same security S2 cross-check: for each candidate UTXO returned, the source transaction is fetched and verified to have txid() matching the server’s tx_hash, and its output script byte-equal to the script the server claimed. Defends against a malicious or buggy ElectrumX serving altered bytes (mirrors find_dmint_funding_utxo()’s round-4 defense).

The fallback path returns fresh contracts only — UTXOs that have been mined from at least once are skipped (their state advanced and their scripthash drifted; following the spend chain forward to locate the current head is filed as deferred work).

Parameters:
  • client (Any) – An open pyrxd.network.electrumx.ElectrumXClient.

  • token_ref (GlyphRef) – The token’s permanent 36-byte ref (the deploy commit’s vout-0 outpoint, LE-reversed). Equivalently: GlyphRef(txid=commit_txid, vout=0).

  • initial_state (DmintV1ContractInitialState | None) – If supplied, fast-path. If None, walk from the deploy reveal.

  • limit (int | None) – If supplied, cap the result list at this many contracts. None returns all available.

  • min_confirmations (int) – Skip UTXOs younger than this many blocks. Default 1 (require at least 1 confirmation).

Returns:

A list of DmintContractUtxo for each currently-unspent contract whose script verified S2.

Raises:
  • ValidationError – Inputs malformed (token_ref must point at vout=0); or initial_state has out-of-range fields.

  • NetworkError – Propagated from the ElectrumX client.

Return type:

list[DmintContractUtxo]

pyrxd.glyph.mine_solution(preimage, target, *, algo=DmintAlgo.SHA256D, nonce_width=4, max_attempts=600000000)[source]

Search for a nonce satisfying the V1/V2 dMint PoW target.

Sequential nonce sweep starting at 0. The nonce is encoded as a little-endian unsigned integer of the requested width (4 bytes for V1, 8 bytes for V2 — matches glyph-miner’s nonceBytesForContracts).

Calls verify_sha256d_solution() per candidate; that’s the single source of truth for “does this hash satisfy the target.” Drift between the mining check and the verifier check would let pyrxd produce a nonce that passes locally but fails on-chain (or vice versa).

Parameters:
  • preimage (bytes) – 64-byte preimage from build_pow_preimage().

  • target (int) – 8-byte 64-bit target (the V1/V2 contract’s target state field).

  • algo (DmintAlgo) – Hash algorithm. Only SHA256D is implemented; BLAKE3 and K12 raise NotImplementedError.

  • nonce_width (Literal[4, 8]) – 4 for V1, 8 for V2. Keyword-only and Literal[4, 8] so a stray positional value is a type error rather than a silent V1/V2 confusion.

  • max_attempts (int) – Upper bound on iterations before raising MaxAttemptsError. Defaults to ≈minutes single-core at typical CPython hashlib speeds.

Raises:
  • ValidationErrorpreimage is not 64 bytes, target is not positive, nonce_width is not 4 or 8, or max_attempts is < 1.

  • NotImplementedErroralgo is BLAKE3 or K12.

  • MaxAttemptsError – No solution found within max_attempts iterations. The exception’s attempts and elapsed_s attributes carry telemetry.

Return type:

DmintMineResult

Worked example (small target chosen so the loop completes in ms):

>>> from pyrxd.glyph.dmint import (
...     mine_solution, verify_sha256d_solution, MAX_SHA256D_TARGET,
... )
>>> preimage = b"\x00" * 64
>>> target = MAX_SHA256D_TARGET >> 8  # easy: ~1 in 256 expected
>>> result = mine_solution(preimage, target, nonce_width=4)
>>> verify_sha256d_solution(preimage, result.nonce, target, nonce_width=4)
True
pyrxd.glyph.mine_solution_dispatch(preimage, target, *, nonce_width=4, algo=DmintAlgo.SHA256D, miner_argv=None, max_attempts=600000000, timeout_s=600.0)[source]

Mine a nonce — picks the in-process or subprocess miner from one entrypoint.

Most callers want this helper rather than calling mine_solution() or mine_solution_external() directly. The two paths share semantics — both return a DmintMineResult with a nonce that satisfies the target — but have disjoint parameter sets (max_attempts vs timeout_s, no-argv vs argv). Picking between them was a 30-line wrapper that every demo and operator script ended up rewriting; this function is that wrapper, with the branch in one place.

Dispatch rule:

  • miner_argv is None (default): run mine_solution() in this process. Slow but correct. Use for tests, small examples, and contracts where mining takes < a minute.

  • miner_argv is not None: invoke mine_solution_external() with the supplied argv. The external miner (e.g. pyrxd.contrib.miner, a custom binary, or glyph-miner) runs as a subprocess and returns a verified nonce via the JSON-over-stdio protocol. The local re-verification in mine_solution_external is the load-bearing safety check against a buggy or malicious miner.

Parameters:
  • preimage (bytes) – 64-byte preimage from build_pow_preimage().

  • target (int) – The PoW target.

  • nonce_width (Literal[4, 8]) – 4 for V1 contracts, 8 for V2.

  • algo (DmintAlgo) – Hash algorithm. Currently only SHA256D is implemented; BLAKE3 and K12 raise from mine_solution(). Ignored on the external-miner path (the protocol doesn’t carry an algo field; external miners are assumed SHA256D until the protocol is extended).

  • miner_argv (list[str] | None) – None → in-process; otherwise an argv list passed to subprocess.run() for the external miner. Use [sys.executable, "-m", "pyrxd.contrib.miner"] for the bundled parallel miner.

  • max_attempts (int) – Iteration cap on the in-process path. Ignored on the external-miner path (the external miner caps via timeout_s instead).

  • timeout_s (float) – Subprocess timeout on the external-miner path. Ignored in-process (use max_attempts there).

Returns:

DmintMineResult with the verified nonce.

Raises:
  • MaxAttemptsError – in-process exhausted max_attempts, or external miner exceeded timeout_s / explicitly signalled exhaustion.

  • ValidationError – external miner returned a malformed response or a nonce that fails local verification.

Return type:

DmintMineResult

pyrxd.glyph.mine_solution_external(preimage, target, *, miner_argv, nonce_width=4, timeout_s=600.0)[source]

Delegate nonce search to an external miner via JSON-over-subprocess.

Spawns miner_argv as a subprocess, writes one JSON line to its stdin, reads one JSON line from its stdout, and re-verifies the returned nonce locally. The local re-verification is the load-bearing safety check — a wrong nonce from the external process raises rather than getting silently embedded in a transaction.

The miner is expected to:

  1. Read one JSON object from stdin: {"preimage_hex", "target_hex", "nonce_width"}.

  2. Search for a valid nonce.

  3. Write one JSON object to stdout — on a hit (exit 0): {"nonce_hex", "attempts", "elapsed_s"}; on nonce-space exhaustion (exit 2, added in 0.5.1): {"exhausted": true} (pyrxd then raises MaxAttemptsError immediately rather than waiting for the parent timeout to fire).

A bundled reference implementation ships at pyrxd.contrib.miner (added in 0.5.1) — see Parallel mining and the external-miner protocol for the full protocol spec and operational notes. Invoke it via:

miner_argv=[sys.executable, "-m", "pyrxd.contrib.miner"]

Warning

Supply-chain risk: pyrxd does NOT pin or verify the miner binary. miner_argv[0] is resolved by the OS at exec time, so a malicious binary earlier in $PATH can intercept calls. The local nonce re-verification (below) defends against the miner returning a wrong nonce, but cannot detect side-channel exfiltration: a malicious miner sees the preimage (which encodes the contract ref + miner binding) and can leak it out-of-band over the network.

Mitigations the caller should consider:

  • Invoke with an absolute path (["/usr/local/bin/glyph-miner", ...]) rather than a bare name to bypass $PATH resolution.

  • Verify the binary’s checksum against the upstream release before first use.

  • Run pyrxd in an environment where $PATH is controlled (e.g. a dedicated user account, sandbox, or container).

For testing and trusted environments the bare-name form is fine.

Parameters:
  • preimage (bytes) – 64-byte preimage from build_pow_preimage().

  • target (int) – The PoW target.

  • miner_argv (list[str]) – argv passed to subprocess.run() (e.g. ["glyph-miner", "--stdin"]). The first element must be a binary or shell-resolvable name; pyrxd does not pin a specific miner. See the supply-chain warning above.

  • nonce_width (Literal[4, 8]) – 4 for V1, 8 for V2.

  • timeout_s (float) – Hard timeout. The subprocess is killed and MaxAttemptsError raised on expiry.

Raises:
  • ValidationError – The miner returned a malformed JSON response, a nonce of wrong width, or a nonce that fails local verification.

  • MaxAttemptsError – The miner exceeded timeout_s.

  • FileNotFoundErrorminer_argv[0] is not on PATH.

Return type:

DmintMineResult

pyrxd.glyph.parse_mutable_nft_script(script)[source]

Parse a mutable NFT output script, returning (mutable_ref, payload_hash) or None.

Parameters:

script (bytes)

Return type:

tuple[GlyphRef, bytes] | None

pyrxd.glyph.sign_metadata(metadata, private_key, algo='ecdsa-secp256k1')[source]

Return a new GlyphMetadata with creator.sig populated.

The private key’s compressed public key is embedded as creator.pubkey. The signing protocol is:

  1. Build canonical CBOR with sig=”” and the pubkey.

  2. commit_hash = SHA256d(cbor)

  3. message = SHA256(“glyph-v2-creator:” || commit_hash)

  4. sig = ECDSA(private_key, message) [low-s DER, no double-hash]

Parameters:
  • metadata (GlyphMetadata) – GlyphMetadata to sign. Any existing creator field is replaced.

  • private_key (PrivateKey) – pyrxd PrivateKey — the token deployer’s key.

  • algo (str) – Signing algorithm identifier (default: “ecdsa-secp256k1”).

Returns:

A frozen copy of metadata with creator.sig set.

Return type:

GlyphMetadata

pyrxd.glyph.split_qualified_name(qualified)[source]

Split "alice.rxd" into ("alice", "rxd").

Names with no domain (e.g. "alice") default to domain "rxd" — matching Photonic’s behavior. Names with multiple dots use the LAST dot as the domain separator (so "foo.bar.rxd" is ("foo.bar", "rxd")).

Parameters:

qualified (str)

Return type:

tuple[str, str]

pyrxd.glyph.target_to_difficulty(target, algo=DmintAlgo.SHA256D)[source]

Convert PoW target to difficulty (approximate).

Parameters:
Return type:

int

pyrxd.glyph.verify_creator_signature(metadata)[source]

Verify the creator signature embedded in metadata.

Returns:

(True, “”) if valid; (False, reason) if invalid or missing.

Parameters:

metadata (GlyphMetadata)

Return type:

tuple[bool, str]

pyrxd.glyph.verify_sha256d_solution(preimage, nonce, target, *, nonce_width=8)[source]

Verify a SHA256d PoW solution.

Valid if: hash[0..4] == 0x00000000 AND int.from_bytes(hash[4..12], ‘big’) < target

target is clamped to MAX_SHA256D_TARGET before comparison — a caller-supplied target above the maximum would make the check trivially pass for any hash that starts with four zero bytes.

Parameters:
  • nonce_width (Literal[4, 8]) – 4 for V1 contracts, 8 for V2. Default 8 preserves the pre-V1-support behavior. Passed as keyword-only so a stray positional 4 vs 8 is a type error rather than a silent V1/V2 confusion.

  • preimage (bytes)

  • nonce (bytes)

  • target (int)

Return type:

bool

pyrxd.glyph.wave_attrs_from_metadata(metadata)[source]

Convenience wrapper: extract WaveAttrs from a parsed GlyphMetadata (typically from GlyphInspector.extract_reveal_metadata()).

Returns None for non-WAVE metadata or legacy-shape WAVE without attrs.name (which RXinDexer cannot index).

Parameters:

metadata (GlyphMetadata)

Return type:

WaveAttrs | None