diff --git a/cmd/server/main.go b/cmd/server/main.go index 3c5a3d8..9884517 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -56,8 +56,8 @@ func main() { // 设置收款回调 uman.OnPaymentComplete(func(payment *usdtman.USDTPayment) { - log.Printf("💰 收到 USDT: %s -> %.6f USDT (确认数: %d, TxID: %s)", - payment.From, payment.Amount, payment.Confirmations, payment.TxID) + log.Printf("💰 收到 USDT: %s -> %s USDT (确认数: %d, TxID: %s)", + payment.From, payment.GetAmountString(), payment.Confirmations, payment.TxID) paymentLock.Lock() paymentEvents = append(paymentEvents, *payment) @@ -69,6 +69,10 @@ func main() { broadcastPayment(payment) }) + // test code + uman.AddAddress("TWwGSYwpSzT6GTBr4AQw9QF6m4VVui3UGc") // tronlink trc20 gasfree 地址 + uman.Start() + http.HandleFunc("/start", startMonitor) http.HandleFunc("/stop", stopMonitor) http.HandleFunc("/add-address", addAddress) @@ -222,7 +226,8 @@ func broadcastPayment(payment *usdtman.USDTPayment) { message := map[string]interface{}{ "type": "usdt_payment", "address": payment.Address, - "amount": payment.Amount, + "amount": payment.GetAmountFloat(), + "amountRaw": payment.Amount.String(), "from": payment.From, "txId": payment.TxID, "block": payment.BlockNumber, diff --git a/index.html b/index.html index 7fa1a1f..2e7c358 100644 --- a/index.html +++ b/index.html @@ -113,7 +113,7 @@ data.data.payments.map(p => '' + new Date(p.Timestamp).toLocaleString() + '' + '' + p.Address.substring(0, 10) + '...' + - '' + p.Amount.toFixed(6) + ' USDT' + + '' + (p.Amount / 1000000).toFixed(6) + ' USDT' + '' + p.From.substring(0, 10) + '...' + '' + p.Confirmations + '' + '' + diff --git a/tron_usdt_monitor.go b/tron_usdt_monitor.go index 7d0f0f1..58dcc2f 100644 --- a/tron_usdt_monitor.go +++ b/tron_usdt_monitor.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "io" + "math/big" "net/http" "net/url" "strings" @@ -43,7 +44,7 @@ type USDTMan struct { // USDTPayment USDT 收款信息 type USDTPayment struct { Address string - Amount float64 + Amount *big.Int // 原始金额(micro USDT,10^-6),例如 13260000 = 13.26 USDT TxID string BlockNumber int64 Timestamp int64 @@ -51,23 +52,57 @@ type USDTPayment struct { Confirmations int64 } -// TronGridTransaction 交易信息 +// GetAmountFloat 获取浮点数金额(USDT) +func (p *USDTPayment) GetAmountFloat() float64 { + divisor := new(big.Float).SetInt64(1000000) + amount := new(big.Float).SetInt(p.Amount) + result := new(big.Float).Quo(amount, divisor) + f, _ := result.Float64() + return f +} + +// GetAmountString 获取字符串金额(USDT,保留6位小数) +func (p *USDTPayment) GetAmountString() string { + return fmt.Sprintf("%.6f", p.GetAmountFloat()) +} + +// MarshalJSON 自定义 JSON 序列化 +func (p *USDTPayment) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Address string `json:"Address"` + Amount float64 `json:"Amount"` + AmountRaw string `json:"AmountRaw"` + TxID string `json:"TxID"` + BlockNumber int64 `json:"BlockNumber"` + Timestamp int64 `json:"Timestamp"` + From string `json:"From"` + Confirmations int64 `json:"Confirmations"` + }{ + Address: p.Address, + Amount: p.GetAmountFloat(), + AmountRaw: p.Amount.String(), + TxID: p.TxID, + BlockNumber: p.BlockNumber, + Timestamp: p.Timestamp, + From: p.From, + Confirmations: p.Confirmations, + }) +} + +// TronGridTransaction TRC20 交易信息 type TronGridTransaction struct { - Ret []map[string]interface{} `json:"ret"` - TxID string `json:"txID"` - BlockNumber int64 `json:"blockNumber"` - BlockTimeStamp int64 `json:"block_timestamp"` - RawData struct { - Contract []struct { - Parameter struct { - Value struct { - Amount int64 `json:"amount"` - To string `json:"to_address"` - From string `json:"owner_address"` - } `json:"value"` - } `json:"parameter"` - } `json:"contract"` - } `json:"raw_data"` + TransactionID string `json:"transaction_id"` + BlockTimestamp int64 `json:"block_timestamp"` + From string `json:"from"` + To string `json:"to"` + Type string `json:"type"` + Value string `json:"value"` + TokenInfo struct { + Symbol string `json:"symbol"` + Address string `json:"address"` + Decimals int `json:"decimals"` + Name string `json:"name"` + } `json:"token_info"` } // TronGridAccountTransactions 账户交易列表 @@ -75,8 +110,8 @@ type TronGridAccountTransactions struct { Success bool `json:"success"` Data []TronGridTransaction `json:"data"` Meta struct { - PageSize int `json:"page_size"` - Fingerprint string `json:"fingerprint"` + At int64 `json:"at"` + PageSize int `json:"page_size"` } `json:"meta"` } @@ -221,28 +256,51 @@ func (m *USDTMan) checkAddress(address string) { for _, txn := range transactions { // 检查是否已处理 m.txnMutex.RLock() - processed := m.processedTxns[txn.TxID] + processed := m.processedTxns[txn.TransactionID] m.txnMutex.RUnlock() if processed { continue } + // 获取交易的区块号(需要通过 transaction_id 查询) + blockNumber, err := m.getTransactionBlock(txn.TransactionID) + if err != nil { + fmt.Printf("Error getting transaction block: %v\n", err) + continue + } + // 计算确认数 - confirmations := currentBlock - txn.BlockNumber + confirmations := currentBlock - blockNumber if confirmations < m.minConfirmations { continue } - // 解析交易数据 - payment := m.parseTransaction(&txn, address, confirmations) - if payment == nil { + // 检查是否是转入该地址 + if !strings.EqualFold(txn.To, address) { continue } + // 解析金额(使用 big.Int) + amount := new(big.Int) + amount, ok := amount.SetString(txn.Value, 10) + if !ok || amount.Sign() <= 0 { + continue + } + + payment := &USDTPayment{ + Address: address, + Amount: amount, + TxID: txn.TransactionID, + BlockNumber: blockNumber, + Timestamp: txn.BlockTimestamp, + From: txn.From, + Confirmations: confirmations, + } + // 标记为已处理 m.txnMutex.Lock() - m.processedTxns[txn.TxID] = true + m.processedTxns[txn.TransactionID] = true m.txnMutex.Unlock() // 触发回调 @@ -293,6 +351,46 @@ func (m *USDTMan) getUSDTTransactions(address string) ([]TronGridTransaction, er return result.Data, nil } +// getTransactionBlock 获取交易的区块号 +func (m *USDTMan) getTransactionBlock(txID string) (int64, error) { + url := fmt.Sprintf("%s/wallet/gettransactioninfobyid?value=%s", TronGridAPI, txID) + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return 0, err + } + + if m.apiKey != "" { + req.Header.Set("TRON-PRO-API-KEY", m.apiKey) + } + + resp, err := m.httpClient.Do(req) + if err != nil { + return 0, err + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return 0, err + } + + if resp.StatusCode != 200 { + return 0, fmt.Errorf("TronGrid API error: %d", resp.StatusCode) + } + + var result struct { + BlockNumber int64 `json:"blockNumber"` + } + + fmt.Println("body", string(body)) + if err := json.Unmarshal(body, &result); err != nil { + return 0, err + } + + return result.BlockNumber, nil +} + // getCurrentBlock 获取当前区块高度 func (m *USDTMan) getCurrentBlock() (int64, error) { url := fmt.Sprintf("%s/wallet/getnowblock", TronGridAPI) @@ -329,41 +427,7 @@ func (m *USDTMan) getCurrentBlock() (int64, error) { return blockInfo.BlockHeader.RawData.Number, nil } -// parseTransaction 解析交易 -func (m *USDTMan) parseTransaction(txn *TronGridTransaction, targetAddress string, confirmations int64) *USDTPayment { - if len(txn.Ret) == 0 || txn.Ret[0]["contractRet"] != "SUCCESS" { - return nil - } - - if len(txn.RawData.Contract) == 0 { - return nil - } - - contract := txn.RawData.Contract[0] - value := contract.Parameter.Value - - // 转换地址格式 - to := value.To - from := value.From - - // 检查是否是目标地址 - if !strings.EqualFold(to, targetAddress) { - return nil - } - - // USDT 是 6 位小数 - amount := float64(value.Amount) / 1000000.0 - - return &USDTPayment{ - Address: targetAddress, - Amount: amount, - TxID: txn.TxID, - BlockNumber: txn.BlockNumber, - Timestamp: txn.BlockTimeStamp, - From: from, - Confirmations: confirmations, - } -} +// parseTransaction 解析交易(已废弃,直接在 checkAddress 中处理) // GetAddresses 获取所有监听地址 func (m *USDTMan) GetAddresses() []string {