client

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: 24 Imported by: 0

Documentation

Overview

Package client provides the maker + taker helpers that drive a SeqOB lift to settlement. The settlement itself REUSES the proven seqdex same-chain PSET co-sign (pkg/swap.{Request,Accept,Complete} + wallet.Service.CompleteSwap); nothing here rebuilds it.

Because the relay courier is opaque and end-to-end encrypted (review B1), this package owns the E2E crypto: each peer derives a shared key by ECDH between its ephemeral session key and the peer's, and seals the inner swap message with AES-256-GCM. The relay only ever sees ciphertext.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrXcPeerFailed = errors.New("peer failed the cross-chain lift") // wraps an XcFail from the counterparty
	ErrXcBadTerms   = errors.New("cross-chain terms rejected")
	ErrXcRefunded   = errors.New("cross-chain leg refunded")
)

Sentinel errors.

View Source
var ErrXcRefundNotDue = errors.New("btc refund locktime not yet reached")

RefundTakerBTC spends the taker's BTC leg through the CLTV refund path once T_btc has passed. With wait=false it returns ErrXcRefundNotDue when early.

Functions

func RefundTakerBTC

func RefundTakerBTC(ops XcOps, leg *xchain.LegLock, key *xchain.Key, locktime uint32, spendFeeSats uint64, wait bool, poll time.Duration) (string, error)

func RefundTakerSEQ

func RefundTakerSEQ(ops XcOps, leg *xchain.LegLock, key *xchain.Key, locktime uint32,
	assetHex string, spendFeeSats uint64, wait bool, poll time.Duration) (string, error)

RefundTakerSEQ spends the taker's SEQ leg through the CLTV refund path once T_seq has passed. With wait=false it returns ErrXcRefundNotDue when early.

func RefundTakerSubmarine

func RefundTakerSubmarine(ops SubTakerOps, leg *xchain.LegLock, key *xchain.Key, seqLocktime uint32, spendFeeAtoms uint64) (string, error)

RefundTakerSubmarine reclaims the taker's asset HTLC via its CLTV branch after T_seq, for a NORMAL swap where the maker never paid. Mirrors RefundTakerBTC.

func ValidateRequestAgainstOffer

func ValidateRequestAgainstOffer(req *seqdexv1.SwapRequest, o *seqobv1.Offer) error

ValidateRequestAgainstOffer binds a decrypted taker SwapRequest to the maker's OWN signed offer before the maker co-signs, so a malicious taker cannot drain the maker at an arbitrary price or in unexpected assets.

The taker is the proposer: it PAYS asset_p (the maker's want_asset) and RECEIVES asset_r (the maker's offer_asset). For a fill of amount_r of the offer_asset, the maker must be paid at least the offered ratio:

amount_p / amount_r >= want_amount / offer_amount

which, cleared of division and evaluated with math/big to avoid uint64 overflow, is amount_p * offer_amount >= want_amount * amount_r. Pro-rata fills round the pay leg up (the taker pays the ceil), so this lower bound is exactly "the ratio matches within integer rounding" from the maker's side; overpayment only benefits the maker and is allowed. The fill may not exceed the offered amount (partial fills allowed).

Types

type Backend

type Backend interface {
	// ProposerBuildRequest selects the taker's UTXOs and builds the proposer
	// SwapRequest. conf says which legs are confidential (so the proposer's output
	// is blinded or explicit, and its inputs reveal real or zero blinders).
	ProposerBuildRequest(req ProposalReq, conf LegConfidentiality) (*seqdexv1.SwapRequest, error)
	// ResponderComplete runs the maker CompleteSwap responder: add own UTXOs,
	// change, the any-asset fee vout, then BlindPset IFF blind (else finalize
	// explicit), then sign the maker's own inputs.
	ResponderComplete(req *seqdexv1.SwapRequest, blind bool) (*seqdexv1.SwapAccept, error)
	// ProposerFinalize signs the taker's inputs, validates, and broadcasts.
	ProposerFinalize(acc *seqdexv1.SwapAccept) (*seqdexv1.SwapComplete, string, error)
}

Backend is the chain-side settlement plumbing the LiveWallet drives. The real implementation (realbackend.go) wires these to pkg/trade.NewSwapTx + pkg/swap.{Request,Accept,Complete} + the Ocean/LWK wallet CompleteSwap; the mock implementation in tests records the calls so the blinded vs explicit paths can be asserted without a live node. Settlement logic is NOT reimplemented here.

type Crypter

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

Crypter seals/opens the inner swap payload for one session peer. The symmetric key is sha256(ECDH(myPriv, peerPub)) over secp256k1 (btcec, the repo's curve library). Both peers derive the same key because ECDH is symmetric.

func NewCrypter

func NewCrypter(myPriv *btcec.PrivateKey, peerPub *btcec.PublicKey) (*Crypter, error)

NewCrypter derives the session AEAD from this peer's private session key and the counterparty's public session key.

func NewMakerCrypterFromLift

func NewMakerCrypterFromLift(makerOfferPriv *btcec.PrivateKey, takerSessionPubkey []byte) (*Crypter, error)

NewMakerCrypterFromLift derives a maker's per-lift E2E crypter from the maker's offer private key (its key doubles as its session key) and the taker's session pubkey, as delivered in From.lift_requested. The maker then drives the existing Maker.HandleRequest to open the taker's sealed SwapRequest and seal its accept.

ITEM C (relay-MITM re-attack: VERIFIED FALSE POSITIVE; DoS only, NOT a CT leak). The taker side already derives its key from the SIGNED, VERIFIED offer's maker pubkey (round-1 fix in cmd/seqob-cli), not from the relay echo, so the taker leg is leak-proof. The remaining concern raised was that the maker still trusts the relay-supplied takerSessionPubkey here. That is denial-of-service, not a confidentiality leak, for a structural reason: a Crypter holds ONE symmetric key per peer used for BOTH Open and Seal (see Crypter above). The taker sealed its SwapRequest under the key derived from the REAL maker+taker pubkeys, so the maker must OPEN that ciphertext under the matching key BEFORE it ever produces a SwapAccept (see Maker.HandleRequest). If a malicious relay substitutes its own pubkey for takerSessionPubkey, the maker derives the WRONG key, the AES-GCM Open fails, and NO SwapAccept is ever sealed; so there is nothing to leak. The relay also holds no private key, so it cannot itself decrypt the taker->maker leg to mount a man-in-the-middle. Net effect of substitution: the lift simply fails. No functional change is required for this item.

func (*Crypter) Open

func (c *Crypter) Open(sealed []byte) ([]byte, error)

Open decrypts nonce||ciphertext produced by Seal under the matching key.

func (*Crypter) Seal

func (c *Crypter) Seal(plaintext []byte) ([]byte, error)

Seal encrypts plaintext, returning nonce||ciphertext.

type LegConfidentiality

type LegConfidentiality struct {
	TakerInputsConfidential bool // taker funds with confidential UTXOs
	TakerRecvConfidential   bool // taker receives into a confidential address
	MakerInputsConfidential bool // maker funds with confidential UTXOs
	MakerRecvConfidential   bool // maker receives into a confidential address
}

LegConfidentiality classifies which legs of a same-chain swap carry blinding material. Sequentia confidential transactions are OPT-IN: a swap may be fully confidential, fully explicit, or mixed, and settlement MUST work in all three. NeedsBlinding drives whether the responder runs BlindPset or finalizes explicit.

func (LegConfidentiality) Mode

func (l LegConfidentiality) Mode() string

Mode returns "explicit", "confidential", or "mixed".

func (LegConfidentiality) NeedsBlinding

func (l LegConfidentiality) NeedsBlinding() bool

NeedsBlinding reports whether any leg is confidential (so the tx must be blinded).

type LiveSubMakerOps

type LiveSubMakerOps struct{ Sub *xchain.SubmarineSwap }

LiveSubMakerOps implements SubMakerOps over a real submarine swap.

func (*LiveSubMakerOps) RunNormal

func (o *LiveSubMakerOps) RunNormal(p xchain.NormalParams, key *xchain.Key, fee uint64) (*xchain.NormalResult, error)

type LiveSubReverseMakerOps

type LiveSubReverseMakerOps struct{ Sub *xchain.SubmarineSwap }

LiveSubReverseMakerOps / LiveSubReverseTakerOps bind the seams to a real *xchain.SubmarineSwap (built with NewHashLock(P) for the maker, or NewHashLockFromHash(H) for the taker).

func (*LiveSubReverseMakerOps) AwaitReversePayment

func (o *LiveSubReverseMakerOps) AwaitReversePayment(label string, timeout time.Duration) (uint64, error)

func (*LiveSubReverseMakerOps) OfferReverseMakerSecret

func (*LiveSubReverseMakerOps) RefundReverseSEQ

func (o *LiveSubReverseMakerOps) RefundReverseSEQ(leg *xchain.LegLock, key *xchain.Key, nLockTime uint32, fee uint64) (string, error)

type LiveSubReverseTakerOps

type LiveSubReverseTakerOps struct{ Sub *xchain.SubmarineSwap }

func (*LiveSubReverseTakerOps) ClaimSEQLeg

func (o *LiveSubReverseTakerOps) ClaimSEQLeg(leg *xchain.LegLock, key *xchain.Key, fee uint64) (string, error)

func (*LiveSubReverseTakerOps) InjectSecret

func (o *LiveSubReverseTakerOps) InjectSecret(secret []byte) error

func (*LiveSubReverseTakerOps) PayInvoice

func (o *LiveSubReverseTakerOps) PayInvoice(bolt11 string, wantHash []byte, amountMsat uint64) ([]byte, error)

func (*LiveSubReverseTakerOps) VerifySEQLeg

func (o *LiveSubReverseTakerOps) VerifySEQLeg(hashH, claimPub, refundPub, providedScript []byte, seqLocktime uint32,
	txid string, vout uint32, amount uint64, assetID string, minConf int) (*xchain.VerifiedSEQLeg, error)

func (*LiveSubReverseTakerOps) VerifySeqAnchorBuried

func (o *LiveSubReverseTakerOps) VerifySeqAnchorBuried(seqBlockHash string, minAnchorDepth int64) (*xchain.SubAnchorEvidence, error)

type LiveSubTakerOps

type LiveSubTakerOps struct {
	Sub *xchain.SubmarineSwap
	SEQ *xchain.Chain
}

LiveSubTakerOps implements SubTakerOps over a real submarine swap + its SEQ node.

func (*LiveSubTakerOps) AwaitInvoicePaid

func (o *LiveSubTakerOps) AwaitInvoicePaid(label string, timeout time.Duration) (uint64, error)

func (*LiveSubTakerOps) LockSEQLeg

func (o *LiveSubTakerOps) LockSEQLeg(claimPub, refundPub []byte, amountCoins, assetLabel string, locktime uint32) (*xchain.LegLock, string, error)

func (*LiveSubTakerOps) MintInvoice

func (o *LiveSubTakerOps) MintInvoice(preimage []byte, amountMsat uint64, cltv uint32, label, desc string) (string, error)

func (*LiveSubTakerOps) RefundSEQLeg

func (o *LiveSubTakerOps) RefundSEQLeg(leg *xchain.LegLock, key *xchain.Key, nLockTime uint32, fee uint64) (string, error)

func (*LiveSubTakerOps) SeqBlockAnchorHeightOf

func (o *LiveSubTakerOps) SeqBlockAnchorHeightOf(blockHash string) (int64, error)

func (*LiveSubTakerOps) SeqTip

func (o *LiveSubTakerOps) SeqTip() (int64, error)

type LiveWallet

type LiveWallet struct {
	Backend Backend

	TakerInputsConfidential  bool
	TakerRecvConfidential    bool
	MakerOutputsConfidential bool
}

LiveWallet is the production Wallet. It is a THIN decision layer: it computes the proposal legs and the blinded-vs-explicit choice, then delegates every chain operation to a Backend (realbackend.go wires that to the proven seqdex settlement; tests use a mock Backend). No settlement logic lives here.

The confidentiality flags describe THIS wallet's posture (Sequentia confidential txs are opt-in); they are not assumed. As taker it declares whether it funds with confidential UTXOs and receives confidentially; as maker it declares whether its own outputs are confidential. The maker's per-swap blind decision additionally inspects the proposer's half (real input blinders or a confidential output), so a confidential taker is always honored.

func (*LiveWallet) ProposerBuildRequest

func (w *LiveWallet) ProposerBuildRequest(o *seqobv1.Offer, takeBase uint64, takerFeeAsset string) (*seqdexv1.SwapRequest, error)

ProposerBuildRequest (taker) computes the legs and confidentiality, then builds the SwapRequest via the backend.

func (*LiveWallet) ProposerFinalize

func (w *LiveWallet) ProposerFinalize(acc *seqdexv1.SwapAccept) (*seqdexv1.SwapComplete, string, error)

ProposerFinalize (taker) delegates sign+validate+broadcast to the backend.

func (*LiveWallet) ResponderComplete

func (w *LiveWallet) ResponderComplete(req *seqdexv1.SwapRequest) (*seqdexv1.SwapAccept, error)

ResponderComplete (maker) decides whether the swap must be blinded, then runs the responder via the backend. blind is true if the proposer revealed any confidential input, or its half has a confidential output, or this maker's own outputs are confidential; otherwise the swap is finalized EXPLICIT.

type LiveXcOps

type LiveXcOps struct {
	Swap *xchain.Swap
	BTC  *xchain.BitcoinChain
	SEQ  *xchain.Chain
}

LiveXcOps implements XcOps over a real swap and its chains.

func (*LiveXcOps) BtcConfirmations

func (o *LiveXcOps) BtcConfirmations(txid string) (int, error)

func (*LiveXcOps) BtcTip

func (o *LiveXcOps) BtcTip() (int64, error)

func (*LiveXcOps) ClaimBTCLeg

func (o *LiveXcOps) ClaimBTCLeg(leg *xchain.LegLock, key *xchain.Key, fee uint64) (string, error)

func (*LiveXcOps) ClaimSEQLeg

func (o *LiveXcOps) ClaimSEQLeg(leg *xchain.LegLock, key *xchain.Key, fee uint64) (string, error)

func (*LiveXcOps) InjectSecret

func (o *LiveXcOps) InjectSecret(secret []byte) error

func (*LiveXcOps) LockBTCLeg

func (o *LiveXcOps) LockBTCLeg(claimPub, refundPub []byte, amountCoins string, locktime uint32) (*xchain.LegLock, int64, error)

func (*LiveXcOps) LockSEQLeg

func (o *LiveXcOps) LockSEQLeg(claimPub, refundPub []byte, amountCoins, assetLabel string, locktime uint32) (*xchain.LegLock, string, error)

func (*LiveXcOps) RefundBTCLeg

func (o *LiveXcOps) RefundBTCLeg(leg *xchain.LegLock, key *xchain.Key, nLockTime uint32, fee uint64) (string, error)

func (*LiveXcOps) RefundSEQLeg

func (o *LiveXcOps) RefundSEQLeg(leg *xchain.LegLock, key *xchain.Key, nLockTime uint32, fee uint64) (string, error)

func (*LiveXcOps) SeqAnchorHeightOf

func (o *LiveXcOps) SeqAnchorHeightOf(blockHash string) (int64, error)

func (*LiveXcOps) SeqBlockHashOfTx

func (o *LiveXcOps) SeqBlockHashOfTx(txid string) (string, error)

func (*LiveXcOps) SeqBroadcast

func (o *LiveXcOps) SeqBroadcast(rawHex string) (string, error)

func (*LiveXcOps) SeqFeeRate

func (o *LiveXcOps) SeqFeeRate(assetHex string) (uint64, bool)

func (*LiveXcOps) SeqTip

func (o *LiveXcOps) SeqTip() (int64, error)

func (*LiveXcOps) VerifyBTCLeg

func (o *LiveXcOps) VerifyBTCLeg(hashH, makerClaimPub, takerRefundPub, providedScript []byte, btcLocktime uint32,
	txid string, vout uint32, amount uint64, minConf int) (*xchain.VerifiedBTCLeg, error)

func (*LiveXcOps) VerifySEQLeg

func (o *LiveXcOps) VerifySEQLeg(hashH, claimPub, refundPub, providedScript []byte, seqLocktime uint32,
	txid string, vout uint32, amount uint64, assetID string, minConf int) (*xchain.VerifiedSEQLeg, error)

func (*LiveXcOps) VerifySeqLegSafe

func (o *LiveXcOps) VerifySeqLegSafe(seqBlockHash string, btcLegHeight int64) (*xchain.AnchorEvidence, error)

func (*LiveXcOps) WatchSEQClaim

func (o *LiveXcOps) WatchSEQClaim(leg *xchain.LegLock) (string, []byte, error)

type Maker

type Maker struct {
	Wallet Wallet
	// Offer is the maker's OWN signed resting offer. When set, HandleRequest binds
	// every co-sign to it (asset legs, price floor, remaining size) so a malicious
	// taker cannot drain the maker at an arbitrary price. Left nil only in unit
	// tests that exercise the raw message flow.
	Offer *seqobv1.Offer
}

Maker drives the responder side: it opens the taker's sealed SwapRequest, runs the existing CompleteSwap path, and seals the SwapAccept back.

func (*Maker) HandleRequest

func (m *Maker) HandleRequest(sealedReq []byte, c *Crypter) (sealedAccept []byte, err error)

HandleRequest opens a sealed SwapRequest, runs ResponderComplete, and returns the sealed SwapAccept.

ITEM C (relay-MITM re-attack: VERIFIED FALSE POSITIVE; DoS only, NOT a CT leak). The Crypter c may have been derived from a relay-supplied taker session pubkey (see NewMakerCrypterFromLift). That is safe by ORDERING: this method's FIRST act is c.Open(sealedReq), and only on success does it go on to Seal a SwapAccept. The taker sealed sealedReq under the key from the real maker+taker pubkeys, so a relay that substituted its own pubkey yields a mismatched key, c.Open below fails, and the method returns before any SwapAccept exists, leaving nothing the relay can decrypt. Key substitution is therefore a denial-of-service (the lift fails), never a confidentiality leak.

type MakerForwardParams

type MakerForwardParams struct {
	// NewOps binds the settlement engine to the taker's hashlock once it arrives
	// (the maker never knows the secret; it builds from the hash).
	NewOps  func(hashH []byte) (XcOps, error)
	Crypter *Crypter

	// Tip queries usable BEFORE the hashlock exists (terms are minted first).
	BtcTip func() (int64, error)
	SeqTip func() (int64, error)

	AssetHex  string // SEQ asset we deliver (offer pair base)
	SeqAmount uint64 // atoms we lock
	BtcAmount uint64 // sats we require
	FeeBtc    uint64 // advisory fee surfaced in terms

	// Locktime deltas above the respective tips. T_btc must be LONGER IN TIME
	// than T_seq, and T_seq must leave the taker room for a REAL parent
	// confirmation before its claim: 100 parent blocks ~16 h vs 240 SEQ slots
	// ~2 h. (The RFQ's 50-slot delta was tuned for regtest; on a live parent a
	// taker with the default 120-slot minimum window rightly refuses it.)
	BtcLocktimeDelta uint32 // default 100
	SeqLocktimeDelta uint32 // default 240

	MinBTCConf   int    // confirmations required on the taker's BTC leg (default 1; testnet-grade — depth, not anchoring, protects the maker's BTC side)
	SpendFeeSats uint64 // fee target in native sats (default 1000)
	Timing       XcTiming
	Log          func(format string, args ...interface{})

	// OnUpdate is invoked after every state transition that creates or changes
	// recoverable value (keys minted, legs funded/verified, secret learned,
	// settled/refunded). The caller must persist the result snapshot there: the
	// per-lift keys exist nowhere else, and losing them mid-swap burns a leg.
	OnUpdate func(*MakerForwardResult)
}

MakerForwardParams configures RunMakerForward. Amounts and the asset come from the maker's own SIGNED offer (whole-HTLC: the lift's take_amount must equal the offer's base amount; the caller enforces that before starting the driver).

type MakerForwardResult

type MakerForwardResult struct {
	HashH        []byte
	BtcClaimKey  *xchain.Key // claims the taker's BTC leg once the secret is known
	SeqRefundKey *xchain.Key // refunds our SEQ leg after T_seq
	BtcLocktime  uint32
	SeqLocktime  uint32
	BtcLeg       *xchain.LegLock // the taker's verified BTC leg
	SeqLeg       *xchain.LegLock
	SeqBlockHash string
	Secret       []byte
	BtcClaimTxid string
	SeqRefundTx  string
	Settled      bool
}

MakerForwardResult reports the lift's evolving state; with OnUpdate it is the maker's persistence record (the keys are the recovery material).

func ResumeMakerForward

func ResumeMakerForward(p MakerForwardResumeParams) (*MakerForwardResult, error)

ResumeMakerForward finishes a maker forward session after a restart: it drives the same on-chain settle loop RunMakerForward ends with, so a mid-swap crash or courier timeout completes (claim on the taker's reveal) or refunds (after T_seq) instead of stranding the maker's asset leg.

func RunMakerForward

func RunMakerForward(p MakerForwardParams, in <-chan []byte, send XcSend) (*MakerForwardResult, error)

RunMakerForward executes the forward handshake as the maker: mint per-lift terms, verify the taker's confirmed BTC leg, lock the SEQ leg, then watch for the taker's claim and redeem the BTC leg with the revealed secret — or refund the SEQ leg once T_seq passes without a claim. `in` delivers sealed courier frames for this session; the driver owns Open (first act) and Seal.

type MakerForwardResumeParams

type MakerForwardResumeParams struct {
	Ops          XcOps
	BtcLeg       *xchain.LegLock // the taker's BTC leg (we claim it with the secret)
	SeqLeg       *xchain.LegLock // our asset leg (we watch it / refund it)
	BtcClaimKey  *xchain.Key
	SeqRefundKey *xchain.Key
	HashH        []byte
	BtcLocktime  uint32
	SeqLocktime  uint32
	AssetHex     string
	BtcAmount    uint64
	SeqAmount    uint64
	SpendFeeSats uint64
	Timing       XcTiming
	OnUpdate     func(*MakerForwardResult)
	Log          func(string, ...interface{})
}

MakerForwardResumeParams reconstructs a maker forward session from persisted state after a restart. All legs/keys/locktimes come from the on-disk record; the driver re-enters the on-chain settle loop with no courier.

type MakerReverseParams

type MakerReverseParams struct {
	// NewOps binds the settlement engine to the freshly minted SECRET (the
	// maker is the secret holder in reverse).
	NewOps  func(secret []byte) (XcOps, error)
	Crypter *Crypter

	// Tip queries usable BEFORE the engine exists.
	BtcTip func() (int64, error)
	SeqTip func() (int64, error)

	AssetHex  string // SEQ asset we buy (offer pair base)
	SeqAmount uint64 // atoms we require
	BtcAmount uint64 // sats we pay
	FeeBtc    uint64 // advisory fee surfaced in terms

	BtcLocktimeDelta uint32 // default 100 (our BTC refund if the taker vanishes; ~16h)
	SeqLocktimeDelta uint32 // default 240 (the taker's refund horizon; ~2h)

	MinBTCConf     int    // confirmations we need on our OWN BTC leg before the anchor gate (default 1)
	SeqClaimMargin uint32 // never reveal the secret closer than this to T_seq (default 10)
	SpendFeeSats   uint64
	Timing         XcTiming
	Log            func(format string, args ...interface{})

	// OnUpdate persists the evolving result; in reverse the SECRET is the
	// maker's crown jewel and is minted here, so the first call (before any
	// coins move) must already durably hold it and both keys.
	OnUpdate func(*MakerReverseResult)
}

MakerReverseParams configures RunMakerReverse.

type MakerReverseResult

type MakerReverseResult struct {
	Secret       []byte
	HashH        []byte
	SeqClaimKey  *xchain.Key // claims the taker's asset leg (reveals the secret)
	BtcRefundKey *xchain.Key // refunds our BTC leg after T_btc
	BtcLocktime  uint32
	SeqLocktime  uint32
	BtcLeg       *xchain.LegLock // our funded BTC leg
	BtcLegHeight int64
	SeqLeg       *xchain.LegLock // the taker's verified asset leg
	SeqBlockHash string
	SeqClaimTxid string
	BtcRefundTx  string
	Settled      bool
}

MakerReverseResult is the reverse maker's persistence record.

func ResumeMakerReverse

func ResumeMakerReverse(p MakerReverseResumeParams) (*MakerReverseResult, error)

ResumeMakerReverse finishes a reverse maker session after a restart. If the taker's asset leg is present and still claimable, it anchor-gates and claims it (revealing the secret; the taker then claims BTC). Otherwise it refunds the maker's BTC leg once T_btc passes. Claim and refund are mutually exclusive, so the secret is never revealed on a path we also refund.

func RunMakerReverse

func RunMakerReverse(p MakerReverseParams, in <-chan []byte, send XcSend) (*MakerReverseResult, error)

RunMakerReverse executes the reverse handshake as the maker: mint the secret and per-lift keys, fund the BTC leg FIRST, announce it with the terms, verify the taker's asset leg through the anchor gate, and claim it (revealing the secret; the taker then claims the BTC leg). If the taker never funds, the session ends with the BTC leg persisted for a T_btc refund.

type MakerReverseResumeParams

type MakerReverseResumeParams struct {
	Ops            XcOps
	BtcLeg         *xchain.LegLock // ours; refunded after T_btc if we cannot settle
	SeqLeg         *xchain.LegLock // the taker's asset leg (nil if never funded/verified)
	SeqBlockHash   string          // the taker leg's confirming block (for the anchor gate)
	Secret         []byte
	HashH          []byte
	SeqClaimKey    *xchain.Key // claims the taker's asset leg (reveals the secret)
	BtcRefundKey   *xchain.Key // refunds our BTC leg after T_btc
	BtcLocktime    uint32
	SeqLocktime    uint32
	AssetHex       string
	BtcAmount      uint64
	SeqAmount      uint64
	SeqClaimMargin uint32
	MinBTCConf     int
	SpendFeeSats   uint64
	Timing         XcTiming
	OnUpdate       func(*MakerReverseResult)
	Log            func(string, ...interface{})
}

MakerReverseResumeParams reconstructs a reverse maker session from persisted state. The maker holds the secret and has funded the BTC leg; depending on how far the swap got, resume either claims the taker's asset leg (if the taker funded it and it is still claimable before T_seq) or refunds the maker's own BTC leg after T_btc. All material comes from the on-disk record.

type MakerReverseSubmarineParams

type MakerReverseSubmarineParams struct {
	// NewMakerOps binds the settlement engine to the maker-generated secret (the
	// maker builds the SubmarineSwap with NewHashLock(secret)).
	NewMakerOps func(secret []byte) SubReverseMakerOps
	Crypter     *Crypter
	SeqTip      func() (int64, error)

	AssetHex    string // SEQ asset the maker sells
	SeqAmount   uint64 // atoms the offer sells
	InvoiceMsat uint64 // BTC-LN the offer wants

	SeqLocktimeDelta uint32 // T_seq above the current SEQ tip (default 240)
	InvoiceCLTV      uint32 // min_final_cltv on the maker's invoice
	Timing           XcTiming
	Log              func(format string, args ...interface{})
}

type MakerReverseSubmarineResult

type MakerReverseSubmarineResult struct {
	HashH        []byte
	SeqRefundKey *xchain.Key // refunds the asset after T_seq if the taker never pays
	SeqLeg       *xchain.LegLock
	SeqLocktime  uint32
	Bolt11       string
	Label        string
	PaidMsat     uint64
	Settled      bool
}

func RunMakerReverseSubmarine

func RunMakerReverseSubmarine(p MakerReverseSubmarineParams, in <-chan []byte, send XcSend) (*MakerReverseSubmarineResult, error)

RunMakerReverseSubmarine executes the reverse maker-secret handshake: receive the taker's claim pubkey, generate P, lock the asset (claim=taker) + issue a plain invoice on H, announce it, and wait for the taker to pay. If the taker never pays, the caller refunds the asset via RefundReverseSEQ after T_seq (the result carries the leg + refund key).

type MakerSubmarineParams

type MakerSubmarineParams struct {
	// NewMakerOps binds the settlement engine once H arrives (the maker knows only
	// H, so it builds the SubmarineSwap with NewHashLockFromHash(H)).
	NewMakerOps func(hashH []byte) SubMakerOps
	Crypter     *Crypter
	SeqTip      func() (int64, error)

	AssetHex    string // SEQ asset the offer sells
	SeqAmount   uint64 // atoms the offer sells (whole-HTLC lift)
	InvoiceMsat uint64 // BTC-LN the offer wants (cross-checks the taker's bolt11)

	SeqLocktimeDelta uint32 // T_seq above the current SEQ tip (default 240)
	MinAnchorDepth   int64  // the funding anchor-depth gate before paying (default 3, >=2)
	SpendFeeAtoms    uint64 // fee for the maker's asset-claim spend (default 1000)
	AnchorTimeout    time.Duration
	Timing           XcTiming
	Log              func(format string, args ...interface{})
}

MakerSubmarineParams configures RunMakerSubmarineNormal. Amounts come from the SIGNED offer; the courier peer and relay are untrusted beyond it.

type MakerSubmarineResult

type MakerSubmarineResult struct {
	HashH        []byte
	SeqClaimKey  *xchain.Key
	SeqLocktime  uint32
	Preimage     []byte
	SeqClaimTxid string
	Settled      bool
}

MakerSubmarineResult is returned even alongside an error, carrying whatever was accomplished (for retry: the maker holds P after a successful pay).

func RunMakerSubmarineNormal

func RunMakerSubmarineNormal(p MakerSubmarineParams, in <-chan []byte, send XcSend) (*MakerSubmarineResult, error)

RunMakerSubmarineNormal executes the NORMAL submarine handshake as the maker: advertise per-lift terms with a fresh SEQ-claim key, receive the taker's funded asset HTLC + BOLT11, and settle (verify -> anchor-gate -> pay -> claim) via SubmarineSwap.RunNormal. `in` delivers sealed courier frames for this session.

type ProposalReq

type ProposalReq struct {
	Offer         *seqobv1.Offer
	TakeBase      uint64
	TakerFeeAsset string
	PayAsset      string
	PayAmount     uint64
	RecvAsset     string
	RecvAmount    uint64
}

ProposalReq carries the resolved taker proposal legs. The taker is the proposer: it PAYS PayAsset (the maker's want_asset) and RECEIVES RecvAsset (the offer_asset).

type RealBackend

type RealBackend struct {

	// FetchUtxos returns the taker's spendable UTXOs (unblinded with blindKeys).
	FetchUtxos func(addr string, blindKeys [][]byte) ([]explorer.Utxo, error)
	// BroadcastFn publishes a raw tx hex and returns its txid.
	BroadcastFn func(txHex string) (string, error)
	// CompleteSwapFn runs the maker responder and MUST honor blind (skip BlindPset
	// for an explicit swap). Wired to wallet.Service.CompleteSwap building blocks.
	CompleteSwapFn func(req *seqdexv1.SwapRequest, blind bool) (signedPSET string, combined []swap.UnblindedInput, err error)
	// contains filtered or unexported fields
}

RealBackend wires the LiveWallet to the PROVEN seqdex same-chain settlement. It reimplements nothing:

  • taker proposer = pkg/trade.NewSwapTx (build PSETv2) + pkg/swap.Request
  • taker finalize = pkg/trade.Wallet.Sign + pkg/swap.Complete + broadcast
  • maker responder = CompleteSwapFn (wallet.Service.CompleteSwap building blocks: SelectUtxos, UpdatePset, the any-asset fee vout per Principle 4, BlindPset IFF blind, SignPset) + pkg/swap.Accept

The three func-field seams (FetchUtxos, BroadcastFn, CompleteSwapFn) are wired to a live Ocean/LWK wallet + node at deploy time; until then they are nil and the relevant method returns errNotWired. Unit tests use a mock Backend, so a live node is needed only for the step-3 acceptance test.

CONFIDENTIAL IS OPT-IN: the taker side honors conf (a confidential output gets the taker's blinding key; an explicit output gets none, and explicit UTXOs reveal all-zero blinders). The maker side passes `blind` through to CompleteSwapFn, which MUST skip BlindPset when blind is false.

func NewRealBackend

func NewRealBackend(net *network.Network, takerPriv, takerBlinding []byte) *RealBackend

NewRealBackend builds a RealBackend with the taker's signing + blinding keys. The maker-side and node seams are set as fields by the deployment.

func (*RealBackend) ProposerBuildRequest

func (b *RealBackend) ProposerBuildRequest(req ProposalReq, conf LegConfidentiality) (*seqdexv1.SwapRequest, error)

ProposerBuildRequest selects the taker's UTXOs, builds the proposer PSETv2 (confidential or explicit per conf), and wraps it in a SwapRequest.

func (*RealBackend) ProposerFinalize

func (b *RealBackend) ProposerFinalize(acc *seqdexv1.SwapAccept) (*seqdexv1.SwapComplete, string, error)

ProposerFinalize signs the taker's inputs, validates via pkg/swap.Complete, extracts the raw tx, and broadcasts.

func (*RealBackend) ResponderComplete

func (b *RealBackend) ResponderComplete(req *seqdexv1.SwapRequest, blind bool) (*seqdexv1.SwapAccept, error)

ResponderComplete runs the maker CompleteSwap (blinded or explicit per blind), then wraps the maker-signed PSET in a SwapAccept.

func (*RealBackend) TakerAddress

func (b *RealBackend) TakerAddress() string

TakerAddress returns the taker wallet's confidential address (the script to fund; its explicit form shares the same scriptPubKey).

type StubWallet

type StubWallet struct {
	// Name disambiguates the placeholder PSET/txid (e.g. "maker" / "taker").
	Name string
}

StubWallet is an IN-MEMORY, NON-CHAIN Wallet for Phase-1 testing and the CLI demo. It produces structurally-correct SwapRequest/SwapAccept/SwapComplete messages (with placeholder PSET strings) so the lift -> courier -> settle MESSAGE FLOW can be exercised without a live Ocean/LWK wallet. It performs NO blinding, signing, or broadcasting. The production path is LiveWallet (live.go).

func (*StubWallet) ProposerBuildRequest

func (w *StubWallet) ProposerBuildRequest(o *seqobv1.Offer, takeBase uint64, takerFeeAsset string) (*seqdexv1.SwapRequest, error)

ProposerBuildRequest builds the taker's SwapRequest. The taker is the proposer: it PAYS want_asset (AssetP) and RECEIVES offer_asset (AssetR).

func (*StubWallet) ProposerFinalize

func (w *StubWallet) ProposerFinalize(acc *seqdexv1.SwapAccept) (*seqdexv1.SwapComplete, string, error)

ProposerFinalize is the taker side. The production impl signs+broadcasts; the stub derives a deterministic placeholder txid.

func (*StubWallet) ResponderComplete

func (w *StubWallet) ResponderComplete(req *seqdexv1.SwapRequest) (*seqdexv1.SwapAccept, error)

ResponderComplete is the maker side. The production impl runs the real CompleteSwap; the stub just echoes a "maker-signed" transaction.

type SubMakerOps

type SubMakerOps interface {
	// RunNormal verifies the taker's funded asset HTLC, waits for it to be
	// anchor-buried, pays the BOLT11 (learning P), and claims the asset with P.
	RunNormal(p xchain.NormalParams, makerClaimKey *xchain.Key, seqClaimFee uint64) (*xchain.NormalResult, error)
}

SubMakerOps is the narrow settlement seam the maker driver runs against. LiveSubMakerOps binds it to a real *xchain.SubmarineSwap; tests fake it.

type SubReverseMakerOps

type SubReverseMakerOps interface {
	OfferReverseMakerSecret(p xchain.ReverseMakerSecretParams) (*xchain.ReverseMakerSecretOffer, error)
	AwaitReversePayment(label string, timeout time.Duration) (uint64, error)
	RefundReverseSEQ(leg *xchain.LegLock, key *xchain.Key, nLockTime uint32, fee uint64) (string, error)
}

SubReverseMakerOps is the settlement seam for the reverse maker.

type SubReverseTakerOps

type SubReverseTakerOps interface {
	VerifySEQLeg(hashH, claimPub, refundPub, providedScript []byte, seqLocktime uint32,
		txid string, vout uint32, amount uint64, assetID string, minConf int) (*xchain.VerifiedSEQLeg, error)
	VerifySeqAnchorBuried(seqBlockHash string, minAnchorDepth int64) (*xchain.SubAnchorEvidence, error)
	PayInvoice(bolt11 string, wantHash []byte, amountMsat uint64) ([]byte, error)
	InjectSecret(secret []byte) error
	ClaimSEQLeg(leg *xchain.LegLock, key *xchain.Key, fee uint64) (string, error)
}

SubReverseTakerOps is the settlement seam for the reverse taker.

type SubTakerOps

type SubTakerOps interface {
	SeqTip() (int64, error)
	// LockSEQLeg funds the asset HTLC (claim=maker, refund=taker) from the taker's
	// wallet, returning the leg and the confirming Sequentia block hash.
	LockSEQLeg(claimPub, refundPub []byte, amountCoins, assetLabel string, locktime uint32) (*xchain.LegLock, string, error)
	// MintInvoice mints a plain BOLT11 on the taker's node bound to preimage P.
	MintInvoice(preimage []byte, amountMsat uint64, cltv uint32, label, desc string) (string, error)
	// AwaitInvoicePaid blocks until that invoice is paid (the taker's BTC-LN).
	AwaitInvoicePaid(label string, timeout time.Duration) (uint64, error)
	// RefundSEQLeg reclaims the asset HTLC via its CLTV branch after T_seq.
	RefundSEQLeg(leg *xchain.LegLock, key *xchain.Key, nLockTime uint32, fee uint64) (string, error)
	// SeqBlockAnchorHeightOf reports a Sequentia block's Bitcoin-anchor height
	// (conveyed to the maker for transparency; the maker re-reads it).
	SeqBlockAnchorHeightOf(blockHash string) (int64, error)
}

SubTakerOps is the settlement seam the taker driver runs against.

type Taker

type Taker struct {
	Wallet Wallet
}

Taker drives the proposer side of a lift. It builds the SwapRequest, seals it for the relay courier, and finalizes the SwapAccept it gets back. The relay only ever moves the sealed bytes (SwapMsg.ciphertext), never the plaintext.

func (*Taker) Finalize

func (t *Taker) Finalize(sealedAccept []byte, c *Crypter) (sealedComplete []byte, txid string, err error)

Finalize opens the maker's sealed SwapAccept, blinds+signs+broadcasts via the wallet, and returns the sealed SwapComplete plus the broadcast txid.

func (*Taker) Propose

func (t *Taker) Propose(o *seqobv1.Offer, takeBase uint64, feeAsset string, c *Crypter) (sealed []byte, req *seqdexv1.SwapRequest, err error)

Propose builds and seals the SwapRequest for an offer lift. It returns both the sealed bytes to courier and the plaintext SwapRequest (for the caller's logs).

type TakerForwardParams

type TakerForwardParams struct {
	Ops     XcOps
	Crypter *Crypter

	Secret       []byte      // 32-byte preimage; Ops' hashlock must be built from it
	BtcRefundKey *xchain.Key // refunds our BTC leg after T_btc
	SeqClaimKey  *xchain.Key // claims the SEQ leg (the claim reveals Secret on-chain)

	ExpectAsset     string // SEQ asset hex the offer sells (required)
	ExpectSeqAmount uint64 // atoms the offer promises (required; whole-HTLC lift)
	ExpectBtcAmount uint64 // sats the offer wants (required)
	MaxFeeBtc       uint64 // refuse terms whose fee_btc exceeds this

	// Locktime sanity. T_btc bounds how long our BTC can be locked before the
	// refund path opens; T_seq must leave us a real window to claim after the
	// anchor gate, and we must never reveal the secret when the maker's refund
	// path is about to open (it could race our claim with its refund). The
	// window must cover a REAL parent confirmation (median ~10 min, long tail)
	// plus the maker's SEQ lock (~3 min worst) plus the anchor gate, in ~30 s
	// SEQ slots — hence the 120-slot (~1 h) default; a maker quoting less is
	// refused BEFORE any BTC moves.
	MaxBtcLockDelta   uint32 // default 400 parent blocks
	MinSeqClaimWindow uint32 // default 120 SEQ blocks at terms time
	SeqClaimMargin    uint32 // default 10 SEQ blocks: refuse to claim closer than this to T_seq

	MinBTCConf   int    // confirmations before announcing our BTC leg (default 1)
	SpendFeeSats uint64 // fee target in native sats (default 1000)
	Timing       XcTiming
	Log          func(format string, args ...interface{})

	// OnBtcLegFunded is invoked the moment the BTC leg is funded, BEFORE the
	// confirmation wait: the caller must persist the result's leg, locktime,
	// and its own keys/secret there, so a crash during the (potentially long)
	// wait never strands coins behind an unknowable redeem script.
	OnBtcLegFunded func(*TakerForwardResult)
}

TakerForwardParams configures RunTakerForward. Expectations come from the SIGNED offer the taker verified before lifting; the courier peer and relay are untrusted beyond it.

type TakerForwardResult

type TakerForwardResult struct {
	Terms        *XcMsg
	BtcLeg       *xchain.LegLock
	BtcLegHeight int64
	BtcLocktime  uint32
	SeqLeg       *xchain.LegLock
	SeqClaimTxid string
	Evidence     *xchain.AnchorEvidence
}

TakerForwardResult is returned even alongside an error once the BTC leg is funded, so the caller can persist it and refund after T_btc.

func RunTakerForward

func RunTakerForward(p TakerForwardParams, send XcSend, recv XcRecv) (*TakerForwardResult, error)

RunTakerForward executes the forward handshake as the taker: request terms, fund + confirm the BTC leg, announce it, verify the maker's SEQ leg through the anchor gate, and claim it (revealing the secret). The maker then extracts the secret from our claim on-chain; no further courier messages are needed.

type TakerReverseParams

type TakerReverseParams struct {
	// NewOps binds the settlement engine to the hashlock H once the maker's
	// terms arrive. The taker does not know the secret, but its SEQ leg script
	// and its BTC claim both embed H, so the swap must be built from the H in
	// the terms (mirrors the forward maker's NewOps(hashH)).
	NewOps  func(hashH []byte) (XcOps, error)
	Crypter *Crypter

	BtcClaimKey  *xchain.Key // claims the maker's BTC leg once the secret is revealed
	SeqRefundKey *xchain.Key // refunds our SEQ leg after T_seq if the maker never claims

	ExpectAsset     string // SEQ asset hex we sell (required)
	ExpectSeqAmount uint64 // atoms we deliver (required; whole-HTLC lift)
	ExpectBtcAmount uint64 // sats the offer pays (required)
	MaxFeeBtc       uint64 // refuse terms whose fee_btc exceeds this

	// Locktime sanity. T_btc is when the MAKER can take its BTC back, so it
	// must leave us real runway to claim after the secret appears; T_seq is
	// when WE can refund our asset leg, so it must fit the funding +
	// confirmation + the maker's gate-and-claim.
	MinBtcClaimDelta uint32 // T_btc >= btcTip + this (default 30 parent blocks)
	MinSeqFundWindow uint32 // T_seq >= seqTip + this (default 120 SEQ blocks)
	BtcClaimMargin   uint32 // refuse to claim closer than this to T_btc (default 6)

	MinBTCConf   int    // confirmations required on the MAKER's BTC leg before we fund (default 1)
	SpendFeeSats uint64 // fee target in native sats (default 1000)
	Timing       XcTiming
	Log          func(format string, args ...interface{})

	// OnSeqLegFunded is invoked the moment our SEQ leg is funded, BEFORE any
	// further wait: the caller must persist the result there so a crash never
	// strands the asset behind an unknowable redeem script.
	OnSeqLegFunded func(*TakerReverseResult)
}

TakerReverseParams configures RunTakerReverse. Expectations come from the SIGNED offer (a maker BUY: it gives BTC, wants the asset).

type TakerReverseResult

type TakerReverseResult struct {
	Terms        *XcMsg // the maker's XcBtcLegLocked (terms ride in it)
	BtcLeg       *xchain.LegLock
	BtcLocktime  uint32
	SeqLeg       *xchain.LegLock
	SeqBlockHash string
	SeqLocktime  uint32
	Secret       []byte
	BtcClaimTxid string
	SeqRefundTx  string
}

TakerReverseResult is returned even alongside an error once the SEQ leg is funded, so the caller can persist it and refund after T_seq.

func RunTakerReverse

func RunTakerReverse(p TakerReverseParams, send XcSend, recv XcRecv) (*TakerReverseResult, error)

RunTakerReverse executes the reverse handshake as the taker: request terms (shipping both taker keys), verify the maker's BTC leg through our own node's confirmation, fund the SEQ asset leg, then watch our leg for the maker's claim (which reveals the secret on-chain) and claim the BTC leg — or refund the SEQ leg once T_seq passes without a claim.

type TakerReverseSubmarineParams

type TakerReverseSubmarineParams struct {
	// NewTakerOps binds the engine once H arrives (taker builds the SubmarineSwap
	// with NewHashLockFromHash(H); it learns P only by paying).
	NewTakerOps func(hashH []byte) SubReverseTakerOps
	Crypter     *Crypter

	SeqClaimKey *xchain.Key // claims the asset with the learned P

	ExpectAsset       string // SEQ asset hex the offer sells (required)
	ExpectSeqAmount   uint64 // atoms the offer promises (required)
	ExpectInvoiceMsat uint64 // BTC-LN we expect to pay (required)

	MinAnchorDepth int64  // anchor-depth gate we require before paying (>=2, default 3)
	SpendFeeAtoms  uint64 // asset-claim fee (default 1000)
	Timing         XcTiming
	Log            func(format string, args ...interface{})

	// OnPaid is invoked the instant the invoice is paid and P is learned, BEFORE
	// the asset claim, so the caller persists P + the leg: a crash between paying
	// (irreversible) and claiming must not lose P (the only key to the asset).
	OnPaid func(preimage []byte, leg *xchain.LegLock)
}

type TakerReverseSubmarineResult

type TakerReverseSubmarineResult struct {
	Terms        *XcMsg
	SeqLeg       *xchain.LegLock
	Anchor       *xchain.SubAnchorEvidence
	Preimage     []byte
	SeqClaimTxid string
	Settled      bool
}

func RunTakerReverseSubmarine

func RunTakerReverseSubmarine(p TakerReverseSubmarineParams, send XcSend, recv XcRecv) (*TakerReverseSubmarineResult, error)

RunTakerReverseSubmarine executes the reverse maker-secret handshake as the taker: request terms with a fresh claim key, verify the maker's locked asset HTLC, run the anchor-depth gate BEFORE paying, pay the invoice (learning P), and claim the asset. It NEVER pays until the asset HTLC is anchor-buried, because a plain invoice cannot be refunded once paid.

type TakerSubmarineParams

type TakerSubmarineParams struct {
	Ops     SubTakerOps
	Crypter *Crypter

	Secret       []byte      // 32-byte preimage P (taker chooses; the invoice is minted on it)
	SeqRefundKey *xchain.Key // refunds the asset HTLC after T_seq

	ExpectAsset       string // SEQ asset hex the offer sells (required)
	ExpectSeqAmount   uint64 // atoms the offer promises (required)
	ExpectInvoiceMsat uint64 // BTC-LN we expect to receive (mint the invoice for this)

	MinSeqClaimWindow uint32 // refuse terms whose T_seq leaves less than this window (default 120)
	InvoiceCLTV       uint32 // min_final_cltv on the minted invoice (0 = node default)
	SpendFeeAtoms     uint64 // refund fee (default 1000)
	Timing            XcTiming
	Log               func(format string, args ...interface{})

	// OnAssetFunded is invoked the moment the asset HTLC is funded, so the caller
	// persists the leg + keys + secret before the (potentially long) settle wait.
	OnAssetFunded func(*TakerSubmarineResult)
}

TakerSubmarineParams configures RunTakerSubmarineNormal. Expectations come from the SIGNED offer the taker verified before lifting.

type TakerSubmarineResult

type TakerSubmarineResult struct {
	Terms        *XcMsg
	SeqLeg       *xchain.LegLock
	SeqBlock     string
	SeqLocktime  uint32
	Bolt11       string
	InvoiceLabel string
	PaidMsat     uint64
	Settled      bool
}

TakerSubmarineResult is returned even alongside an error once the asset HTLC is funded, so the caller can persist it and refund after T_seq.

func RunTakerSubmarineNormal

func RunTakerSubmarineNormal(p TakerSubmarineParams, send XcSend, recv XcRecv) (*TakerSubmarineResult, error)

RunTakerSubmarineNormal executes the NORMAL submarine handshake as the taker: request terms, mint a BOLT11 on P, fund the asset HTLC (claim=maker), announce both, and wait for the invoice to be paid (its BTC-LN). If the maker never pays before T_seq, the caller refunds the asset HTLC (RefundTakerSubmarine).

type Wallet

type Wallet interface {
	// ProposerBuildRequest (taker side): build the proposer PSET that pays the
	// maker its want_asset and receives the offer_asset, for takeBase base atoms
	// (partial fills allowed). takerFeeAsset selects the open-market fee asset.
	ProposerBuildRequest(o *seqobv1.Offer, takeBase uint64, takerFeeAsset string) (*seqdexv1.SwapRequest, error)

	// ResponderComplete (maker side): run the existing CompleteSwap responder
	// path against the taker's SwapRequest -> SwapAccept.
	ResponderComplete(req *seqdexv1.SwapRequest) (*seqdexv1.SwapAccept, error)

	// ProposerFinalize (taker side): blind+sign own inputs, validate, broadcast ->
	// SwapComplete plus the broadcast txid.
	ProposerFinalize(acc *seqdexv1.SwapAccept) (*seqdexv1.SwapComplete, string, error)
}

Wallet is the settlement contract the lift handshake drives. It is expressed in terms of the EXISTING seqdex swap wire messages (seqdexv1.SwapRequest / SwapAccept / SwapComplete) so the production implementation is a thin adapter over the proven primitives — it does NOT reimplement settlement:

  • ProposerBuildRequest -> pkg/trade.NewSwapTx + pkg/swap.Request
  • ResponderComplete -> wallet.Service.CompleteSwap + pkg/swap.Accept
  • ProposerFinalize -> pkg/trade.Wallet.Sign + pkg/swap.Complete + broadcast

See live.go for the concrete reuse skeleton, and stubwallet.go for the in-memory implementation used by the Phase-1 tests and CLI.

type XcLeg

type XcLeg struct {
	Txid         string `json:"txid"`
	Vout         uint32 `json:"vout"`
	Amount       uint64 `json:"amount"`
	Asset        string `json:"asset,omitempty"` // SEQ leg only (asset id hex)
	RedeemScript string `json:"redeem_script"`   // hex
	Locktime     uint32 `json:"locktime"`
	Height       int64  `json:"height,omitempty"`        // confirmed height (BTC leg, for anchor ordering)
	BlockHash    string `json:"block_hash,omitempty"`    // SEQ leg, for the anchor gate
	AnchorHeight int64  `json:"anchor_height,omitempty"` // SEQ leg's Bitcoin-anchor height
}

XcLeg is a funded HTLC leg conveyed over the courier. Byte fields are hex. The receiver re-derives and byte-compares the redeem script before trusting any leg (the relay and the message are untrusted); amounts/asset/locktime bind it to terms.

type XcMsg

type XcMsg struct {
	Type XcMsgType `json:"type"`

	// Terms (maker -> taker, forward) / BtcLegLocked terms (reverse).
	MakerBtcClaimPub string `json:"maker_btc_claim_pub,omitempty"` // forward: maker claims BTC with s
	MakerSeqClaimPub string `json:"maker_seq_claim_pub,omitempty"` // reverse: maker claims SEQ with s
	MakerRefundPub   string `json:"maker_refund_pub,omitempty"`    // forward: maker's SEQ refund; reverse: maker's BTC refund
	BtcLocktime      uint32 `json:"btc_locktime,omitempty"`        // T_btc (the longer leg)
	SeqLocktime      uint32 `json:"seq_locktime,omitempty"`        // T_seq (the shorter leg)
	BtcAmount        uint64 `json:"btc_amount,omitempty"`          // sats
	SeqAmount        uint64 `json:"seq_amount,omitempty"`          // asset atoms
	FeeBtc           uint64 `json:"fee_btc,omitempty"`

	// Secret hash + taker pubkeys.
	HashH             string `json:"hash_h,omitempty"`               // sha256(secret), hex
	TakerSeqClaimPub  string `json:"taker_seq_claim_pub,omitempty"`  // forward: taker claims SEQ with s
	TakerBtcRefundPub string `json:"taker_btc_refund_pub,omitempty"` // forward: taker refunds BTC after T_btc
	TakerSeqRefundPub string `json:"taker_seq_refund_pub,omitempty"` // reverse: taker refunds SEQ after T_seq
	TakerBtcClaimPub  string `json:"taker_btc_claim_pub,omitempty"`  // reverse: taker claims BTC with s

	Leg *XcLeg `json:"leg,omitempty"` // the leg this message conveys

	// Submarine-swap (Lightning) fields. The BTC leg is a BOLT11 payment, not an
	// on-chain HTLC, so these carry it instead of an XcLeg. See xcourier_submarine.go.
	Bolt11         string `json:"bolt11,omitempty"`           // submarine: the invoice bound to H
	MinAnchorDepth uint32 `json:"min_anchor_depth,omitempty"` // submarine: the gate depth (transparency)
	SettleTxid     string `json:"settle_txid,omitempty"`      // submarine: the on-chain asset-claim txid (informational)

	Preimage string `json:"preimage,omitempty"` // XcSecretRevealed
	Code     string `json:"code,omitempty"`     // XcFail
	Message  string `json:"message,omitempty"`  // XcFail
}

XcMsg is the tagged union for the cross-chain courier. Only the fields relevant to Type are set; omitempty keeps each message compact.

func OpenXcMsg

func OpenXcMsg(sealed []byte, c *Crypter) (*XcMsg, error)

OpenXcMsg opens a sealed cross-chain courier message with the session Crypter.

func (*XcMsg) Seal

func (m *XcMsg) Seal(c *Crypter) ([]byte, error)

Seal JSON-marshals the message and seals it for the courier with the session Crypter.

type XcMsgType

type XcMsgType string

xcourier.go defines the CROSS-CHAIN lift handshake messages. They travel as the plaintext sealed inside the existing opaque relay courier (SwapMsg.ciphertext): the relay only ever moves sealed bytes and never parses these. They are JSON (not protobuf) by deliberate choice: both endpoints are new code, the relay is blind, and JSON keeps the multi-round HTLC handshake easy to evolve. The on-chain settlement itself is the proven pkg/xchain HTLC swap; this is only the negotiation + leg-exchange transport that replaces the RFQ HTTP round-trips.

Handshake (taker always initiates the courier exchange, as in the same-chain path):

FORWARD  (offer.direction = BTC_TO_ASSET; taker pays BTC, secret holder = TAKER)
  taker -> XcTermsRequest
  maker -> XcTerms{maker_btc_claim_pub, maker_refund_pub (seq), btc/seq locktime+amount, fee_btc}
  taker -> XcBtcLegFunded{hash_h, leg(BTC), taker_seq_claim_pub, taker_btc_refund_pub}
  maker -> XcSeqLegLocked{leg(SEQ, with block_hash+anchor_height)}
  taker  : VerifySeqLegSafe (anchor gate) then ClaimSEQLeg -> reveals s on-chain
  maker  : watches the SEQ claim, extracts s, ClaimBTCLeg

REVERSE  (offer.direction = ASSET_TO_BTC; taker sells the asset, secret holder = MAKER)
  taker -> XcTermsRequest{taker_seq_refund_pub, taker_btc_claim_pub}
           (the BTC HTLC's claim branch pays the taker, so its key must reach
           the maker BEFORE the maker funds that leg)
  maker -> XcBtcLegLocked{hash_h, leg(BTC), maker_seq_claim_pub, maker_refund_pub (btc),
           seq locktime, btc/seq amounts (the terms ride in this message)}
  taker  : verify the BTC leg, await its confirmation, fund the SEQ leg
  taker -> XcSeqLegFunded{leg(SEQ, with block_hash+anchor_height)}
  maker  : VerifySeqLegSafe then ClaimSEQLeg -> reveals s
  maker -> XcSecretRevealed{preimage}   (taker can also read s off the SEQ-claim scriptSig)
  taker  : ClaimBTCLeg
const (
	XcTermsRequest   XcMsgType = "terms_request"
	XcTerms          XcMsgType = "terms"           // forward: maker's per-lift terms
	XcBtcLegFunded   XcMsgType = "btc_leg_funded"  // forward: taker funded the BTC leg
	XcSeqLegLocked   XcMsgType = "seq_leg_locked"  // forward: maker locked the SEQ leg
	XcBtcLegLocked   XcMsgType = "btc_leg_locked"  // reverse: maker locked the BTC leg
	XcSeqLegFunded   XcMsgType = "seq_leg_funded"  // reverse: taker funded the SEQ leg
	XcSecretRevealed XcMsgType = "secret_revealed" // reverse: maker reveals s after claiming SEQ
	XcFail           XcMsgType = "fail"
)
const (
	XcSubTermsRequest XcMsgType = "sub_terms_request"
	XcSubTerms        XcMsgType = "sub_terms"        // normal: maker's per-lift terms
	XcSubAssetFunded  XcMsgType = "sub_asset_funded" // normal: taker funded the asset HTLC + bolt11
	XcSubAssetLocked  XcMsgType = "sub_asset_locked" // reverse: maker locked the asset HTLC + bolt11
	XcSubSettled      XcMsgType = "sub_settled"      // maker claimed the asset (informational)
)

xcourier_submarine.go adds the SUBMARINE-SWAP (Sequentia asset on-chain <-> BTC over Lightning) lift handshake to the cross-chain courier. It reuses the same opaque relay transport, session Crypter, and XcMsg envelope (Seal/OpenXcMsg) as the on-chain cross-chain path (xcourier.go); only the message TYPES and the BTC-leg representation differ — the BTC leg is a BOLT11 payment (XcMsg.Bolt11), not an on-chain HTLC (XcLeg). The Sequentia asset leg and the anchor-depth gate are the proven pkg/xchain primitives (submarine.go), unchanged.

v1 = the NORMAL direction (offer sells a Sequentia asset for BTC-LN; secret holder = TAKER). It needs no hold-invoice plugin (maker pays with core `pay`).

NORMAL  (ln_direction = ASSET_ONCHAIN_FOR_BTC_LN; taker sells asset, receives BTC-LN)
  taker -> XcSubTermsRequest
  maker -> XcSubTerms{maker_seq_claim_pub, seq_locktime, seq_amount, min_anchor_depth}
  taker  : mint a BOLT11 on a chosen preimage P (payment_hash = H); fund the asset
           HTLC (claim=maker, refund=taker)
  taker -> XcSubAssetFunded{hash_h, taker_seq_refund_pub, leg(SEQ block_hash+anchor),
           bolt11}
  maker  : VerifySEQLeg -> wait the funding is anchor-buried >= min_anchor_depth ->
           Pay(bolt11) (learns P) -> ClaimSEQLeg(P)
  maker -> XcSubSettled{settle_txid}   (informational; the taker already has its BTC-LN)
  If the maker never pays, the taker refunds the asset HTLC after seq_locktime.

The REVERSE maker-secret direction (ln_direction = BTC_LN_FOR_ASSET_ONCHAIN) is the mirror: taker -> XcSubTermsRequest{taker_seq_claim_pub}; maker -> XcSubAssetLocked{hash_h, maker_seq_refund_pub, seq_locktime, leg(SEQ), bolt11}; the taker gates the maker's asset HTLC (VerifySeqAnchorBuried) BEFORE paying the invoice, learns P, and claims the asset. Wired in a later slice.

type XcOps

type XcOps interface {
	// BTC side.
	BtcTip() (int64, error)
	BtcConfirmations(txid string) (int, error)
	LockBTCLeg(claimPub, refundPub []byte, amountCoins string, locktime uint32) (*xchain.LegLock, int64, error)
	VerifyBTCLeg(hashH, makerClaimPub, takerRefundPub, providedScript []byte, btcLocktime uint32,
		txid string, vout uint32, amount uint64, minConf int) (*xchain.VerifiedBTCLeg, error)
	ClaimBTCLeg(leg *xchain.LegLock, key *xchain.Key, fee uint64) (string, error)
	RefundBTCLeg(leg *xchain.LegLock, key *xchain.Key, nLockTime uint32, fee uint64) (string, error)

	// SEQ side (always the anchored Sequentia/Elements node).
	SeqTip() (int64, error)
	SeqAnchorHeightOf(blockHash string) (int64, error)
	SeqBlockHashOfTx(txid string) (string, error)
	SeqFeeRate(assetHex string) (uint64, bool)
	LockSEQLeg(claimPub, refundPub []byte, amountCoins, assetLabel string, locktime uint32) (*xchain.LegLock, string, error)
	VerifySEQLeg(hashH, claimPub, refundPub, providedScript []byte, seqLocktime uint32,
		txid string, vout uint32, amount uint64, assetID string, minConf int) (*xchain.VerifiedSEQLeg, error)
	VerifySeqLegSafe(seqBlockHash string, btcLegHeight int64) (*xchain.AnchorEvidence, error)
	ClaimSEQLeg(leg *xchain.LegLock, key *xchain.Key, fee uint64) (string, error)
	WatchSEQClaim(leg *xchain.LegLock) (claimTxid string, secret []byte, err error)
	InjectSecret(secret []byte) error
	RefundSEQLeg(leg *xchain.LegLock, key *xchain.Key, nLockTime uint32, fee uint64) (string, error)
	SeqBroadcast(rawHex string) (string, error)
}

XcOps is the narrow settlement seam the drivers run against. LiveXcOps binds it to a real *xchain.Swap + chains; tests substitute a fake to exercise the handshake without RPC. Method contracts are pkg/xchain's.

type XcRecv

type XcRecv func(timeout time.Duration) ([]byte, error)

XcRecv blocks for the next sealed courier frame, up to timeout.

type XcSend

type XcSend func(sealed []byte) error

XcSend delivers one sealed courier frame to the peer (a relay SwapMsg write).

type XcTiming

type XcTiming struct {
	Poll         time.Duration // chain/watch polling cadence (default 5s)
	TermsWait    time.Duration // taker: wait for XcTerms (default 2m)
	BtcConfWait  time.Duration // taker: wait for own BTC-leg confirmation (default 90m)
	SeqLockWait  time.Duration // taker: wait for XcSeqLegLocked after announcing (default 15m; maker's LockSEQLeg alone can take ~3m)
	AnchorWait   time.Duration // taker: wait for the anchor gate to pass (default 20m)
	TermsReqWait time.Duration // maker: wait for XcTermsRequest (default 2m)
	BtcFundWait  time.Duration // maker: wait for XcBtcLegFunded (default 2h; covers the taker's conf wait)
}

XcTiming bundles the polling/wait knobs; zero values take the defaults.

Jump to

Keyboard shortcuts

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