xchain

package
v0.0.0-...-ea68448 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 8, 2026 License: MIT Imports: 21 Imported by: 0

Documentation

Overview

Package xchain implements the SeqDEX cross-chain atomic-swap mechanism (Phase 5, milestone 1): a "Design A" single-secret HTLC swap between a Bitcoin-script leg (a Bitcoin/Elements parent chain) and a Sequentia-asset leg (the anchored Sequentia chain).

Design A in one line: both legs are locked to the SAME hashlock H = sha256(secret) and the SAME secret. Redeeming either leg reveals the preimage on-chain, which the counterparty then uses to redeem the other — that single shared secret is what makes the swap atomic.

The locking script is identical on both chains (it is plain Bitcoin Script, which elementsd evaluates unchanged in Elements mode):

OP_IF
    OP_SHA256 <H> OP_EQUALVERIFY <claim_pub> OP_CHECKSIG     # redeem branch
OP_ELSE
    <locktime> OP_CHECKLOCKTIMEVERIFY OP_DROP <refund_pub> OP_CHECKSIG  # refund
OP_ENDIF

paid to P2SH. The redeem (IF) branch reveals the preimage; the refund (ELSE) branch spends back to the locker after nLockTime reaches <locktime> (CLTV).

The Sequentia value-add — the whole point of this milestone — is the "anchor-shortened ordering" enforced by the Swap orchestrator (see orchestrator.go): lock the BTC leg first, then require the Sequentia leg to land in a Sequentia block whose anchorheight >= the BTC-leg's block height (paper Principle 7). Because of Bitcoin anchoring, if the BTC leg is later reorged the SEQ leg reorgs with it, so the SEQ leg needs only ~1 confirmation with NO extra reorg-protection buffer.

Index

Constants

This section is empty.

Variables

View Source
var (

	// ErrAnchorOrdering is returned by the orchestrator when the Sequentia leg
	// does NOT satisfy the anchor-shortened ordering rule (its block's
	// anchorheight is below the BTC-leg's confirmation height, or the anchor
	// status is not "ok"). Proceeding would forfeit the reorg-safety that
	// anchoring provides, so the orchestrator refuses.
	ErrAnchorOrdering = errors.New("xchain: SEQ leg violates anchor-shortened ordering (anchorheight < BTC-leg height, or anchor not ok)")

	// ErrBTCLegInvalid is returned by the maker when the taker's claimed BTC leg
	// does not match the quote (wrong H, wrong redeemScript, wrong amount/asset,
	// or it does not pay the expected HTLC P2SH). The maker MUST refuse to lock
	// its SEQ leg in this case.
	ErrBTCLegInvalid = errors.New("xchain: taker BTC leg invalid (does not match quote)")

	// ErrBTCLegUnconfirmed is returned when the taker's BTC leg is not yet
	// confirmed to the required depth. The taker must confirm it before the
	// maker will lock the SEQ leg (the BTC-leg-first ordering rule).
	ErrBTCLegUnconfirmed = errors.New("xchain: taker BTC leg not confirmed to required depth")

	// ErrSEQLegInvalid is the reverse (asset->BTC) mirror of ErrBTCLegInvalid:
	// returned when the taker's funded SEQ asset leg does not match the quote
	// (wrong H, wrong redeemScript, wrong amount/asset, or it does not pay the
	// expected HTLC P2SH). The maker MUST refuse to reveal the secret (claim the
	// SEQ leg) in this case.
	ErrSEQLegInvalid = errors.New("xchain: taker SEQ leg invalid (does not match quote)")

	// ErrSEQLegUnconfirmed is the reverse mirror of ErrBTCLegUnconfirmed: the
	// taker's SEQ asset leg is not yet confirmed to the required depth.
	ErrSEQLegUnconfirmed = errors.New("xchain: taker SEQ leg not confirmed to required depth")

	// ErrLNLegInvalid means the Lightning leg of a submarine swap does not match
	// the swap (invoice hash != H, wrong amount, or a revealed preimage that does
	// not hash to H). The maker MUST NOT proceed.
	ErrLNLegInvalid = errors.New("xchain: submarine LN leg invalid (does not match swap)")

	// ErrLNLegTimeout means a Lightning-leg wait (e.g. a hold invoice never
	// reaching "accepted") exceeded its deadline; fall back to the refund path.
	ErrLNLegTimeout = errors.New("xchain: submarine LN leg timed out")
)

Functions

func BitcoinChainParams

func BitcoinChainParams(name string) (*chaincfg.Params, error)

BitcoinChainParams selects btcd chain params for the BTC parent leg.

"regtest"            -> RegressionNetParams (bcrt HRP, 0x6f/0xc4 prefixes)
"testnet4"           -> testNet4Params (tb HRP, testnet3 prefixes)
"testnet3"/"testnet" -> TestNet3Params
"mainnet"            -> MainNetParams

regtest and testnet4 produce IDENTICAL transaction serialization (the task's "regtest == testnet4 format"); only address prefixes differ, and the maker's claim never needs to round-trip an address (it spends by outpoint).

func NewCLNLNLeg

func NewCLNLNLeg(socketPath string) *clnLNLeg

NewCLNLNLeg builds a Lightning leg backed by the CLN node whose lightning-rpc socket is at socketPath (e.g. <lightning-dir>/<network>/lightning-rpc).

func P2SHHash160

func P2SHHash160(redeemScript []byte) string

P2SHHash160 returns hex(hash160(redeemScript)); handy for cross-checking the node's decodescript output. The funding P2SH address is obtained from the node via decodescript (see client.DecodeScriptP2SH) to avoid any address prefix mismatch.

Types

type AnchorEvidence

type AnchorEvidence struct {
	BTCLegHeight     int64
	SeqBlockHash     string
	SeqBlockAnchor   int64
	AnchorStatus     string
	NodeAnchorHeight int64
	Certified        bool // the SEQ block is quorum-certified (immediately final)
	CertPresent      bool // the node reported certification (false = pre-upgrade node)
	OK               bool
}

AnchorEvidence captures what VerifySeqLegSafe checked, for proof/printing.

type AnchorStatus

type AnchorStatus struct {
	ValidateAnchor bool   `json:"validateanchor"`
	TipHeight      int64  `json:"tipheight"`
	AnchorHeight   int64  `json:"anchorheight"`
	AnchorHash     string `json:"anchorhash"`
	AnchorStatus   string `json:"anchorstatus"`
}

AnchorStatus is the subset of getanchorstatus we care about.

type BitcoinChain

type BitcoinChain struct {
	// contains filtered or unexported fields
}

BitcoinChain is the maker's view of the REAL Bitcoin parent (bitcoind regtest or testnet4). It is the Bitcoin-format analog of Chain (chain.go, which is Elements-format): it talks to a genuine bitcoind over the same JSON-RPC plumbing (RPC) but parses Bitcoin transactions — 8-byte satoshi values, no asset commitments — and derives the HTLC P2SH address itself via btcd rather than via Elements decodescript.

Only the calls the maker's BTC leg needs are implemented: read the raw funding tx + its confirmations (to VERIFY the taker's HTLC), broadcast the claim, learn the destination scriptPubKey, the height, and detect the taker's SEQ-side claim is NOT here — that stays on the SEQ (Elements) Chain.

func NewBitcoinChain

func NewBitcoinChain(rpc *RPC, wallet string, params *chaincfg.Params) *BitcoinChain

NewBitcoinChain binds a BitcoinChain to a bitcoind RPC + wallet and chain params (regtest/testnet4). The wallet is used for getnewaddress (claim destination) and, on regtest, generatetoaddress/sendtoaddress in the verification harness; HTLC verification/claim themselves are wallet-agnostic.

func (*BitcoinChain) AddressScript

func (c *BitcoinChain) AddressScript(addr string) ([]byte, error)

AddressScript decodes a Bitcoin address (under this chain's params) and returns its scriptPubKey.

func (*BitcoinChain) BlockCount

func (c *BitcoinChain) BlockCount() (int64, error)

BlockCount returns the current Bitcoin chain height.

func (*BitcoinChain) Broadcast

func (c *BitcoinChain) Broadcast(rawHex string) (string, error)

Broadcast submits a raw Bitcoin tx hex and returns its txid.

func (*BitcoinChain) Confirmations

func (c *BitcoinChain) Confirmations(txid string) (int, error)

Confirmations returns the number of confirmations of a Bitcoin tx via the wallet's gettransaction (works for wallet txs only); used by the harness.

func (*BitcoinChain) FeeRate

func (c *BitcoinChain) FeeRate() float64

FeeRate returns the configured HTLC funding fee rate (sat/vB), 0 if unset.

func (*BitcoinChain) NewDestScript

func (c *BitcoinChain) NewDestScript() ([]byte, error)

NewDestScript returns a fresh wallet address' scriptPubKey for the maker to receive its claimed BTC, derived locally from the address (no Elements validateaddress dependency). It accepts any standard Bitcoin address type the wallet hands out (P2PKH, P2SH, bech32 v0/v1).

func (*BitcoinChain) Params

func (c *BitcoinChain) Params() *chaincfg.Params

Params returns the chain params this BTC leg uses.

func (*BitcoinChain) RPC

func (c *BitcoinChain) RPC() *RPC

RPC exposes the underlying wallet-scoped client (for the harness / ad-hoc).

func (*BitcoinChain) RawTx

func (c *BitcoinChain) RawTx(txid string) (string, error)

RawTx fetches a transaction's raw (Bitcoin-serialized) hex via getrawtransaction <txid> false. Works for any tx the node has indexed (txindex) or that is in a wallet/mempool — including the taker-funded HTLC the maker did not create.

func (*BitcoinChain) RawTxAndConfirmations

func (c *BitcoinChain) RawTxAndConfirmations(txid string) (string, int, error)

RawTxAndConfirmations returns both the raw hex and the confirmation count in one verbose getrawtransaction (the maker needs both to VerifyFundedHTLC).

func (*BitcoinChain) SetFeeRate

func (c *BitcoinChain) SetFeeRate(satVb float64)

SetFeeRate sets the sat/vB fee rate used when funding HTLC legs.

func (*BitcoinChain) TxConfirmations

func (c *BitcoinChain) TxConfirmations(txid string) (int, error)

TxConfirmations returns a tx's confirmation count via the verbose getrawtransaction (Bitcoin returns "confirmations" only when the tx is in a block or watched; 0/absent => unconfirmed).

type BitcoinLeg

type BitcoinLeg struct {
	// contains filtered or unexported fields
}

BitcoinLeg builds and spends a Design-A HTLC on a REAL Bitcoin chain (regtest or testnet4) — as opposed to ElementsLeg, which only works against an Elements-mode parent. It is the maker's "real-bitcoind leg": when the parent is a genuine bitcoind, the taker funds/refunds the BTC HTLC with a real Bitcoin signer (btc.js in the browser wallet) and the maker must VERIFY and CLAIM that HTLC in Bitcoin transaction format.

The HTLC redeemScript is byte-for-byte the SAME generic Bitcoin Script the ElementsLeg uses (it comes from primitive.go's HashLock.LockScript). Only the transaction envelope differs:

  • Bitcoin values are 8-byte little-endian satoshis; there are NO asset commitments and NO explicit fee output (fee = sum(in) - sum(out)).
  • Serialization is Bitcoin wire format (wire.MsgTx), parsed/built with btcd, NOT go-elements.
  • The legacy P2SH sighash is txscript.CalcSignatureHash with SIGHASH_ALL, and the spend is a legacy (non-segwit) P2SH scriptSig: <sig> <preimage> OP_TRUE <redeemScript> (redeem / IF branch) <sig> OP_FALSE <redeemScript> (refund / ELSE branch) — the SAME unlock-item order ElementsLeg uses (the RedeemUnlockItems / RefundUnlockItems from the shared LockPrimitive), with the redeemScript pushed last.

Address/serialization parameters are selectable for regtest vs testnet4 via BitcoinChainParams (testnet4 reuses testnet3's address prefixes + "tb" HRP, which is all the maker needs — it never relies on the network magic or the genesis hash for HTLC work).

func NewBitcoinLeg

func NewBitcoinLeg(prim LockPrimitive, params *chaincfg.Params) *BitcoinLeg

NewBitcoinLeg returns a BTC-leg builder for the given hashlock primitive and chain params (use BitcoinChainParams to pick regtest/testnet4).

func (*BitcoinLeg) BuildClaimTx

func (l *BitcoinLeg) BuildClaimTx(redeemScript []byte, in BitcoinSpendInput, key *Key) (string, error)

BuildClaimTx builds a signed redeem (IF-branch) spend revealing the preimage, paying (Amount-Fee) to DestPK. This is the maker's BTC-leg claim. Returns the serialized Bitcoin tx hex for sendrawtransaction.

func (*BitcoinLeg) BuildRefundTx

func (l *BitcoinLeg) BuildRefundTx(redeemScript []byte, in BitcoinSpendInput, locktime uint32, key *Key) (string, error)

BuildRefundTx builds a signed refund (ELSE-branch) spend, valid once nLockTime reaches the CLTV locktime. The maker does NOT refund BTC in the implemented direction (the taker refunds its own BTC leg), but this is provided for symmetry with ElementsLeg.Refund and for completeness.

func (*BitcoinLeg) HTLCScript

func (l *BitcoinLeg) HTLCScript(claimPub, refundPub []byte, locktime uint32) ([]byte, error)

HTLCScript renders the redeemScript for the given pubkeys/CLTV locktime. It is identical to ElementsLeg.HTLCScript (same generic Bitcoin Script).

func (*BitcoinLeg) P2SHAddress

func (l *BitcoinLeg) P2SHAddress(redeemScript []byte) (string, error)

P2SHAddress derives the base58 P2SH address for a redeemScript using this leg's chain params. Unlike the Elements leg (which asks the node via decodescript), the maker derives the Bitcoin HTLC address itself so it never depends on the bitcoind wallet importing the script.

func (*BitcoinLeg) P2SHScriptPubKey

func (l *BitcoinLeg) P2SHScriptPubKey(redeemScript []byte) ([]byte, error)

P2SHScriptPubKey returns the scriptPubKey (OP_HASH160 <h160> OP_EQUAL) that an output funding this redeemScript's P2SH must carry. Used by VerifyFundedHTLC to locate the HTLC output by exact scriptPubKey match.

func (*BitcoinLeg) Side

func (l *BitcoinLeg) Side() Leg

Side reports which leg this builder serves (always the BTC leg).

func (*BitcoinLeg) VerifyFundedHTLC

func (l *BitcoinLeg) VerifyFundedHTLC(
	rawTxHex string,
	hashH, makerClaimPub, takerRefundPub []byte,
	btcLocktime uint32,
	wantAmount uint64,
	confirmations, minConf int,
) (*FundedBTCHTLC, error)

VerifyFundedHTLC validates the taker's funded BTC-leg HTLC against the agreed parameters, working entirely in Bitcoin transaction format.

Given the raw funding tx (Bitcoin-serialized hex, e.g. from getrawtransaction) and the agreed HTLC params (hashH, makerClaimPub, takerRefundPub, btcLocktime, expected amount in sats), it:

  1. recomputes the canonical Design-A redeemScript from (hashH, claim, refund, locktime) — identical to what btc.js builds;
  2. computes that script's P2SH scriptPubKey under this leg's chain params;
  3. parses the funding tx as a wire.MsgTx (Bitcoin format, NOT go-elements) and locates the output paying that exact scriptPubKey;
  4. checks the output value equals the agreed amount and that the funding tx has at least minConf confirmations.

On success it returns the funded outpoint + value so the maker can later BuildClaimTx against it.

type BitcoinSpendInput

type BitcoinSpendInput struct {
	TxID   string // funding txid (big-endian display order)
	Vout   uint32
	Amount uint64 // value of the HTLC output, in satoshis
	DestPK []byte // scriptPubKey of the redeem/refund destination
	Fee    uint64 // fee in satoshis (subtracted from Amount; no explicit fee output)
}

BitcoinSpendInput identifies the HTLC output being spent on the BTC leg.

type Chain

type Chain struct {
	// contains filtered or unexported fields
}

Chain wraps an RPC client with the higher-level operations a swap leg needs on one Elements-mode node: funding an HTLC P2SH output, locating the funded vout, fetching destination scripts, mining, broadcasting, and (for the Sequentia leg) reading anchor metadata.

func NewChain

func NewChain(rpc *RPC, wallet string) *Chain

NewChain binds a Chain to a node and its wallet name.

func (*Chain) AddressScriptPubKey

func (c *Chain) AddressScriptPubKey(addr string) (string, error)

AddressScriptPubKey returns the hex scriptPubKey the node derives for an address (used to match the HTLC P2SH against the funded output).

func (*Chain) AssetBalance

func (c *Chain) AssetBalance(assetID string) (uint64, error)

AssetBalance returns the wallet's confirmed balance of an asset, in atoms. assetID may be "" to query the default pegged "bitcoin" asset (the BTC leg's reserve). For an issued SEQ asset, pass its hex id. Uses getbalance with an explicit asset to avoid pulling in confidential-balance machinery.

func (*Chain) BestBlockHash

func (c *Chain) BestBlockHash() (string, error)

BestBlockHash returns the current tip hash.

func (*Chain) BlockAnchorHeight

func (c *Chain) BlockAnchorHeight(blockHash string) (int64, error)

BlockAnchorHeight returns the anchorheight recorded in a Sequentia block header (getblockheader <hash> true). This is the per-block anchor commitment the SEQ-side claimant checks against the BTC-leg height.

func (*Chain) BlockCertification

func (c *Chain) BlockCertification(blockHash string) (certified, present bool, err error)

BlockCertification reports whether a Sequentia block is quorum-certified (immediately final): its committee countersignatures reached the quorum. This is stronger than anchoring — anchoring binds the leg to Bitcoin, quorum certification means the committee finalized it. `present` is false against a node predating the getblockheader poscertified field, so the caller can keep anchor-only behavior during a rolling upgrade rather than break.

func (*Chain) BlockCount

func (c *Chain) BlockCount() (int64, error)

BlockCount returns the current chain height.

func (*Chain) BlockHashOfTx

func (c *Chain) BlockHashOfTx(txid string) (string, error)

BlockHashOfTx returns the block hash that confirmed a tx (the SEQ-leg block whose anchorheight the orchestrator verifies). Uses getrawtransaction so it works for txs the local wallet did NOT create — e.g. the taker-funded SEQ leg the reverse maker must verify; gettransaction errors -5 ("Invalid or non-wallet transaction id") on a non-wallet txid. Relies on the node's txindex (the DEX node has it).

func (*Chain) Broadcast

func (c *Chain) Broadcast(rawHex string) (string, error)

Broadcast submits a raw tx hex and returns its txid.

func (*Chain) Confirmations

func (c *Chain) Confirmations(txid string) (int, error)

Confirmations returns a wallet tx's confirmation count.

func (*Chain) ExtractPreimage

func (c *Chain) ExtractPreimage(txid string, wantHash []byte) ([]byte, error)

ExtractPreimage reads the preimage off a redeem spend's input-0 scriptSig. This is the maker's secret-extraction step: the daemon watches the SEQ chain for the taker's claim, then recovers the preimage `s` (the data push whose sha256 equals the swap's hashlock H) so it can claim the BTC leg with it.

It tokenizes every input's scriptSig and returns the first pushed data item whose SHA256 equals wantHash. Returning the raw bytes (rather than a contains-check) is what lets the maker actually USE the secret.

func (*Chain) FeeExchangeRate

func (c *Chain) FeeExchangeRate(assetHex string) (uint64, bool)

FeeExchangeRate returns the node's any-asset fee exchange rate for assetHex (the integer getfeeexchangerates publishes: the asset's value in native-sats x 1e8), and whether a positive rate is published. The rate map may be keyed by human label (asset registry) rather than hex, so resolve labels via dumpassetlabels. Returns (0,false) on any RPC error / missing asset / non-positive rate so callers fall back to the native fee. Used to size an asset-denominated fee so its native-equivalent value stays within the node's relay fee bounds (maxfeerate).

func (*Chain) GetAnchorStatus

func (c *Chain) GetAnchorStatus() (*AnchorStatus, error)

GetAnchorStatus reads the node's current anchor view.

func (*Chain) LockHTLC

func (c *Chain) LockHTLC(redeemScript []byte, amountCoins, assetLabel string) (*FundedHTLC, error)

LockHTLC pays `amountCoins` (a decimal string, e.g. "10") of the given asset to the HTLC's P2SH address and returns the funded outpoint. assetLabel may be "" to pay the chain's default (pegged "bitcoin") asset — that is how the BTC leg is funded.

func (*Chain) Mine

func (c *Chain) Mine(n int) error

Mine generates n blocks to a fresh address in this wallet.

func (*Chain) NewDestScript

func (c *Chain) NewDestScript() ([]byte, error)

NewDestScript returns a fresh address' scriptPubKey to receive a redeem/refund.

func (*Chain) OutputAt

func (c *Chain) OutputAt(txid string, vout uint32) (*ChainOutput, error)

OutputAt returns the (txid, vout) output of any on-chain tx.

func (*Chain) P2SHAddress

func (c *Chain) P2SHAddress(redeemScript []byte) (string, error)

P2SHAddress asks the node to derive the P2SH address for a redeemScript. Using the node's own decodescript avoids any address-prefix mismatch between btcd/go-elements encodings and the node's network parameters.

func (*Chain) PeggedAsset

func (c *Chain) PeggedAsset() (string, error)

PeggedAsset returns the chain's pegged "bitcoin" asset id (used as the BTC leg's asset when building spends).

func (*Chain) RPC

func (c *Chain) RPC() *RPC

RPC exposes the underlying wallet-scoped client (for ad-hoc calls in tests).

func (*Chain) RedeemScriptSigContains

func (c *Chain) RedeemScriptSigContains(txid, needleHex string) (bool, string, error)

RedeemScriptSigContains reports whether the given on-chain spend's input 0 scriptSig asm contains the hex needle (used to prove the preimage was revealed on-chain, so the counterparty can read it).

func (*Chain) SpenderOf

func (c *Chain) SpenderOf(txid string, vout uint32) (string, error)

SpenderOf reports the txid that spent the given outpoint, or "" if it is still unspent. The maker uses this to detect the taker's SEQ-leg claim: when the SEQ-leg output is spent, the spender's scriptSig carries the preimage.

It uses gettxout (which returns null once an outpoint is spent) to learn that a spend happened, then scans the mempool + recent blocks for the spender.

func (*Chain) TryBroadcast

func (c *Chain) TryBroadcast(rawHex string) (string, error)

TryBroadcast is like Broadcast but returns the node's rejection error (used by the negative refund-before-timeout test).

func (*Chain) TxConfirmations

func (c *Chain) TxConfirmations(txid string) (int, error)

TxConfirmations returns a tx's confirmation count via getrawtransaction, which (unlike gettransaction) works for txs the local wallet did not create — e.g. the taker-funded BTC leg the maker must verify. Returns 0 if unconfirmed.

type ChainOutput

type ChainOutput struct {
	ValueAtoms      uint64
	AssetID         string
	ScriptPubKeyHex string
}

ChainOutput is a tx output's value/asset/scriptPubKey, as the maker needs it to verify the taker's BTC-leg funding.

type Direction

type Direction struct{}

Direction documents which way value flows. We implement and document ONE direction: the secret-holder (Alice) holds BTC and wants the SEQ asset; the counterparty (Bob) holds the SEQ asset and wants BTC. A single preimage, chosen by Alice, locks both legs (Design A).

Flow (with the anchor-shortened ordering as the headline rule):

  1. Alice (secret holder) LOCKS the BTC leg first. claim=Bob, refund=Alice, CLTV = btcLocktime (the LONGER timeout — Alice refunds last).
  2. Once the BTC leg is confirmed at parent height Hp, Bob LOCKS the SEQ leg. claim=Alice, refund=Bob, CLTV = seqLocktime (the SHORTER timeout).
  3. ORDERING CHECK (the Sequentia value-add): the SEQ leg must land in a Sequentia block whose anchorheight >= Hp, and getanchorstatus must be "ok". VerifySeqLegSafe enforces this; if it fails the orchestrator refuses to treat the SEQ leg as safe (ErrAnchorOrdering). Because of anchoring, once this holds the SEQ leg needs only ~1 confirmation with NO extra reorg buffer: if the BTC leg is later reorged, the SEQ block (anchored to the now-orphaned parent height) reorgs with it, reverting BOTH legs together — proven in test/functional/feature_anchor_swap_consistency.py.
  4. Alice REDEEMS the SEQ leg with the preimage (revealing it on-chain).
  5. Bob reads the preimage off Alice's SEQ redeem and REDEEMS the BTC leg.

If a counterparty stalls, the locker REFUNDS via the CLTV branch after its timeout (RefundPath).

type ElementsLeg

type ElementsLeg struct {
	// contains filtered or unexported fields
}

ElementsLeg builds and spends a Design-A HTLC on an Elements-mode chain. Both swap legs use it:

  • The "BTC" leg runs on the parent (anchor-source) node, paid in that chain's pegged "bitcoin" asset. Although we call it the BTC leg, the parent is an Elements node, so its transactions are Elements-serialized; the HTLC is nonetheless an ordinary Bitcoin-Script P2SH HTLC (the same OP_IF/OP_SHA256/OP_CLTV script), which elementsd evaluates unchanged. The script + spend logic are byte-identical to a real Bitcoin HTLC; only the transaction envelope is Elements.

  • The "SEQ" leg runs on the anchored Sequentia node, paid in an issued asset.

We deliberately use explicit (UNCONFIDENTIAL) HTLC outputs — confidential outputs (CT/blinding) are unnecessary for this mechanism proof and would only complicate the sighash. The redeemScript is leg-agnostic; the only per-leg data is the asset id of the output.

Script primitives (the redeemScript itself and the scriptSig assembly) come from btcd's txscript; the transaction body, the legacy sighash and value/ asset serialization come from go-elements. This split mirrors the package brief: btcd for Bitcoin-Script, go-elements for the Sequentia/Elements tx.

func NewElementsLeg

func NewElementsLeg(leg Leg, prim LockPrimitive) *ElementsLeg

NewElementsLeg returns a leg builder for the given chain side.

func (*ElementsLeg) HTLCScript

func (l *ElementsLeg) HTLCScript(claimPub, refundPub []byte, locktime uint32) ([]byte, error)

HTLCScript renders the redeemScript for the given pubkeys/CLTV locktime.

func (*ElementsLeg) Redeem

func (l *ElementsLeg) Redeem(redeemScript []byte, in ElementsSpendInput, key *Key) (string, error)

Redeem builds a signed redeem (IF-branch) spend revealing the preimage.

func (*ElementsLeg) Refund

func (l *ElementsLeg) Refund(redeemScript []byte, in ElementsSpendInput, locktime uint32, key *Key) (string, error)

Refund builds a signed refund (ELSE-branch) spend, valid once nLockTime reaches the CLTV locktime.

func (*ElementsLeg) Side

func (l *ElementsLeg) Side() Leg

Leg reports which side this builder serves.

type ElementsSpendInput

type ElementsSpendInput struct {
	TxID    string // funding txid (big-endian display order)
	Vout    uint32
	Amount  uint64 // value of the HTLC output, in atoms
	AssetID string // 32-byte asset id, hex (big-endian display order)
	DestSPK []byte // scriptPubKey of the redeem/refund destination
	Fee     uint64 // fee in atoms; emitted as an explicit fee output (empty SPK)
}

ElementsSpendInput identifies the HTLC output being spent on an Elements leg.

type FundedBTCHTLC

type FundedBTCHTLC struct {
	TxID          string
	Vout          uint32
	Amount        uint64 // satoshis
	Confirmations int
	ScriptPubKey  []byte // the P2SH scriptPubKey the output paid
}

FundedBTCHTLC is the maker's verified view of a real-Bitcoin HTLC funding.

type FundedHTLC

type FundedHTLC struct {
	TxID    string
	Vout    uint32
	Amount  uint64 // atoms
	AssetID string
}

FundedHTLC is the outpoint + value + asset of a funded HTLC output.

type HashLock

type HashLock struct {
	Hash   []byte // 32-byte sha256(secret); the public part of the lock.
	Secret []byte // 32-byte preimage; required to build a redeem spend.
}

HashLock is the Design-A SHA256 hashlock primitive. It holds the hash H of the swap secret; only the redeeming party needs Secret set, and only when actually building a redeem spend (RedeemUnlockItems checks it).

func NewHashLock

func NewHashLock(secret []byte) *HashLock

NewHashLock builds a HashLock from a known preimage (the secret-holder side).

func NewHashLockFromHash

func NewHashLockFromHash(hash []byte) *HashLock

NewHashLockFromHash builds a HashLock from only the hash (the counterparty side, before the secret is revealed on-chain).

func (*HashLock) Kind

func (h *HashLock) Kind() string

func (*HashLock) LockScript

func (h *HashLock) LockScript(claimPub, refundPub []byte, locktime uint32) ([]byte, error)

LockScript renders the Design-A HTLC redeemScript. It is identical to contrib/sequentia/swap-demo.py's htlc_script() and to a real BTC<->SEQ HTLC.

func (*HashLock) RedeemUnlockItems

func (h *HashLock) RedeemUnlockItems(sig []byte) ([][]byte, error)

RedeemUnlockItems: <sig> <preimage> OP_TRUE — selects the IF branch and satisfies SHA256(<preimage>)==H plus the claim-key CHECKSIG. (The leg appends the redeemScript push afterwards.)

func (*HashLock) RefundUnlockItems

func (h *HashLock) RefundUnlockItems(sig []byte) ([][]byte, error)

RefundUnlockItems: <sig> OP_FALSE — selects the ELSE (refund) branch.

type Key

type Key struct {
	// contains filtered or unexported fields
}

Key is a regtest-only EC keypair used to sign HTLC spends on either leg. Both legs use ECDSA over secp256k1 with a DER-encoded signature plus a 1-byte SIGHASH_ALL flag, so a single Key type serves both btcd and go-elements signing.

func KeyFromBytes

func KeyFromBytes(b []byte) *Key

KeyFromBytes reconstructs a Key from 32 secret bytes (e.g. read from a regtest key file — never pass secrets on the command line).

func NewKey

func NewKey() (*Key, error)

NewKey generates a fresh random keypair (regtest only).

func (*Key) Bytes

func (k *Key) Bytes() []byte

Bytes returns the 32-byte secret scalar (for persisting to a key file).

func (*Key) PubKey

func (k *Key) PubKey() []byte

PubKey returns the 33-byte compressed public key, as embedded in the HTLC redeemScript.

func (*Key) SignDER

func (k *Key) SignDER(sighash []byte) []byte

SignDER signs a 32-byte sighash and returns the DER-encoded signature WITHOUT the trailing sighash-type byte (the caller appends it, since the flag value is identical on both legs). Low-S is enforced by btcec, satisfying standard relay policy.

type LNLeg

type LNLeg interface {
	// NodeID returns the node's Lightning id (hex pubkey).
	NodeID() (string, error)

	// Pay pays a BOLT11 whose payment_hash MUST equal wantHash and returns the
	// preimage the payee revealed. The caller uses that preimage to redeem the
	// SEQ leg. It fails if the invoice's hash != wantHash (so the maker never
	// pays an invoice not tied to the swap secret).
	Pay(bolt11 string, wantHash []byte, amountMsat uint64) (preimage []byte, err error)

	// CreateHoldInvoice registers a hold invoice on paymentHash H for amountMsat
	// with the given cltv/label/description and returns the BOLT11 for the taker
	// to pay. Requires a holdinvoice plugin on the node.
	CreateHoldInvoice(paymentHash []byte, amountMsat uint64, cltvExpiry uint32, label, description string) (bolt11 string, err error)

	// WaitHeld blocks until the taker's HTLC for paymentHash is accepted-and-held
	// (state "accepted") or the deadline. Returns the held amount (msat).
	WaitHeld(paymentHash []byte, timeout time.Duration) (heldMsat uint64, err error)

	// SettleHold resolves the held invoice with the preimage: the maker takes the
	// incoming BTC-LN. Only call once the SEQ-side reveal is anchor-deep.
	SettleHold(paymentHash, preimage []byte) error

	// CancelHold fails the held invoice back to the payer (timeout / refund path).
	CancelHold(paymentHash []byte) error

	// CreateInvoice creates a plain BOLT11 whose payment_hash = SHA256(preimage)
	// for amountMsat. The node knows the preimage, so it settles normally on
	// payment (revealing P to the payer).
	CreateInvoice(preimage []byte, amountMsat uint64, cltvExpiry uint32, label, description string) (bolt11 string, err error)

	// WaitInvoicePaid blocks until the invoice with the given label is paid (or
	// the deadline / invoice expiry), returning the received amount (msat).
	WaitInvoicePaid(label string, timeout time.Duration) (paidMsat uint64, err error)
}

LNLeg abstracts the maker's Bitcoin-Lightning leg of a submarine swap. It is the LN analogue of btcBackend; it deliberately does NOT reuse btcBackend because an LN leg has no funded P2SH, txid or block height.

type Leg

type Leg int

Leg identifies which chain a primitive is operating on. The two legs differ only in transaction serialization / sighash (Bitcoin-script vs Elements); the lock script itself is byte-for-byte identical, so a LockPrimitive is leg-agnostic and the leg-specific work lives in the *Leg builders.

const (
	// LegBTC is the Bitcoin-script leg (the parent / anchor-source chain).
	LegBTC Leg = iota
	// LegSEQ is the Sequentia-asset leg (the anchored chain).
	LegSEQ
)

func (Leg) String

func (l Leg) String() string

type LegLock

type LegLock struct {
	Script   []byte
	Funded   *FundedHTLC
	Locktime uint32
}

LegLock records a funded HTLC leg.

type LockPrimitive

type LockPrimitive interface {
	// Kind is a short human label ("hashlock", "ptlc", ...).
	Kind() string

	// LockScript builds the redeemScript for the given claim/refund pubkeys
	// and CLTV refund locktime.
	LockScript(claimPub, refundPub []byte, locktime uint32) ([]byte, error)

	// RedeemUnlockItems returns the stack items (excluding the trailing
	// redeemScript push, which the leg adds) for the claim/IF branch, given a
	// signature already produced over the spend's sighash and any
	// primitive-specific secret material.
	RedeemUnlockItems(sig []byte) ([][]byte, error)

	// RefundUnlockItems returns the stack items (excluding the trailing
	// redeemScript push) for the refund/ELSE branch, given a signature over
	// the spend's sighash.
	RefundUnlockItems(sig []byte) ([][]byte, error)
}

LockPrimitive abstracts the cryptographic lock used by a swap leg. Today the only implementation is HashLock (a SHA256 hashlock HTLC), but the swap orchestration in orchestrator.go is written purely against this interface so a PTLC / adaptor-signature primitive can be slotted in later without touching the orchestration.

A primitive produces three artefacts, all leg-agnostic raw scripts:

  • LockScript: the redeemScript funded by both parties (-> P2SH address).
  • RedeemUnlockItems: the data items that satisfy the "claim/IF" branch given a signature over the spend (e.g. <sig> <preimage> 1 for a hashlock; just <sig> for a PTLC). The leg builder is responsible for wrapping these into a scriptSig/witness together with the redeemScript.
  • RefundUnlockItems: the data items that satisfy the "refund/ELSE" branch (e.g. <sig> 0 for the CLTV refund).

Splitting "what unlocks the branch" (here) from "how it is serialized into a scriptSig vs a witness" (the leg) is what keeps the abstraction clean across btcd and go-elements.

type NormalParams

type NormalParams struct {
	HashH             []byte // the shared hashlock (== the invoice payment_hash)
	MakerSeqClaimPub  []byte // maker's SEQ-leg claim pubkey (maker claims with P)
	TakerSeqRefundPub []byte // taker's SEQ-leg refund pubkey
	SeqRedeemScript   []byte // the taker's SEQ HTLC redeemScript (verified byte-for-byte)
	SeqLocktime       uint32 // the SEQ HTLC CLTV height (taker's refund branch)
	SeqTxID           string // the taker's SEQ HTLC funding outpoint
	SeqVout           uint32
	SeqAmountAtoms    uint64 // expected SEQ-leg value
	SeqAssetID        string // expected SEQ-leg asset id (internal hex)
	SeqMinConf        int    // min Sequentia confirmations before considering the leg

	Bolt11         string // the invoice the maker pays (reveals P)
	InvoiceMsat    uint64 // expected invoice amount (0 = don't cross-check)
	MinAnchorDepth int64  // Bitcoin-anchor depth required before paying (> 1)
	AnchorTimeout  time.Duration
}

NormalParams is the maker's input for a NORMAL submarine swap. The taker has already funded the SEQ asset HTLC (claim=maker, refund=taker) and given the maker the BOLT11 it wants paid (payment_hash = H).

type NormalResult

type NormalResult struct {
	Anchor       *SubAnchorEvidence
	Preimage     []byte // P, learned from paying the BOLT11
	SeqClaimTxID string // the maker's SEQ-asset claim (spent with P)
}

NormalResult reports the outcome of a completed NORMAL swap.

type Party

type Party struct {
	// Alice = secret holder, funds BTC, receives SEQ.
	AliceClaimSEQ  *Key // Alice claims the SEQ leg
	AliceRefundBTC *Key // Alice refunds the BTC leg she funded
	// Bob = counterparty, funds SEQ, receives BTC.
	BobClaimBTC  *Key // Bob claims the BTC leg
	BobRefundSEQ *Key // Bob refunds the SEQ leg he funded
}

Party holds the per-party keys for a swap. claim keys sign the IF branch on the leg that party receives; refund keys sign the ELSE branch on the leg that party funded.

type RPC

type RPC struct {
	// contains filtered or unexported fields
}

RPC is a minimal Elements/Bitcoin JSON-RPC client over HTTP, scoped to the calls the swap orchestrator needs. It is intentionally self-contained (rather than reusing pkg/explorer/elements) so the xchain mechanism has no dependency on the daemon's explorer types.

func NewRPC

func NewRPC(host string, port int, user, pass string) *RPC

NewRPC builds a client for host:port with HTTP basic auth (cookie user/pass).

func (*RPC) Call

func (c *RPC) Call(out interface{}, method string, params ...interface{}) error

Call invokes method with positional params and unmarshals the result into out (if non-nil).

func (*RPC) CallNamed

func (c *RPC) CallNamed(out interface{}, method string, params map[string]interface{}) error

CallNamed invokes method with a named-parameter object (Bitcoin/Elements JSON-RPC supports {"params": {...}}); used where positional args are awkward, e.g. sendtoaddress with a non-default assetlabel.

func (*RPC) WithWallet

func (c *RPC) WithWallet(name string) *RPC

WithWallet returns a shallow copy targeting the named wallet endpoint.

type ReverseMakerSecretOffer

type ReverseMakerSecretOffer struct {
	Bolt11    string
	HashH     []byte
	SeqLeg    *LegLock
	SeqBlock  string // the Sequentia block that confirmed the asset HTLC (for the taker's anchor gate)
	Label     string
	AmountSat uint64
}

ReverseMakerSecretOffer is what the maker hands the taker: the invoice to pay and the funded asset HTLC to verify + anchor-gate before paying.

type ReverseMakerSecretParams

type ReverseMakerSecretParams struct {
	TakerSeqClaimPub  []byte // taker's SEQ-leg claim pubkey (taker claims with P)
	MakerSeqRefundPub []byte // maker's SEQ-leg refund pubkey (reclaim after CLTV)
	SeqLocktime       uint32 // the SEQ HTLC CLTV height (maker's refund branch)
	SeqAmountCoins    string // the SEQ asset amount to lock (decimal string)
	SeqAssetLabel     string // the SEQ asset to lock

	InvoiceMsat  uint64 // the BTC-LN amount to invoice
	InvoiceCLTV  uint32 // the invoice's min_final_cltv_expiry
	InvoiceLabel string // a unique label for the invoice (also used to await payment)
	InvoiceDesc  string // the invoice description
}

ReverseMakerSecretParams is the maker's input for the plugin-free reverse mode. The SubmarineSwap MUST have been built with a full HashLock (NewHashLock(P)) so the maker knows P.

type ReverseParams

type ReverseParams struct {
	HashH             []byte // the shared hashlock (taker generated P; maker knows only H until reveal)
	TakerSeqClaimPub  []byte // taker's SEQ-leg claim pubkey (taker claims with P)
	MakerSeqRefundPub []byte // maker's SEQ-leg refund pubkey (maker reclaims after timeout)
	SeqLocktime       uint32 // the SEQ HTLC CLTV height (maker's refund branch)
	SeqAmountCoins    string // the SEQ asset amount to lock (decimal string)
	SeqAssetLabel     string // the SEQ asset to lock

	InvoiceMsat  uint64        // the BTC-LN amount to hold-invoice
	InvoiceCLTV  uint32        // the hold invoice's min_final_cltv_expiry
	InvoiceLabel string        // a unique label for the hold invoice
	InvoiceDesc  string        // the hold invoice description
	HeldTimeout  time.Duration // how long to wait for the taker's LN HTLC to be held

	MinAnchorDepth int64         // Bitcoin-anchor depth of the SEQ claim required before settling (> 1)
	ClaimTimeout   time.Duration // how long to wait for the taker's on-chain SEQ claim
	AnchorTimeout  time.Duration // how long to wait for that claim to be anchor-buried
}

ReverseParams is the maker's input for a REVERSE submarine swap (the DEX taker buys the asset with BTC-LN). The taker generated the secret and sent the maker H; the maker issues a hold invoice on H and locks the SEQ asset HTLC (claim=taker, refund=maker).

type ReverseResult

type ReverseResult struct {
	Bolt11       string // the hold invoice the taker paid
	HeldMsat     uint64 // the BTC-LN amount held
	SeqLeg       *LegLock
	SeqClaimTxID string // the taker's on-chain SEQ claim (revealed P)
	Anchor       *SubAnchorEvidence
	Preimage     []byte // P, extracted from the taker's SEQ claim
	Settled      bool   // the maker settled the held BTC-LN invoice
}

ReverseResult reports the outcome of a completed REVERSE swap.

type SubAnchorEvidence

type SubAnchorEvidence struct {
	SeqBlockHash     string
	SeqBlockAnchor   int64 // the SEQ block's recorded Bitcoin-anchor height
	NodeAnchorHeight int64 // the node's current Bitcoin-anchor tip
	AnchorDepth      int64 // NodeAnchorHeight - SeqBlockAnchor (Bitcoin blocks burying the anchor)
	MinAnchorDepth   int64
	AnchorStatus     string
	Certified        bool
	CertPresent      bool
	OK               bool
}

SubAnchorEvidence records what the submarine anchor-depth gate checked.

type SubmarineSwap

type SubmarineSwap struct {
	*Swap // SEQ-leg operations + shared hashlock (btcBackend nil, unused)
	// contains filtered or unexported fields
}

SubmarineSwap orchestrates a SeqLN Phase 2 submarine swap: a Sequentia asset ON-CHAIN leg <-> BTC over VANILLA Lightning, bound by one shared SHA256 preimage (Case A; design in SequentiaByClaude/doc/sequentia/seqln-phase2-submarine-swaps.md).

It REUSES the on-chain SEQ leg unchanged: it embeds *Swap purely for its SEQ-leg operations (VerifySEQLeg / LockSEQLeg / ClaimSEQLeg / RefundSEQLeg / WatchSEQClaim / InjectSecret) and the shared hashlock. The BTC leg is NOT an on-chain HTLC here, so the embedded Swap's btcBackend is nil and never used; the BTC leg is the LNLeg instead.

The maker plays the Boltz role: non-custodial, runs a SeqLN/CLN node on Bitcoin, and is the Sequentia on-chain HTLC counterparty. Worst case is a timelock refund on each leg.

The one Sequentia-specific safety rule (design §3.2) is the anchor-depth secret-reveal gate: because a Sequentia tx is final only to its Bitcoin-anchor depth, the maker must not take the IRREVERSIBLE Lightning action until the relevant Sequentia tx is buried by min_anchor_depth Bitcoin blocks. Unlike the same-block DEX ordering (VerifySeqLegSafe, deliberately 0-conf on the anchor axis), a submarine cross-leg point needs a real depth > 1 (min_anchor_depth):

  • NORMAL: gate the SEQ *funding* before the maker pays the BOLT11.
  • REVERSE: gate the taker's SEQ *claim* (which revealed P) before the maker settles the held BTC-LN invoice.

func NewSubmarineSwap

func NewSubmarineSwap(seq *Chain, ln LNLeg, prim *HashLock) *SubmarineSwap

NewSubmarineSwap wires a submarine orchestrator to the anchored Sequentia node (SEQ leg) and a Bitcoin-Lightning leg (a SeqLN/CLN node on Bitcoin). prim is the shared hashlock: for a NORMAL swap the taker holds the secret and the maker learns it by paying the invoice; for a REVERSE swap the taker holds the secret and reveals it by claiming the SEQ asset on-chain.

func (*SubmarineSwap) AwaitInvoicePaid

func (m *SubmarineSwap) AwaitInvoicePaid(label string, timeout time.Duration) (uint64, error)

AwaitInvoicePaid blocks until the invoice with the given label is paid on THIS node (or the deadline). Generic sibling of AwaitReversePayment.

func (*SubmarineSwap) AwaitReversePayment

func (m *SubmarineSwap) AwaitReversePayment(label string, timeout time.Duration) (uint64, error)

AwaitReversePayment blocks until the taker pays the invoice (label), returning the received amount (msat). After this the taker holds P (learned from paying) and can claim the asset; if it never returns before SeqLocktime the maker reclaims the asset via RefundReverseSEQ.

func (*SubmarineSwap) MintInvoice

func (m *SubmarineSwap) MintInvoice(preimage []byte, amountMsat uint64, cltvExpiry uint32, label, description string) (string, error)

MintInvoice creates a plain BOLT11 on THIS node bound to preimage (payment_hash = SHA256(preimage)). Used by the NORMAL-direction TAKER, which chooses P and mints the invoice it wants the maker to pay (paying it delivers the taker's BTC-LN and reveals P to the maker so it can claim the asset). Delegates to the LN leg's CreateInvoice.

func (*SubmarineSwap) OfferReverseMakerSecret

func (m *SubmarineSwap) OfferReverseMakerSecret(p ReverseMakerSecretParams) (*ReverseMakerSecretOffer, error)

OfferReverseMakerSecret performs the maker's side of the plugin-free reverse swap up to the point the taker must act: it locks the asset HTLC (claim=taker) and issues a plain invoice on H = SHA256(P). The maker must know P (the swap was built with NewHashLock(P)). The returned offer carries everything the taker needs to verify the HTLC and run its own anchor-depth gate before paying.

func (*SubmarineSwap) PayInvoice

func (m *SubmarineSwap) PayInvoice(bolt11 string, wantHash []byte, amountMsat uint64) ([]byte, error)

PayInvoice pays a BOLT11 whose payment_hash MUST equal wantHash and returns the revealed preimage. Used by the REVERSE-direction TAKER, which pays the maker's invoice and thereby LEARNS P (then claims the asset with it). It refuses any invoice not bound to the swap H. Delegates to the LN leg's Pay.

func (*SubmarineSwap) RefundReverseSEQ

func (m *SubmarineSwap) RefundReverseSEQ(seqLeg *LegLock, makerRefundKey *Key, nLockTime uint32, fee uint64) (string, error)

RefundReverseSEQ builds the maker's SEQ-leg CLTV refund for a REVERSE swap that stalled (taker paid neither leg through, or the claim never buried). It is a thin pass-through to RefundSEQLeg so callers drive the whole refund path (CancelHold on the LN side + this on the SEQ side) from the SubmarineSwap.

func (*SubmarineSwap) RunNormal

func (m *SubmarineSwap) RunNormal(p NormalParams, makerClaimKey *Key, seqClaimFee uint64) (*NormalResult, error)

RunNormal executes the maker side of a NORMAL submarine swap:

  1. VERIFY the taker's funded SEQ HTLC matches the quote (claimable by maker).
  2. WAIT for the SEQ *funding* to be anchor-buried to MinAnchorDepth. Only then is it safe for the maker to take the irreversible Lightning action, because once the funding is that deep it will not reorg out from under the claim.
  3. PAY the BOLT11 -> learn P (the maker is now committed on the LN side).
  4. CLAIM the SEQ asset with P (the maker holds the claim key).

The maker never pays the invoice until step 2 passes, so a Bitcoin reorg cannot leave it out-of-pocket on LN with an un-fundable SEQ claim.

func (*SubmarineSwap) RunReverse

func (m *SubmarineSwap) RunReverse(p ReverseParams) (*ReverseResult, error)

RunReverse executes the maker side of a REVERSE submarine swap:

  1. CREATE a hold invoice on H (the maker will settle it only with P).
  2. WAIT for the taker's LN HTLC to be HELD (accepted, pending). Only after the BTC-LN is inbound-and-held does the maker put its own asset at risk.
  3. LOCK the SEQ asset HTLC (claim=taker with P, refund=maker after timeout).
  4. WATCH for the taker's on-chain SEQ claim and EXTRACT P from it.
  5. WAIT for that SEQ claim to be anchor-buried to MinAnchorDepth. THE gate: the maker must not settle the BTC-LN leg until the SEQ claim that revealed P is anchor-deep, or a Bitcoin reorg could revert the SEQ claim after the maker has already taken the BTC-LN.
  6. SETTLE the held invoice with P (the maker collects the BTC-LN).

The returned *ReverseResult always carries whatever was accomplished so the caller can drive the refund path (CancelHold + RefundReverseSEQ) on any error. The maker's SEQ refund PRIVATE key is not needed here — it is only used later, at CLTV time, via RefundReverseSEQ; RunReverse only needs the refund PUBKEY (p.MakerSeqRefundPub) to build the HTLC.

func (*SubmarineSwap) VerifySeqAnchorBuried

func (m *SubmarineSwap) VerifySeqAnchorBuried(seqBlockHash string, minAnchorDepth int64) (*SubAnchorEvidence, error)

VerifySeqAnchorBuried is the submarine anchor-depth gate: it confirms the Sequentia block seqBlockHash is anchored, quorum-certified (if the node reports certification), and that its Bitcoin anchor is BURIED by at least minAnchorDepth Bitcoin blocks (node anchor tip - block anchorheight >= minAnchorDepth). This is the cross-leg safety point; minAnchorDepth MUST be > 1 for submarine swaps (1 is unsafe, design §3.2).

type Swap

type Swap struct {
	// contains filtered or unexported fields
}

Swap orchestrates a single Design-A cross-chain HTLC swap. It is written purely against LockPrimitive and the leg builders, so swapping in a PTLC primitive later requires no orchestration change.

The BTC (parent / anchor-source) leg is pluggable via btcBackend: it runs either against an Elements-mode parent (NewSwap) or a REAL bitcoind regtest/ testnet4 (NewSwapBitcoin). The SEQ (anchored Sequentia) leg is always Elements-format.

func NewSwap

func NewSwap(btc, seq *Chain, prim *HashLock) *Swap

NewSwap wires an orchestrator to an ELEMENTS-mode parent (BTC leg) and the anchored Sequentia node (SEQ leg). This is the original constructor and the default for back-compat.

func NewSwapBitcoin

func NewSwapBitcoin(btc *BitcoinChain, seq *Chain, prim *HashLock) *Swap

NewSwapBitcoin wires an orchestrator to a REAL bitcoind parent (BTC leg, in Bitcoin transaction format) and the anchored Sequentia node (SEQ leg, still Elements-format). This is the "real-bitcoind-leg": use it when the parent is a genuine bitcoind (regtest or testnet4), where the taker funds/refunds the BTC HTLC with a real Bitcoin signer and the maker must verify/claim it in Bitcoin format.

func (*Swap) ClaimBTCLeg

func (s *Swap) ClaimBTCLeg(leg *LegLock, bobClaim *Key, fee uint64) (string, error)

ClaimBTCLeg performs step 5: Bob redeems the BTC leg with the now-revealed preimage. Returns the redeem txid. The spend is built in the BTC backend's transaction format (Elements or Bitcoin).

func (*Swap) ClaimSEQLeg

func (s *Swap) ClaimSEQLeg(leg *LegLock, aliceClaim *Key, fee uint64) (string, error)

ClaimSEQLeg performs step 4: Alice redeems the SEQ leg with the preimage, revealing it on-chain. Returns the redeem txid.

func (*Swap) HashHex

func (s *Swap) HashHex() string

HashHex returns the swap hashlock H as hex (known to both parties).

func (*Swap) InjectSecret

func (s *Swap) InjectSecret(secret []byte) error

InjectSecret fills in the preimage on a Swap's shared hashlock after the maker has read it off the taker's SEQ-leg claim. It verifies sha256(secret) == H before accepting it, so a malformed extraction can never produce a wrong-key claim. After this, Swap.ClaimBTCLeg can build the BTC redeem.

func (*Swap) LockBTCLeg

func (s *Swap) LockBTCLeg(claimPub, refundPub []byte, amountCoins string, locktime uint32) (*LegLock, int64, error)

LockBTCLeg performs step 1: Alice locks the BTC leg first. Returns the funded leg and the parent height at which it confirmed (Hp), which the SEQ-leg ordering check is measured against.

func (*Swap) LockSEQLeg

func (s *Swap) LockSEQLeg(claimPub, refundPub []byte, amountCoins, assetLabel string, locktime uint32) (*LegLock, string, error)

LockSEQLeg performs step 2: Bob locks the SEQ leg only after the BTC leg is on-chain (paper principle 7). It mines one Sequentia block and returns the funded leg plus the hash of the block that confirmed it (for the ordering check). The caller must then call VerifySeqLegSafe before treating it as safe.

func (*Swap) RefundBTCLeg

func (s *Swap) RefundBTCLeg(leg *LegLock, makerRefund *Key, nLockTime uint32, fee uint64) (string, error)

RefundBTCLeg builds, broadcasts, and returns the txid of the BTC-leg CLTV refund (ELSE branch) at the given nLockTime. It is the reverse-direction (asset->BTC) mirror of RefundSEQLeg: the maker funds the BTC leg, so it must be able to reclaim that BTC after btcLocktime if the taker never funds/claims the SEQ leg. Delegates to the BTC backend (Elements or Bitcoin tx format).

func (*Swap) RefundSEQLeg

func (s *Swap) RefundSEQLeg(leg *LegLock, bobRefund *Key, nLockTime uint32, fee uint64) (string, error)

RefundSEQLeg builds (but does not broadcast) the SEQ-leg CLTV refund for Bob, at the given nLockTime. Returns the raw tx hex so callers can demonstrate the pre-timeout rejection and the post-timeout acceptance.

func (*Swap) SecretHex

func (s *Swap) SecretHex() string

SecretHex returns the swap preimage as hex (Alice side only).

func (*Swap) VerifyBTCLeg

func (s *Swap) VerifyBTCLeg(
	hashH, makerClaimPub, takerRefundPub, providedScript []byte,
	btcLocktime uint32,
	txid string, vout uint32, amount uint64, assetID string,
	minConf int,
) (*VerifiedBTCLeg, error)

VerifyBTCLeg checks that the taker's claimed BTC leg really is a Design-A HTLC that pays the agreed amount, embeds the agreed hashlock H, is claimable by the MAKER's claim key and refundable by the TAKER's refund key after btcLocktime, is funded to the matching P2SH, and is confirmed. It returns a *LegLock the maker can feed to ClaimBTCLeg once it has the secret.

makerClaimPub is the maker's BTC-leg claim pubkey (from the quote); takerRefundPub is the taker's BTC-leg refund pubkey (from ProposeXchainSwap). providedScript is the redeemScript the taker sent; it must equal the script we recompute from (H, makerClaimPub, takerRefundPub, btcLocktime) byte-for-byte.

func (*Swap) VerifySEQLeg

func (s *Swap) VerifySEQLeg(
	hashH, makerSeqClaimPub, takerSeqRefundPub, providedScript []byte,
	seqLocktime uint32,
	txid string, vout uint32, amount uint64, assetID string,
	minConf int,
) (*VerifiedSEQLeg, error)

VerifySEQLeg is the reverse-direction mirror of VerifyBTCLeg: it checks that the taker's funded SEQ asset leg really is a Design-A HTLC paying the agreed asset+amount, embedding the agreed hashlock H, claimable by the MAKER's seq-claim key and refundable by the TAKER's seq-refund key after seqLocktime, funded to the matching P2SH, and confirmed. It returns a *LegLock (inside VerifiedSEQLeg) the maker feeds to ClaimSEQLeg once VerifySeqLegSafe passes.

The SEQ leg is ALWAYS Elements-format (the anchored Sequentia node), so unlike VerifyBTCLeg this is not backend-pluggable; it runs directly against s.seqLeg + s.seq. makerSeqClaimPub is the maker's SEQ-leg claim pubkey (it generated H and will reveal the secret); takerSeqRefundPub is the taker's SEQ-leg refund pubkey; providedScript must equal the script we recompute byte-for-byte.

func (*Swap) VerifySeqLegSafe

func (s *Swap) VerifySeqLegSafe(seqBlockHash string, btcLegHeight int64) (*AnchorEvidence, error)

VerifySeqLegSafe is the anchor-shortened ordering check (step 3): it confirms the Sequentia block carrying the SEQ leg anchors at a height >= the BTC-leg height AND that the node's anchor status is "ok". Returns ErrAnchorOrdering (wrapped) if not — the orchestrator must NOT let the SEQ-side claimant treat the leg as final unless this passes.

func (*Swap) WatchSEQClaim

func (s *Swap) WatchSEQClaim(seqLeg *LegLock) (claimTxid string, secret []byte, err error)

type VerifiedBTCLeg

type VerifiedBTCLeg struct {
	Leg            *LegLock
	Height         int64
	Confirmations  int
	ExpectedScript []byte
}

VerifiedBTCLeg is the maker's view of the taker's BTC-leg HTLC, reconstructed and checked so the maker can later claim it.

type VerifiedSEQLeg

type VerifiedSEQLeg struct {
	Leg            *LegLock
	Height         int64
	Confirmations  int
	BlockHash      string
	ExpectedScript []byte
}

WatchSEQClaim polls the SEQ chain for a spend of the SEQ-leg outpoint and, when found, extracts the preimage from its scriptSig. It returns the claim txid and the secret. It does NOT block forever; callers run it on a ticker.

We detect the claim by asking the node for spends of the funding outpoint via gettxout (nil => spent) and then scanning recent blocks/mempool for the spender; to keep the MVP self-contained we instead accept the claim txid the taker can be observed to have broadcast, so the daemon scans by re-deriving it from the funding tx's spend. See SpenderOf. VerifiedSEQLeg is the maker's view of the taker's SEQ asset-leg HTLC in the REVERSE (asset->BTC) direction, reconstructed and checked so the maker can later claim it (revealing the secret). BlockHash is the Sequentia block that confirmed the leg, fed to VerifySeqLegSafe for the anchor-ordering gate.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL