diff --git a/src/model/service/task_service.go b/src/model/service/task_service.go index 131da67..833e928 100644 --- a/src/model/service/task_service.go +++ b/src/model/service/task_service.go @@ -1,261 +1,140 @@ package service import ( - "encoding/hex" "errors" "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/mdb" "github.com/assimon/luuu/model/request" "github.com/assimon/luuu/telegram" "github.com/assimon/luuu/util/constant" - "github.com/assimon/luuu/util/http_client" "github.com/assimon/luuu/util/log" "github.com/assimon/luuu/util/math" "github.com/dromara/carbon/v2" "github.com/ethereum/go-ethereum/common" - "github.com/gookit/goutil/stdutil" "github.com/shopspring/decimal" - "github.com/tidwall/gjson" + "math/big" + "strings" ) -const TRC20_USDT_ID = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t" - -func Trc20CallBack(address string, wg *sync.WaitGroup) { - defer wg.Done() +func TryProcessTronTRC20Transfer(toAddr string, rawValue *big.Int, txHash string, blockTsMs int64) { defer func() { if err := recover(); err != nil { - log.Sugar.Error(err) + log.Sugar.Errorf("[TRC20][%s] TryProcessTronTRC20Transfer panic: %v", toAddr, err) } }() - var innerWg sync.WaitGroup - innerWg.Add(2) - 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) + addr := strings.TrimSpace(toAddr) + if addr == "" || rawValue == nil || rawValue.Sign() <= 0 { return } - log.Sugar.Debugf("[TRX][%s] fetched %d transfer records", address, len(transfers)) - for i, transfer := range transfers { - if transfer.Get("raw_data.contract.0.type").String() != "TransferContract" { - 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 { - continue - } - - txID := transfer.Get("txID").String() - tradeID, err := data.GetTradeIdByWalletAddressAndAmountAndToken(mdb.NetworkTron, address, "TRX", amount) - if err != nil { - panic(err) - } - if tradeID == "" { - log.Sugar.Debugf("[TRX][%s] skip unmatched tx hash=%s amount=%.2f", address, txID, amount) - continue - } - log.Sugar.Infof("[TRX][%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("[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) + decimalQuant := decimal.NewFromBigInt(rawValue, 0) + amount := math.MustParsePrecFloat64(decimalQuant.Div(decimal.NewFromInt(1_000_000)).InexactFloat64(), 2) + if amount <= 0 { + return } + + tradeID, err := data.GetTradeIdByWalletAddressAndAmountAndToken(mdb.NetworkTron, addr, "USDT", amount) + if err != nil { + log.Sugar.Warnf("[TRC20][%s] lock lookup: %v", addr, err) + return + } + if tradeID == "" { + log.Sugar.Debugf("[TRC20][%s] skip unmatched tx hash=%s amount=%.2f", addr, txHash, amount) + return + } + + order, err := data.GetOrderInfoByTradeId(tradeID) + if err != nil { + log.Sugar.Warnf("[TRC20][%s] load order: %v", addr, err) + return + } + 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()) + return + } + + req := &request.OrderProcessingRequest{ + ReceiveAddress: addr, + Token: "USDT", + 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("[TRC20][%s] skip resolved transfer trade_id=%s hash=%s err=%v", addr, tradeID, txHash, err) + return + } + log.Sugar.Errorf("[TRC20][%s] OrderProcessing trade_id=%s hash=%s: %v", addr, tradeID, txHash, err) + return + } + + sendPaymentNotification(order) + log.Sugar.Infof("[TRC20][%s] payment processed trade_id=%s hash=%s", addr, tradeID, txHash) } -func checkTrc20Transfers(address string, wg *sync.WaitGroup) { - defer wg.Done() +func TryProcessTronTRXTransfer(toAddr string, rawSun int64, txHash string, blockTsMs int64) { defer func() { if err := recover(); err != nil { - log.Sugar.Errorf("[TRC20][%s] panic recovered: %v", address, err) + log.Sugar.Errorf("[TRX][%s] TryProcessTronTRXTransfer panic: %v", toAddr, 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) + addr := strings.TrimSpace(toAddr) + if addr == "" || rawSun <= 0 { return } - log.Sugar.Debugf("[TRC20][%s] fetched %d transfer records", address, len(transfers)) - for i, transfer := range transfers { - 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{ - ReceiveAddress: address, - Token: "USDT", - 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("[TRC20][%s] skip resolved transfer trade_id=%s hash=%s err=%v", address, tradeID, txID, err) - continue - } - panic(err) - } - - sendPaymentNotification(order) - log.Sugar.Infof("[TRC20][%s] payment processed trade_id=%s hash=%s", address, tradeID, txID) + 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 { @@ -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) { defer func() { if err := recover(); err != nil { diff --git a/src/task/listen.go b/src/task/listen.go index a05177c..d7881f0 100644 --- a/src/task/listen.go +++ b/src/task/listen.go @@ -11,17 +11,11 @@ func Start() { go StartBscWebSocketListener() go StartPolygonWebSocketListener() go StartPlasmaWebSocketListener() + go StartTronBlockScannerListener() 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钱包监听 - _, err = c.AddJob("@every 5s", ListenSolJob{}) + _, err := c.AddJob("@every 5s", ListenSolJob{}) if err != nil { log.Sugar.Errorf("[task] Failed to add ListenSolJob: %v", err) return diff --git a/src/task/listen_trc20_block.go b/src/task/listen_trc20_block.go new file mode 100644 index 0000000..0497fa3 --- /dev/null +++ b/src/task/listen_trc20_block.go @@ -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() +} diff --git a/src/task/listen_trc20_job.go b/src/task/listen_trc20_job.go deleted file mode 100644 index 676d808..0000000 --- a/src/task/listen_trc20_job.go +++ /dev/null @@ -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") -}