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 covenantClaimResult— output ofbuild_claim_tx()FinalizeResult— output ofbuild_finalize_tx()ForfeitResult— output ofbuild_forfeit_tx()build_claim_tx— spend MakerOffer → create MakerClaimed UTXObuild_finalize_tx— spend MakerClaimed → release photons to Takerbuild_forfeit_tx— Maker reclaims after claimDeadlinecompute_p2sh_code_hash— derive the expectedClaimedCodeHash a covenant checksGravityTrade— high-level async swap orchestrator (Phase 3b)TradeConfig— tunable parameters for GravityTradeConfirmationStatus— BTC confirmation poll result
- class pyrxd.gravity.ActiveOffer[source]¶
Bases:
objectState of a live Gravity MakerOffer on Radiant.
Returned by
GravityMakerSession.create_offer()and required by all subsequent lifecycle methods.- offer¶
The original
GravityOffercovenant parameters.
- maker_offer_result¶
Raw tx details from
build_maker_offer_tx.
- __init__(offer, maker_offer_result, offer_txid, offer_vout, offer_photons)¶
- Parameters:
offer (GravityOffer)
maker_offer_result (MakerOfferResult)
offer_txid (str)
offer_vout (int)
offer_photons (int)
- Return type:
None
- offer: GravityOffer¶
- maker_offer_result: MakerOfferResult¶
- class pyrxd.gravity.ClaimResult[source]¶
Bases:
objectClaimResult(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)¶
- class pyrxd.gravity.ConfirmationStatus[source]¶
Bases:
objectStatus returned by
GravityTrade.wait_confirmations().- __init__(txid, confirmations, confirmed, block_height)¶
- class pyrxd.gravity.CovenantArtifact[source]¶
Bases:
objectA loaded, pre-compiled covenant artifact with parameter substitution.
- __init__(contract, hex_template, abi)¶
- classmethod from_json(json_text, *, allow_legacy=False)[source]¶
Load from raw artifact JSON (e.g. from a custom compiled artifact).
- Parameters:
- Return type:
- 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:
- substitute(params)[source]¶
Substitute constructor params into the hex template.
Returns the full redeem script bytes. Raises
ValidationErrorif 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: -
intparams: pass Pythonint-bytes/Ripemd160/Sha256/PubKeyparams: pass hex string
- class pyrxd.gravity.FinalizeResult[source]¶
Bases:
objectFinalizeResult(tx_hex: ‘str’, txid: ‘str’, tx_size: ‘int’, fee_sats: ‘int’, output_photons: ‘int’)
- __init__(tx_hex, txid, tx_size, fee_sats, output_photons)¶
- class pyrxd.gravity.ForfeitResult[source]¶
Bases:
objectForfeitResult(tx_hex: ‘str’, txid: ‘str’, tx_size: ‘int’, fee_sats: ‘int’, output_photons: ‘int’)
- __init__(tx_hex, txid, tx_size, fee_sats, output_photons)¶
- class pyrxd.gravity.GravityMakerSession[source]¶
Bases:
objectManage the full lifecycle of a Gravity BTC↔RXD atomic swap offer.
This class handles the Maker’s side of the swap:
Build and broadcast the MakerOffer tx (
create_offer).Poll for the Taker’s claim (
wait_for_claim).Broadcast a cancel tx if the Taker never claims (
cancel_offer).Query current state (
check_status).
- Parameters:
rxd_client – Connected
ElectrumXClientfor Radiant chain operations (broadcast, query UTXOs).btc_source – A
BtcDataSource— used only by subclasses / extensions that need BTC confirmation data. May beNonefor pure Radiant operations.maker_priv – Maker’s secp256k1 private key wrapped in
PrivateKeyMaterial.poll_interval_seconds – Seconds between UTXO polls in
wait_for_claim. Default 30.
Examples
Typical Maker flow:
async with ElectrumXClient(["wss://electrumx.example.com"]) as rxd: session = GravityMakerSession(rxd_client=rxd, maker_priv=priv) params = GravityOfferParams( offer=offer, funding_txid="...", funding_vout=0, funding_photons=5_100_000, fee_sats=100_000, ) active = await session.create_offer(params) claim_txid = await session.wait_for_claim(active, timeout_seconds=3600) if claim_txid is None: cancel_txid = await session.cancel_offer(active)
- __init__(rxd_client, maker_priv, btc_source=None, poll_interval_seconds=30)[source]¶
- Parameters:
rxd_client (ElectrumXClient)
maker_priv (PrivateKeyMaterial)
btc_source (BtcDataSource | None)
poll_interval_seconds (int)
- Return type:
None
- async cancel_offer(offer, fee_sats=1000, maker_address='')[source]¶
Broadcast the cancel (MakerOffer.cancel()) transaction.
Reclaims the MakerOffer UTXO before the claim deadline using
build_cancel_tx. This is only valid if the Taker has NOT yet claimed the UTXO.- Parameters:
offer (ActiveOffer) – The
ActiveOfferto cancel.fee_sats (int) – Miner fee in photons for the cancel tx. Default 1000.
maker_address (str) – Maker’s Radiant P2PKH address to receive the reclaimed photons. Required — must be a valid Radiant address.
- Returns:
The cancel tx’s txid.
- Return type:
- Raises:
ValidationError – If
maker_addressis empty or the offer redeem is invalid.NetworkError – On broadcast failure.
- async check_status(offer)[source]¶
Return the current status of the offer UTXO.
Queries the Radiant ElectrumX server for the MakerOffer P2SH UTXO.
Returns one of:
"open"— UTXO is still unspent (offer not yet claimed)."claimed"— UTXO no longer in unspent set (Taker has claimed)."expired"— claim_deadline has passed and UTXO is unspent(Maker can now forfeit).
"unknown"— UTXO not found and not yet past deadline(may be unconfirmed or already finalized/cancelled).
- Parameters:
offer (ActiveOffer) – The
ActiveOfferto check.- Returns:
One of
"open","claimed","expired","unknown".- Return type:
- Raises:
NetworkError – On ElectrumX query failure.
- async create_offer(offer_params)[source]¶
Build and broadcast the MakerOffer funding tx.
The offer UTXO is a P2SH output locked to
offer_params.offer’s MakerOffer covenant. Once broadcast, the Taker can claim it by spending it withbuild_claim_tx.- Parameters:
offer_params (GravityOfferParams) – Funding-UTXO details and the
GravityOffercovenant.- Returns:
Populated with the resulting txid and UTXO details.
- Return type:
- Raises:
ValidationError – On any parameter format or covenant validation error.
NetworkError – On broadcast failure.
- async wait_for_claim(offer, timeout_seconds=3600)[source]¶
Poll for the Taker’s claim transaction.
Polls
get_utxos()on the MakerOffer P2SH script hash. When the UTXO disappears from the unspent set the Taker has claimed it.This method cannot directly return the claim txid — ElectrumX’s
listunspentAPI only reports which UTXOs are currently unspent. Once the offer UTXO is spent (claimed), we return the offer’s txid as a sentinel so the caller knows which offer was claimed. Callers that need the actual claim txid should fetch the spending tx separately (e.g. viaget_transactionon the address history).- Parameters:
offer (ActiveOffer) – The
ActiveOfferreturned bycreate_offer.timeout_seconds (int) – Maximum seconds to wait. Returns
Noneon timeout.
- Returns:
The offer txid (as a claimed-sentinel) on success, or
Noneon timeout.- Return type:
str or None
- class pyrxd.gravity.GravityOffer[source]¶
Bases:
objectAll parameters a Maker commits into a MakerOffer covenant.
Mirrors
CovenantParamsinpyrxd.spv.proofbut 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)¶
- Parameters:
- Return type:
None
- validate_deadline_from_now(accept_short_deadline=False)[source]¶
Check that
claim_deadlineis at leastMIN_DEADLINE_FROM_NOW_HOURSfrom now.Raises
ValidationErrorunless accept_short_deadline isTrue(audit 04-S1 guard: Taker needs time to confirm BTC + build SPV proof + finalize on Radiant).- Parameters:
accept_short_deadline (bool)
- Return type:
None
- class pyrxd.gravity.GravityOfferParams[source]¶
Bases:
objectParameters required to create a new Gravity MakerOffer.
These are the funding-UTXO details for the Maker’s side. The
GravityOfferitself (covenant bytecode, BTC-side params, etc.) is built externally (e.g. viabuild_gravity_offer) and passed asoffer.- offer¶
Fully populated
GravityOfferwithoffer_redeem_hexset.
- change_address¶
Optional Radiant P2PKH address for change output. See
build_maker_offer_txfor semantics.- Type:
str | None
- __init__(offer, funding_txid, funding_vout, funding_photons, fee_sats, change_address=None)¶
- offer: GravityOffer¶
- class pyrxd.gravity.GravityTrade[source]¶
Bases:
objectOrchestrate a complete Gravity BTC↔RXD atomic swap.
- Parameters:
radiant_network – Connected
ElectrumXClientfor Radiant chain operations (broadcast, fetch tx/block).bitcoin_source – A
BtcDataSourcefor Bitcoin chain data (tx fetch, Merkle proof, block headers).config – Optional
TradeConfig. Uses defaults if not provided.
Examples
Typical Taker flow:
async with ElectrumXClient(["wss://electrumx.example.com"]) as rxd: trade = GravityTrade(radiant_network=rxd, bitcoin_source=btc_src) claim = await trade.claim( offer=offer, offer_txid="...", offer_vout=0, offer_photons=10_000_000, fee_sats=1000, taker_privkey=privkey, ) btc_txid = "..." # broadcast BTC payment externally status = await trade.wait_confirmations(btc_txid) result = await trade.finalize( btc_txid=btc_txid, offer=offer, claimed_txid=claim.txid, claimed_vout=0, claimed_photons=claim.output_photons, taker_address="...", fee_sats=1000, )
- __init__(*, radiant_network, bitcoin_source, config=None)[source]¶
- Parameters:
radiant_network (ElectrumXClient)
bitcoin_source (BtcDataSource)
config (TradeConfig | None)
- Return type:
None
- async claim(offer, offer_txid, offer_vout, offer_photons, fee_sats, taker_privkey)[source]¶
Spend the MakerOffer UTXO, creating a MakerClaimed UTXO.
Broadcasts the claim transaction to the Radiant network and returns a
ClaimResult.The claim transaction requires Taker’s signature (audit 04-S3).
build_claim_txindependently verifies the code hash before signing (audit 05-F-13).- Parameters:
offer (GravityOffer) – The
GravityOfferposted by the Maker.offer_txid (str) – Radiant txid of the MakerOffer funding output.
offer_vout (int) – Output index of the MakerOffer UTXO.
offer_photons (int) – Value of the MakerOffer UTXO in photons.
fee_sats (int) – Radiant miner fee in photons.
taker_privkey (PrivateKeyMaterial) – Taker’s secp256k1 private key.
- Return type:
- async finalize(btc_txid, offer, claimed_txid, claimed_vout, claimed_photons, taker_address, fee_sats, btc_tx_height=None)[source]¶
Fetch the BTC SPV proof, verify it, and broadcast the finalize tx.
This method always runs the full
SpvProofBuilderverifier chain — there is no way to bypass verification at this level.- Parameters:
btc_txid (str) – Bitcoin transaction ID of the Taker’s BTC payment.
offer (GravityOffer) – The
GravityOfferoriginally posted by the Maker. Used to constructCovenantParamsfor SPV proof verification.claimed_txid (str) – Radiant txid of the MakerClaimed UTXO (output of
claim()).claimed_vout (int) – Output index of the MakerClaimed UTXO.
claimed_photons (int) – Value of the MakerClaimed UTXO in photons.
taker_address (str) – Taker’s Radiant P2PKH address to receive the photons.
fee_sats (int) – Radiant miner fee in photons.
btc_tx_height (int | None) – Optional: Bitcoin block height where btc_txid was confirmed. If not provided, the orchestrator will determine it automatically.
- Raises:
SpvVerificationError – If any SPV verifier rejects the proof.
NetworkError – On any network failure fetching BTC data.
ValidationError – On any parameter format error.
- Return type:
- async wait_confirmations(btc_txid, min_confirmations=None)[source]¶
Poll Bitcoin until btc_txid reaches the required confirmations.
- Parameters:
- Returns:
Always has
confirmed=Trueon return (raises on timeout).- Return type:
- Raises:
NetworkError – If polling exceeds
config.max_poll_attempts.ValidationError – If btc_txid is not a valid 64-char hex string.
- class pyrxd.gravity.MakerOfferResult[source]¶
Bases:
objectOutput of
build_maker_offer_tx()— the MakerOffer funding tx.- __init__(tx_hex, txid, tx_size, offer_p2sh, fee_sats, output_photons)¶
- class pyrxd.gravity.TradeConfig[source]¶
Bases:
objectTunable parameters for GravityTrade.
- max_poll_attempts¶
Maximum number of polls before
wait_confirmationsraisesNetworkError. Default 120 (= 2 hours at 60s).- Type:
- accept_short_deadline¶
If
True, suppress the 24h deadline guard (audit 04-S1). Only for testing — do NOT set in production.- Type:
- 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:
- __init__(min_btc_confirmations=6, poll_interval_seconds=60, max_poll_attempts=120, accept_short_deadline=False, deadline_warning_seconds=7200)¶
- 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
claimedRedeemHexmatchesexpectedClaimedCodeHashbefore 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:
- 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_proofmust be a fully-verifiedSpvProofproduced bySpvProofBuilder.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
takerRadiantPkhstate.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 thefinalize()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
totalPhotonsInOutputfloor — baked in at offer creation time. The finalize tx is rejected on-chain ifoutput[0].value < totalPhotonsInOutput, so we validate here before burning relay fees. Passoffer.photons_offeredwhen calling fromGravityTrade. Defaults to 0 (no floor check) for callers that have already verified externally.header_slots (int | None)
branch_slots (int | None)
- Return type:
- pyrxd.gravity.build_forfeit_tx(offer, funding_txid, funding_vout, funding_photons, maker_address, fee_sats)[source]¶
Build the Radiant
forfeit()tx: Maker reclaims afterclaimDeadline.Can only be built once
offer.claim_deadlinehas passed (i.e. the current wall-clock time is >=claim_deadline).Sets
nLockTime = claim_deadlineforOP_CHECKLOCKTIMEVERIFY. Sets input sequence to0xFFFFFFFE(<0xFFFFFFFF— required for CLTV to be evaluated).scriptSig layout:
OP_1 <claimed redeem script>
OP_1(selector 1) selects theforfeit()function.- Parameters:
offer (GravityOffer) –
GravityOfferwhoseclaim_deadlinehas 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:
- 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')[source]¶
Build a
GravityOfferwith real covenant redeem scripts generated from the bundled artifacts.This is the top-level entry point for Maker-side offer construction. Internally it:
Validates the claim deadline (S1 guard).
Loads the MakerClaimed covenant artifact and substitutes code-section params.
Computes
expectedClaimedCodeHash = hash256(P2SH_scriptPubKey)from the substituted redeem script.Loads the MakerOffer artifact and substitutes its params (including the code hash from step 3).
Returns a
GravityOfferwith 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.
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:
- 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
hashOutputHashesextension. 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
GravityOfferwithoffer_redeem_hexset.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_satsphotons.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 fullfunding_photons - fee_satsis locked in the P2SH, so surplus aboveoffer.photons_offeredstays with the covenant to fund the later claim/finalize tx fees. When set (two-output): the P2SH receives exactlyoffer.photons_offeredand the remainder goes to a P2PKH output atchange_address. Use the two-output form only whenoffer.photons_offeredalready includes a buffer for downstream claim/finalize fees — otherwise the covenant will reject those txs.
- Return type:
- pyrxd.gravity.compute_p2sh_code_hash(redeem_script)[source]¶
Compute expectedClaimedCodeHash:
hash256of the P2SH scriptPubKey.This is what MakerOffer checks on-chain:
hash256(tx.outputs[0].codeScript) == expectedClaimedCodeHash
For P2SH outputs the
codeScriptis the 23-byteOP_HASH160 <hash> OP_EQUALscriptPubKey.Audit 05-F-13 fix: caller passes the claimed redeem script; we derive the hash independently rather than trusting a caller-supplied value.
- pyrxd.gravity.validate_claim_deadline(claim_deadline, *, min_future_seconds=86400, bypass=False)[source]¶
Raise
ValidationErrorifclaim_deadlineis not at leastmin_future_secondsfrom 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).