pyrxd.gravity — Cross-chain atomic swaps

pyrxd.gravity — Gravity protocol covenant transaction builders and orchestrator.

Phase 3a implements the Radiant-side covenant transaction builders that correspond to the JS prototype’s claim_tx.js, finalize_tx.js, and forfeit_tx.js.

Phase 3b adds the high-level GravityTrade orchestrator that wraps the full 4-step BTC↔RXD swap into a single async class.

Public surface

  • GravityOffer — all Maker-committed parameters for a covenant

  • ClaimResult — output of build_claim_tx()

  • FinalizeResult — output of build_finalize_tx()

  • ForfeitResult — output of build_forfeit_tx()

  • build_claim_tx — spend MakerOffer → create MakerClaimed UTXO

  • build_finalize_tx — spend MakerClaimed → release photons to Taker

  • build_forfeit_tx — Maker reclaims after claimDeadline

  • compute_p2sh_code_hash — derive the expectedClaimedCodeHash a covenant checks

  • GravityTrade — high-level async swap orchestrator (Phase 3b)

  • TradeConfig — tunable parameters for GravityTrade

  • ConfirmationStatus— BTC confirmation poll result

class pyrxd.gravity.ActiveOffer[source]

Bases: object

State of a live Gravity MakerOffer on Radiant.

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

offer

The original GravityOffer covenant parameters.

Type:

pyrxd.gravity.types.GravityOffer

maker_offer_result

Raw tx details from build_maker_offer_tx.

Type:

pyrxd.gravity.types.MakerOfferResult

offer_txid

Radiant txid of the confirmed MakerOffer funding output.

Type:

str

offer_vout

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

Type:

int

offer_photons

Photons locked in the MakerOffer P2SH output.

Type:

int

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

None

offer: GravityOffer
maker_offer_result: MakerOfferResult
offer_txid: str
offer_vout: int
offer_photons: int
class pyrxd.gravity.CappedFeeWalletSource[source]

Bases: object

A capped FeeUtxoSource over a fixed pre-funded pool.

Parameters:
  • pool – The pre-funded inventory: small plain-RXD FeeInput UTXOs the capped-pool wallet owns. Each must be a bare P2PKH UTXO whose pkh matches its own WIF (validated). Must be non-empty and free of duplicate outpoints (a duplicate would double-spend).

  • total_cap_photons – Hard cumulative ceiling on dispensed value. Dispensing stops once the next input would push the running total over this — before handing it out.

  • max_per_input_photons – Optional per-input ceiling. If given, construction fails when any pool UTXO exceeds it, keeping the “a fee input is small” invariant structural rather than assumed.

__init__(pool, *, total_cap_photons, max_per_input_photons=None)[source]
Parameters:
Return type:

None

property dispensed_photons: int

Cumulative value handed out so far.

property funded_photons: int

Total value of the pre-funded pool. This is the ceiling only if the pool key is isolated from the operator’s main wallet (a deployment property this class cannot verify — see the module docstring and the design note’s residuals).

next_fee_input()[source]

Dispense (commit) the next pool UTXO.

Raises FeePoolExhaustedError — fail-closed — when the pool is empty or the next input would exceed total_cap_photons. Dispense-once: the returned UTXO is never returned again.

Return type:

FeeInput

property remaining_inputs: int

Count of pool UTXOs not yet dispensed (physical inventory; some may be blocked by the cap — see remaining_photons for the actually-spendable budget).

property remaining_photons: int

Photons that next_fee_input() will actually dispense from here — the in-order prefix of remaining inputs that fits under the cap. Dispensing is in-order and stops at the first input that would exceed the cap (head-of-line), so this is 0 once the next input no longer fits, giving a tower an honest “page now” signal that matches dispense behaviour.

property total_cap_photons: int

The configured cumulative software ceiling.

class pyrxd.gravity.ClaimResult[source]

Bases: object

ClaimResult(tx_hex: ‘str’, txid: ‘str’, tx_size: ‘int’, offer_p2sh: ‘str’, claimed_p2sh: ‘str’, fee_sats: ‘int’, output_photons: ‘int’)

__init__(tx_hex, txid, tx_size, offer_p2sh, claimed_p2sh, fee_sats, output_photons)
Parameters:
  • tx_hex (str)

  • txid (str)

  • tx_size (int)

  • offer_p2sh (str)

  • claimed_p2sh (str)

  • fee_sats (int)

  • output_photons (int)

Return type:

None

tx_hex: str
txid: str
tx_size: int
offer_p2sh: str
claimed_p2sh: str
fee_sats: int
output_photons: int
class pyrxd.gravity.ConfirmationStatus[source]

Bases: object

Status returned by GravityTrade.wait_confirmations().

__init__(txid, confirmations, confirmed, block_height)
Parameters:
  • txid (str)

  • confirmations (int)

  • confirmed (bool)

  • block_height (int | None)

Return type:

None

txid: str
confirmations: int
confirmed: bool
block_height: int | None
class pyrxd.gravity.CovenantArtifact[source]

Bases: object

A loaded, pre-compiled covenant artifact with parameter substitution.

__init__(contract, hex_template, abi)
Parameters:
Return type:

None

constructor_params()[source]

Return the constructor ABI params in declaration order.

Return type:

list[dict]

classmethod from_json(json_text, *, allow_legacy=False)[source]

Load from raw artifact JSON (e.g. from a custom compiled artifact).

Parameters:
  • json_text (str)

  • allow_legacy (bool)

Return type:

CovenantArtifact

classmethod load(name, *, allow_legacy=False)[source]

Load a bundled artifact by stem name (without .artifact.json).

Available artifacts: - maker_offer - maker_covenant_6x12_p2wpkh - maker_covenant_flat_6x12_p2wpkh - maker_covenant_trade

Parameters:
Return type:

CovenantArtifact

substitute(params)[source]

Substitute constructor params into the hex template.

Returns the full redeem script bytes. Raises ValidationError if any required param is missing, any placeholder remains unfilled, or any fixed-width typed param (Ripemd160 / Sha256 / PubKey) has the wrong byte length — these would silently encode as the wrong push and produce an on-chain-rejected covenant.

Values: - int params: pass Python int - bytes/Ripemd160/Sha256/PubKey params: pass hex string

Parameters:

params (dict[str, Any])

Return type:

bytes

contract: str
hex_template: str
abi: list[dict]
class pyrxd.gravity.FeeInput[source]

Bases: object

A plain-RXD P2PKH UTXO that pays the miner fee for an HTLC spend.

The single covenant output carries the asset and cannot also pay the fee, so every HTLC spend joins a fee input the spender owns. The whole surplus (value - out0_value) is consumed as the miner fee — there is no change output (the covenant forbids a second output), so size value upstream so the surplus clears the per-kB min-relay fee but is not wastefully large.

__init__(txid, vout, value, scriptpubkey, wif)
Parameters:
Return type:

None

txid: str
vout: int
value: int
scriptpubkey: bytes
wif: str
class pyrxd.gravity.FinalizeResult[source]

Bases: object

FinalizeResult(tx_hex: ‘str’, txid: ‘str’, tx_size: ‘int’, fee_sats: ‘int’, output_photons: ‘int’)

__init__(tx_hex, txid, tx_size, fee_sats, output_photons)
Parameters:
  • tx_hex (str)

  • txid (str)

  • tx_size (int)

  • fee_sats (int)

  • output_photons (int)

Return type:

None

tx_hex: str
txid: str
tx_size: int
fee_sats: int
output_photons: int
class pyrxd.gravity.ForfeitResult[source]

Bases: object

ForfeitResult(tx_hex: ‘str’, txid: ‘str’, tx_size: ‘int’, fee_sats: ‘int’, output_photons: ‘int’)

__init__(tx_hex, txid, tx_size, fee_sats, output_photons)
Parameters:
  • tx_hex (str)

  • txid (str)

  • tx_size (int)

  • fee_sats (int)

  • output_photons (int)

Return type:

None

tx_hex: str
txid: str
tx_size: int
fee_sats: int
output_photons: int
class pyrxd.gravity.GravityMakerSession[source]

Bases: object

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

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

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

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

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

  4. Query current state (check_status).

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

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

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

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

Examples

Typical Maker flow:

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

None

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

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

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

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

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

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

Returns:

The cancel tx’s txid.

Return type:

str

Raises:
async check_status(offer)[source]

Return the current status of the offer UTXO.

Queries the Radiant ElectrumX server for the MakerOffer P2SH UTXO.

Returns one of:

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

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

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

    (Maker can now forfeit).

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

    (may be unconfirmed or already finalized/cancelled).

Parameters:

offer (ActiveOffer) – The ActiveOffer to check.

Returns:

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

Return type:

str

Raises:

NetworkError – On ElectrumX query failure.

async create_offer(offer_params)[source]

Build and broadcast the MakerOffer funding tx.

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

Parameters:

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

Returns:

Populated with the resulting txid and UTXO details.

Return type:

ActiveOffer

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

Poll for the Taker’s claim transaction.

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

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

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

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

Returns:

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

Return type:

str or None

class pyrxd.gravity.GravityOffer[source]

Bases: object

All parameters a Maker commits into a MakerOffer covenant.

Mirrors CovenantParams in pyrxd.spv.proof but adds Radiant-side fields and the two precomputed redeem scripts.

__init__(btc_receive_hash, btc_receive_type, btc_satoshis, chain_anchor, anchor_height, merkle_depth, taker_radiant_pkh, claim_deadline, photons_offered, offer_redeem_hex, claimed_redeem_hex, expected_code_hash_hex, expected_nbits=None, expected_nbits_next=None)
Parameters:
  • btc_receive_hash (bytes)

  • btc_receive_type (str)

  • btc_satoshis (int)

  • chain_anchor (bytes)

  • anchor_height (int)

  • merkle_depth (int)

  • taker_radiant_pkh (bytes)

  • claim_deadline (int)

  • photons_offered (int)

  • offer_redeem_hex (str)

  • claimed_redeem_hex (str)

  • expected_code_hash_hex (str)

  • expected_nbits (bytes | None)

  • expected_nbits_next (bytes | None)

Return type:

None

expected_nbits: bytes | None = None
expected_nbits_next: bytes | None = None
validate_deadline_from_now(accept_short_deadline=False)[source]

Check that claim_deadline is at least MIN_DEADLINE_FROM_NOW_HOURS from now.

Raises ValidationError unless accept_short_deadline is True (audit 04-S1 guard: Taker needs time to confirm BTC + build SPV proof + finalize on Radiant).

Parameters:

accept_short_deadline (bool)

Return type:

None

btc_receive_hash: bytes
btc_receive_type: str
btc_satoshis: int
chain_anchor: bytes
anchor_height: int
merkle_depth: int
taker_radiant_pkh: bytes
claim_deadline: int
photons_offered: int
offer_redeem_hex: str
claimed_redeem_hex: str
expected_code_hash_hex: str
class pyrxd.gravity.GravityOfferParams[source]

Bases: object

Parameters required to create a new Gravity MakerOffer.

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

offer

Fully populated GravityOffer with offer_redeem_hex set.

Type:

pyrxd.gravity.types.GravityOffer

funding_txid

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

Type:

str

funding_vout

Output index of the Maker’s funding UTXO.

Type:

int

funding_photons

Value of the Maker’s funding UTXO in photons.

Type:

int

fee_sats

Miner fee in photons for the MakerOffer funding tx.

Type:

int

change_address

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

Type:

str | None

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

None

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

Bases: object

Orchestrate a complete Gravity BTC↔RXD atomic swap.

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

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

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

Examples

Typical Taker flow:

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

None

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

Spend the MakerOffer UTXO, creating a MakerClaimed UTXO.

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

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

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

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

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

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

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

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

Return type:

ClaimResult

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

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

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

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

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

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

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

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

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

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

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

Raises:
Return type:

FinalizeResult

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

Poll Bitcoin until btc_txid reaches the required confirmations.

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

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

Returns:

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

Return type:

ConfirmationStatus

Raises:
class pyrxd.gravity.HtlcCovenant[source]

Bases: object

A built HTLC covenant: the funded SPK + the bindings a spend must satisfy.

variant

“ft” | “nft” | “rxd”.

Type:

str

funded_spk

The scriptPubKey of the covenant UTXO the maker locks the asset into.

Type:

bytes

prologue_len

Length of the compiled body (== len(funded_spk) for NFT/RXD; the offset of the FT epilogue weld for FT). The bare-0xbd guard pins to this.

Type:

int

taker_holder_script / maker_holder_script

The holder scripts output[0] of a claim (taker) / refund (maker) must equal; the covenant binds hash256 of each.

expected_taker_hash / expected_maker_hash

hash256(taker_holder_script) / hash256(maker_holder_script) — the values baked into the covenant.

genesis_ref

The 36-byte genesis outpoint ref (FT/NFT); b"" for RXD.

Type:

bytes

hashlock

The 32-byte H = SHA256(p).

Type:

bytes

refund_csv

The relative-timelock block count for the refund branch.

Type:

int

__init__(variant, funded_spk, prologue_len, taker_holder_script, maker_holder_script, expected_taker_hash, expected_maker_hash, genesis_ref, hashlock, refund_csv)
Parameters:
Return type:

None

variant: str
funded_spk: bytes
prologue_len: int
taker_holder_script: bytes
maker_holder_script: bytes
expected_taker_hash: bytes
expected_maker_hash: bytes
genesis_ref: bytes
hashlock: bytes
refund_csv: int
class pyrxd.gravity.MakerOfferResult[source]

Bases: object

Output of build_maker_offer_tx() — the MakerOffer funding tx.

__init__(tx_hex, txid, tx_size, offer_p2sh, fee_sats, output_photons)
Parameters:
  • tx_hex (str)

  • txid (str)

  • tx_size (int)

  • offer_p2sh (str)

  • fee_sats (int)

  • output_photons (int)

Return type:

None

tx_hex: str
txid: str
tx_size: int
offer_p2sh: str
fee_sats: int
output_photons: int
class pyrxd.gravity.OfferReceive[source]

Bases: object

A per-offer BTC receive destination derived from a maker account xpub.

Persist offer_index with the offer: the maker needs it to (a) spend the received BTC via the matching child key and (b) never reuse it for another live offer.

__init__(btc_receive_hash, btc_receive_type, offer_index)
Parameters:
  • btc_receive_hash (bytes)

  • btc_receive_type (str)

  • offer_index (int)

Return type:

None

btc_receive_hash: bytes
btc_receive_type: str
offer_index: int
class pyrxd.gravity.RadiantChainIO[source]

Bases: object

Thin chain helper over an ElectrumXClient-like object.

Provides exactly what the leg needs: broadcast, confirmation depth, and the on-chain value of a covenant output. NOT unified with GravityTrade (that drives the SPV-oracle finalize swap, a different protocol).

The injected client must expose broadcast(raw)->txid, get_transaction_verbose(txid)->dict (with confirmations), and get_utxos(script_hash)->list (records with tx_hash/tx_pos/value).

__init__(client)[source]
Return type:

None

async broadcast(raw_tx)[source]
Parameters:

raw_tx (bytes)

Return type:

str

async confirmations(txid)[source]
Parameters:

txid (str)

Return type:

int

async covenant_unspent_incl_mempool(outpoint)[source]

Mempool-AWARE liveness of a covenant outpoint — the complement to find_covenant_utxo’s mempool-BLIND scantxoutset scan.

True = unspent considering the mempool; False = spent (confirmed OR by a PENDING mempool tx); None = the client cannot answer (the caller keeps its own idempotency guard). Lets the autonomous claim executor treat a covenant already spent IN THE MEMPOOL as claimed — killing the per-tick re-carve drain WITHOUT a durable cross-restart store and WITHOUT the SeenStore’s eviction blind spot (a truly-unspent covenant, e.g. after a claim is evicted by a reorg, correctly re-fires).

Parameters:

outpoint (str)

Return type:

bool | None

async find_covenant_utxo(spk, *, expected_value=None)[source]

Locate the funded covenant UTXO for spk -> (outpoint, value, height).

Scans the UTXO set of the covenant scriptPubKey (ElectrumX script-hash = sha256(spk) reversed). The covenant funds exactly one output, so there is one matching UTXO; if expected_value is given, the match must equal it (a wrong value is a mis-funded covenant -> fail-closed). The returned value is the ON-CHAIN value, never a self-report.

Parameters:
  • spk (bytes)

  • expected_value (int | None)

Return type:

tuple[str, int, int]

class pyrxd.gravity.RadiantCovenantLeg[source]

Bases: object

The concrete Radiant radiant_leg (HTLC covenant claim/refund).

Parameters:
  • network – Radiant network tag (regtest test chains bypass the audit gate).

  • maker_pkh (taker_pkh /) – The taker (claim) and maker (refund) Radiant holder pubkey-hashes. The covenant binds hash256(holder(pkh)); these must reproduce the terms’ taker_dest_hash/maker_dest_hash (asserted in expected_covenant_scriptpubkey()).

  • chain_io – A RadiantChainIO (broadcast + confirmations + UTXO value).

  • fee_source – A FeeUtxoSource supplying the fee input for each spend.

  • min_confirmations – Confirmations required before the funded covenant value is trusted.

  • audit_cleared – Explicit opt-in for a value-bearing network (see pyrxd.btc_wallet.htlc_leg.require_audit_cleared()).

__init__(*, network, taker_pkh, maker_pkh, chain_io, fee_source, min_confirmations=1, audit_cleared=False)[source]
Parameters:
Return type:

None

async claim_asset(record, preimage)[source]

Build + broadcast the TAKER’s claim spend (reveals p). Returns the txid.

Parameters:
  • record (SwapRecord)

  • preimage (bytes)

Return type:

str

async covenant_outpoint(terms)[source]

Locate the funded covenant UTXO txid:vout by scanning its SPK’s UTXO set.

The maker locks the asset into the covenant SPK (a pure function of the terms); the leg finds that single funded UTXO on-chain via ElectrumX. The carrier value is bound to terms.radiant_amount so a mis-funded covenant fails closed.

Parameters:

terms (NegotiatedTerms)

Return type:

str

async expected_covenant_scriptpubkey(terms)[source]

The covenant SPK the on-chain lock must equal (built from the terms).

Parameters:

terms (NegotiatedTerms)

Return type:

bytes

async refund_asset(record)[source]

Build + broadcast the MAKER’s CSV refund spend. Returns the txid.

Parameters:

record (SwapRecord)

Return type:

str

class pyrxd.gravity.RefAuthenticityIndexer[source]

Bases: Protocol

The minimal indexer surface needed to verify a genesis REF is real.

Implementations resolve a genesis-outpoint ref to its on-chain reveal as a ResolvedRef. resolve_ref is async (the underlying glyph.get_token RPC is async) and MUST raise (not return None optimistically) when the indexer cannot reach a definitive answer — the caller treats None, any missing/invalid field, or any exception as fail-closed. Returning None means “no such token” (also fail-closed).

__init__(*args, **kwargs)
async resolve_ref(genesis_ref)[source]

Resolve genesis_ref to its reveal, or None if unknown.

Parameters:

genesis_ref (bytes)

Return type:

ResolvedRef | None

class pyrxd.gravity.ResolvedRef[source]

Bases: object

A trusted indexer’s resolution of a genesis ref to its on-chain reveal.

This is the inspectable surface the gate binds against — the gate does NOT trust a bare boolean from the indexer; it re-checks each field against what the taker agreed to. A real adapter populates this from the indexer’s glyph.get_token response (ref_outpoint/payload_hash/confirmations + a decoded gly marker); a test fake constructs it directly.

genesis_outpoint

The 36-byte genesis outpoint (txid||vout) the indexer says this token was minted at. Binding (a)/(d): MUST equal the advertised genesis_ref.

Type:

bytes

has_gly_marker

True iff the reveal carries a gly envelope (binding (b)). A bare singleton on a plain wallet UTXO has none — the exact R1 forgery.

Type:

bool

payload_hash

The reveal’s payload-commitment hash (binding (c)), or b"" if the indexer did not supply one.

Type:

bytes

confirmations

Confirmations on the genesis tx (binding (e)). A negative/None value is treated as fail-closed by the gate.

Type:

int

__init__(genesis_outpoint, has_gly_marker, payload_hash, confirmations)
Parameters:
  • genesis_outpoint (bytes)

  • has_gly_marker (bool)

  • payload_hash (bytes)

  • confirmations (int)

Return type:

None

genesis_outpoint: bytes
has_gly_marker: bool
payload_hash: bytes
confirmations: int
class pyrxd.gravity.RxinDexerRefAdapter[source]

Bases: object

Resolve a genesis ref to a ResolvedRef via RXinDexer glyph.get_token.

Implements the RefAuthenticityIndexer protocol the pre-lock gate awaits. Maps the indexer’s token dict to the inspectable fields the gate binds:

  • genesis_outpoint — from the token’s ref_outpoint (txid:vout), re-encoded to the 36-byte wire ref so it compares equal to the advertised genesis_ref. (glyph.get_token only returns genuinely-minted Glyph tokens, so a resolvable token IS a gly reveal — see has_gly_marker.)

  • has_gly_markerTrue whenever the indexer returned a token dict for the ref (the indexer only indexes real gly envelopes). A bare wallet-UTXO singleton (the R1 forgery) resolves to None and the gate fails closed.

  • payload_hash — from payload_hash (bytes), or b"" if absent.

  • confirmations — read separately from the genesis tx via chain_io (glyph.get_token does not carry confs).

NOTE (T7 plan D3): a single indexer is a SPOF, and decoding a token dict is NOT SPV authenticity (no Merkle/header binding). For the regtest milestone the local node is ground truth; SPV-bound / multi-source cross-checking is the audit-gated track. This adapter is the single-indexer regtest backend.

__init__(indexer, chain_io)[source]
Parameters:

chain_io (RadiantChainIO)

Return type:

None

async resolve_ref(genesis_ref)[source]
Parameters:

genesis_ref (bytes)

Return type:

ResolvedRef | None

class pyrxd.gravity.SeenStore[source]

Bases: object

In-memory H-freshness store (the coordinator’s reserve/has_seen).

Records every hashlock H the coordinator has committed to funding, so a reused H is rejected for BOTH reasons: economic (free-option replay) and cross-swap preimage replay. reserve(H) is the authoritative atomic test-and-set the coordinator calls PRE-broadcast; has_seen is a read-only advisory probe (the pre-lock gate’s cheap early-reject), never the binding decision.

NON-DURABLE (durable = False): a plain set, so freshness does NOT survive a restart or a second process. That is acceptable only for a single-process, single-shot run that mints a fresh H per swap (the dust runbook); the coordinator’s construct-time guard refuses this store on a value-bearing network unless the operator passes CoordinatorConfig(accept_nondurable_seen=True). A durable replacement (SQLite INSERT OR IGNORE keyed on H, declaring durable = True) is deferred to the external-audit track; it MUST stay non-blocking (asyncio.to_thread behind an async reserve) and fsync the reservation BEFORE the BTC broadcast. The method shape is duck-compatible so that durable store drops in unchanged.

__init__()[source]
Return type:

None

durable = False
has_seen(hashlock)[source]
Parameters:

hashlock (bytes)

Return type:

bool

mark_seen(hashlock)[source]
Parameters:

hashlock (bytes)

Return type:

None

reserve(hashlock)[source]

Atomically record H if unseen; True if freshly reserved, else False.

Atomic on the single-threaded event loop precisely because there is no await between the membership test and the add.

Parameters:

hashlock (bytes)

Return type:

bool

class pyrxd.gravity.TradeConfig[source]

Bases: object

Tunable parameters for GravityTrade.

min_btc_confirmations

Minimum on-chain BTC confirmations before finalizing. MUST equal the covenant’s header-depth N (the finalize path verifies exactly N consecutive headers from the anchor; a proof with fewer is rejected). Default 6 — matches the default N=6 covenant and Bitcoin’s standard finality convention (~1h). N is a per-offer MAKER knob: raise it (e.g. 12) for high-value/irreversible assets to roughly double the reorg cost, at the price of a longer wait. When using a covenant built with a different N, set this to that N (audit 2026-05-24: the two must match).

Type:

int

poll_interval_seconds

Seconds between confirmation polls. Default 60.

Type:

float

max_poll_attempts

Maximum number of polls before wait_confirmations raises NetworkError. Default 120 (= 2 hours at 60s).

Type:

int

accept_short_deadline

If True, suppress the 24h deadline guard (audit 04-S1). Only for testing — do NOT set in production.

Type:

bool

deadline_warning_seconds

Emit a WARNING log in finalize() when the Maker’s claim deadline is less than this many seconds away. Default 7200 (2 hours). Set to 0 to disable. Takers should finalize immediately if this fires (audit 04-S1 forfeit race).

Type:

int

__init__(min_btc_confirmations=6, poll_interval_seconds=60, max_poll_attempts=120, accept_short_deadline=False, deadline_warning_seconds=7200)
Parameters:
  • min_btc_confirmations (int)

  • poll_interval_seconds (float)

  • max_poll_attempts (int)

  • accept_short_deadline (bool)

  • deadline_warning_seconds (int)

Return type:

None

accept_short_deadline: bool = False
deadline_warning_seconds: int = 7200
max_poll_attempts: int = 120
min_btc_confirmations: int = 6
poll_interval_seconds: float = 60
pyrxd.gravity.build_claim_tx(offer, funding_txid, funding_vout, funding_photons, fee_sats, taker_privkey, accept_short_deadline=False)[source]

Build the Radiant claim() spending tx: MakerOffer → MakerClaimed.

Requires Taker’s private key to produce a Radiant signature satisfying MakerOffer.claim(takerSig) — prevents third-party state-advance grief (audit 04-S3).

Audit 05-F-13: verifies claimedRedeemHex matches expectedClaimedCodeHash before building, so the tx won’t be rejected on-chain.

scriptSig layout:

<takerSig+hashtype> OP_1 <offer redeem script>
Parameters:
  • offer (GravityOffer) – Fully populated GravityOffer (validated in __post_init__).

  • funding_txid (str) – Hex txid of the MakerOffer UTXO being spent.

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

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

  • fee_sats (int) – Miner fee in photons (== satoshis on Radiant).

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

  • accept_short_deadline (bool) – If True, suppress the 24-hour deadline guard (audit 04-S1).

Return type:

ClaimResult

pyrxd.gravity.build_finalize_tx(spv_proof, claimed_redeem_hex, funding_txid, funding_vout, funding_photons, to_address, fee_sats, minimum_output_photons=0, header_slots=None, branch_slots=None)[source]

Build the Radiant finalize() tx: MakerClaimed → Taker’s address.

The spv_proof must be a fully-verified SpvProof produced by SpvProofBuilder.build() — this is the only way to construct one.

No Radiant signature is required — the covenant accepts the scriptSig based on the SPV proof data alone. Output routing is enforced by the covenant’s committed takerRadiantPkh state.

scriptSig layout (pushed bottom-to-top; last push is TOP at exec):

<h1> <h2> ... <hN> <branch> <rawTx> OP_0 <claimed redeem script>

OP_0 (empty push = selector 0) selects the finalize() function.

Parameters:
  • spv_proof (SpvProof) – Fully-verified SPV proof (only obtainable from SpvProofBuilder).

  • claimed_redeem_hex (str) – Hex of MakerClaimed locking bytecode.

  • funding_txid (str) – Txid of the MakerClaimed UTXO being spent.

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

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

  • to_address (str) – Taker’s Radiant P2PKH address.

  • fee_sats (int) – Miner fee in photons.

  • minimum_output_photons (int) – The covenant’s totalPhotonsInOutput floor — baked in at offer creation time. The finalize tx is rejected on-chain if output[0].value < totalPhotonsInOutput, so we validate here before burning relay fees. Pass offer.photons_offered when calling from GravityTrade. Defaults to 0 (no floor check) for callers that have already verified externally.

  • header_slots (int | None)

  • branch_slots (int | None)

Return type:

FinalizeResult

pyrxd.gravity.build_forfeit_tx(offer, funding_txid, funding_vout, funding_photons, maker_address, fee_sats)[source]

Build the Radiant forfeit() tx: Maker reclaims after claimDeadline.

Can only be built once offer.claim_deadline has passed (i.e. the current wall-clock time is >= claim_deadline).

Sets nLockTime = claim_deadline for OP_CHECKLOCKTIMEVERIFY. Sets input sequence to 0xFFFFFFFE (< 0xFFFFFFFF — required for CLTV to be evaluated).

scriptSig layout:

OP_1 <claimed redeem script>

OP_1 (selector 1) selects the forfeit() function.

Parameters:
  • offer (GravityOffer) – GravityOffer whose claim_deadline has already passed.

  • funding_txid (str) – Txid of the MakerClaimed UTXO being forfeited.

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

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

  • maker_address (str) – Maker’s Radiant P2PKH address to receive the reclaimed photons.

  • fee_sats (int) – Miner fee in photons.

Return type:

ForfeitResult

pyrxd.gravity.build_gravity_offer(maker_pkh, maker_pk, taker_pk, taker_radiant_pkh, btc_receive_hash, btc_receive_type, btc_satoshis, btc_chain_anchor, expected_nbits, anchor_height, merkle_depth, claim_deadline, photons_offered, expected_nbits_next=None, accept_short_deadline=False, covenant_artifact_name='maker_covenant_flat_12x20_sentinel_all', offer_artifact_name='maker_offer', used_btc_receive_hashes=None, reject_low_difficulty=True, min_difficulty_nbits=None)[source]

Build a GravityOffer with real covenant redeem scripts generated from the bundled artifacts.

This is the top-level entry point for Maker-side offer construction. Internally it:

  1. Validates the claim deadline (S1 guard).

  2. Loads the MakerClaimed covenant artifact and substitutes code-section params.

  3. Computes expectedClaimedCodeHash = hash256(P2SH_scriptPubKey) from the substituted redeem script.

  4. Loads the MakerOffer artifact and substitutes its params (including the code hash from step 3).

  5. Returns a GravityOffer with both redeem scripts populated.

Parameters:
  • accept_short_deadline (bool) – Override the 24h deadline guard. Set True only for test harnesses you control — never because a counterparty asks.

  • covenant_artifact_name (str) – Override the MakerClaimed artifact stem.

  • offer_artifact_name (str) – Override the MakerOffer artifact stem.

  • used_btc_receive_hashes (set[bytes] | None) – Optional set of btc_receive_hash values already committed to other LIVE offers by this Maker. If the new btc_receive_hash is in this set, the call is rejected.

  • reject_low_difficulty (bool) – Enforce a difficulty FLOOR on the committed nBits (audit 2026-05-29 F-02). The covenant only pins nBits == committed, so a min-difficulty commit (e.g. the ffff001d footgun) lets an attacker mine a fake SPV header chain off the real anchor for ~$0. With this True the committed target must be strictly harder than the floor. Covenant-less retained uses (bridge-in / oracle / gate) MUST set this True — and SHOULD also pass min_difficulty_nbits, because the default floor (difficulty-1) only blocks the difficulty-1 class, not a target merely easier than mainnet. Defaults True (secure-by-default, audit 2026-05-29 F-02 follow-up): a real mainnet nBits is far harder than difficulty-1 so it passes the default floor; only a difficulty-1-class commit (ffff001d) is rejected. Pass reject_low_difficulty=False for regtest/test offers that use ffff001d.

  • min_difficulty_nbits (bytes | None) – Optional 4-byte wire nBits defining the difficulty floor used when reject_low_difficulty is True. Source this from the live block header at anchor_height for a real network-difficulty floor; if omitted, the floor defaults to difficulty-1 (a coarse footgun guard only).

  • maker_pkh (bytes)

  • maker_pk (bytes)

  • taker_pk (bytes)

  • taker_radiant_pkh (bytes)

  • btc_receive_hash (bytes)

  • btc_receive_type (str)

  • btc_satoshis (int)

  • btc_chain_anchor (bytes)

  • expected_nbits (bytes)

  • anchor_height (int)

  • merkle_depth (int)

  • claim_deadline (int)

  • photons_offered (int)

  • expected_nbits_next (bytes | None)

Return type:

Any

Warning

CROSS-OFFER REPLAY (audit 2026-05-24 C-ECON-1). A Bitcoin payment cannot reference a Radiant offer, so the covenant binds the payment only by btc_receive_hash + btc_satoshis + btc_chain_anchor. If the same btc_receive_hash (BTC receive address) + amount is reused across two offers with overlapping anchor windows, one BTC payment + one SPV proof can finalize BOTH offers — a taker pays once and takes two assets. There is NO on-chain or automatic defense; the earlier “per-offer-derived btcReceiveHash (H1)” control described in the design notes was never implemented. The Maker MUST use a fresh, unique BTC receive address per offer. Pass used_btc_receive_hashes to have this function reject reuse it can see; offers built by separate processes are the caller’s responsibility.

PREFER build_gravity_offer_derived(), which derives a distinct receive address per offer from the maker’s account xpub — that is the structural fix (distinct address ⇒ distinct code hash ⇒ replay impossible) and needs no caller-side live-set tracking. This raw-hash entry point remains for callers that manage receive addresses themselves; the used_btc_receive_hashes guard is only best-effort.

pyrxd.gravity.build_gravity_offer_derived(account_xpub, offer_index, *, maker_pkh, maker_pk, taker_pk, taker_radiant_pkh, btc_satoshis, btc_chain_anchor, expected_nbits, anchor_height, merkle_depth, claim_deadline, photons_offered, expected_nbits_next=None, accept_short_deadline=False, covenant_artifact_name='maker_covenant_flat_12x20_sentinel_all', offer_artifact_name='maker_offer', reject_low_difficulty=True, min_difficulty_nbits=None)[source]

Build an offer whose BTC receive address is DERIVED per-offer (replay-safe).

This is the structural fix for the cross-offer replay (C-ECON-1 / “H1”): the receive hash is derived from account_xpub at offer_index via pyrxd.gravity.receive.derive_offer_btc_receive(), so every offer commits to a DISTINCT BTC address. A payment to one offer’s address cannot satisfy another offer’s covenant (different btcReceiveHash ⇒ different code hash), so one BTC payment can finalize at most one offer — no caller-supplied live-set bookkeeping required.

Prefer this over passing a raw btc_receive_hash to build_gravity_offer(). The caller MUST allocate a fresh, never-reused offer_index per offer (a persistent monotonic counter per account) and hold the matching xprv to spend received BTC.

Returns:

(GravityOffer, OfferReceive) — persist OfferReceive.offer_index with the offer so the maker can later spend the received BTC and never reuse it.

Parameters:
  • account_xpub (Any)

  • offer_index (int)

  • maker_pkh (bytes)

  • maker_pk (bytes)

  • taker_pk (bytes)

  • taker_radiant_pkh (bytes)

  • btc_satoshis (int)

  • btc_chain_anchor (bytes)

  • expected_nbits (bytes)

  • anchor_height (int)

  • merkle_depth (int)

  • claim_deadline (int)

  • photons_offered (int)

  • expected_nbits_next (bytes | None)

  • accept_short_deadline (bool)

  • covenant_artifact_name (str)

  • offer_artifact_name (str)

  • reject_low_difficulty (bool)

  • min_difficulty_nbits (bytes | None)

Return type:

tuple[Any, Any]

pyrxd.gravity.build_htlc_claim_tx(*, covenant, covenant_outpoint, carrier_value, preimage, fee)[source]

Build the TAKER’s claim spend: reveal p, pay the single output to the taker.

Covenant scriptSig = <preimage push> <OP_0> (preimage first/under, selector last/on top). The single output pays the covenant’s pinned TAKER holder script at the carrier value; the fee input’s full surplus is the miner fee (no change).

Parameters:
Return type:

Transaction

pyrxd.gravity.build_htlc_covenant_ft(*, genesis_txid, genesis_vout, amount, taker_pkh, maker_pkh, hashlock, refund_csv)[source]

Build the FT-variant HTLC covenant (genesis ref bound via the FT epilogue weld).

Parameters:
Return type:

HtlcCovenant

pyrxd.gravity.build_htlc_covenant_nft(*, genesis_txid, genesis_vout, nft_carrier_value, taker_pkh, maker_pkh, hashlock, refund_csv)[source]

Build the NFT-variant HTLC covenant (singleton d8<ref> inside the body).

Parameters:
Return type:

HtlcCovenant

pyrxd.gravity.build_htlc_covenant_rxd(*, amount, taker_pkh, maker_pkh, hashlock, refund_csv)[source]

Build the RXD-variant HTLC covenant (native RXD: NO genesis ref, NO ref ops).

Parameters:
Return type:

HtlcCovenant

pyrxd.gravity.build_htlc_refund_tx(*, covenant, covenant_outpoint, carrier_value, fee)[source]

Build the MAKER’s CSV refund spend (function selector OP_1, after the timelock).

Covenant scriptSig = <OP_1> ONLY (no preimage, no sig — gated by the relative timelock). The covenant input’s nSequence = covenant.refund_csv and tx.version = 2 so BIP68 engages. The single output pays the covenant’s pinned MAKER holder script; the fee input’s full surplus is the miner fee.

Parameters:
Return type:

Transaction

pyrxd.gravity.build_maker_offer_tx(offer, funding_txid, funding_vout, funding_photons, fee_sats, maker_privkey, change_address=None)[source]

Build the Radiant funding tx that deploys a MakerOffer P2SH UTXO.

Spends a plain P2PKH UTXO owned by the Maker and creates a P2SH output locked to the MakerOffer redeem script. Once confirmed, the Taker can spend it with build_claim_tx().

The P2SH scriptPubKey is:

OP_HASH160 <hash160(offer_redeem)> OP_EQUAL

Signing uses standard BIP143 P2PKH sighash (the input is a plain P2PKH UTXO, not a covenant) with Radiant’s hashOutputHashes extension. The scriptCode for signing is the P2PKH scriptPubKey of the funding input, derived from the Maker’s compressed public key.

Parameters:
  • offer (GravityOffer) – Fully populated GravityOffer with offer_redeem_hex set.

  • funding_txid (str) – Hex txid of the Maker’s P2PKH UTXO being spent.

  • funding_vout (int) – Output index of the Maker’s P2PKH UTXO.

  • funding_photons (int) – Value of the Maker’s P2PKH UTXO in photons.

  • fee_sats (int) – Miner fee in photons. The offer output receives funding_photons - fee_sats photons.

  • maker_privkey (PrivateKeyMaterial) – Maker’s secp256k1 private key (PrivateKeyMaterial). Used to sign the P2PKH input and derive the P2PKH scriptCode for hashing.

  • change_address (str | None) – Default None (single-output): the full funding_photons - fee_sats is locked in the P2SH, so surplus above offer.photons_offered stays with the covenant to fund the later claim/finalize tx fees. When set (two-output): the P2SH receives exactly offer.photons_offered and the remainder goes to a P2PKH output at change_address. Use the two-output form only when offer.photons_offered already includes a buffer for downstream claim/finalize fees — otherwise the covenant will reject those txs.

Return type:

MakerOfferResult

pyrxd.gravity.compute_p2sh_code_hash(redeem_script)[source]

Compute expectedClaimedCodeHash: hash256 of the P2SH scriptPubKey.

This is what MakerOffer checks on-chain:

hash256(tx.outputs[0].codeScript) == expectedClaimedCodeHash

For P2SH outputs the codeScript is the 23-byte OP_HASH160 <hash> OP_EQUAL scriptPubKey.

Audit 05-F-13 fix: caller passes the claimed redeem script; we derive the hash independently rather than trusting a caller-supplied value.

Parameters:

redeem_script (bytes)

Return type:

bytes

pyrxd.gravity.derive_offer_btc_receive(account_xpub, offer_index)[source]

Derive a unique P2WPKH receive hash for one offer.

Parameters:
  • account_xpub (str | bytes | Xpub) – The maker’s BIP32 account-level xpub (e.g. the public form of m/84'/0'/0'). Child derivation is non-hardened so it is reproducible from the xpub; the maker holds the matching xprv to spend received BTC.

  • offer_index (int) – A per-offer, never-reused non-hardened index. The caller owns allocation (a monotonic counter) — this function is pure and does NOT track which indices have been issued.

Returns:

OfferReceive with the derived 20-byte receive hash and the index.

Raises:

ValidationError – on an out-of-range index or an unusable xpub.

Return type:

OfferReceive

pyrxd.gravity.validate_claim_deadline(claim_deadline, *, min_future_seconds=86400, bypass=False)[source]

Raise ValidationError if claim_deadline is not at least min_future_seconds from now (default: 24h).

This is the Python port of the S1 check in extract_p2sh_code_hash.js (audit 04 finding S1: a short deadline lets Maker race-snipe Taker’s claim).

Parameters:
  • bypass (bool) – Only set True for test harnesses you control. Never set because a counterparty asked you to.

  • claim_deadline (int)

  • min_future_seconds (int)

Return type:

None

async pyrxd.gravity.verify_ref_authenticity(indexer, genesis_ref, *, asset_variant, min_confirmations, expected_payload_hash=None)[source]

Hard pre-payment gate: confirm the covenant’s REF is a real minted asset.

await this BEFORE the taker pays any BTC for an FT/NFT swap. Plain-RXD swaps carry no ref and are skipped. Enforces the five bindings (a)-(e) documented at module level and fails closed on EVERY uncertain outcome: indexer unreachable/error, None (unknown token), a missing/invalid field, genesis-outpoint ≠ ref, absent gly marker, payload mismatch, or a genesis shallower than min_confirmations.

Parameters:
  • indexer (RefAuthenticityIndexer) – a trusted RefAuthenticityIndexer. A lying or attacker-controlled indexer defeats this gate — the taker must use an indexer they trust (the audit-gated track adds SPV/multi-source cross-checking; a single indexer is a SPOF, see T7 plan D3).

  • genesis_ref (bytes) – the 36-byte genesis outpoint ref baked into the covenant. This IS the advertised asset’s identity (binding d).

  • asset_variant (str) – “rxd” | “ft” | “nft”. Only ft/nft carry a ref to verify.

  • min_confirmations (int) – required confirmations on the genesis tx (binding e). Must be a non-negative int.

  • expected_payload_hash (bytes | None) – if the taker agreed to a specific payload, the reveal’s payload hash MUST match it (binding c). None skips this single binding (the others still apply).

Raises:

ValidationError – if the ref is not provably the advertised authentic asset. The caller MUST NOT pay the counter-leg (BTC or ETH) when this raises.

Return type:

None