210 lines
5.1 KiB
Go
210 lines
5.1 KiB
Go
package bitcoin
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"time"
|
|
|
|
"btcphrasechecker/types"
|
|
)
|
|
|
|
// Analyzer implements the ChainAnalyzer interface for Bitcoin
|
|
type Analyzer struct {
|
|
addressCount int
|
|
}
|
|
|
|
// NewAnalyzer creates a new Bitcoin analyzer
|
|
func NewAnalyzer(addressCount int) *Analyzer {
|
|
return &Analyzer{
|
|
addressCount: addressCount,
|
|
}
|
|
}
|
|
|
|
// DeriveAddresses derives Bitcoin addresses from seed
|
|
func (a *Analyzer) DeriveAddresses(seed []byte, count int) ([]types.AddressInfo, error) {
|
|
return DeriveAddresses(seed, count)
|
|
}
|
|
|
|
// GetAddressInfo fetches address information
|
|
func (a *Analyzer) GetAddressInfo(address string) (balance, txCount uint64, received, sent uint64, err error) {
|
|
return GetAddressInfo(address)
|
|
}
|
|
|
|
// GetTransactions fetches transactions for an address
|
|
func (a *Analyzer) GetTransactions(address string) ([]types.Transaction, error) {
|
|
return GetTransactions(address)
|
|
}
|
|
|
|
// GetChain returns the chain type
|
|
func (a *Analyzer) GetChain() types.Chain {
|
|
return types.ChainBitcoin
|
|
}
|
|
|
|
// AnalyzeWallet performs comprehensive wallet analysis
|
|
func AnalyzeWallet(seed []byte, addressCount int, verbose bool) (*types.WalletSummary, error) {
|
|
summary := &types.WalletSummary{
|
|
ActiveAddresses: make([]types.AddressInfo, 0),
|
|
TransactionHistory: make([]types.TransactionDetail, 0),
|
|
}
|
|
|
|
// Derive all addresses
|
|
addresses, err := DeriveAddresses(seed, addressCount)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if verbose {
|
|
fmt.Println("Bitcoin Wallet Analysis")
|
|
fmt.Println("======================")
|
|
fmt.Println()
|
|
}
|
|
|
|
// Check each derivation path
|
|
pathStats := make(map[string]struct {
|
|
count int
|
|
received uint64
|
|
sent uint64
|
|
})
|
|
|
|
for _, pathInfo := range StandardPaths {
|
|
if verbose {
|
|
fmt.Printf("Checking %s: %s - %s\n", pathInfo.Name, pathInfo.Path, pathInfo.Desc)
|
|
fmt.Println("-----------------------------------------------------------")
|
|
}
|
|
|
|
pathAddresses := filterByPath(addresses, pathInfo.Path)
|
|
|
|
for _, addr := range pathAddresses {
|
|
balance, txCount, received, sent, err := GetAddressInfo(addr.Address)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
addr.Balance = balance
|
|
addr.TxCount = int(txCount)
|
|
addr.Received = received
|
|
addr.Sent = sent
|
|
|
|
if txCount > 0 {
|
|
if verbose {
|
|
fmt.Printf(" %s\n", addr.Address)
|
|
fmt.Printf(" Path: %s\n", addr.Path)
|
|
fmt.Printf(" Balance: %.8f BTC\n", float64(balance)/100000000.0)
|
|
fmt.Printf(" Received: %.8f BTC\n", float64(received)/100000000.0)
|
|
fmt.Printf(" Sent: %.8f BTC\n", float64(sent)/100000000.0)
|
|
fmt.Printf(" Transactions: %d\n", txCount)
|
|
}
|
|
|
|
summary.TotalBalance += balance
|
|
summary.TotalReceived += received
|
|
summary.TotalSent += sent
|
|
summary.TotalTxCount += int(txCount)
|
|
summary.ActiveAddresses = append(summary.ActiveAddresses, addr)
|
|
|
|
// Update path stats
|
|
ps := pathStats[pathInfo.Path]
|
|
ps.count++
|
|
ps.received += received
|
|
ps.sent += sent
|
|
pathStats[pathInfo.Path] = ps
|
|
}
|
|
|
|
// Rate limiting
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
|
|
if verbose {
|
|
fmt.Println()
|
|
}
|
|
}
|
|
|
|
return summary, nil
|
|
}
|
|
|
|
// FetchTransactionHistory fetches and processes all transactions for active addresses
|
|
func FetchTransactionHistory(summary *types.WalletSummary) error {
|
|
var allTransactions []types.TransactionDetail
|
|
receiveTxMap := make(map[string]bool)
|
|
sendTxMap := make(map[string]bool)
|
|
|
|
for _, addr := range summary.ActiveAddresses {
|
|
if addr.TxCount == 0 {
|
|
continue
|
|
}
|
|
|
|
txs, err := GetTransactions(addr.Address)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
for _, tx := range txs {
|
|
txType := "self"
|
|
amount := uint64(0)
|
|
|
|
if tx.Received > 0 && tx.Sent == 0 {
|
|
txType = "received"
|
|
amount = tx.Received
|
|
receiveTxMap[tx.TxID] = true
|
|
} else if tx.Sent > 0 && tx.Received == 0 {
|
|
txType = "sent"
|
|
amount = tx.Sent
|
|
sendTxMap[tx.TxID] = true
|
|
} else if tx.Received > 0 && tx.Sent > 0 {
|
|
txType = "self"
|
|
amount = tx.Received
|
|
}
|
|
|
|
detail := types.TransactionDetail{
|
|
TxID: tx.TxID,
|
|
Time: tx.Time,
|
|
BlockHeight: tx.BlockHeight,
|
|
Confirmed: tx.Confirmed,
|
|
Type: txType,
|
|
Amount: amount,
|
|
Address: addr.Address,
|
|
}
|
|
|
|
allTransactions = append(allTransactions, detail)
|
|
}
|
|
|
|
// Rate limiting
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
|
|
// Sort by time (oldest first)
|
|
sort.Slice(allTransactions, func(i, j int) bool {
|
|
return allTransactions[i].Time.Before(allTransactions[j].Time)
|
|
})
|
|
|
|
// Calculate running balance
|
|
balance := uint64(0)
|
|
for i := range allTransactions {
|
|
tx := &allTransactions[i]
|
|
if tx.Type == "received" {
|
|
balance += tx.Amount
|
|
} else if tx.Type == "sent" {
|
|
if balance >= tx.Amount {
|
|
balance -= tx.Amount
|
|
}
|
|
}
|
|
tx.Balance = balance
|
|
}
|
|
|
|
summary.TransactionHistory = allTransactions
|
|
summary.ReceiveTxCount = len(receiveTxMap)
|
|
summary.SendTxCount = len(sendTxMap)
|
|
|
|
return nil
|
|
}
|
|
|
|
// filterByPath filters addresses by derivation path prefix
|
|
func filterByPath(addresses []types.AddressInfo, pathPrefix string) []types.AddressInfo {
|
|
var filtered []types.AddressInfo
|
|
for _, addr := range addresses {
|
|
if len(addr.Path) >= len(pathPrefix) && addr.Path[:len(pathPrefix)] == pathPrefix {
|
|
filtered = append(filtered, addr)
|
|
}
|
|
}
|
|
return filtered
|
|
}
|