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 ¶
- Variables
- func RefundTakerBTC(ops XcOps, leg *xchain.LegLock, key *xchain.Key, locktime uint32, ...) (string, error)
- func RefundTakerSEQ(ops XcOps, leg *xchain.LegLock, key *xchain.Key, locktime uint32, ...) (string, error)
- func RefundTakerSubmarine(ops SubTakerOps, leg *xchain.LegLock, key *xchain.Key, seqLocktime uint32, ...) (string, error)
- func ValidateRequestAgainstOffer(req *seqdexv1.SwapRequest, o *seqobv1.Offer) error
- type Backend
- type Crypter
- type LegConfidentiality
- type LiveSubMakerOps
- type LiveSubReverseMakerOps
- func (o *LiveSubReverseMakerOps) AwaitReversePayment(label string, timeout time.Duration) (uint64, error)
- func (o *LiveSubReverseMakerOps) OfferReverseMakerSecret(p xchain.ReverseMakerSecretParams) (*xchain.ReverseMakerSecretOffer, error)
- func (o *LiveSubReverseMakerOps) RefundReverseSEQ(leg *xchain.LegLock, key *xchain.Key, nLockTime uint32, fee uint64) (string, error)
- type LiveSubReverseTakerOps
- func (o *LiveSubReverseTakerOps) ClaimSEQLeg(leg *xchain.LegLock, key *xchain.Key, fee uint64) (string, error)
- func (o *LiveSubReverseTakerOps) InjectSecret(secret []byte) error
- func (o *LiveSubReverseTakerOps) PayInvoice(bolt11 string, wantHash []byte, amountMsat uint64) ([]byte, error)
- func (o *LiveSubReverseTakerOps) VerifySEQLeg(hashH, claimPub, refundPub, providedScript []byte, seqLocktime uint32, ...) (*xchain.VerifiedSEQLeg, error)
- func (o *LiveSubReverseTakerOps) VerifySeqAnchorBuried(seqBlockHash string, minAnchorDepth int64) (*xchain.SubAnchorEvidence, error)
- type LiveSubTakerOps
- func (o *LiveSubTakerOps) AwaitInvoicePaid(label string, timeout time.Duration) (uint64, error)
- func (o *LiveSubTakerOps) LockSEQLeg(claimPub, refundPub []byte, amountCoins, assetLabel string, locktime uint32) (*xchain.LegLock, string, error)
- func (o *LiveSubTakerOps) MintInvoice(preimage []byte, amountMsat uint64, cltv uint32, label, desc string) (string, error)
- func (o *LiveSubTakerOps) RefundSEQLeg(leg *xchain.LegLock, key *xchain.Key, nLockTime uint32, fee uint64) (string, error)
- func (o *LiveSubTakerOps) SeqBlockAnchorHeightOf(blockHash string) (int64, error)
- func (o *LiveSubTakerOps) SeqTip() (int64, error)
- type LiveWallet
- func (w *LiveWallet) ProposerBuildRequest(o *seqobv1.Offer, takeBase uint64, takerFeeAsset string) (*seqdexv1.SwapRequest, error)
- func (w *LiveWallet) ProposerFinalize(acc *seqdexv1.SwapAccept) (*seqdexv1.SwapComplete, string, error)
- func (w *LiveWallet) ResponderComplete(req *seqdexv1.SwapRequest) (*seqdexv1.SwapAccept, error)
- type LiveXcOps
- func (o *LiveXcOps) BtcConfirmations(txid string) (int, error)
- func (o *LiveXcOps) BtcTip() (int64, error)
- func (o *LiveXcOps) ClaimBTCLeg(leg *xchain.LegLock, key *xchain.Key, fee uint64) (string, error)
- func (o *LiveXcOps) ClaimSEQLeg(leg *xchain.LegLock, key *xchain.Key, fee uint64) (string, error)
- func (o *LiveXcOps) InjectSecret(secret []byte) error
- func (o *LiveXcOps) LockBTCLeg(claimPub, refundPub []byte, amountCoins string, locktime uint32) (*xchain.LegLock, int64, error)
- func (o *LiveXcOps) LockSEQLeg(claimPub, refundPub []byte, amountCoins, assetLabel string, locktime uint32) (*xchain.LegLock, string, error)
- func (o *LiveXcOps) RefundBTCLeg(leg *xchain.LegLock, key *xchain.Key, nLockTime uint32, fee uint64) (string, error)
- func (o *LiveXcOps) RefundSEQLeg(leg *xchain.LegLock, key *xchain.Key, nLockTime uint32, fee uint64) (string, error)
- func (o *LiveXcOps) SeqAnchorHeightOf(blockHash string) (int64, error)
- func (o *LiveXcOps) SeqBlockHashOfTx(txid string) (string, error)
- func (o *LiveXcOps) SeqBroadcast(rawHex string) (string, error)
- func (o *LiveXcOps) SeqFeeRate(assetHex string) (uint64, bool)
- func (o *LiveXcOps) SeqTip() (int64, error)
- func (o *LiveXcOps) VerifyBTCLeg(hashH, makerClaimPub, takerRefundPub, providedScript []byte, ...) (*xchain.VerifiedBTCLeg, error)
- func (o *LiveXcOps) VerifySEQLeg(hashH, claimPub, refundPub, providedScript []byte, seqLocktime uint32, ...) (*xchain.VerifiedSEQLeg, error)
- func (o *LiveXcOps) VerifySeqLegSafe(seqBlockHash string, btcLegHeight int64) (*xchain.AnchorEvidence, error)
- func (o *LiveXcOps) WatchSEQClaim(leg *xchain.LegLock) (string, []byte, error)
- type Maker
- type MakerForwardParams
- type MakerForwardResult
- type MakerForwardResumeParams
- type MakerReverseParams
- type MakerReverseResult
- type MakerReverseResumeParams
- type MakerReverseSubmarineParams
- type MakerReverseSubmarineResult
- type MakerSubmarineParams
- type MakerSubmarineResult
- type ProposalReq
- type RealBackend
- func (b *RealBackend) ProposerBuildRequest(req ProposalReq, conf LegConfidentiality) (*seqdexv1.SwapRequest, error)
- func (b *RealBackend) ProposerFinalize(acc *seqdexv1.SwapAccept) (*seqdexv1.SwapComplete, string, error)
- func (b *RealBackend) ResponderComplete(req *seqdexv1.SwapRequest, blind bool) (*seqdexv1.SwapAccept, error)
- func (b *RealBackend) TakerAddress() string
- type StubWallet
- func (w *StubWallet) ProposerBuildRequest(o *seqobv1.Offer, takeBase uint64, takerFeeAsset string) (*seqdexv1.SwapRequest, error)
- func (w *StubWallet) ProposerFinalize(acc *seqdexv1.SwapAccept) (*seqdexv1.SwapComplete, string, error)
- func (w *StubWallet) ResponderComplete(req *seqdexv1.SwapRequest) (*seqdexv1.SwapAccept, error)
- type SubMakerOps
- type SubReverseMakerOps
- type SubReverseTakerOps
- type SubTakerOps
- type Taker
- type TakerForwardParams
- type TakerForwardResult
- type TakerReverseParams
- type TakerReverseResult
- type TakerReverseSubmarineParams
- type TakerReverseSubmarineResult
- type TakerSubmarineParams
- type TakerSubmarineResult
- type Wallet
- type XcLeg
- type XcMsg
- type XcMsgType
- type XcOps
- type XcRecv
- type XcSend
- type XcTiming
Constants ¶
This section is empty.
Variables ¶
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.
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 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 ¶
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.
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 (*LiveSubReverseMakerOps) OfferReverseMakerSecret ¶
func (o *LiveSubReverseMakerOps) OfferReverseMakerSecret(p xchain.ReverseMakerSecretParams) (*xchain.ReverseMakerSecretOffer, error)
type LiveSubReverseTakerOps ¶
type LiveSubReverseTakerOps struct{ Sub *xchain.SubmarineSwap }
func (*LiveSubReverseTakerOps) ClaimSEQLeg ¶
func (*LiveSubReverseTakerOps) InjectSecret ¶
func (o *LiveSubReverseTakerOps) InjectSecret(secret []byte) error
func (*LiveSubReverseTakerOps) PayInvoice ¶
func (*LiveSubReverseTakerOps) VerifySEQLeg ¶
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 (*LiveSubTakerOps) LockSEQLeg ¶
func (*LiveSubTakerOps) MintInvoice ¶
func (*LiveSubTakerOps) RefundSEQLeg ¶
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 ¶
LiveXcOps implements XcOps over a real swap and its chains.
func (*LiveXcOps) ClaimBTCLeg ¶
func (*LiveXcOps) ClaimSEQLeg ¶
func (*LiveXcOps) InjectSecret ¶
func (*LiveXcOps) LockBTCLeg ¶
func (*LiveXcOps) LockSEQLeg ¶
func (*LiveXcOps) RefundBTCLeg ¶
func (*LiveXcOps) RefundSEQLeg ¶
func (*LiveXcOps) SeqAnchorHeightOf ¶
func (*LiveXcOps) SeqBlockHashOfTx ¶
func (*LiveXcOps) VerifyBTCLeg ¶
func (*LiveXcOps) VerifySEQLeg ¶
func (*LiveXcOps) VerifySeqLegSafe ¶
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 ¶
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.
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 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.