hacks/btcphrasechecker/bitcoin/api.go
2026-02-12 12:26:39 -08:00

162 lines
4.0 KiB
Go

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
}