Files
usdtman/tron_usdt_monitor.go
2026-02-02 23:51:17 +08:00

315 lines
6.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}