first blood

This commit is contained in:
2026-02-02 23:51:17 +08:00
parent f8d24bf788
commit 3d3e814879
5 changed files with 704 additions and 2 deletions

314
tron_usdt_monitor.go Normal file
View File

@@ -0,0 +1,314 @@
package usdtman
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"sync"
"time"
)
// TronUSDTMonitor 监听 USDT 收款
type TronUSDTMonitor struct {
mu sync.RWMutex
addresses map[string]*AddressInfo // 地址映射
apiKey string
running bool
ctx context.Context
cancel context.CancelFunc
onPaymentReceived func(*USDTPayment)
}
// AddressInfo 地址信息
type AddressInfo struct {
Address string
LastBlock int64
TotalReceived float64
}
// USDTPayment USDT 收款信息
type USDTPayment struct {
Address string
Amount float64
TxID string
BlockNumber int64
Timestamp int64
From string
}
// TronGridEvent TronGrid 事件响应
type TronGridEvent struct {
Success bool `json:"success"`
Data []struct {
TransactionID string `json:"transaction_id"`
BlockNumber int64 `json:"block_number"`
BlockTimestamp int64 `json:"block_timestamp"`
ContractAddress string `json:"contract_address"`
EventName string `json:"event_name"`
Result map[string]interface{} `json:"result"`
} `json:"data"`
Meta struct {
PageSize int `json:"page_size"`
} `json:"meta"`
}
const (
TronGridAPI = "https://api.trongrid.io"
USDTContractAddress = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t" // USDT TRC20
)
// NewTronUSDTMonitor 创建监听器
func NewTronUSDTMonitor(apiKey string) *TronUSDTMonitor {
ctx, cancel := context.WithCancel(context.Background())
return &TronUSDTMonitor{
addresses: make(map[string]*AddressInfo),
apiKey: apiKey,
ctx: ctx,
cancel: cancel,
}
}
// AddAddress 添加监听地址
func (m *TronUSDTMonitor) AddAddress(address string) {
m.mu.Lock()
defer m.mu.Unlock()
if _, exists := m.addresses[address]; !exists {
m.addresses[address] = &AddressInfo{
Address: address,
LastBlock: 0,
}
}
}
// RemoveAddress 移除监听地址
func (m *TronUSDTMonitor) RemoveAddress(address string) {
m.mu.Lock()
defer m.mu.Unlock()
delete(m.addresses, address)
}
// SetPaymentCallback 设置收款回调
func (m *TronUSDTMonitor) SetPaymentCallback(callback func(*USDTPayment)) {
m.mu.Lock()
defer m.mu.Unlock()
m.onPaymentReceived = callback
}
// Start 开始监听
func (m *TronUSDTMonitor) Start() error {
m.mu.Lock()
if m.running {
m.mu.Unlock()
return fmt.Errorf("monitor already running")
}
m.running = true
m.mu.Unlock()
go m.monitorLoop()
return nil
}
// Stop 停止监听
func (m *TronUSDTMonitor) Stop() {
m.mu.Lock()
if !m.running {
m.mu.Unlock()
return
}
m.running = false
m.mu.Unlock()
m.cancel()
}
// monitorLoop 监听循环
func (m *TronUSDTMonitor) monitorLoop() {
ticker := time.NewTicker(3 * time.Second)
defer ticker.Stop()
for {
select {
case <-m.ctx.Done():
return
case <-ticker.C:
m.checkAllAddresses()
}
}
}
// checkAllAddresses 检查所有地址
func (m *TronUSDTMonitor) checkAllAddresses() {
m.mu.RLock()
addresses := make([]*AddressInfo, 0, len(m.addresses))
for _, info := range m.addresses {
addresses = append(addresses, info)
}
m.mu.RUnlock()
for _, addrInfo := range addresses {
m.checkAddress(addrInfo)
}
}
// checkAddress 检查单个地址
func (m *TronUSDTMonitor) checkAddress(addrInfo *AddressInfo) {
events, err := m.getUSDTEvents(addrInfo.Address, addrInfo.LastBlock)
if err != nil {
fmt.Printf("Error checking address %s: %v\n", addrInfo.Address, err)
return
}
for _, event := range events {
if event.EventName != "Transfer" {
continue
}
to, ok := event.Result["to"].(string)
if !ok || !strings.EqualFold(to, addrInfo.Address) {
continue
}
from, _ := event.Result["from"].(string)
valueStr, ok := event.Result["value"].(string)
if !ok {
continue
}
amount := parseUSDTAmount(valueStr)
if amount <= 0 {
continue
}
payment := &USDTPayment{
Address: addrInfo.Address,
Amount: amount,
TxID: event.TransactionID,
BlockNumber: event.BlockNumber,
Timestamp: event.BlockTimestamp,
From: from,
}
if event.BlockNumber > addrInfo.LastBlock {
addrInfo.LastBlock = event.BlockNumber
}
m.mu.RLock()
callback := m.onPaymentReceived
m.mu.RUnlock()
if callback != nil {
callback(payment)
}
}
}
// getUSDTEvents 获取 USDT 转账事件
func (m *TronUSDTMonitor) getUSDTEvents(address string, sinceBlock int64) ([]struct {
TransactionID string
BlockNumber int64
BlockTimestamp int64
EventName string
Result map[string]interface{}
}, error) {
url := fmt.Sprintf("%s/v1/contracts/%s/events?event_name=Transfer&only_to=true&min_block_timestamp=%d&limit=200",
TronGridAPI, USDTContractAddress, sinceBlock)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
if m.apiKey != "" {
req.Header.Set("TRON-PRO-API-KEY", m.apiKey)
}
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("TronGrid API error: %d %s", resp.StatusCode, string(body))
}
var result TronGridEvent
if err := json.Unmarshal(body, &result); err != nil {
return nil, err
}
if !result.Success {
return nil, fmt.Errorf("TronGrid API returned success=false")
}
var filtered []struct {
TransactionID string
BlockNumber int64
BlockTimestamp int64
EventName string
Result map[string]interface{}
}
for _, event := range result.Data {
to, ok := event.Result["to"].(string)
if ok && strings.EqualFold(to, address) {
filtered = append(filtered, struct {
TransactionID string
BlockNumber int64
BlockTimestamp int64
EventName string
Result map[string]interface{}
}{
TransactionID: event.TransactionID,
BlockNumber: event.BlockNumber,
BlockTimestamp: event.BlockTimestamp,
EventName: event.EventName,
Result: event.Result,
})
}
}
return filtered, nil
}
// parseUSDTAmount 解析 USDT 金额6位小数
func parseUSDTAmount(valueStr string) float64 {
valueStr = strings.TrimPrefix(valueStr, "0x")
var value int64
fmt.Sscanf(valueStr, "%d", &value)
return float64(value) / 1000000.0
}
// GetAddressInfo 获取地址信息
func (m *TronUSDTMonitor) GetAddressInfo(address string) (*AddressInfo, bool) {
m.mu.RLock()
defer m.mu.RUnlock()
info, exists := m.addresses[address]
return info, exists
}
// GetAllAddresses 获取所有监听地址
func (m *TronUSDTMonitor) GetAllAddresses() []string {
m.mu.RLock()
defer m.mu.RUnlock()
addresses := make([]string, 0, len(m.addresses))
for addr := range m.addresses {
addresses = append(addresses, addr)
}
return addresses
}