Skip to main content
The SDK builds every claim and refund on your side, signed with your swap key. You choose the fee and you decide when to broadcast.

Cooperative and script-path spends

Each HTLC output can be spent two ways:
  • Cooperative (key path). You and the maker co-sign a MuSig2 signature. The transaction is smaller and cheaper, and a refund does not have to wait for the timeout. It needs the maker to answer.
  • Script path. You spend alone: with the preimage for a claim, or after timeoutBlockHeight for a refund. It never depends on the maker.
constructClaim and constructRefund try the cooperative path by default. Chain-swap claims are the exception: they go through constructCooperativeClaim, which signs with the swap’s refund key against the lockup script. A chain claim that takes the script path calls constructClaim with cooperative: false.

Building a spend

Rebuild the SwapScript from the verified create response (fromSubmarine, fromReverse, fromChain) or from a restore entry (fromRestored), then construct the transaction:
The result is a signed transaction. Call hex() and txid() to persist it, and broadcast(network, bitcoinEsploraUrl?, liquidEsploraUrl?) to send it.

Fees

Set the fee explicitly with one of feeSatPerVb or feeAbsoluteSat. The reconciliation helpers also take feeLimits: { maxFeeSatPerVb, maxFeeAbsoluteSat }, which rejects an excessive fee before signing. For live estimates, use the Esplora helpers on the chain you are spending on:
TypeScript
selectEsploraFeeRate picks the largest available target at or below the one you asked for, and returns undefined rather than inventing a default. The helpers make one request per call, refuse redirects, and never retry. The maker’s own feeEstimation() and height() describe the maker’s view. For Liquid, its fee is a fixed policy value and its height can be 0 when unavailable, so do not use either to build a client-side spend.

Stateless reconciliation helpers (TypeScript)

canClaim, canRefund, claimReverseSwap, claimChainSwap, refundSubmarineSwap, and refundChainSwap decide whether a spend is possible right now and build it if so. They return either a signed transaction or { kind: "not-yet", reason }. They keep no state, run no timers, and never broadcast.
TypeScript
How they behave:
  • Refunds, mode: "auto": try a cooperative refund on a failed or expired swap before the timeout, and use the script path once the chain tip reaches timeoutBlockHeight. At that point a stale maker status cannot block the refund; statuses that show the funds were paid or claimed still do. mode: "cooperative" always requires the maker.
  • Claims need chain evidence you verified yourself: lockup: { chain, hex, txid, confirmations } from your own backend. With fewer than minConfirmations (default 1), the helper returns not-yet before revealing the preimage. allowZeroConf: true accepts an unconfirmed lockup but still requires its bytes.
  • Claim deadlines: claims stop claimSafetyMarginBlocks before the timeout (6 blocks on Bitcoin, 60 on Liquid by default).
  • Claims, mode: "auto": prefer cooperation, and if it fails, immediately build the script-path claim against the same lockup. If both fail, the ClaimFallbackFailed error carries both causes; recover through the script path urgently.

Persist first, then broadcast

Save the signed transaction’s hex, txid, chain, and network before you broadcast. A lost broadcast response does not mean the broadcast failed. To retry, rebuild the same transaction from the saved hex and send identical bytes:
TypeScript
The reconciliation helpers accept the saved spend as previous and return it unchanged before looking at new status, height, or fees. Never re-sign a claim or refund just because a broadcast response went missing, and serialize concurrent operations on the same swap. L-USDT and USDT-RGB spends need your wallet to fund or color the transaction; the guides cover each.