package bitcoin import ( "encoding/json" "fmt" "io" "net/http" "time" "btcphrasechecker/types" ) const ( // API base URL (using blockstream.info) apiBaseURL = "https://blockstream.info/api" ) // BlockstreamTransaction represents a transaction from Blockstream API type BlockstreamTransaction struct { Txid string `json:"txid"` Status struct { Confirmed bool `json:"confirmed"` BlockHeight int `json:"block_height"` BlockTime int64 `json:"block_time"` } `json:"status"` Vin []struct { Txid string `json:"txid"` Vout int `json:"vout"` Prevout struct { Scriptpubkey string `json:"scriptpubkey"` ScriptpubkeyAddress string `json:"scriptpubkey_address"` Value uint64 `json:"value"` } `json:"prevout"` } `json:"vin"` Vout []struct { Scriptpubkey string `json:"scriptpubkey"` ScriptpubkeyAddress string `json:"scriptpubkey_address"` Value uint64 `json:"value"` } `json:"vout"` Fee uint64 `json:"fee"` } // GetAddressInfo fetches address information from Blockstream API func GetAddressInfo(address string) (balance, txCount uint64, received, sent uint64, err error) { url := fmt.Sprintf("%s/address/%s", apiBaseURL, address) resp, err := http.Get(url) if err != nil { return 0, 0, 0, 0, err } defer resp.Body.Close() if resp.StatusCode != 200 { return 0, 0, 0, 0, fmt.Errorf("API returned status %d", resp.StatusCode) } body, err := io.ReadAll(resp.Body) if err != nil { return 0, 0, 0, 0, err } var addrData struct { ChainStats struct { FundedTxoSum uint64 `json:"funded_txo_sum"` SpentTxoSum uint64 `json:"spent_txo_sum"` TxCount int `json:"tx_count"` } `json:"chain_stats"` } if err := json.Unmarshal(body, &addrData); err != nil { return 0, 0, 0, 0, err } balance = addrData.ChainStats.FundedTxoSum - addrData.ChainStats.SpentTxoSum txCount = uint64(addrData.ChainStats.TxCount) received = addrData.ChainStats.FundedTxoSum sent = addrData.ChainStats.SpentTxoSum return balance, txCount, received, sent, nil } // GetTransactions fetches all transactions for an address func GetTransactions(address string) ([]types.Transaction, error) { url := fmt.Sprintf("%s/address/%s/txs", apiBaseURL, address) resp, err := http.Get(url) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != 200 { return nil, fmt.Errorf("API returned status %d", resp.StatusCode) } body, err := io.ReadAll(resp.Body) if err != nil { return nil, err } var bsTxs []BlockstreamTransaction if err := json.Unmarshal(body, &bsTxs); err != nil { return nil, err } // Convert to common transaction format var transactions []types.Transaction for _, bsTx := range bsTxs { tx := convertTransaction(bsTx, address) transactions = append(transactions, tx) } return transactions, nil } // convertTransaction converts Blockstream transaction to common format func convertTransaction(bsTx BlockstreamTransaction, forAddress string) types.Transaction { tx := types.Transaction{ TxID: bsTx.Txid, BlockHeight: bsTx.Status.BlockHeight, Confirmed: bsTx.Status.Confirmed, Fee: bsTx.Fee, } if bsTx.Status.BlockTime > 0 { tx.Time = time.Unix(bsTx.Status.BlockTime, 0) } // Calculate received and sent amounts for this address var received, sent uint64 addressSet := make(map[string]bool) // Check inputs (sent from address) for _, vin := range bsTx.Vin { if vin.Prevout.ScriptpubkeyAddress == forAddress { sent += vin.Prevout.Value } addressSet[vin.Prevout.ScriptpubkeyAddress] = true } // Check outputs (received by address) for _, vout := range bsTx.Vout { if vout.ScriptpubkeyAddress == forAddress { received += vout.Value } addressSet[vout.ScriptpubkeyAddress] = true } tx.Received = received tx.Sent = sent tx.NetChange = int64(received) - int64(sent) // Collect all unique addresses for addr := range addressSet { if addr != "" && addr != forAddress { tx.Addresses = append(tx.Addresses, addr) } } return tx }