first blood
This commit is contained in:
314
tron_usdt_monitor.go
Normal file
314
tron_usdt_monitor.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user