Gravity FT↔BTC — first real cross-chain swap (2026-05-21)¶
A live, end-to-end Glyph fungible-token ↔ Bitcoin atomic swap, settled on Radiant mainnet against a real Bitcoin mainnet payment verified by SPV.
What happened¶
A maker offered 1,000 units of a Glyph FT (GSWAP2) for 10,000 sats of BTC. A taker paid the BTC on Bitcoin mainnet; the Radiant covenant verified that payment via an on-chain SPV proof (real BTC headers + Merkle inclusion) and released the FT to the taker. No custodian, no bridge, no trusted relayer — the covenant alone gates the release on proof of the Bitcoin payment.
Verifiable transactions¶
Radiant (Glyph FT side):
FT genesis (GSWAP2, 1,000 units):
3c0cf043c7d760fe9f821feeab1a7fae205f2aab8e9a6268e7d0125997b8b4ea:0(revealeacc7c4f201fa6c29901f9f28342ff8521440fb801bd1be6e738c3a47a3d2859)FT funded into the swap covenant:
9dab8c33199929447d5754e86909e420dc0b8d98d6f68065652c4b66ce3fb110Settlement (finalize, SPV-gated):
04a2f1bfeacaf703b1dcacc59ef3d097d73f973cb12de0265c8133a6001bb789— releases the 1,000 FT units to the taker.
Bitcoin (payment side):
Payment to the maker (10,000 sats, P2WPKH):
d79f30dd7722c988b9debb2df4ff0fb32c3e8e61f29d395da0c3860b40d75294confirmed in block 950,453.Maker receive address:
bc1qdwz0n8l72x5eqzakkqsx42p7pdmepzyrgumwzk
What the covenant verified on-chain (Radiant consensus)¶
Chain-identity anchor: header h1’s prevHash == a committed real mainnet block (950,452).
12 real Bitcoin block headers (950,453–950,464), each with valid proof-of-work and chain-linking.
Merkle inclusion of the payment tx in block 950,453 (depth-13 tree, sentinel-padded to the covenant’s 20-level capacity).
The payment output pays the maker’s committed address for ≥ the required satoshis.
FT conservation + the swap hardening (single ref, exact amount, single output) + a hash-compare pinning the FT to the taker’s address.
Honest scope (what this does and does not show)¶
Real, not synthetic. Real BTC moved on Bitcoin mainnet; the proof uses real headers/Merkle, verified by Radiant consensus (
testmempoolaccept+ broadcast).Taker payment shape (current limitation). This covenant variant requires the taker’s BTC payment to be a single-input segwit tx (the conforming payment was built with pyrxd’s
build_payment_tx). An any-wallet variant (multi-input, change anywhere) is designed, prototyped, and integrated (compiles + passes static guards) but is not yet proven on-chain or audited — see the any-wallet design note. A production “pay from any wallet” claim depends on that work + an external audit of the BTC-tx parser.Not yet audited. This is spike/validation work. External audit of the covenant — especially the SPV parsing path — is a hard gate before any production/mainnet-product claim.
Parameters that matter (operational lessons)¶
The covenant’s
btcChainAnchormust be set just before the BTC payment, with a wide header window (N), or the payment can land outside the provable window. (Set anchor = payment_block − 1 after the payment confirms.)The covenant’s Merkle depth (M) must exceed the real block’s tree depth (mainnet blocks routinely exceed depth 12). Use the sentinel M=20 variant.
The wide-window covenant is ~10 KB, so funding it costs ~1 RXD — a real per-swap cost to weigh in production parameter choice.
Any-wallet variant — multi-input BTC payment PROVEN on mainnet (2026-05-22)¶
The single-input limitation above is now lifted by the any-wallet covenant (on-chain varint input-skip + output-scan), proven end-to-end on mainnet with a real multi-input Bitcoin payment — the exact tx shape the single-input covenant rejects.
Positive (settled):
FT: GSWAPAW (1,000 units, genesis
537c86b69b039d87f2f4023806948d1fe01f945955289296087f0ee3b03e7858:0)Multi-input BTC payment (2 inputs, output[0]=10,000 sats to the maker, output[1]=change):
0ae8365496f1eb20b4e1c82f115326b85133099854f1aa3839de2087a141dc6a(block 950,541).Any-wallet covenant (N=12 headers, M=20 sentinel merkle, varint parser), anchor=block 950,540, funded
922f080f8f09515870ce6fc78e6122ae4e6b52b6e5ac2c7287072e047c21035e.Settlement (finalize over the multi-input proof):
53ee763148f85c843f82b628706480395028522701e408ef4722a61ca5ad4616— the covenant parsed a 2-input BTC tx, scanned its outputs, found the payment, and released the FT. A taker can pay from a normal multi-input wallet.
Negative (rejected on-chain): a real, in-block, Merkle-provable multi-input
tx that does NOT pay the maker, submitted as the proof, was rejected with
mandatory-script-verify-flag-failed (OP_VERIFY) — the parser’s require(found)
fails because the output-scan finds no output paying btcReceiveHash. So the
parser verifies the payment is actually present, not merely that the tx parses.
(validate_anywallet_parse.py additionally covers wrong-hash and
insufficient-value rejection in the offset-logic mirror.)
Still required before production: generalize the output-type match to all
btcReceiveType (the current scan matches P2WPKH only), and an external
audit of the BTC-tx parser (the most security-critical covenant code) — the
hard gate. The any-wallet covenant is ~10.4 KB, so funding it costs ~1.05 RXD;
this size/fee is the cost of on-chain wallet-agnostic payment parsing.
Both-chain artifacts (any-wallet swap): Bitcoin payment 0ae83654… (block
950,541); Radiant settlement 53ee7631…. User BTC swept back (ce90a7f9…).
NFT↔BTC variant — first NFT swap settled on mainnet (2026-05-23)¶
The covenant design extends from fungible tokens to one-of-one Glyph NFTs. An NFT was minted, locked into an NFT swap covenant, and released to a taker against a real Bitcoin payment verified by SPV — then the forfeit (reclaim) path and a full set of negatives were proven.
Why NFT ≠ FT (and why it’s actually simpler)¶
An FT holder is welded to its code-script (the codeScriptHashValueSum
epilogue) — it cannot be moved into a foreign covenant; that was the original
FT blocker. An NFT is welded only to its singleton ref
(OP_PUSHINPUTREFSINGLETON, 0xd8), so it can be held directly in a
covenant. The funded UTXO is the covenant bytecode itself, carrying
d8<ref> inside its body.
The dominant security fact (verified from Radiant-Core source,
validation.h:919-968): consensus only enforces singleton-outputs ⊆
singleton-inputs — it permits burning the NFT (zero output copies) and
never requires it to land on any particular output. So “exactly one output, to
the right destination” is enforced solely by the covenant body
(outputs.length==1 + refOutputCount(ref)==1 + a value pin +
hash-compare). There is no consensus backstop. For an irreversible one-of-one,
the covenant is the sole guarantor of conservation — strictly more load-bearing
than FT. (An earlier draft wrongly claimed consensus requires the singleton on
exactly one output; a security-focused divergent review caught it.)
Verifiable transactions¶
Radiant (NFT side):
NFT genesis (commit):
ff5c20f6c4445584a261764eefdeada0228bda632d704367c4b04659c58ee940:0(reveal217fca8405e4c925b79121741ef65fb328aa87b7762d9cbc3abeb4f9169fd263)NFT funded into the swap covenant:
e10798b3c10b85c6f8c6b18fcd647481ad5b5abb333e2f224d2cea1708bde09f(testmempoolacceptthen broadcast — the first on-chain proof that an NFT singleton can be held in a covenant and its conservation accepted by consensus)Settlement (finalize, SPV-gated):
cb3f6d9cc0df2c6b179a26088a26b3aea6929ef0b2e72ab1a9166507255538cc— releases the NFT to the taker.
Bitcoin (payment side):
Payment to the maker (11,000 sats, P2WPKH):
092b331d0e352d1bad80c9cc1776d038bf29cd4a77809541d78b4a116249b388, confirmed in block 950,763.Covenant
btcChainAnchor= block 950,762 (payment block − 1, set just-in-time per the operational lessons).
What the covenant verified on-chain¶
Real-headers SPV proof: anchor (h1.prev == block 950,762), 12 real BTC headers (950,763–950,774) with valid PoW + chain-linking, Merkle inclusion of the payment (real depth 13, sentinel-padded to the covenant’s M=20 capacity — the depth that broke an earlier M=12 attempt).
The any-wallet BTC-tx parser (the payment was a 1-input / 2-output P2WPKH tx).
NFT singleton conservation: the ref carried from the covenant input to the taker NFT output.
NFT hardening:
outputs.length==1,refOutputCount(ref)==1,outputs[0].value == nftCarrierValue, and a hash-compare pinning the NFT to the taker’s standard 63-byte NFT script.
Forfeit + negatives (a second NFT)¶
A second NFT (genesis c6e0e6d9…:0) was minted and locked into a covenant with
a past deadline.
Forfeit success (CLTV reclaim → maker):
60d7cedd58720a50befc3c652b7760e95296b0942fe7c04f4e8f3e353fd1ca12.Five negatives — all rejected by consensus (
testmempoolacceptallowed=false), each for the precise reason:Case
Rejection
burn (output not NFT-shaped)
OP_NUMEQUALVERIFY—refOutputCount(ref)==1(covenant-only guard)clone (two NFT outputs)
consensus
invalid-transaction-reference-operations(singleton can’t duplicate — consensus + covenant)wrong destination
false top stack —
hash256(output[0]) != expectedMakerNftHash(covenant-only)wrong carrier value
OP_NUMEQUALVERIFY—outputs[0].value != nftCarrierValuepre-deadline reclaim
Locktime requirement not satisfied (CLTV)
The burn and wrong-destination cases are caught only by the covenant body — empirical confirmation that the covenant-only conservation holds with no consensus backstop.
Honest scope (NFT)¶
Real, not synthetic. Real BTC moved on Bitcoin mainnet; the SPV proof used real headers/Merkle, verified by Radiant consensus.
Proven on-chain: the NFT prologue + singleton-conservation interaction, the finalize and forfeit paths, and the covenant-only negatives.
Not yet audited. NFT irreversibility (no fungible make-whole) raises the bar: external audit of the SPV/BTC-tx parser is a hard gate before any production/mainnet-product claim, weightier than for FT because conservation is covenant-only.
Incident: weak test key — real BTC lost (operator error, not the covenant)¶
During setup the maker/taker keys were hand-generated with
PrivateKey(b'\x03'*31 + bytes([secrets.randbelow(255)+1])) — only 254
possible private keys (the maker BTC key was 0303…03ee). A weak-key watcher
bot brute-forced it and swept the 11,000-sat payment in the same block
(950,763) it confirmed, to bc1qfmkwac2g0e5aqv4j28jjjrf0485cs3rpp9ke7f (not
ours). The funds are unrecoverable. The theft tx’s witness pubkey matched the
maker key, confirming brute-force — not a covenant bug.
This was operator error in a throwaway command, fully orthogonal to the swap
mechanism (which performed exactly as designed). Production key generation
(pyrxd.btc_wallet.keys.generate_keypair(), CSPRNG + rejection sampling) was
never involved. Lesson recorded: never hand-write key material for any address
that will receive funds; always use the CSPRNG generator.
Taproot-HTLC atomic swap — Radiant leg proven on mainnet (2026-05-24)¶
The atomic-swap redesign (the secure replacement for the SPV-oracle swap; see
DEADLINE_RACE_PANEL_2026-05-24.md + the plan 2026-05-24-feat-gravity-taproot-htlc-atomic-swap-plan.md).
This proves the Radiant leg — the hashlock claim(preimage) release and the
tx.age CSV refund() — on mainnet for all three swap asset types. The BTC-side
Taproot HTLC module (src/pyrxd/btc_wallet/taproot.py) is BIP341-vector-validated
but the full cross-chain BTC↔RXD atomic swap (real BTC HTLC + secret-scrape)
is Phase 4 and NOT yet done.
Shared secret: H = sha256(p) = the covenant hashlock; claim(preimage) reveals
p on-chain in the scriptSig (the cross-chain reveal channel). refund() is the
maker’s tx.age >= refundCsv (BIP68) reclaim. Covenants: GravityHtlcCovenant
{Ft 222B, Nft 171B, Rxd 141B} — selector dispatch (claim=OP_0 / refund=OP_1),
OP_0 OP_OUTPUTBYTECODE OP_HASH256 destination pin.
Verifiable transactions (Radiant mainnet)¶
RXD↔BTC HTLC — 4/4 proven:
claim (correct preimage) →
428b14018342f65267a34c7a3cd26fe4eba5c97e66d6da633eb8446b992f1187wrong-preimage claim → REJECTED
OP_EQUALVERIFY(sha256(preimage)==hashlock)premature refund → REJECTED
Locktime requirement not satisfied(CSV gate)matured refund (after 2 confs) →
9f008f50a00f8e5791c761cc0dd67e69d22d21dde1fbf6a36c8cd5dc07756ec9
FT↔BTC HTLC:
FT minted (genesis
678e3907…:0, 1,000,000 units), funded into the covenant, claim (preimage) →e75f4d8dd4303bb90fd0b94c26be19f00eef24bb2bc34b39fd6369a922cacda8(FT released to taker; FT L1 ref-conservation + L2 codeScriptHashValueSum intact).
NFT↔BTC HTLC:
NFT minted (genesis
5624089236…:0), funded into the covenant, claim (preimage) →8333e80bf3d36e728fb2c3bbddfdd937d0d70d999909ecb66facba98387ff318(NFT released to taker; singleton conserved through the covenant).wrong-preimage claim → REJECTED
OP_EQUALVERIFY.
Honest scope of the FT/NFT refund¶
The FT and NFT refund routes (tx.age CSV + maker hash-compare) are present
and static-guard-verified in their compiled covenants, and are the byte-identical
CSV mechanism proven on-chain by the RXD matured refund (9f008f50…). They were
NOT separately broadcast: each would require minting a fresh 2nd asset (the first
mint is consumed by the claim covenant) to prove a mechanism that is identical to
the already-proven RXD refund. The premature-refund negative is proven (RXD). This
is a deliberate, recorded scope decision, not an omission.
What this does and does NOT show¶
Proven on mainnet: the Radiant leg of all 3 swap types — hashlock claim (with the preimage revealed on-chain) + CSV refund + the wrong-preimage and premature-refund negatives. The HTLC covenant machinery works under live consensus.
NOT done: the full cross-chain atomic swap — a real Bitcoin Taproot HTLC funded on BTC mainnet, the secret revealed on one chain and scraped to claim the other, with the timelock-ordering margin enforced (Phase 4: coordinator + state machine + watchtower).
Hard gate: external audit of the cross-chain atomicity + the parser/SPV path + the covenants — before any production/mainnet-product claim. NFT/RXD have no consensus conservation backstop (RXD especially), so the covenant body is the sole guarantor there.
Operational lessons applied: per-kB relay fee (0.10 RXD/kB on the maintainer’s node, size×rate); minimal-pushed CSV operands; multi-function selector dispatch; hash-compare output pin; CSPRNG keys only (no hand-written keys, per the prior weak-key incident).
Phase 4b — FIRST FULL CROSS-CHAIN BTC↔RXD ATOMIC SWAP (mainnet, 2026-05-24)¶
This is the milestone Phases 1–3 could not show: the two legs bound by one secret, settled end-to-end on real chains. Bitcoin mainnet Taproot HTLC ↔ Radiant mainnet RXD covenant. The preimage revealed on the BTC chain was scraped from the on-chain witness and used to claim the Radiant asset — atomicity demonstrated live.
Shared secret: H = sha256(p) = e2caff98248059dc566c7ea10a32208132fefc984d0e80880a162ef509f1d21c
(fresh CSPRNG; p = f683f50e…e3d854c2). Role: MAKER_SECRET_TAKER_LOCKS_BTC_FIRST.
Happy path — COMPLETED (verifiable on both mainnets)¶
Taker locks BTC (P2TR HTLC, claim leaf
OP_SHA256 <H> OP_EQUALVERIFY <makerX> OP_CHECKSIG, refund leaf<takerX> OP_CHECKSIGVERIFY <6> OP_CSV): funding tx30310df9375884bba726019008078731377ce18e6a75348934e769b76f28ffc3(vout 0, 2500 sats) → HTLC addrbc1p78e72e5gazcklekwtyy9j50cqv36mp43phcq9s9nu2xlsqxl84fq9vlh8d.Maker locks the RXD asset in the 141-byte HTLC covenant (amount=100000 pin, selector dispatch, hash-compare dest pin): funding
c4882a6eaecd7abacf2cb0125f7b619f2414618da63cd4f57f44f580b248451b(vout 0, 500000 photons carrier).Maker claims BTC revealing
pin the witness: BTC claim0e2ba620073b5bd08ddfa6d418912eff7705eaab947afdbe56040a833e8ef6e3(confirmed).Taker scrapes
pfrom the on-chain BTC claim witness (taproot.scrape_secret, matched bysha256(p)==Hover every witness push — fetched from the network, not local) and claims the RXD asset: RXD claimd9f8dee91ba7a1f874b4003e44898beddc4fac00ea670920990ed4589a8f67db— covenant scriptSig =20<p>00(preimage + OP_0 selector), 0.055 RXD → taker1GYMMrSqtjuybUQZZ35MwwWQa9yRM221bt. The preimage in the RXD scriptSig is BYTE-IDENTICAL to the one in the BTC claim witness: the cross-chain binding, proven.
Refund safety net (the atomicity guarantee for MUTUAL_REFUND / MAKER_STALLS)¶
The BTC CSV-refund leaf is what makes this atomic (vs the old non-atomic SPV-oracle swap):
Premature BTC refund → REJECTED
non-BIP68-final(mainnet mempool, consensus-enforced): refund-test HTLC fundingd0c32b60ebb72b9503eaa4a82dc720b1c96451d95cf45ff03a8fa3e8594ee759(1500 sats); the v2/nSequence=6 refund tx is valid but rejected before the 6-block relative timelock matures. This is the consensus gate that lets the taker reclaim BTC if the maker never claims — and forbids reclaiming early.Matured BTC refund → ACCEPTED on mainnet (with the FIXED leaf):
e29e9b310baa829370780e40e8336bcc9b689f3af42a0b0a4f411e9199a48ffd. The original refund leaf (<pk> OP_CHECKSIGVERIFY <timeout> OP_CSV OP_DROP) was BROKEN — it ended with an empty stack and every refund was rejectedStack size must be exactly one after execution. The fixed leaf (<timeout> OP_CSV OP_DROP <pk> OP_CHECKSIG) was funded into a fresh HTLC (74f4a8f7ea4be98ddb7e826345f305763ae8e2c5fe4b88816e84d220a08cdac4:0, 600 sats), the premature refund was again rejectednon-BIP68-final, and after the 6-block CSV matured (funding block 950900 → tip 950906) the refund spent the HTLC via the refund leaf (v2, nSequence=6, witness = sig + script + control-block), paying 450 sats to the taker. The BTC refund path is now proven BOTH WAYS on mainnet: premature → rejected, matured → accepted.The Radiant
tx.age/CSV refund leg is byte-identical to the already-proven RXD matured refund9f008f50a00f8e5791c761cc0dd67e69d22d21dde1fbf6a36c8cd5dc07756ec9(Phase 3).
Honest scope¶
PROVEN end-to-end on mainnet: the full cross-chain atomic swap happy path (both legs, one secret, scrape-from-chain) + the BTC premature-refund consensus rejection.
A bug found + corrected mid-run (recorded for honesty): the first covenant was built with
amount=50000000but funded with only 100000 photons →OP_OUTPUTVALUE >= 50Mmade BOTH claim and refund unsatisfiable; 100000 photons (0.001 RXD) stranded at7df1e7ef80c76710b11b8cbc977592b68948f51faef15651db4cd7d82e62e535:0. Lesson: the covenantamountpin MUST equal (or be ≤) the actual funded carrier. The v2 covenant used a matching pin and claimed cleanly. A working demo is not an audit; the amount/carrier coupling is exactly the kind of thing the external audit must cover.NOT yet shown: the matured BTC refund broadcast (pending block maturity); a watchtower (Phase 5). External audit remains the HARD GATE before any production claim.
Phase 4b (cont.) — ALL THREE asset types swapped cross-chain on mainnet + refund-leaf bug¶
Following the RXD swap above, the full cross-chain HTLC swap (real BTC Taproot HTLC ↔ Radiant covenant, preimage scraped off the BTC witness and used on Radiant) was proven end-to-end on mainnet for FT and NFT as well — reusing the FT/NFT assets minted in Phase 3 (re-funded from their prior-claim holder UTXOs into fresh HTLC covenants).
Full cross-chain swaps — 3/3 happy paths (verifiable on both chains)¶
Asset |
BTC HTLC fund |
BTC claim (reveals p) |
Radiant asset claim (uses p) |
|---|---|---|---|
RXD |
|
|
|
FT (1,000,000 units) |
|
|
|
NFT (singleton) |
|
|
|
For each: the preimage p pushed in the Radiant claim scriptSig is byte-identical
to the p revealed in that swap’s Bitcoin claim witness (independently scraped from the
network copy via taproot.scrape_secret, matched by sha256(p)==H). FT conservation
(L1 ref + L2 codeScriptHash) and NFT singleton conservation held through their covenants.
BUG FOUND + FIXED: the BTC refund leaf was unspendable¶
The matured-refund proof (the other half of atomicity) surfaced a real bug. The BTC
refund leaf was built as <refundPk> OP_CHECKSIGVERIFY <timeout> OP_CSV OP_DROP. Tracing
the stack with witness [sig]: CHECKSIGVERIFY drains the stack to empty, then OP_CSV
(verify-don’t-pop) + OP_DROP leaves it empty — so every refund spend is rejected by
Bitcoin consensus:
mempool-script-verify-flag-failed (Stack size must be exactly one after execution)
This is NOT a timelock failure — the CSV maturity gate passed (tip 950899 > the
6-block maturity of funding d0c32b60… at block 950892); the earlier non-BIP68-final
rejection had only proven the timelock gate, never that the leaf executes. The fix
(canonical BOLT-3 / Boltz ordering — timelock first, value-leaving OP_CHECKSIG last):
<timeout> OP_CSV OP_DROP <refundPk> OP_CHECKSIG # ends with exactly one truthy item
Fixed in src/pyrxd/btc_wallet/taproot.py::refund_leaf_script. This changes the
taptree → the HTLC address, so HTLCs built before the fix are refund-unspendable
(claim-only) — but the swap HTLCs above were all claimed, so no funds were lost.
Test-gap lesson: the taproot suite asserted address-derivation (BIP341 vectors) and
signature crypto, but never executed either leaf through an interpreter or broadcast —
so this stack-discipline bug was invisible. The matured-refund on-chain test is what
caught it. A fresh HTLC 74f4a8f7…a08cdac4:0 was funded with the FIXED leaf to re-prove
the matured refund on-chain.
Honest refund status (do NOT overclaim)¶
Radiant-side refund (tx.age/CSV): PROVEN on mainnet (
9f008f50…, Phase 3).BTC-side premature refund: REJECTED
non-BIP68-final(timelock gate proven).BTC-side matured refund: PROVEN on mainnet with the FIXED leaf —
e29e9b310baa829370780e40e8336bcc9b689f3af42a0b0a4f411e9199a48ffd(450 sats to the taker after the 6-block CSV). The original leaf was broken (empty-stack); the fix (<timeout> OP_CSV OP_DROP <pk> OP_CHECKSIG) settles. The BTC refund path is now proven both ways (premature rejected, matured accepted). External audit remains the hard gate before real value.
ETH↔RXD atomic swap — FIRST cross-chain swap on a SECOND counter-chain (2026-05-25)¶
The Gravity atomic-swap primitive generalized beyond Bitcoin: the first ETH↔RXD
cross-chain atomic swap, Ethereum Sepolia testnet ↔ Radiant mainnet, settled
end-to-end. Same one-secret mechanism as BTC↔RXD; the counter-chain leg is a Solidity
HTLC instead of a Taproot tapscript. The Radiant covenant was UNCHANGED (it is
counterparty-chain-agnostic: sha256(preimage)==H + tx.age CSV).
Shared secret: H = sha256(p) = 0x7c5801e95fa158046651bed20abfd6dc0bc3f3fb2f62203c81e6e8fc33dc4aab
(fresh CSPRNG; p = daa041bf…81be0db8). Role: MAKER_SECRET_TAKER_LOCKS_COUNTERCHAIN_FIRST.
Happy path — COMPLETED (verifiable on both chains)¶
Taker locks ETH FIRST — deploys + funds the native-ETH HTLC contract on Sepolia (
EthHtlc.sol: sha256-precompile hashlock, claim(preimage)/refund-after-timeout, CEI+settled, EOA-only recipients): contract0x35d07083A967c24B90873f3366BEf90dc3c46Caa, deploy tx0x5f1a8ca3817937d98ec134fd00ccf93aa6d0bc7d02a235bcaaeb6f61d3c90c85(0.001 ETH).Pre-RXD-lock gate PASSED — the taker verified the on-chain contract’s runtime logic (immutable slots masked) == the committed artifact AND the immutables read back via getters (hashlock/claimant/refundee/timeout) == the negotiated terms AND the funded balance == 0.001 ETH, before the maker locked RXD.
Maker locks the RXD asset SECOND — 141-byte RXD HTLC covenant (amount pin 5M ≤ carrier 5.5M), funded
12f93d6e4fe14983cf3a8ae17ce1144cfdc835f05b7babe93054280882965059:0(5,500,000 photons) on Radiant mainnet.Maker claims the ETH revealing
pin calldata + aClaimed(p)event: Sepolia claim0x30d06fe783054c98f25b4cb010e83e9b2d66ae22c069b4f9be802e24a0b2961e.Taker scrapes
pfrom the Sepolia claim (calldata + event-log data, matched bysha256(candidate)==Hover every 32-byte window — the C-PARSER discipline; no offset parsing) and claims the RXD asset: RXD claim3704227cadcc3b4cf9ee1cd6ceb62219a9a1cc9a5cce9b7cb52bc709c91e26c8— covenant scriptSig =20<p>00, 0.105 RXD → taker16bxKGPGq8dqpwwEGmt1F3wPHrFtkXcNPs. Thepin the RXD scriptSig is BYTE-IDENTICAL to theprevealed in the Sepolia ETH claim — the two legs welded across Ethereum and Radiant by one secret.
What this shows / honest scope¶
PROVEN end-to-end: the cross-chain atomic-swap primitive works on a SECOND counter-chain (Ethereum) with no change to the Radiant side — the same secret settles an EVM contract leg and the Radiant covenant leg. ETH was easier than BTC in the expected ways (sha256 precompile,
ptrivially read from calldata/event, no witness-scraping), and the new surface (a fund-custodying contract) carries the expected new risk.NOT yet shown: the ETH-side matured refund on-chain (the absolute-
block.timestamptimeout + the relative-RXD-CSV ordering, with theDRXD-lock-deadline term, is designed in the plan; a deliberate MUTUAL_REFUND is the next exercise) and the reverted-but-minedp-leak path (covered by the pure tests, not yet on-chain).Cross-net caveat: this was Sepolia (resettable, weak finality) ↔ Radiant mainnet. Margins for any real-value ETH swap must come from MAINNET ETH finality (≥2 epochs), not Sepolia timing — the Phase-4a margin model (
require_measured=True) enforces this.HARD GATE: external audit of
EthHtlc.sol+ the cross-chain atomicity before any real-value (mainnet ETH) use. A Sepolia proof-of-mechanism is not an audit.
Software: contracts/EthHtlc.sol (+ committed artifact), src/pyrxd/eth_wallet/
(secret/locator/keys/rpc/htlc_leg), harness eth_sepolia_swap.py. Per-leg margin-model
fix (ETH 12s vs RXD interval) landed first. Full suite 3208 green.
ETH refund — proven BOTH WAYS on Sepolia (the MUTUAL_REFUND safety net)¶
A fresh HTLC (0xE00B4d1E0597716B2380fDE648AbdC353D764d40, 0.001 ETH, short +180s
timeout) where the maker deliberately never claims — proving the taker reclaims:
Premature refund → REVERTED on-chain (status 0, contract
NotYetExpired): tx0x95058c3210d6f97b0aa9ec30167f045f21d113fd33aa9426b4547ea88324d0bd, mined at block ts 1779694200 < timeout 1779694243. Therequire(block.timestamp >= timeout)gate forbids early reclaim.Matured refund → SUCCEEDED (status 1): tx
0xe45155220ba4663e22db773fd1cbb0e6fdc4182272135dfa39fb7e17b3262c43, mined at block ts 1779694272 >= timeout; the HTLC contract balance dropped to 0 (drained to the takerrefundee). Taker-unilateral — no maker signature.
So the ETH leg’s atomicity safety net is proven: worst case is “both refund,” never one-sided loss. Combined with the happy path above, the ETH↔RXD swap is proven on both the claim and refund branches (Sepolia), mirroring the BTC↔RXD both-ways proof.
Lesson surfaced (recorded, not yet fixed): the leg broadcast the premature refund
without an eth_call preflight, so it wasted gas on a guaranteed revert. Add a preflight
eth_call (or estimate_gas, which reverts on a would-fail tx) before broadcasting
claim/refund, to fail fast off-chain instead of burning gas on-chain. Not a safety bug
(the contract correctly rejected it); a gas-efficiency + UX fix for the leg.