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
timeoutBlockHeightfor 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 theSwapScript from the verified create response (fromSubmarine, fromReverse, fromChain) or from a restore entry (fromRestored), then construct the transaction:
hex() and txid() to persist it, and broadcast(network, bitcoinEsploraUrl?, liquidEsploraUrl?) to send it.
Fees
Set the fee explicitly with one offeeSatPerVb 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
- 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 reachestimeoutBlockHeight. 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 thanminConfirmations(default 1), the helper returnsnot-yetbefore revealing the preimage.allowZeroConf: trueaccepts an unconfirmed lockup but still requires its bytes. - Claim deadlines: claims stop
claimSafetyMarginBlocksbefore 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, theClaimFallbackFailederror 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
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.