Documentation
¶
Index ¶
- Constants
- Variables
- func CombineFinalSig(combinedNoncePoint *btcec.PublicKey, allSigs []*musig2.PartialSignature, ...) (*schnorr.Signature, error)
- func ComputeTweakedOutputKey(keys []*btcec.PublicKey, merkleRoot []byte) (*btcec.PublicKey, error)
- func IsBolt12Invoice(invoice string) bool
- func IsBolt12Offer(offer string) bool
- func IsValidBolt12Offer(offer string) bool
- func NewPrevOutputFetcher(prevOut *wire.TxOut, prevOutPoint wire.OutPoint) txscript.PrevOutputFetcher
- func ParsePartialSignatureScalar32(sigHex string) (*musig2.PartialSignature, error)
- func ParsePubNonce(nonceHex string) ([66]byte, error)
- func SatsFromBolt12Offer(offer string) int
- func SerializePubNonce(nonce [66]byte) string
- func TaprootMessage(tx *wire.MsgTx, inputIndex int, prevOutFetcher txscript.PrevOutputFetcher) ([32]byte, error)
- func VerifyFinalSig(msg [32]byte, finalSig *schnorr.Signature, tweakedOutputKey *btcec.PublicKey) error
- type ChainSwap
- func (s *ChainSwap) Claim(txid string)
- func (s *ChainSwap) Fail(err string)
- func (s *ChainSwap) Refund(txid string)
- func (s *ChainSwap) RefundFailed(err string)
- func (s *ChainSwap) RefundUnilaterally(txid string)
- func (s *ChainSwap) ServerLock(txid string)
- func (s *ChainSwap) UserLock(txid string)
- func (s *ChainSwap) UserLockedFailed(err string)
- type ChainSwapEvent
- type ChainSwapEventCallback
- type ChainSwapEventHandler
- type ChainSwapState
- type ChainSwapStatus
- type ClaimEvent
- type ClaimTransactionParams
- type CreateEvent
- type ExplorerClient
- type FailEvent
- type HTLCComponents
- type Invoice
- type MuSigContext
- func (c *MuSigContext) AggregateNonces(theirNonce [66]byte) ([66]byte, error)
- func (c *MuSigContext) GenerateNonce() ([66]byte, error)
- func (c *MuSigContext) Keys() []*btcec.PublicKey
- func (c *MuSigContext) OurPartialSign(combinedNonce [66]byte, keys []*btcec.PublicKey, msg [32]byte, ...) (*musig2.PartialSignature, error)
- type Offer
- type RefundEvent
- type RefundEventUnilaterally
- type RefundFailedEvent
- type RefundHTLCComponents
- type ResumeChainSwapParams
- type ServerLockEvent
- type Swap
- type SwapHandler
- func (h *SwapHandler) ChainSwapArkToBtc(ctx context.Context, amount uint64, btcDestinationAddress string, ...) (*ChainSwap, error)
- func (h *SwapHandler) ChainSwapBtcToArk(_ context.Context, amount uint64, network *chaincfg.Params, ...) (*ChainSwap, error)
- func (h *SwapHandler) ClaimVHTLC(ctx context.Context, preimage []byte, vhtlcOpts vhtlc.Opts) (string, error)
- func (h *SwapHandler) GetInvoice(ctx context.Context, amount uint64, postProcess func(swap Swap) error) (Swap, error)
- func (h *SwapHandler) GetVHTLCFunds(ctx context.Context, vhtlcOpts []vhtlc.Opts) ([]types.Vtxo, error)
- func (h *SwapHandler) PayInvoice(ctx context.Context, invoice string, unilateralRefund func(swap Swap) error) (*Swap, error)
- func (h *SwapHandler) PayOffer(ctx context.Context, offer string, lightningUrl string, ...) (*Swap, error)
- func (h *SwapHandler) RefundArkToBTCSwap(ctx context.Context, swapId string, vhtlcOpts vhtlc.Opts, ...) (string, error)
- func (h *SwapHandler) RefundBtcToArkSwap(ctx context.Context, swapId string, amount uint64, userLockupTxid string, ...) (string, error)
- func (h *SwapHandler) RefundSwap(ctx context.Context, swapType, swapId string, withReceiver bool, ...) (string, error)
- func (h *SwapHandler) ResumeChainSwap(ctx context.Context, params ResumeChainSwapParams) (*ChainSwap, error)
- func (h *SwapHandler) SettleVHTLCWithClaimPath(ctx context.Context, vhtlcOpts vhtlc.Opts, preimage []byte) (string, error)
- func (h *SwapHandler) SettleVHTLCWithCollaborativeRefundPath(ctx context.Context, vhtlcOpts vhtlc.Opts, ...) (string, error)
- func (h *SwapHandler) SettleVhtlcWithRefundPath(ctx context.Context, vhtlcOpts vhtlc.Opts) (string, error)
- type SwapStatus
- type TransactionStatus
- type UserLockEvent
- type UserLockFailedEvent
Constants ¶
const ( OFFER_CHAINS uint64 = 2 OFFER_METADATA uint64 = 4 OFFER_CURRENCY uint64 = 6 OFFER_AMOUNT uint64 = 8 OFFER_DESCRIPTION uint64 = 10 OFFER_FEATURES uint64 = 12 OFFER_ABSOLUTE_EXPIRY uint64 = 14 OFFER_PATHS uint64 = 16 OFFER_ISSUER uint64 = 18 OFFER_QUANTITY_MAX uint64 = 20 OFFER_ISSUER_ID uint64 = 22 INVOICE_PAYMENT_HASH uint64 = 168 INVOICE_AMOUNT uint64 = 170 )
BOLT12 TLV types
const ( SwapTypeSubmarine = "submarine" SwapTypeChain = "chain" )
Variables ¶
var ErrorNoVtxosFound = fmt.Errorf("no vtxos found for the given vhtlc opts")
Functions ¶
func CombineFinalSig ¶
func CombineFinalSig( combinedNoncePoint *btcec.PublicKey, allSigs []*musig2.PartialSignature, keys []*btcec.PublicKey, msg [32]byte, merkleRoot []byte, ) (*schnorr.Signature, error)
CombineFinalSig combines two partial signatures into a final Schnorr signature. Uses taproot tweaked combine so verification is against the tweaked output key.
func ComputeTweakedOutputKey ¶
ComputeTweakedOutputKey computes the P2TR output key for {keys, merkleRoot}. Useful for debug verification before broadcast.
func IsBolt12Invoice ¶
func IsBolt12Offer ¶
func IsValidBolt12Offer ¶
func NewPrevOutputFetcher ¶
func ParsePartialSignatureScalar32 ¶
func ParsePartialSignatureScalar32(sigHex string) (*musig2.PartialSignature, error)
ParsePartialSignatureScalar32 parses Boltz partial sig format: 32-byte scalar S (hex). This is NOT musig2.PartialSignature encoding; do not call sig.Decode for this format.
func ParsePubNonce ¶
func SatsFromBolt12Offer ¶
func SerializePubNonce ¶
func TaprootMessage ¶
func TaprootMessage( tx *wire.MsgTx, inputIndex int, prevOutFetcher txscript.PrevOutputFetcher, ) ([32]byte, error)
TaprootMessage computes the BIP341 sighash (32 bytes) for key-path signing.
Types ¶
type ChainSwap ¶
type ChainSwap struct {
Id string
Amount uint64
Preimage []byte
UserBtcLockupAddress string
VhtlcOpts vhtlc.Opts
UserLockTxid string
ServerLockTxid string
ClaimTxid string
RefundTxid string
Timestamp int64
Status ChainSwapStatus
Error string
SwapRespJson string
IsArkToBtc bool
// contains filtered or unexported fields
}
func NewChainSwap ¶
func (*ChainSwap) RefundFailed ¶
func (*ChainSwap) RefundUnilaterally ¶
func (*ChainSwap) ServerLock ¶
func (*ChainSwap) UserLockedFailed ¶
type ChainSwapEvent ¶
type ChainSwapEvent interface {
// contains filtered or unexported methods
}
ChainSwapEvent is a marker interface for typed domain events Each event type represents a specific state transition with its own data
type ChainSwapEventCallback ¶
type ChainSwapEventCallback func(event ChainSwapEvent)
ChainSwapEventCallback is called whenever a chain swap event occurs
type ChainSwapEventHandler ¶
type ChainSwapEventHandler interface {
// HandleSwapCreated handles initial swap creation
HandleSwapCreated(ctx context.Context, update boltz.SwapUpdate) error
// HandleLockupFailed handles various failure scenarios
HandleLockupFailed(ctx context.Context, update boltz.SwapUpdate) error
HandleUserLockedMempool(ctx context.Context, update boltz.SwapUpdate) error
// HandleUserLocked handles user lockup confirmation
HandleUserLocked(ctx context.Context, update boltz.SwapUpdate) error
// HandleServerLockedMempool handles server lockup (ready to claim)
HandleServerLockedMempool(ctx context.Context, update boltz.SwapUpdate) error
// HandleServerLocked handles server lockup (ready to claim)
HandleServerLocked(ctx context.Context, update boltz.SwapUpdate) error
HandleSwapExpired(ctx context.Context, update boltz.SwapUpdate) error
HandleTransactionFailed(ctx context.Context, update boltz.SwapUpdate) error
GetState() ChainSwapState
}
ChainSwapEventHandler defines swap-specific behavior for different swap directions. This interface uses the strategy pattern to extract common WebSocket monitoring logic.
func NewArkToBtcHandler ¶
func NewArkToBtcHandler( swapHandler *SwapHandler, state ChainSwapState, network *chaincfg.Params, btcClaimPrivKey *btcec.PrivateKey, preimage []byte, btcDestinationAddress string, swapResp *boltz.CreateChainSwapResponse, boltzClaimPubKey *btcec.PublicKey, swapTree boltz.SwapTree, ) ChainSwapEventHandler
func NewBtcToArkHandler ¶
func NewBtcToArkHandler( swapHandler *SwapHandler, chainSwapState ChainSwapState, preimage []byte, refundKey *btcec.PrivateKey, swapResp *boltz.CreateChainSwapResponse, ) ChainSwapEventHandler
type ChainSwapState ¶
type ChainSwapStatus ¶
type ChainSwapStatus int
const ( // Pending states ChainSwapPending ChainSwapStatus = iota ChainSwapUserLocked ChainSwapServerLocked // Success states ChainSwapClaimed // Failed states ChainSwapUserLockedFailed ChainSwapFailed ChainSwapRefundFailed ChainSwapRefunded ChainSwapRefundedUnilaterally )
type ClaimEvent ¶
ClaimEvent is emitted when swap is successfully claimed
type ClaimTransactionParams ¶
type CreateEvent ¶
type ExplorerClient ¶
type ExplorerClient interface {
BroadcastTransaction(tx *wire.MsgTx) (string, error)
GetFeeRate() (float64, error)
GetCurrentBlockHeight() (uint32, error)
GetTransactionStatus(txid string) (*TransactionStatus, error)
GetTransaction(txid string) (string, error)
}
func NewExplorerClient ¶
func NewExplorerClient(baseURL string) ExplorerClient
type HTLCComponents ¶
type HTLCComponents struct {
PreimageHash [20]byte // HASH160 of the preimage (20 bytes)
ClaimPubKey [32]byte // X-only public key for claim (32 bytes)
}
HTLCComponents contains the parsed components from a Boltz HTLC script
type Invoice ¶
func DecodeBolt12Invoice ¶
type MuSigContext ¶
type MuSigContext struct {
// contains filtered or unexported fields
}
MuSigContext holds just what we need for the Boltz cooperative (2-of-2) MuSig2 flow. We intentionally DO NOT use musig2.Session API here to avoid tweak/sort/session mismatch and to match the proven working pattern from your tree signer code:
- GenNonces (keep SecNonce + PubNonce) - send PubNonce to Boltz - receive their PubNonce + their partial sig (S-only 32B scalar) - AggregateNonces - musig2.Sign(... WithTaprootSignTweak(merkleRoot) ...) - musig2.CombineSigs(... WithTaprootTweakedCombine(...))
func NewMuSigContext ¶
func NewMuSigContext(ourPriv *btcec.PrivateKey, theirPub *btcec.PublicKey) (*MuSigContext, error)
NewMuSigContext creates a MuSig2 context for 2-of-2 signing. IMPORTANT: ordering must match Boltz expectation. We keep "their key first" in Keys().
func (*MuSigContext) AggregateNonces ¶
func (c *MuSigContext) AggregateNonces(theirNonce [66]byte) ([66]byte, error)
AggregateNonces aggregates our pubnonce and their pubnonce.
func (*MuSigContext) GenerateNonce ¶
func (c *MuSigContext) GenerateNonce() ([66]byte, error)
GenerateNonce generates a fresh MuSig2 nonce pair (secret+public). WARNING: Never reuse c.ourNonces.SecNonce across different messages/txs.
func (*MuSigContext) Keys ¶
func (c *MuSigContext) Keys() []*btcec.PublicKey
Keys returns the signer keyset in the canonical order used in this swap flow. We intentionally return [their, ours] because you noted Boltz expects server first.
func (*MuSigContext) OurPartialSign ¶
func (c *MuSigContext) OurPartialSign( combinedNonce [66]byte, keys []*btcec.PublicKey, msg [32]byte, merkleRoot []byte, ) (*musig2.PartialSignature, error)
OurPartialSign produces our partial signature for the given message, using the aggregated nonce and Taproot tweak (merkleRoot/scriptRoot).
merkleRoot MUST be 32 bytes (taproot script root).
type Offer ¶
type Offer struct {
ID string
Amount uint64
AmountInSats uint64
Description []byte
DescriptionStr string
AbsoluteExpiry uint64
QuantityMax uint64
}
func DecodeBolt12Offer ¶
type RefundEvent ¶
RefundEvent is emitted when swap is refunded
type RefundEventUnilaterally ¶
RefundEventUnilaterally is emitted when swap is refunded
type RefundFailedEvent ¶
RefundFailedEvent is emitted when refund attempt fails
type RefundHTLCComponents ¶
type RefundHTLCComponents struct {
RefundPubKey [32]byte // X-only public key for refund (32 bytes)
Timeout uint32 // CSV timeout in blocks
}
RefundHTLCComponents contains the parsed components from a Boltz HTLC refund leaf script
func ValidateRefundLeafScript ¶
func ValidateRefundLeafScript(outputHex string) (*RefundHTLCComponents, error)
ValidateRefundLeafScript parses and validates a Boltz HTLC refund leaf script. The expected structure follows Boltz's refund format:
OP_PUSHBYTES_32 <refund_pubkey> OP_CHECKSIGVERIFY OP_PUSHBYTES_2 <timeout> OP_CHECKLOCKTIMEVERIFY (absolute block height)
Note: Boltz uses CLTV (absolute timelock) not CSV (relative timelock) for refund paths.
Example:
input: "207599756afc49ebf5a6f3ac5848ef0afe934edd7b669bca02029acf10cc7f83acad02f802b1" -> refundPubKey: 7599756afc49ebf5a6f3ac5848ef0afe934edd7b669bca02029acf10cc7f83ac -> timeout: 760 blocks (0x02f8 little-endian)
type ResumeChainSwapParams ¶
type ResumeChainSwapParams struct {
SwapID string
From boltz.Currency
To boltz.Currency
Amount uint64
PreimageHex string
BoltzResponseJSON string
UserBtcAddress string
UserLockTxid string
ServerLockTxid string
ClaimTxid string
RefundTxid string
Status ChainSwapStatus
Error string
Timestamp int64
Network *chaincfg.Params
EventCallback ChainSwapEventCallback
UnilateralRefundCB func(swapId string, opts vhtlc.Opts) error
}
type ServerLockEvent ¶
ServerLockEvent is emitted when server (Boltz) locks funds
type SwapHandler ¶
type SwapHandler struct {
// contains filtered or unexported fields
}
func NewSwapHandler ¶
func NewSwapHandler( arkClient arksdk.ArkClient, transportClient client.TransportClient, indexerClient indexer.Indexer, boltzSvc *boltz.Api, esploraURL string, privateKey *btcec.PrivateKey, timeout uint32, ) (*SwapHandler, error)
func (*SwapHandler) ChainSwapArkToBtc ¶
func (h *SwapHandler) ChainSwapArkToBtc( ctx context.Context, amount uint64, btcDestinationAddress string, network *chaincfg.Params, eventCallback ChainSwapEventCallback, unilateralRefundCallback func(swapId string, opts vhtlc.Opts) error, ) (*ChainSwap, error)
ChainSwapArkToBtc performs an Ark → Bitcoin on-chain atomic swap This is the main entry point for swapping Ark VTXOs to Bitcoin on-chain Send ARK VTXO -> Receive BTC UTXO Boltz locks BTC UTXO and it sends details on how user can claim it in claimDetails and where to send ARK VTXO in lockupDetails claimLeaf(claimDetials) is used by user to cooperative claim BTC tx LockupDetails should container VHTLC address where Boltz's Fulmine is receiver 1. generate preimage 2. POST /swap/chain: preimageHash, claimPubKey, refundPubKey
{
"id": "KEBsfLtqhsmA",
"claimDetails": {
"serverPublicKey": "02a9750704fdf536a573472938b4457be73e75513ff5ba3d017b2d73e070055026",
"amount": 2797,
"lockupAddress": "bcrt1pyz4djuc8eqn9na9s5l5lqg24uawv5ycaw6a3r9vaz0w3ewen7maq7ldt8q",
"timeoutBlockHeight": 542,
"swapTree": {
"claimLeaf": {
"version": 192,
"output": "82012088a914608bc8a727928e8aa18c7a2489c003deb47ff08388207599756afc49ebf5a6f3ac5848ef0afe934edd7b669bca02029acf10cc7f83acac"
},
"refundLeaf": {
"version": 192,
"output": "20a9750704fdf536a573472938b4457be73e75513ff5ba3d017b2d73e070055026ad021e02b1"
}
}
},
"lockupDetails": {
"serverPublicKey": "025067f8c4f61cf3bcbf131edbe0256d890332d2cdba64355a4153db1101e84cd0",
"amount": 3000,
"lockupAddress": "tark1qz4a0tydelxxun8w62zz3zjk36sr6aqrs58gmne23r9ea37jwx9awtw542kccdpm6nsuwfdw808r56humw46hqrrrg8dsem5v6hqu5d97zgl6c",
"timeoutBlockHeight": 1769647586,
"timeouts": {
"refund": 1769647586,
"unilateralClaim": 6144,
"unilateralRefund": 6144,
"unilateralRefundWithoutReceiver": 12288
}
}
}
what user needs to validate? - from claimDetails validate claimLeaf ? maybe we dont needs since for us it is important that we can refund vhtlc , validate HTLC - from lockupDetails validate(recreate) vhtlc address claimPubKey and preimage are used to claim BTC tx refundPubKey is used to refund ARK VTXO tx after timeout if something goes wrong
- SendOffchain -> send VTXO to address claimable by Boltz Fulmine(receiverPubKey)
- Once Boltz notices VTXO, it will send(lock) coins on BTC mainnet - BTC lockup TX
- Boltz send server.mempool and server.confimed events via WebSocket and we than decide when to claim 5.1 Cooperative claim so Boltz doesnt need to scan mainchain for preimage 5.2 Unilateral claim if Boltz not responsive
- User Refunds in case something goes wrong, we should schedule unilateral refund? 6.1 Try Cooperative Refund 6.2 Try Unilateral Refund
- Quote mechanism: What if user sends(locks) less amount than what he announced in swap request? in transaction.lockupfailed get quote and accept quote
func (*SwapHandler) ChainSwapBtcToArk ¶
func (h *SwapHandler) ChainSwapBtcToArk( _ context.Context, amount uint64, network *chaincfg.Params, eventCallback ChainSwapEventCallback, ) (*ChainSwap, error)
ChainSwapBtcToArk performs a Bitcoin on-chain → Ark atomic swap
This is the reverse direction: user locks BTC on-chain, receives Ark VTXOs Send BTC -> Receive VTXO
{
"id": "rZfDV8vtQ5Jk",
"claimDetails": {
"serverPublicKey": "025067f8c4f61cf3bcbf131edbe0256d890332d2cdba64355a4153db1101e84cd0",
"amount": 2801,
"lockupAddress": "tark1qz4a0tydelxxun8w62zz3zjk36sr6aqrs58gmne23r9ea37jwx9a0g5xczda6llpnevn3gnw3muwwnw9cze8988g0j2dvhssdfqkkg8n4jt5ln",
"timeoutBlockHeight": 1769678334,
"timeouts": {
"refund": 1769678334,
"unilateralClaim": 6144,
"unilateralRefund": 6144,
"unilateralRefundWithoutReceiver": 12288
}
},
"lockupDetails": {
"serverPublicKey": "028923258347dd79d51195e2054d9f92a6c4cfcbce86a92e3b9e2f7b51a0750d2b",
"amount": 3000,
"lockupAddress": "bcrt1pugmgfs2zx4w48w2cgnsvvrhpdy0zlntdz8gch2rz6tafnm8v8ewqm5mpjg",
"timeoutBlockHeight": 760,
"swapTree": {
"claimLeaf": {
"version": 192,
"output": "82012088a9140f49a45d0bea33b5be812590dc8d284a0ebe195c88208923258347dd79d51195e2054d9f92a6c4cfcbce86a92e3b9e2f7b51a0750d2bac"
},
"refundLeaf": {
"version": 192,
"output": "207599756afc49ebf5a6f3ac5848ef0afe934edd7b669bca02029acf10cc7f83acad02f802b1"
}
},
}
}
func (*SwapHandler) ClaimVHTLC ¶
func (*SwapHandler) GetInvoice ¶
func (*SwapHandler) GetVHTLCFunds ¶
func (*SwapHandler) PayInvoice ¶
func (*SwapHandler) RefundArkToBTCSwap ¶
func (*SwapHandler) RefundBtcToArkSwap ¶
func (h *SwapHandler) RefundBtcToArkSwap( ctx context.Context, swapId string, amount uint64, userLockupTxid string, boltzSwapRespJson string, ) (string, error)
RefundBtcToArkSwap performs a BTC→ARK refund by: 1. Reading swap data from the ChainSwap struct (populated by service layer from DB) 2. Checking if CLTV timeout has passed 3. Creating and signing a refund transaction spending the lockup UTXO via script-path 4. Sending BTC to fulmine boarding address 5. Broadcasting the transaction 6. Waiting for confirmation 7. Calling Settle() to board the BTC as VTXO
This function is called when a BTC→ARK swap fails and needs to be refunded. The BTC is claimed from the lockup address using the refund script path (CLTV timeout) and sent to a fulmine boarding address, then settled to become a VTXO.
The swap data is persisted in the DB by the service layer and passed in via the ChainSwap struct (BoltzCreateResponseJSON and UserLockupTxHex fields). Refunds work even after service restart and even if Boltz API is unavailable.
func (*SwapHandler) RefundSwap ¶
func (*SwapHandler) ResumeChainSwap ¶
func (h *SwapHandler) ResumeChainSwap( ctx context.Context, params ResumeChainSwapParams, ) (*ChainSwap, error)
func (*SwapHandler) SettleVHTLCWithClaimPath ¶
func (h *SwapHandler) SettleVHTLCWithClaimPath( ctx context.Context, vhtlcOpts vhtlc.Opts, preimage []byte, ) (string, error)
SettleVHTLCWithClaimPath settles a VHTLC using the claim path (revealing preimage) via batch session. This is used for reverse submarine swaps where Fulmine is the receiver.
func (*SwapHandler) SettleVHTLCWithCollaborativeRefundPath ¶
func (h *SwapHandler) SettleVHTLCWithCollaborativeRefundPath( ctx context.Context, vhtlcOpts vhtlc.Opts, partialForfeitTx, proof, message string, signerSession tree.SignerSession, ) (string, error)
func (*SwapHandler) SettleVhtlcWithRefundPath ¶
func (h *SwapHandler) SettleVhtlcWithRefundPath( ctx context.Context, vhtlcOpts vhtlc.Opts, ) (string, error)
SettleVhtlcWithRefundPath settles a VHTLC using the refund path via batch session. This is used for submarine swaps where Fulmine is the sender and needs to recover funds.
type SwapStatus ¶
type SwapStatus int
const ( SwapPending SwapStatus = iota SwapFailed SwapSuccess )
type TransactionStatus ¶
type UserLockEvent ¶
UserLockEvent is emitted when user locks funds (Ark VTXO or BTC UTXO)
type UserLockFailedEvent ¶
UserLockFailedEvent is emitted when user lock fails