Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
225 changes: 78 additions & 147 deletions mobile/swaps.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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)
}
Expand All @@ -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,
Expand All @@ -149,7 +166,7 @@ func CreateReverseClaimTransaction(endpoint string, id string, claimLeaf string,
Cooperative: true,
},
},
fee,
buildFee(feeRate, minerFee),
boltzApi,
)
if err != nil {
Expand All @@ -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 {
Expand All @@ -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
}
Loading