SwapChan
Never holds your keys · your wallet signs
blocktools CLI

Cross-chain swap: send a UTXO coin into THORChain (OP_RETURN memo)

Swap a UTXO coin (DOGE, LTC, BCH, BTC) for an asset on another chain — RUNE, ETH, another UTXO coin — by paying the coin to a THORChain vault with a…

Swap a UTXO coin (DOGE, LTC, BCH, BTC) for an asset on another chain — RUNE, ETH, another UTXO coin — by paying the coin to a THORChain vault with a swap memo in an OP_RETURN output. One transaction, in THORChain's inbound order: VOUT0 = vault, VOUT1 = change, last output OP_RETURN <memo> (dev.thorchain.org, "Sending Transactions"). The shipped tools build it (tx-build --memo/--op-return), check its THORChain shape (tx-envelope verify_thor_send), and sign it behind the typed-code confirmation (tx-sign, which shows the memo on the terminal). A THORChain node (or any quote service) gives you the vault and memo; it is a cross-chain swap, so the destination asset arrives on its own chain.

1. Quote → vault, memo

Ask a THORChain node for the inbound of a swap from your coin to the destination asset:

rpc NODE '/thorchain/quote/swap?from_asset=DOGE.DOGE&to_asset=THOR.RUNE&amount=AMOUNT_1E8&destination=thor1...'

It returns inbound_address (the vault), memo, and the recommended expiry/gas_rate. For a UTXO inbound the memo is a short swap string such as =:r:thor174jsu5agtgztcmcly96ac9mrtf5n2k98dhmunu (=:ASSET:DEST[:LIM][:AFFILIATE:FEE]); confirm its destination and asset before you send.

2. Build the inbound (vault + change + OP_RETURN memo)

tx-build --memo STRING (or --op-return HEX for raw bytes) adds the zero-value OP_RETURN as the last output and pays its vbytes out of coin selection. --send SATS --change ADDR pays SATS to the vault and the remainder (less fee) back to you; --sweep sends the whole input to the vault. The memo is refused above the chain's relay limit (BCH 220 bytes, otherwise 80). Verify the build with tx-envelope verify_thor_send (exact [vault, optional change, OP_RETURN memo] shape, non-RBF, proven input values, fee cap) and with the shared tx-verify policy. The single zero-value OP_RETURN is accepted only when you pass --expect-memo MEMO with the exact payload; without it, or with a memo that differs by one byte, it is an unexpected_output:

tx-build --chain doge --utxos /in/u-doge.json --to D5kTEGxmas71USKAcAaJptKjq8NJc6EK8C --send 100000000 --change DFpN6QqFfUm3gKNaxN6tNcab1FArL9cZLE --fee-rate 1000 --memo '=:r:thor174jsu5agtgztcmcly96ac9mrtf5n2k98dhmunu' | tx-verify --chain doge --expect-to D5kTEGxmas71USKAcAaJptKjq8NJc6EK8C --change DFpN6QqFfUm3gKNaxN6tNcab1FArL9cZLE --expect-memo '=:r:thor174jsu5agtgztcmcly96ac9mrtf5n2k98dhmunu'

tx-build --chain doge --utxos utxos.json --to VAULT --send AMOUNT --change YOUR_ADDR --fee-rate RATE --memo 'MEMO' > inbound.json
tx-envelope --json '{"op":"verify_thor_send","chain":"doge","tx":"'"$(jq -r .psbt inbound.json)"'","vault":"VAULT","memo":"MEMO","amount":"AMOUNT","change":"YOUR_ADDR","taker":"YOUR_ADDR"}'

3. Sign (the memo is shown) and broadcast

tx-sign requires --expect-to VAULT for the real destination and --expect-memo MEMO for the single OP_RETURN (exact match, else refused before signing); the memo is also printed on the /dev/tty summary (UTF-8 if printable, else hex) so you confirm it before typing the code. A second OP_RETURN, or an OP_RETURN carrying value, is still refused.

tx-sign --tx inbound.json --chain doge --expect-to VAULT --change YOUR_ADDR --expect-memo 'MEMO' < key   # shows the memo; type the code
tx-broadcast --chain doge --url NODE < inbound.signed

Track the cross-chain leg at /thorchain/tx/status/<inbound-txid>; the destination asset arrives on its own chain, so your UTXO chain shows only the coin leaving to the vault (that is expected, not a failure).

Measured on mainnet

A DOGE→RUNE swap on 2026-10-27 used vault DF5ndBX4Fd6yUcPvAs9SkbH5r4VaZE6huG and memo =:r:thor174jsu5agtgztcmcly96ac9mrtf5n2k98dhmunu; the DOGE inbound was 6d0a0978df7782ce418e777e435406c7c2b9bab88225cd0c543894ed8c80dfdb.

Tests

scripts/e2e.d/thorchain-utxo.sh drives the shipped path node-free on a synthetic DOGE UTXO: tx-build --memo → tx-envelope verify_thor_send (ok) → tx-verify (ok) → tx-sign (pty, public key 1, memo shown, --expect-memo) → tx-decode confirms the OP_RETURN, and proves two OP_RETURNs or a non-zero-value OP_RETURN are still refused, as is the memo with no or a one-byte-different --expect-memo. scripts/e2e.d/memo-regtest.sh runs the same inbound against the real pinned LTC/DOGE/BCH regtest nodes. UNTESTED in CI: the live THORChain quote and the cross-chain settlement.

See thorchain-swap-evm.md for the reverse direction (an EVM token into a swap) and thorchain-thorname.md for affiliate names.