Skip to content
Open
Show file tree
Hide file tree
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
6 changes: 6 additions & 0 deletions clients/MerkleTreeStorageCalls.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,8 @@ func storeMerkleTreeWithRetry(cfg *config.Config, contractAddress string, merkle
}
merkleRootHash := common.HexToHash(merkleRootStr)

log.Printf("\nMerkleroothash: %v", merkleRootHash)

// Parse leaves string into array of bytes
// Expected format: plain strings that will be converted to bytes
var leafHashes [][32]byte
Expand All @@ -226,6 +228,10 @@ func storeMerkleTreeWithRetry(cfg *config.Config, contractAddress string, merkle
continue
}

if !strings.HasPrefix(leafStr, "0x") {
leafStr = "0x" + leafStr
}

// Convert plain string to bytes
leafHashes = append(leafHashes, common.HexToHash(leafStr))
}
Expand Down
198 changes: 165 additions & 33 deletions da/op_return_rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ var (
backoffFactor = 2.0
requestTimeout = 30 * time.Second

// Circuit breaker configuration
// Circuit breaker for RPC calls
rpcMutex sync.RWMutex
failureCount int
lastFailTime time.Time
circuitOpen bool
circuitTimeout = 60 * time.Second
maxFailures = 5
)
Expand All @@ -41,10 +45,9 @@ type response struct {
}

type utxo struct {
Txid string `json:"txid"`
Vout int `json:"vout"`
Amount float64 `json:"amount"`
Address string `json:"address,omitempty"`
Txid string `json:"txid"`
Vout int `json:"vout"`
Amount float64 `json:"amount"`
}

type signedtx struct {
Expand Down Expand Up @@ -271,24 +274,23 @@ func GetRawAddress() string {
}

func CalculateRequired(numInputs int, dataSize int) float64 {
return float64(53+numInputs*68+dataSize) * float64(0.00000002)
return float64(53+numInputs*68+dataSize) * float64(0.00000001)
}

func FilterUTXOs(unspent string, length int) ([]map[string]interface{}, float64, string) {
func FilterUTXOs(unspent string, length int) ([]map[string]interface{}, float64) {
inputs := []map[string]interface{}{}
if unspent == "" {
return inputs, 0.0, ""
return inputs, 0.0
}
var t []json.RawMessage
err := json.Unmarshal([]byte(unspent), &t)
if err != nil {
log.Printf("Failed to unmarshal response: %v", err)
return inputs, 0.0, ""
return inputs, 0.0
}
totalAmt := 0.0
numInputs := 0
required := 0.0
var changeAddress string

log.Printf("Found %d UTXOs to process", len(t))

Expand All @@ -297,7 +299,7 @@ func FilterUTXOs(unspent string, length int) ([]map[string]interface{}, float64,
err := json.Unmarshal(t[numInputs], &u)
if err != nil {
log.Printf("Failed to unmarshal response: %v", err)
return inputs, 0.0, ""
return inputs, 0.0
} else {
log.Printf("UTXO : %+v", u)
}
Expand All @@ -312,24 +314,19 @@ func FilterUTXOs(unspent string, length int) ([]map[string]interface{}, float64,
totalAmt += (float64(u.Amount) * 100000000)
required = (CalculateRequired(numInputs+1, length) * 100000000)

// Use the address from the first UTXO as change address
if numInputs == 0 && u.Address != "" {
changeAddress = u.Address
}

log.Printf("Current total: %f BTC, required: %f BTC", totalAmt, required)

if totalAmt >= required {
break
}
numInputs++
if numInputs >= 10 {
return []map[string]interface{}{}, 0.0, ""
return []map[string]interface{}{}, 0.0
}
}
change := (totalAmt - required) / float64(100000000)
log.Printf("Inputs: %v, Change: %f, Change Address: %s", inputs, change, changeAddress)
return inputs, float64(change), changeAddress
change := ((totalAmt - required) * 100000000) / float64(100000000)
log.Printf("Inputs: %v, Change: %f", inputs, change)
return inputs, float64(change)
}

func CreateRawTransaction(inputs []map[string]interface{}, address string, change float64, data string) string {
Expand All @@ -343,10 +340,7 @@ func CreateRawTransaction(inputs []map[string]interface{}, address string, chang
return ""
}

// Convert BTC to satoshis (1 BTC = 100,000,000 satoshis)
changeSatoshis := int64(change * 100000000)

log.Printf("Creating raw transaction with %d inputs, change address %s, change amount %f BTC (%d satoshis)", len(inputs), address, change, changeSatoshis)
log.Printf("Creating raw transaction with %d inputs, change address %s, change amount %f", len(inputs), address, change)

payload := map[string]interface{}{
"jsonrpc": "1.0",
Expand Down Expand Up @@ -576,43 +570,45 @@ func CreateOPReturnTransaction(data string) string {
}

// Step 2: Filter UTXOs
inputs, change, changeAddress := FilterUTXOs(unspent, len(data))
inputs, change := FilterUTXOs(unspent, len(data))
if len(inputs) == 0 {
log.Printf("No suitable UTXOs found for transaction")
return ""
}

if changeAddress == "" {
log.Printf("No change address found in UTXOs")
// Step 3: Get raw address
rawaddr := GetRawAddress()
if rawaddr == "" {
log.Printf("Failed to get raw address")
return ""
}

// Step 3: Create raw transaction using change address from UTXOs
rawtscn := CreateRawTransaction(inputs, changeAddress, change, data)
// Step 4: Create raw transaction
rawtscn := CreateRawTransaction(inputs, rawaddr, change, data)
if rawtscn == "" {
log.Printf("Failed to create raw transaction")
return ""
}

// Step 4: Decode for verification (optional)
// Step 5: Decode for verification (optional)
DecodeRawTransaction(rawtscn)

// Step 5: Sign transaction
// Step 6: Sign transaction
signtscn := SignRawTransaction(rawtscn)
if signtscn == "" {
log.Printf("Failed to sign transaction")
return ""
}

// Step 6: Parse signed transaction
// Step 7: Parse signed transaction
var sgn signedtx
err := json.Unmarshal([]byte(signtscn), &sgn)
if err != nil {
log.Printf("Failed to unmarshal signed transaction response: %v", err)
return ""
}

// Step 7: Send signed transaction
// Step 8: Send signed transaction
sendtscn := SendSignedTransaction(sgn.Hex)
if sendtscn == "" {
log.Printf("Failed to send signed transaction")
Expand All @@ -623,6 +619,142 @@ func CreateOPReturnTransaction(data string) string {
return sendtscn
}

// MempoolSpaceTxResponse represents the response from mempool.space transaction API
type MempoolSpaceTxResponse struct {
Txid string `json:"txid"`
Fee int `json:"fee"`
Status struct {
Confirmed bool `json:"confirmed"`
BlockTime int64 `json:"block_time"`
} `json:"status"`
}

// MempoolSpacePriceResponse represents the response from mempool.space historical price API
type MempoolSpacePriceResponse struct {
Prices []struct {
Time int64 `json:"time"`
USD int64 `json:"USD"`
} `json:"prices"`
}

// FetchTransactionFromMempoolSpace fetches transaction details from mempool.space API
func FetchTransactionFromMempoolSpace(txHash string) (*MempoolSpaceTxResponse, error) {
url := fmt.Sprintf("https://mempool.space/api/tx/%s", txHash)

log.Printf("Fetching transaction details from mempool.space: %s", url)

req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}

client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to make request: %w", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("API returned status code: %d", resp.StatusCode)
}

body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body: %w", err)
}

var txResponse MempoolSpaceTxResponse
err = json.Unmarshal(body, &txResponse)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}

log.Printf("Fetched transaction fee: %d satoshis, block time: %d", txResponse.Fee, txResponse.Status.BlockTime)
return &txResponse, nil
}

// FetchHistoricalBTCPrice fetches historical BTC price in USD from mempool.space API
func FetchHistoricalBTCPrice(timestamp int64) (float64, error) {
url := fmt.Sprintf("https://mempool.space/api/v1/historical-price?currency=USD&timestamp=%d", timestamp)

log.Printf("Fetching BTC price from mempool.space: %s", url)

req, err := http.NewRequest("GET", url, nil)
if err != nil {
return 0, fmt.Errorf("failed to create request: %w", err)
}

client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Do(req)
if err != nil {
return 0, fmt.Errorf("failed to make request: %w", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return 0, fmt.Errorf("API returned status code: %d", resp.StatusCode)
}

body, err := io.ReadAll(resp.Body)
if err != nil {
return 0, fmt.Errorf("failed to read response body: %w", err)
}

var priceResponse MempoolSpacePriceResponse
err = json.Unmarshal(body, &priceResponse)
if err != nil {
return 0, fmt.Errorf("failed to unmarshal response: %w", err)
}

if len(priceResponse.Prices) == 0 {
return 0, fmt.Errorf("no price data available")
}

// Convert price from integer (representing price * 100) to float
price := float64(priceResponse.Prices[0].USD) / 100.0

log.Printf("Fetched BTC price: $%.2f USD", price)
return price, nil
}

// CalculateTransactionFeeUSD calculates the transaction fee in USD
func CalculateTransactionFeeUSD(txHash string, blockTime int64) (float64, error) {
// Fetch transaction details
txResponse, err := FetchTransactionFromMempoolSpace(txHash)
if err != nil {
return 0, fmt.Errorf("failed to fetch transaction: %w", err)
}

// Determine timestamp to use for price lookup
var timestamp int64
if txResponse.Status.BlockTime != 0 {
// Use block time from transaction if available
timestamp = txResponse.Status.BlockTime
} else if blockTime != 0 {
// Use provided block time as fallback
timestamp = blockTime
} else {
// Use current time as last resort
timestamp = time.Now().Unix()
}

// Fetch historical BTC price
price, err := FetchHistoricalBTCPrice(timestamp)
if err != nil {
return 0, fmt.Errorf("failed to fetch BTC price: %w", err)
}

// Convert fee from satoshis to BTC (1 BTC = 100,000,000 satoshis)
feeBTC := float64(txResponse.Fee) / 100000000.0

// Calculate fee in USD
feeUSD := feeBTC * price

log.Printf("Transaction fee: %d satoshis (%.8f BTC) * $%.2f = $%.2f USD", txResponse.Fee, feeBTC, price, feeUSD)
return feeUSD, nil
}

func InitOPReturnRPC(endpoint string, auth string, passphrase string) {
BTCEndpoint = endpoint
Auth = auth
Expand Down
15 changes: 15 additions & 0 deletions da/super_proof.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,4 +276,19 @@ func processNonBTCTxSuperProof(cfg *config.Config) {
}

log.Printf("Updated super proof with BTC TX hash: %s", superProof.ID)

// Calculate and store transaction fee in USD
transactionFeeUSD, err := CalculateTransactionFeeUSD(btcTxHash, 0)
if err != nil {
log.Printf("Error calculating transaction fee: %v", err)
// Continue execution even if fee calculation fails
} else {
err = models.UpdateSuperProofWithTransactionFee(superProof.ID, transactionFeeUSD)
if err != nil {
log.Printf("Error updating super proof with transaction fee: %v", err)
// Continue execution even if fee update fails
} else {
log.Printf("Successfully updated super proof with transaction fee: $%.2f USD", transactionFeeUSD)
}
}
}
10 changes: 9 additions & 1 deletion da/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package da
import (
// "context"

"strings"

"github.com/ethereum/go-ethereum/ethclient"

// "github.com/cosmos/cosmos-sdk/crypto/keyring"
Expand Down Expand Up @@ -75,6 +77,9 @@ func HashBlockSubscriber(cfg *config.Config) {

// Use keccak256 hash of the ABI proof as leaf for merkle tree
proofHash := utils.Keccak256Hash(msg[1])
if !strings.HasPrefix(proofHash, "0x") {
proofHash = "0x" + proofHash
}
aggr.Aggregate(proofHash)

// Store proof hash for merkle tree storage (without 0x prefix for contract)
Expand All @@ -98,7 +103,10 @@ func HashBlockSubscriber(cfg *config.Config) {
return
}

log.Println("Aggregated Data: ", aggr.data)
if !strings.HasPrefix(merkle_root, "0x") {
merkle_root = "0x" + merkle_root
}

log.Println("Aggregated Proof: ", merkle_root)
aggr.data = ""

Expand Down
Loading