diff --git a/mobile/swaps.go b/mobile/swaps.go index 5af7f1b5f6..00146d9f4f 100644 --- a/mobile/swaps.go +++ b/mobile/swaps.go @@ -21,33 +21,78 @@ func leaf(script string) txscript.TapLeaf { } } -func CreateClaimTransaction(endpoint string, id string, claimLeaf string, refundLeaf string, privateKey string, servicePubKey string, transactionHash string, pubNonce string) error { - swapTree := &boltz.SwapTree{ - ClaimLeaf: leaf(claimLeaf), - RefundLeaf: leaf(refundLeaf), - } - - // Decode the hex string to bytes +func parseSwapKeys(privateKey, servicePubKey string) (*btcec.PrivateKey, *btcec.PublicKey, error) { privKeyBytes, err := hex.DecodeString(privateKey) if err != nil { - fmt.Printf("Failed to decode hex string: %v", err) + return nil, nil, fmt.Errorf("decode private key hex: %w", err) } - - // Create the private key using btcec keys, _ := btcec.PrivKeyFromBytes(privKeyBytes) - // Decode the hex string to bytes servicePubKeyBytes, err := hex.DecodeString(servicePubKey) if err != nil { - return fmt.Errorf("Error decoding service public key hex: %s", err) + return nil, nil, fmt.Errorf("decode service pub key hex: %w", err) } - // Parse the public key - servicePubKeyFormatted, err := secp256k1.ParsePubKey(servicePubKeyBytes) + servicePub, err := secp256k1.ParsePubKey(servicePubKeyBytes) if err != nil { - return fmt.Errorf("Error parsing service public key %s", err) + return nil, nil, fmt.Errorf("parse service pub key: %w", err) + } + + return keys, servicePub, nil +} + +// buildFee returns an exact-sats Fee when minerFee > 0, otherwise falls back +// to a sats/vbyte rate. boltz.Fee.IsValid() requires exactly one to be set. +func buildFee(feeRate, minerFee int32) boltz.Fee { + if minerFee > 0 { + sats := uint64(minerFee) + return boltz.Fee{Sats: &sats} } + satPerVbyte := float64(feeRate) + return boltz.Fee{SatsPerVbyte: &satPerVbyte} +} +func broadcastTx(txHex string, isTestnet bool) (string, error) { + broadcastUrl := "https://mempool.space/api/tx" + if isTestnet { + broadcastUrl = "https://mempool.space/testnet/api/tx" + } + + req, err := http.NewRequest("POST", broadcastUrl, bytes.NewBufferString(txHex)) + if err != nil { + return "", fmt.Errorf("failed to create HTTP request: %v", err) + } + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return "", fmt.Errorf("failed to send HTTP request: %v", err) + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("failed to read response body: %v", err) + } + + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("non-200 response: %d, body: %s", resp.StatusCode, string(body)) + } + + return string(body), nil +} + +func CreateClaimTransaction(endpoint string, id string, claimLeaf string, refundLeaf string, privateKey string, servicePubKey string, transactionHash string, pubNonce string) error { + keys, servicePubKeyFormatted, err := parseSwapKeys(privateKey, servicePubKey) + if err != nil { + return err + } + + swapTree := &boltz.SwapTree{ + ClaimLeaf: leaf(claimLeaf), + RefundLeaf: leaf(refundLeaf), + } if err := swapTree.Init(boltz.CurrencyBtc, false, keys, servicePubKeyFormatted); err != nil { return fmt.Errorf("Error initializing swap tree %s", err) } @@ -70,47 +115,27 @@ func CreateClaimTransaction(endpoint string, id string, claimLeaf string, refund } func CreateReverseClaimTransaction(endpoint string, id string, claimLeaf string, refundLeaf string, privateKey string, servicePubKey string, preimageHex string, transactionHex string, lockupAddress string, destinationAddress string, feeRate int32, minerFee int32, isTestnet bool) error { - var toCurrency = boltz.CurrencyBtc - var network *boltz.Network + network := boltz.MainNet if isTestnet { network = boltz.TestNet - } else { - network = boltz.MainNet } boltzApi := &boltz.Api{URL: endpoint} - // Decode the hex string to bytes - privKeyBytes, err := hex.DecodeString(privateKey) - if err != nil { - fmt.Printf("Failed to decode hex string: %v", err) - } - - // Create the private key using btcec - keys, _ := btcec.PrivKeyFromBytes(privKeyBytes) - - // Decode the hex string to bytes - servicePubKeyBytes, err := hex.DecodeString(servicePubKey) - if err != nil { - return fmt.Errorf("Error decoding service public key hex: %s", err) - } - - // Parse the public key - servicePubKeyFormatted, err := secp256k1.ParsePubKey(servicePubKeyBytes) + keys, servicePubKeyFormatted, err := parseSwapKeys(privateKey, servicePubKey) if err != nil { - return fmt.Errorf("Error parsing service public key %s", err) + return err } swapTree := &boltz.SwapTree{ ClaimLeaf: leaf(claimLeaf), RefundLeaf: leaf(refundLeaf), } - if err := swapTree.Init(boltz.CurrencyBtc, false, keys, servicePubKeyFormatted); err != nil { return fmt.Errorf("Error initializing swap tree %s", err) } - lockupTransaction, err := boltz.NewTxFromHex(toCurrency, transactionHex, nil) + lockupTransaction, err := boltz.NewTxFromHex(boltz.CurrencyBtc, transactionHex, nil) if err != nil { return fmt.Errorf("Error constructing lockup tx %s", err) } @@ -125,14 +150,6 @@ func CreateReverseClaimTransaction(endpoint string, id string, claimLeaf string, return fmt.Errorf("Error decoding preimage hex string: %w", err) } - var fee boltz.Fee - if minerFee > 0 { - sats := uint64(minerFee) - fee = boltz.Fee{Sats: &sats} - } else { - satPerVbyte := float64(feeRate) - fee = boltz.Fee{SatsPerVbyte: &satPerVbyte} - } claimTransaction, _, err := boltz.ConstructTransaction( network, boltz.CurrencyBtc, @@ -149,7 +166,7 @@ func CreateReverseClaimTransaction(endpoint string, id string, claimLeaf string, Cooperative: true, }, }, - fee, + buildFee(feeRate, minerFee), boltzApi, ) if err != nil { @@ -161,95 +178,41 @@ func CreateReverseClaimTransaction(endpoint string, id string, claimLeaf string, return fmt.Errorf("could not serialize claim transaction: %w", err) } - var broadcastUrl string - if isTestnet { - broadcastUrl = "https://mempool.space/testnet/api/tx" - } else { - broadcastUrl = "https://mempool.space/api/tx" - } - - // Create HTTP request - req, err := http.NewRequest("POST", broadcastUrl, bytes.NewBufferString(txHex)) + body, err := broadcastTx(txHex, isTestnet) if err != nil { - return fmt.Errorf("failed to create HTTP request: %v", err) + return err } - req.Header.Set("Content-Type", "application/json") - // Execute HTTP request - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return fmt.Errorf("failed to send HTTP request: %v", err) - } - defer resp.Body.Close() - - // Read response - body, err := io.ReadAll(resp.Body) - if err != nil { - return fmt.Errorf("failed to read response body: %v", err) - } - - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("non-200 response: %d, body: %s", resp.StatusCode, string(body)) - } - - fmt.Printf("Transaction broadcasted successfully: %s\n", string(body)) + fmt.Printf("Transaction broadcasted successfully: %s\n", body) return nil } -func CreateRefundTransaction(endpoint string, id string, claimLeaf string, refundLeaf string, transactionHex string, privateKey string, servicePubKey string, feeRate int32, timeoutBlockHeight int32, destinationAddress string, lockupAddress string, cooperative bool, isTestnet bool) (string, error) { - var toCurrency = boltz.CurrencyBtc - - var network *boltz.Network +func CreateRefundTransaction(endpoint string, id string, claimLeaf string, refundLeaf string, transactionHex string, privateKey string, servicePubKey string, feeRate int32, minerFee int32, timeoutBlockHeight int32, destinationAddress string, lockupAddress string, cooperative bool, isTestnet bool) (string, error) { + network := boltz.MainNet if isTestnet { network = boltz.TestNet - } else { - network = boltz.MainNet } boltzApi := &boltz.Api{URL: endpoint} - // Decode the hex string to bytes - privKeyBytes, err := hex.DecodeString(privateKey) - if err != nil { - fmt.Printf("Failed to decode hex string: %v\n", err) - return "", fmt.Errorf("failed to decode hex string: %v", err) - } - - // Create the private key using btcec - keys, _ := btcec.PrivKeyFromBytes(privKeyBytes) - - // Decode the hex string to bytes - servicePubKeyBytes, err := hex.DecodeString(servicePubKey) + keys, servicePubKeyFormatted, err := parseSwapKeys(privateKey, servicePubKey) if err != nil { - return "", fmt.Errorf("error decoding service public key hex: %s", err) + return "", err } - // Parse the public key - servicePubKeyFormatted, err := secp256k1.ParsePubKey(servicePubKeyBytes) - if err != nil { - return "", fmt.Errorf("error parsing service public key %s", err) - } - - // Creating the swapTree swapTree := &boltz.SwapTree{ ClaimLeaf: leaf(claimLeaf), RefundLeaf: leaf(refundLeaf), } - fmt.Println("SwapTree created successfully") - if err := swapTree.Init(boltz.CurrencyBtc, false, keys, servicePubKeyFormatted); err != nil { return "", fmt.Errorf("error initializing swap tree %s", err) } - satPerVbyte := float64(feeRate) - - lockupTransaction, err := boltz.NewTxFromHex(toCurrency, transactionHex, nil) + lockupTransaction, err := boltz.NewTxFromHex(boltz.CurrencyBtc, transactionHex, nil) if err != nil { return "", fmt.Errorf("error constructing lockup tx %v", err) } - fmt.Println("Lockup transaction constructed successfully") vout, _, err := lockupTransaction.FindVout(network, lockupAddress) if err != nil { @@ -273,56 +236,24 @@ func CreateRefundTransaction(endpoint string, id string, claimLeaf string, refun Cooperative: cooperative, }, }, - boltz.Fee{SatsPerVbyte: &satPerVbyte}, + buildFee(feeRate, minerFee), boltzApi, ) - if err != nil { return "", fmt.Errorf("could not create refund transaction: %w", err) } - fmt.Println("Refund transaction constructed successfully") txHex, err := refundTransaction.Serialize() if err != nil { return "", fmt.Errorf("could not serialize refund transaction: %w", err) } - fmt.Println("Refund transaction serialized successfully") - - var broadcastUrl string - if isTestnet { - broadcastUrl = "https://mempool.space/testnet/api/tx" - } else { - broadcastUrl = "https://mempool.space/api/tx" - } - - // Create HTTP request - req, err := http.NewRequest("POST", broadcastUrl, bytes.NewBufferString(txHex)) - if err != nil { - return "", fmt.Errorf("failed to create HTTP request: %v", err) - } - req.Header.Set("Content-Type", "application/json") - // Execute HTTP request - client := &http.Client{} - resp, err := client.Do(req) + body, err := broadcastTx(txHex, isTestnet) if err != nil { - return "", fmt.Errorf("failed to send HTTP request: %v", err) - } - defer resp.Body.Close() - - // Read response - body, err := io.ReadAll(resp.Body) - if err != nil { - return "", fmt.Errorf("failed to read response body: %v", err) - } - - if resp.StatusCode != http.StatusOK { - return "", fmt.Errorf("non-200 response: %d, body: %s", resp.StatusCode, string(body)) + return "", err } - txid := string(body) - fmt.Printf("Transaction broadcasted successfully: %s\n", txid) - fmt.Println("Transaction broadcasted successfully") + fmt.Printf("Transaction broadcasted successfully: %s\n", body) - return txid, nil + return body, nil }