162 lines
4.0 KiB
Go
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
|
|
}
|