This commit is contained in:
Ginta
2026-04-16 19:52:03 +08:00
parent f7c5f678c4
commit b05b976071
4 changed files with 474 additions and 269 deletions
+84 -206
View File
@@ -1,261 +1,140 @@
package service package service
import ( import (
"encoding/hex"
"errors" "errors"
"fmt" "fmt"
"math/big"
"net/http"
"strings"
"sync"
"github.com/assimon/luuu/config"
tron "github.com/assimon/luuu/crypto"
"github.com/assimon/luuu/model/data" "github.com/assimon/luuu/model/data"
"github.com/assimon/luuu/model/mdb" "github.com/assimon/luuu/model/mdb"
"github.com/assimon/luuu/model/request" "github.com/assimon/luuu/model/request"
"github.com/assimon/luuu/telegram" "github.com/assimon/luuu/telegram"
"github.com/assimon/luuu/util/constant" "github.com/assimon/luuu/util/constant"
"github.com/assimon/luuu/util/http_client"
"github.com/assimon/luuu/util/log" "github.com/assimon/luuu/util/log"
"github.com/assimon/luuu/util/math" "github.com/assimon/luuu/util/math"
"github.com/dromara/carbon/v2" "github.com/dromara/carbon/v2"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/gookit/goutil/stdutil"
"github.com/shopspring/decimal" "github.com/shopspring/decimal"
"github.com/tidwall/gjson" "math/big"
"strings"
) )
const TRC20_USDT_ID = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t" func TryProcessTronTRC20Transfer(toAddr string, rawValue *big.Int, txHash string, blockTsMs int64) {
func Trc20CallBack(address string, wg *sync.WaitGroup) {
defer wg.Done()
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
log.Sugar.Error(err) log.Sugar.Errorf("[TRC20][%s] TryProcessTronTRC20Transfer panic: %v", toAddr, err)
} }
}() }()
var innerWg sync.WaitGroup addr := strings.TrimSpace(toAddr)
innerWg.Add(2) if addr == "" || rawValue == nil || rawValue.Sign() <= 0 {
go checkTrxTransfers(address, &innerWg)
go checkTrc20Transfers(address, &innerWg)
innerWg.Wait()
}
func checkTrxTransfers(address string, wg *sync.WaitGroup) {
defer wg.Done()
defer func() {
if err := recover(); err != nil {
log.Sugar.Errorf("[TRX][%s] panic recovered: %v", address, err)
}
}()
client := http_client.GetHttpClient()
startTime := carbon.Now().AddHours(-24).TimestampMilli()
endTime := carbon.Now().TimestampMilli()
url := fmt.Sprintf("https://api.trongrid.io/v1/accounts/%s/transactions", address)
resp, err := client.R().SetQueryParams(map[string]string{
"order_by": "block_timestamp,desc",
"limit": "100",
"only_to": "true",
"min_timestamp": stdutil.ToString(startTime),
"max_timestamp": stdutil.ToString(endTime),
}).SetHeader("TRON-PRO-API-KEY", config.TRON_GRID_API_KEY).Get(url)
if err != nil {
panic(err)
}
if resp.StatusCode() != http.StatusOK {
panic(fmt.Sprintf("TRX API returned status %d", resp.StatusCode()))
}
success := gjson.GetBytes(resp.Body(), "success").Bool()
if !success {
panic("TRX API response indicates failure")
}
transfers := gjson.GetBytes(resp.Body(), "data").Array()
if len(transfers) == 0 {
log.Sugar.Debugf("[TRX][%s] no transfer records found", address)
return return
} }
log.Sugar.Debugf("[TRX][%s] fetched %d transfer records", address, len(transfers))
for i, transfer := range transfers { decimalQuant := decimal.NewFromBigInt(rawValue, 0)
if transfer.Get("raw_data.contract.0.type").String() != "TransferContract" { amount := math.MustParsePrecFloat64(decimalQuant.Div(decimal.NewFromInt(1_000_000)).InexactFloat64(), 2)
continue
}
if transfer.Get("ret.0.contractRet").String() != "SUCCESS" {
continue
}
toAddressHex := transfer.Get("raw_data.contract.0.parameter.value.to_address").String()
toBytes, err := hex.DecodeString(toAddressHex)
if err != nil {
log.Sugar.Errorf("[TRX][%s] decode address failed on tx #%d: %v", address, i, err)
continue
}
if tron.EncodeCheck(toBytes) != address {
continue
}
rawAmount := transfer.Get("raw_data.contract.0.parameter.value.amount").String()
decimalQuant, err := decimal.NewFromString(rawAmount)
if err != nil {
log.Sugar.Errorf("[TRX][%s] parse amount failed on tx #%d: %v", address, i, err)
continue
}
amount := math.MustParsePrecFloat64(decimalQuant.Div(decimal.NewFromInt(1000000)).InexactFloat64(), 2)
if amount <= 0 { if amount <= 0 {
continue return
} }
txID := transfer.Get("txID").String() tradeID, err := data.GetTradeIdByWalletAddressAndAmountAndToken(mdb.NetworkTron, addr, "USDT", amount)
tradeID, err := data.GetTradeIdByWalletAddressAndAmountAndToken(mdb.NetworkTron, address, "TRX", amount)
if err != nil { if err != nil {
panic(err) log.Sugar.Warnf("[TRC20][%s] lock lookup: %v", addr, err)
return
} }
if tradeID == "" { if tradeID == "" {
log.Sugar.Debugf("[TRX][%s] skip unmatched tx hash=%s amount=%.2f", address, txID, amount) log.Sugar.Debugf("[TRC20][%s] skip unmatched tx hash=%s amount=%.2f", addr, txHash, amount)
continue return
} }
log.Sugar.Infof("[TRX][%s] matched trade_id=%s hash=%s amount=%.2f", address, tradeID, txID, amount)
order, err := data.GetOrderInfoByTradeId(tradeID) order, err := data.GetOrderInfoByTradeId(tradeID)
if err != nil { if err != nil {
panic(err) log.Sugar.Warnf("[TRC20][%s] load order: %v", addr, err)
}
blockTimestamp := transfer.Get("block_timestamp").Int()
createTime := order.CreatedAt.TimestampMilli()
if blockTimestamp < createTime {
log.Sugar.Warnf("[TRX][%s] skip tx %s because block time %d is before order create time %d", address, txID, blockTimestamp, createTime)
continue
}
req := &request.OrderProcessingRequest{
ReceiveAddress: address,
Token: "TRX",
Network: mdb.NetworkTron,
TradeId: tradeID,
Amount: amount,
BlockTransactionId: txID,
}
err = OrderProcessing(req)
if err != nil {
if errors.Is(err, constant.OrderBlockAlreadyProcess) || errors.Is(err, constant.OrderStatusConflict) {
log.Sugar.Infof("[TRX][%s] skip resolved transfer trade_id=%s hash=%s err=%v", address, tradeID, txID, err)
continue
}
panic(err)
}
sendPaymentNotification(order)
log.Sugar.Infof("[TRX][%s] payment processed trade_id=%s hash=%s", address, tradeID, txID)
}
}
func checkTrc20Transfers(address string, wg *sync.WaitGroup) {
defer wg.Done()
defer func() {
if err := recover(); err != nil {
log.Sugar.Errorf("[TRC20][%s] panic recovered: %v", address, err)
}
}()
client := http_client.GetHttpClient()
startTime := carbon.Now().AddHours(-24).TimestampMilli()
endTime := carbon.Now().TimestampMilli()
url := fmt.Sprintf("https://api.trongrid.io/v1/accounts/%s/transactions/trc20", address)
resp, err := client.R().SetQueryParams(map[string]string{
"order_by": "block_timestamp,desc",
"limit": "100",
"only_to": "true",
"min_timestamp": stdutil.ToString(startTime),
"max_timestamp": stdutil.ToString(endTime),
}).SetHeader("TRON-PRO-API-KEY", config.TRON_GRID_API_KEY).Get(url)
if err != nil {
panic(err)
}
if resp.StatusCode() != http.StatusOK {
panic(fmt.Sprintf("TRC20 API returned status %d", resp.StatusCode()))
}
success := gjson.GetBytes(resp.Body(), "success").Bool()
if !success {
panic("TRC20 API response indicates failure")
}
transfers := gjson.GetBytes(resp.Body(), "data").Array()
if len(transfers) == 0 {
log.Sugar.Debugf("[TRC20][%s] no transfer records found", address)
return return
} }
log.Sugar.Debugf("[TRC20][%s] fetched %d transfer records", address, len(transfers)) if blockTsMs > 0 && blockTsMs < order.CreatedAt.TimestampMilli() {
log.Sugar.Warnf("[TRC20][%s] skip tx %s because block time %d is before order create time %d", addr, txHash, blockTsMs, order.CreatedAt.TimestampMilli())
for i, transfer := range transfers { return
if transfer.Get("token_info.address").String() != TRC20_USDT_ID {
continue
}
if transfer.Get("to").String() != address {
continue
}
valueStr := transfer.Get("value").String()
decimalQuant, err := decimal.NewFromString(valueStr)
if err != nil {
log.Sugar.Errorf("[TRC20][%s] parse value failed on tx #%d: %v", address, i, err)
continue
}
tokenDecimals := transfer.Get("token_info.decimals").Int()
amount := math.MustParsePrecFloat64(decimalQuant.Div(decimal.New(1, int32(tokenDecimals))).InexactFloat64(), 2)
if amount <= 0 {
continue
}
txID := transfer.Get("transaction_id").String()
tradeID, err := data.GetTradeIdByWalletAddressAndAmountAndToken(mdb.NetworkTron, address, "USDT", amount)
if err != nil {
panic(err)
}
if tradeID == "" {
log.Sugar.Debugf("[TRC20][%s] skip unmatched tx hash=%s amount=%.2f", address, txID, amount)
continue
}
log.Sugar.Infof("[TRC20][%s] matched trade_id=%s hash=%s amount=%.2f", address, tradeID, txID, amount)
order, err := data.GetOrderInfoByTradeId(tradeID)
if err != nil {
panic(err)
}
blockTimestamp := transfer.Get("block_timestamp").Int()
createTime := order.CreatedAt.TimestampMilli()
if blockTimestamp < createTime {
log.Sugar.Warnf("[TRC20][%s] skip tx %s because block time %d is before order create time %d", address, txID, blockTimestamp, createTime)
continue
} }
req := &request.OrderProcessingRequest{ req := &request.OrderProcessingRequest{
ReceiveAddress: address, ReceiveAddress: addr,
Token: "USDT", Token: "USDT",
Network: mdb.NetworkTron, Network: mdb.NetworkTron,
TradeId: tradeID, TradeId: tradeID,
Amount: amount, Amount: amount,
BlockTransactionId: txID, BlockTransactionId: txHash,
} }
err = OrderProcessing(req) err = OrderProcessing(req)
if err != nil { if err != nil {
if errors.Is(err, constant.OrderBlockAlreadyProcess) || errors.Is(err, constant.OrderStatusConflict) { if errors.Is(err, constant.OrderBlockAlreadyProcess) || errors.Is(err, constant.OrderStatusConflict) {
log.Sugar.Infof("[TRC20][%s] skip resolved transfer trade_id=%s hash=%s err=%v", address, tradeID, txID, err) log.Sugar.Infof("[TRC20][%s] skip resolved transfer trade_id=%s hash=%s err=%v", addr, tradeID, txHash, err)
continue return
} }
panic(err) log.Sugar.Errorf("[TRC20][%s] OrderProcessing trade_id=%s hash=%s: %v", addr, tradeID, txHash, err)
return
} }
sendPaymentNotification(order) sendPaymentNotification(order)
log.Sugar.Infof("[TRC20][%s] payment processed trade_id=%s hash=%s", address, tradeID, txID) log.Sugar.Infof("[TRC20][%s] payment processed trade_id=%s hash=%s", addr, tradeID, txHash)
} }
func TryProcessTronTRXTransfer(toAddr string, rawSun int64, txHash string, blockTsMs int64) {
defer func() {
if err := recover(); err != nil {
log.Sugar.Errorf("[TRX][%s] TryProcessTronTRXTransfer panic: %v", toAddr, err)
}
}()
addr := strings.TrimSpace(toAddr)
if addr == "" || rawSun <= 0 {
return
}
decimalQuant := decimal.NewFromInt(rawSun)
amount := math.MustParsePrecFloat64(decimalQuant.Div(decimal.NewFromInt(1_000_000)).InexactFloat64(), 2)
if amount <= 0 {
return
}
tradeID, err := data.GetTradeIdByWalletAddressAndAmountAndToken(mdb.NetworkTron, addr, "TRX", amount)
if err != nil {
log.Sugar.Warnf("[TRX][%s] lock lookup: %v", addr, err)
return
}
if tradeID == "" {
log.Sugar.Debugf("[TRX][%s] skip unmatched tx hash=%s amount=%.2f", addr, txHash, amount)
return
}
order, err := data.GetOrderInfoByTradeId(tradeID)
if err != nil {
log.Sugar.Warnf("[TRX][%s] load order: %v", addr, err)
return
}
if blockTsMs > 0 && blockTsMs < order.CreatedAt.TimestampMilli() {
log.Sugar.Warnf("[TRX][%s] skip tx %s because block time %d is before order create time %d", addr, txHash, blockTsMs, order.CreatedAt.TimestampMilli())
return
}
req := &request.OrderProcessingRequest{
ReceiveAddress: addr,
Token: "TRX",
Network: mdb.NetworkTron,
TradeId: tradeID,
Amount: amount,
BlockTransactionId: txHash,
}
err = OrderProcessing(req)
if err != nil {
if errors.Is(err, constant.OrderBlockAlreadyProcess) || errors.Is(err, constant.OrderStatusConflict) {
log.Sugar.Infof("[TRX][%s] skip resolved transfer trade_id=%s hash=%s err=%v", addr, tradeID, txHash, err)
return
}
log.Sugar.Errorf("[TRX][%s] OrderProcessing trade_id=%s hash=%s: %v", addr, tradeID, txHash, err)
return
}
sendPaymentNotification(order)
log.Sugar.Infof("[TRX][%s] payment processed trade_id=%s hash=%s", addr, tradeID, txHash)
} }
func evmChainLogLabel(chainNetwork string) string { func evmChainLogLabel(chainNetwork string) string {
@@ -273,7 +152,6 @@ func evmChainLogLabel(chainNetwork string) string {
} }
} }
// TryProcessEvmERC20Transfer 处理各 EVM 链上 USDT/USDC(及 Polygon USDC.e)的 Transfer 入账(合约与 network 需一致)。
func TryProcessEvmERC20Transfer(chainNetwork string, contract common.Address, toAddr common.Address, rawValue *big.Int, txHash string, blockTsMs int64) { func TryProcessEvmERC20Transfer(chainNetwork string, contract common.Address, toAddr common.Address, rawValue *big.Int, txHash string, blockTsMs int64) {
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
+2 -8
View File
@@ -11,17 +11,11 @@ func Start() {
go StartBscWebSocketListener() go StartBscWebSocketListener()
go StartPolygonWebSocketListener() go StartPolygonWebSocketListener()
go StartPlasmaWebSocketListener() go StartPlasmaWebSocketListener()
go StartTronBlockScannerListener()
c := cron.New() c := cron.New()
// trc20钱包监听
_, err := c.AddJob("@every 5s", ListenTrc20Job{})
if err != nil {
log.Sugar.Errorf("[task] Failed to add ListenTrc20Job: %v", err)
return
}
log.Sugar.Info("[task] ListenTrc20Job scheduled successfully (@every 5s)")
// solana钱包监听 // solana钱包监听
_, err = c.AddJob("@every 5s", ListenSolJob{}) _, err := c.AddJob("@every 5s", ListenSolJob{})
if err != nil { if err != nil {
log.Sugar.Errorf("[task] Failed to add ListenSolJob: %v", err) log.Sugar.Errorf("[task] Failed to add ListenSolJob: %v", err)
return return
+372
View File
@@ -0,0 +1,372 @@
package task
import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"math/big"
"net/http"
"strings"
"time"
"github.com/assimon/luuu/config"
tron "github.com/assimon/luuu/crypto"
"github.com/assimon/luuu/model/service"
"github.com/assimon/luuu/util/log"
)
const (
TronAPIBase = "https://api.trongrid.io"
// USDT 合约地址 (TRC20 主网)
USDTContractHex = "41a614f803b6fd780986a42c78ec9c7f77e6ded13c" // Base58: TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t
// transfer(address,uint256) 方法签名前4字节
TransferMethodID = "a9059cbb"
PollInterval = 3 * time.Second
RequestTimeout = 10 * time.Second
)
func HexToTronAddress(hexAddr string) (string, error) {
hexAddr = strings.TrimPrefix(hexAddr, "0x")
hexAddr = strings.TrimPrefix(hexAddr, "0X")
raw, err := hex.DecodeString(hexAddr)
if err != nil {
return "", err
}
// 确保有 0x41 前缀(TRON 主网地址前缀)
if len(raw) == 20 {
raw = append([]byte{0x41}, raw...)
}
if len(raw) != 21 {
return "", fmt.Errorf("地址长度非法: %d bytes", len(raw))
}
return tron.EncodeCheck(raw), nil
}
type BlockHeader struct {
RawData struct {
Number int64 `json:"number"`
Timestamp int64 `json:"timestamp"`
WitnessAddress string `json:"witness_address"`
ParentHash string `json:"parentHash"`
Version int `json:"version"`
} `json:"raw_data"`
}
type TriggerSmartContractValue struct {
OwnerAddress string `json:"owner_address"`
ContractAddress string `json:"contract_address"`
Data string `json:"data"`
CallValue int64 `json:"call_value"`
}
type ContractParam struct {
TypeURL string `json:"type_url"`
Value json.RawMessage `json:"value"`
}
type Transaction struct {
TxID string `json:"txID"`
RawData struct {
Contract []struct {
Type string `json:"type"`
Parameter ContractParam `json:"parameter"`
} `json:"contract"`
Timestamp int64 `json:"timestamp"`
FeeLimit int64 `json:"fee_limit"`
} `json:"raw_data"`
Ret []struct {
ContractRet string `json:"contractRet"`
} `json:"ret"`
}
type Block struct {
BlockID string `json:"blockID"`
BlockHeader BlockHeader `json:"block_header"`
Transactions []Transaction `json:"transactions"`
}
type USDTTransfer struct {
TxID string
From string
To string
Raw *big.Int // 原始数值(6 decimals
Status string
}
type TRXTransfer struct {
TxID string
From string
To string
RawSun int64 // 单位: SUN
Status string
}
type TransferContractValue struct {
OwnerAddress string `json:"owner_address"`
ToAddress string `json:"to_address"`
Amount int64 `json:"amount"` // 单位: SUN
}
var httpClient = &http.Client{Timeout: RequestTimeout}
func doPost(url string, body interface{}) ([]byte, error) {
data, _ := json.Marshal(body)
req, err := http.NewRequest("POST", url, bytes.NewReader(data))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
if k := strings.TrimSpace(config.TRON_GRID_API_KEY); k != "" {
req.Header.Set("TRON-PRO-API-KEY", k)
}
resp, err := httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
b, _ := io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(b))
}
return b, nil
}
func GetNowBlock() (*Block, error) {
b, err := doPost(TronAPIBase+"/wallet/getnowblock", map[string]interface{}{})
if err != nil {
return nil, err
}
var block Block
return &block, json.Unmarshal(b, &block)
}
func GetBlockByNum(num int64) (*Block, error) {
b, err := doPost(TronAPIBase+"/wallet/getblockbynum", map[string]interface{}{"num": num})
if err != nil {
return nil, err
}
var block Block
return &block, json.Unmarshal(b, &block)
}
func parseUSDTTransfer(tx Transaction) *USDTTransfer {
if len(tx.RawData.Contract) == 0 {
return nil
}
c := tx.RawData.Contract[0]
if c.Type != "TriggerSmartContract" {
return nil
}
var val TriggerSmartContractValue
if err := json.Unmarshal(c.Parameter.Value, &val); err != nil {
return nil
}
// 检查是否是 USDT 合约
contractHex := strings.ToLower(strings.TrimPrefix(val.ContractAddress, "0x"))
if contractHex != USDTContractHex {
return nil
}
// 解析 data 字段
// 格式: [4字节方法ID][32字节 to 地址][32字节 amount]
data := strings.TrimPrefix(strings.ToLower(val.Data), "0x")
if len(data) < 8+64+64 {
return nil
}
if data[:8] != TransferMethodID {
return nil
}
// to 地址:后 40 个十六进制字符(20 字节)
toHex := data[8+24 : 8+64] // 跳过前12字节填充,取后20字节
toAddr, err := HexToTronAddress(toHex)
if err != nil {
return nil
}
// amount:后 32 字节大整数
amountHex := data[8+64 : 8+64+64]
amountBytes, err := hex.DecodeString(amountHex)
if err != nil {
return nil
}
amountBig := new(big.Int).SetBytes(amountBytes)
// from 地址
fromAddr, err := HexToTronAddress(val.OwnerAddress)
if err != nil {
fromAddr = val.OwnerAddress
}
// 交易状态
status := "SUCCESS"
if len(tx.Ret) > 0 && tx.Ret[0].ContractRet != "" && tx.Ret[0].ContractRet != "SUCCESS" {
status = tx.Ret[0].ContractRet
}
return &USDTTransfer{
TxID: tx.TxID,
From: fromAddr,
To: toAddr,
Raw: amountBig,
Status: status,
}
}
func parseTRXTransfer(tx Transaction) *TRXTransfer {
if len(tx.RawData.Contract) == 0 {
return nil
}
c := tx.RawData.Contract[0]
if c.Type != "TransferContract" {
return nil
}
var val TransferContractValue
if err := json.Unmarshal(c.Parameter.Value, &val); err != nil {
return nil
}
if val.Amount <= 0 {
return nil
}
fromAddr, err := HexToTronAddress(val.OwnerAddress)
if err != nil {
fromAddr = val.OwnerAddress
}
toAddr, err := HexToTronAddress(val.ToAddress)
if err != nil {
toAddr = val.ToAddress
}
status := "SUCCESS"
if len(tx.Ret) > 0 && tx.Ret[0].ContractRet != "" && tx.Ret[0].ContractRet != "SUCCESS" {
status = tx.Ret[0].ContractRet
}
return &TRXTransfer{
TxID: tx.TxID,
From: fromAddr,
To: toAddr,
RawSun: val.Amount,
Status: status,
}
}
func processBlock(block *Block) {
blockTsMs := block.BlockHeader.RawData.Timestamp
for _, tx := range block.Transactions {
if t := parseUSDTTransfer(tx); t != nil {
if t.Status != "SUCCESS" {
continue
}
service.TryProcessTronTRC20Transfer(t.To, t.Raw, t.TxID, blockTsMs)
continue
}
if t := parseTRXTransfer(tx); t != nil {
if t.Status != "SUCCESS" {
continue
}
service.TryProcessTronTRXTransfer(t.To, t.RawSun, t.TxID, blockTsMs)
}
}
}
type Scanner struct {
lastBlock int64
// 统计
totalBlocks int64
totalUSDTTxs int64
totalTRXTxs int64
}
func NewScanner() *Scanner {
return &Scanner{}
}
func (s *Scanner) Init() error {
log.Sugar.Infof("[TRON-BLOCK] node=%s", TronAPIBase)
block, err := GetNowBlock()
if err != nil {
return fmt.Errorf("获取初始块失败: %w", err)
}
s.lastBlock = block.BlockHeader.RawData.Number
log.Sugar.Infof("[TRON-BLOCK] start block=%d", s.lastBlock)
return nil
}
func (s *Scanner) Run() {
log.Sugar.Info("[TRON-BLOCK] start scanning (USDT TRC20 + TRX)")
ticker := time.NewTicker(PollInterval)
defer ticker.Stop()
statTicker := time.NewTicker(60 * time.Second)
defer statTicker.Stop()
for {
select {
case <-statTicker.C:
log.Sugar.Infof("[TRON-BLOCK] stats blocks=%d usdt=%d trx=%d", s.totalBlocks, s.totalUSDTTxs, s.totalTRXTxs)
case <-ticker.C:
s.poll()
}
}
}
func (s *Scanner) poll() {
latest, err := GetNowBlock()
if err != nil {
log.Sugar.Warnf("[TRON-BLOCK] get latest block: %v", err)
return
}
latestNum := latest.BlockHeader.RawData.Number
if latestNum <= s.lastBlock {
return
}
for num := s.lastBlock + 1; num <= latestNum; num++ {
var block *Block
if num == latestNum {
block = latest
} else {
block, err = GetBlockByNum(num)
if err != nil {
log.Sugar.Warnf("[TRON-BLOCK] get block %d: %v", num, err)
continue
}
time.Sleep(200 * time.Millisecond)
}
processBlock(block)
s.lastBlock = num
s.totalBlocks++
for _, tx := range block.Transactions {
if parseUSDTTransfer(tx) != nil {
s.totalUSDTTxs++
} else if parseTRXTransfer(tx) != nil {
s.totalTRXTxs++
}
}
}
}
func StartTronBlockScannerListener() {
scanner := NewScanner()
if err := scanner.Init(); err != nil {
log.Sugar.Errorf("[TRON-BLOCK] init: %v", err)
return
}
scanner.Run()
}
-39
View File
@@ -1,39 +0,0 @@
package task
import (
"sync"
"github.com/assimon/luuu/model/data"
"github.com/assimon/luuu/model/mdb"
"github.com/assimon/luuu/model/service"
"github.com/assimon/luuu/util/log"
)
type ListenTrc20Job struct{}
var gListenTrc20JobLock sync.Mutex
func (r ListenTrc20Job) Run() {
gListenTrc20JobLock.Lock()
defer gListenTrc20JobLock.Unlock()
log.Sugar.Debug("[ListenTrc20Job] Job triggered")
walletAddress, err := data.GetAvailableWalletAddressByNetwork(mdb.NetworkTron)
if err != nil {
log.Sugar.Errorf("[ListenTrc20Job] Failed to get wallet addresses: %v", err)
return
}
if len(walletAddress) <= 0 {
log.Sugar.Debug("[ListenTrc20Job] No available wallet addresses")
return
}
log.Sugar.Infof("[ListenTrc20Job] Found %d wallet addresses to monitor", len(walletAddress))
var wg sync.WaitGroup
for _, address := range walletAddress {
log.Sugar.Infof("[ListenTrc20Job] Listening to address: %s", address.Address)
wg.Add(1)
go service.Trc20CallBack(address.Address, &wg)
}
wg.Wait()
log.Sugar.Debug("[ListenTrc20Job] Job completed")
}