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 }