pyrxd.swap — Same-chain partial-transaction swaps

Same-chain partial-transaction swaps for RXD and Glyph FTs.

A high-level, guard-railed offer/accept API over SIGHASH_SINGLE | ANYONECANPAY signature-level atomicity — the “maker signs one input committing to one output, taker completes and broadcasts” pattern.

Quick start:

from pyrxd.swap import create_offer, accept_offer, Asset, FundingInput

# Maker: give 1000 RXD-photons, want 50 units of FT `ref`.
offer = create_offer(
    give_source_tx=maker_utxo_source_tx,
    give_vout=0,
    maker_key=maker_key,
    receive=Asset(kind="ft", amount=50, ref=ft_ref),
    maker_receive_pkh=maker_pkh,
)
payload = offer.to_dict()          # send over any transport

# Taker: verify + complete + sign.
tx = accept_offer(
    SwapOffer.from_dict(payload),
    funding=[FundingInput(source_tx=taker_ft_source, vout=0, key=taker_key)],
    taker_receive_pkh=taker_pkh,
    taker_change_pkh=taker_pkh,
    fee=500,
)
raw = tx.serialize().hex()         # broadcast

This is distinct from pyrxd.gravity, which does cross-chain atomic swaps gated by SPV proofs. Use this for same-chain RXD/token trades; use Gravity when the two assets live on different chains.

The core (create_offer / accept_offer) is pure — no network. Use the pyrxd.swap.resolve helpers to fetch the transactions an offer references.

class pyrxd.swap.Asset[source]

Bases: object

One side of a trade: plain RXD, or a Glyph fungible token.

amount is in photons. For an FT this is also the token-unit count (Radiant convention: 1 photon = 1 FT unit). ref is the FT’s genesis/commit outpoint (the permanent token identity) and is required for — and only for — kind == "ft".

__init__(kind, amount, ref=None)
Parameters:
Return type:

None

classmethod from_dict(d)[source]
Parameters:

d (dict)

Return type:

Asset

ref: GlyphRef | None = None
to_dict()[source]
Return type:

dict

kind: Literal['rxd', 'ft']
amount: int
class pyrxd.swap.FundingInput[source]

Bases: object

A taker-owned UTXO used to fund the maker’s receive + fee (and/or to pay an FT the maker wants).

source_tx is the taker’s own previous transaction, so its value/script are trusted (the taker controls it). key signs it.

__init__(source_tx, vout, key)
Parameters:
Return type:

None

source_tx: Transaction
vout: int
key: PrivateKey
class pyrxd.swap.SwapOffer[source]

Bases: object

A maker’s signed partial transaction plus everything a taker needs to verify it.

Transport-agnostic. partial_tx_hex holds the maker’s input (signed SINGLE|ANYONECANPAY) and output[0] (what the maker wants to receive). give_source_tx_hex is the full previous transaction that funds the maker’s input, so the taker can read the maker’s real given-asset value/script from the chain rather than trusting the declared terms — and confirm it hashes to the input’s outpoint.

__init__(partial_tx_hex, give_source_tx_hex, give_vout, terms)
Parameters:
Return type:

None

classmethod from_dict(d)[source]
Parameters:

d (dict)

Return type:

SwapOffer

to_dict()[source]
Return type:

dict

partial_tx_hex: str
give_source_tx_hex: str
give_vout: int
terms: SwapTerms
class pyrxd.swap.SwapTerms[source]

Bases: object

The trade as the maker states it: maker gives give, receives receive.

From the taker’s seat this reads in reverse — the taker receives give and pays receive. The terms are a human-readable cross-check; the maker’s signature on the partial tx is what actually enforces them (see pyrxd.swap.partial.accept_offer()).

__init__(give, receive)
Parameters:
Return type:

None

classmethod from_dict(d)[source]
Parameters:

d (dict)

Return type:

SwapTerms

to_dict()[source]
Return type:

dict

give: Asset
receive: Asset
pyrxd.swap.accept_offer(offer, *, funding, taker_receive_pkh, taker_change_pkh, fee)[source]

Complete and sign a maker’s offer, returning a broadcast-ready transaction.

Safety, by construction:

  • The maker’s given asset is read from offer.give_source_tx_hex (verified to hash to the maker input’s outpoint) — never from the declared terms — and reconciled against offer.terms.give.

  • The maker’s receive output (output[0]) is read from the partial tx and reconciled against offer.terms.receive.

  • The maker’s signature is re-verified both before and after the taker completes the transaction, so tampered terms are rejected.

  • Token conservation is enforced per FT ref; RXD change goes to the taker. The taker receives the maker’s given asset in output[1].

fee is the absolute fee in photons; the taker funds it.

Parameters:
Return type:

Transaction

pyrxd.swap.create_offer(*, give_source_tx, give_vout, maker_key, receive, maker_receive_pkh)[source]

Build a maker’s signed partial-swap offer.

The maker offers to spend give_source_tx.outputs[give_vout] (the given asset, owned by maker_key) in exchange for receive paid to maker_receive_pkh in output[0]. The given input is signed SINGLE|ANYONECANPAY so any taker can complete the swap.

The whole given UTXO is spent (its full value flows to the taker); pre-split the UTXO beforehand to sell a partial amount.

Parameters:
Return type:

SwapOffer

async pyrxd.swap.fetch_funding_input(client, *, txid, vout, key)[source]

Resolve one of the taker’s UTXOs into a FundingInput for accept_offer.

Parameters:
Return type:

FundingInput

async pyrxd.swap.fetch_transaction(client, txid)[source]

Fetch and parse a transaction, verifying it actually hashes to txid.

The server-honesty check (computed txid == requested txid) means a hostile or buggy server cannot substitute a different transaction.

Parameters:
Return type:

Transaction