tron ws
This commit is contained in:
@@ -374,6 +374,80 @@ func TryProcessEvmERC20Transfer(chainNetwork string, contract common.Address, to
|
|||||||
log.Sugar.Infof("[%s-%s][%s] payment processed trade_id=%s hash=%s", net, tokenSym, walletAddr, tradeID, txHash)
|
log.Sugar.Infof("[%s-%s][%s] payment processed trade_id=%s hash=%s", net, tokenSym, walletAddr, tradeID, txHash)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TryProcessTronTRC20Transfer 处理 Tron 链上 TRC20-USDT 的 Transfer 入账。
|
||||||
|
func TryProcessTronTRC20Transfer(toAddr string, rawValue *big.Int, txHash string, blockTsMs int64) {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
log.Sugar.Errorf("[TRON-WS] TryProcessTronTRC20Transfer panic: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
walletAddr := strings.TrimSpace(toAddr)
|
||||||
|
if walletAddr == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if rawValue == nil || rawValue.Sign() <= 0 {
|
||||||
|
log.Sugar.Infof("[TRON-USDT][%s] skip non-positive or nil amount", walletAddr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
decimalQuant := decimal.NewFromBigInt(rawValue, 0)
|
||||||
|
amount := math.MustParsePrecFloat64(decimalQuant.Div(decimal.NewFromInt(1_000_000)).InexactFloat64(), 2)
|
||||||
|
if amount <= 0 {
|
||||||
|
log.Sugar.Warnf("[TRON-USDT][%s] skip non-positive amount %.2f", walletAddr, amount)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tradeID, err := data.GetTradeIdByWalletAddressAndAmountAndToken(mdb.NetworkTron, walletAddr, "USDT", amount)
|
||||||
|
if err != nil {
|
||||||
|
log.Sugar.Warnf("[TRON-USDT][%s] lock lookup: %v", walletAddr, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if tradeID == "" {
|
||||||
|
log.Sugar.Debugf("[TRON-USDT][%s] skip unmatched tx hash=%s amount=%.2f", walletAddr, txHash, amount)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
order, err := data.GetOrderInfoByTradeId(tradeID)
|
||||||
|
if err != nil {
|
||||||
|
log.Sugar.Warnf("[TRON-USDT][%s] load order: %v", walletAddr, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if blockTsMs > 0 && blockTsMs < order.CreatedAt.TimestampMilli() {
|
||||||
|
log.Sugar.Warnf("[TRON-USDT][%s] skip tx %s because block time %d is before order create time %d", walletAddr, txHash, blockTsMs, order.CreatedAt.TimestampMilli())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if strings.ToLower(strings.TrimSpace(order.Network)) != mdb.NetworkTron {
|
||||||
|
log.Sugar.Warnf("[TRON-USDT][%s] skip trade_id=%s network=%q", walletAddr, tradeID, order.Network)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if strings.ToUpper(strings.TrimSpace(order.Token)) != "USDT" {
|
||||||
|
log.Sugar.Warnf("[TRON-USDT][%s] skip trade_id=%s token mismatch order=%s", walletAddr, tradeID, order.Token)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &request.OrderProcessingRequest{
|
||||||
|
ReceiveAddress: walletAddr,
|
||||||
|
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("[TRON-USDT][%s] skip resolved trade_id=%s hash=%s err=%v", walletAddr, tradeID, txHash, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Sugar.Errorf("[TRON-USDT][%s] OrderProcessing: %v", walletAddr, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sendPaymentNotification(order)
|
||||||
|
log.Sugar.Infof("[TRON-USDT][%s] payment processed trade_id=%s hash=%s", walletAddr, tradeID, txHash)
|
||||||
|
}
|
||||||
|
|
||||||
func sendPaymentNotification(order *mdb.Orders) {
|
func sendPaymentNotification(order *mdb.Orders) {
|
||||||
msg := fmt.Sprintf(
|
msg := fmt.Sprintf(
|
||||||
"🎉 <b>收款成功通知</b>\n\n"+
|
"🎉 <b>收款成功通知</b>\n\n"+
|
||||||
|
|||||||
+2
-8
@@ -11,17 +11,11 @@ func Start() {
|
|||||||
go StartBscWebSocketListener()
|
go StartBscWebSocketListener()
|
||||||
go StartPolygonWebSocketListener()
|
go StartPolygonWebSocketListener()
|
||||||
go StartPlasmaWebSocketListener()
|
go StartPlasmaWebSocketListener()
|
||||||
|
go StartTronTrc20WebSocketListener()
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func StartBscWebSocketListener() {
|
|||||||
Topics: [][]common.Hash{},
|
Topics: [][]common.Hash{},
|
||||||
}
|
}
|
||||||
|
|
||||||
runEvmWsLogListener("[BSC-WS]", wsURL, query, func(client *ethclient.Client, vLog types.Log) {
|
runWsLogListener("[BSC-WS]", wsURL, query, func(client *ethclient.Client, vLog types.Log) {
|
||||||
if len(vLog.Topics) < 3 {
|
if len(vLog.Topics) < 3 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ func StartEthereumWebSocketListener() {
|
|||||||
Topics: [][]common.Hash{},
|
Topics: [][]common.Hash{},
|
||||||
}
|
}
|
||||||
|
|
||||||
runEvmWsLogListener("[ETH-WS]", wsURL, query, func(client *ethclient.Client, vLog types.Log) {
|
runWsLogListener("[ETH-WS]", wsURL, query, func(client *ethclient.Client, vLog types.Log) {
|
||||||
if len(vLog.Topics) < 3 {
|
if len(vLog.Topics) < 3 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/ethclient"
|
"github.com/ethereum/go-ethereum/ethclient"
|
||||||
)
|
)
|
||||||
|
|
||||||
func runEvmWsLogListener(logPrefix, wsURL string, query ethereum.FilterQuery, handleLog func(*ethclient.Client, types.Log)) {
|
func runWsLogListener(logPrefix, wsURL string, query ethereum.FilterQuery, handleLog func(*ethclient.Client, types.Log)) {
|
||||||
const (
|
const (
|
||||||
minBackoff = 2 * time.Second
|
minBackoff = 2 * time.Second
|
||||||
maxBackoff = 60 * time.Second
|
maxBackoff = 60 * time.Second
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ func StartPlasmaWebSocketListener() {
|
|||||||
Topics: [][]common.Hash{},
|
Topics: [][]common.Hash{},
|
||||||
}
|
}
|
||||||
|
|
||||||
runEvmWsLogListener("[PLASMA-WS]", wsURL, query, func(client *ethclient.Client, vLog types.Log) {
|
runWsLogListener("[PLASMA-WS]", wsURL, query, func(client *ethclient.Client, vLog types.Log) {
|
||||||
if len(vLog.Topics) < 3 {
|
if len(vLog.Topics) < 3 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ func StartPolygonWebSocketListener() {
|
|||||||
Topics: [][]common.Hash{},
|
Topics: [][]common.Hash{},
|
||||||
}
|
}
|
||||||
|
|
||||||
runEvmWsLogListener("[POLYGON-WS]", wsURL, query, func(client *ethclient.Client, vLog types.Log) {
|
runWsLogListener("[POLYGON-WS]", wsURL, query, func(client *ethclient.Client, vLog types.Log) {
|
||||||
if len(vLog.Topics) < 3 {
|
if len(vLog.Topics) < 3 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+103
-26
@@ -1,39 +1,116 @@
|
|||||||
package task
|
package task
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"context"
|
||||||
|
"math/big"
|
||||||
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
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/service"
|
"github.com/assimon/luuu/model/service"
|
||||||
"github.com/assimon/luuu/util/log"
|
"github.com/assimon/luuu/util/log"
|
||||||
|
"github.com/ethereum/go-ethereum"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/ethclient"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ListenTrc20Job struct{}
|
const tronUsdtContractBase58 = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"
|
||||||
|
|
||||||
var gListenTrc20JobLock sync.Mutex
|
// TronGrid 的 JSON-RPC 使用 20-byte hex 地址(去掉 Tron 主网 0x41 前缀)。
|
||||||
|
var tronUsdtTrc20Contract = common.HexToAddress("0xa614f803b6fd780986a42c78ec9c7f77e6ded13c")
|
||||||
|
|
||||||
func (r ListenTrc20Job) Run() {
|
type tronRecipientSnapshot struct {
|
||||||
gListenTrc20JobLock.Lock()
|
addrs map[string]struct{}
|
||||||
defer gListenTrc20JobLock.Unlock()
|
}
|
||||||
log.Sugar.Debug("[ListenTrc20Job] Job triggered")
|
|
||||||
walletAddress, err := data.GetAvailableWalletAddressByNetwork(mdb.NetworkTron)
|
var tronWatchedRecipients atomic.Pointer[tronRecipientSnapshot]
|
||||||
if err != nil {
|
|
||||||
log.Sugar.Errorf("[ListenTrc20Job] Failed to get wallet addresses: %v", err)
|
func StartTronTrc20WebSocketListener() {
|
||||||
return
|
log.Sugar.Infof("[TRON-WS] listening contract base58=%s hex=%s", tronUsdtContractBase58, tronUsdtTrc20Contract.Hex())
|
||||||
}
|
|
||||||
if len(walletAddress) <= 0 {
|
wallets, err := data.GetAvailableWalletAddressByNetwork(mdb.NetworkTron)
|
||||||
log.Sugar.Debug("[ListenTrc20Job] No available wallet addresses")
|
if err != nil {
|
||||||
return
|
log.Sugar.Errorf("[TRON-WS] Failed to get wallet addresses: %v", err)
|
||||||
}
|
return
|
||||||
log.Sugar.Infof("[ListenTrc20Job] Found %d wallet addresses to monitor", len(walletAddress))
|
}
|
||||||
var wg sync.WaitGroup
|
storeTronRecipientsFromWallets(wallets)
|
||||||
for _, address := range walletAddress {
|
go func() {
|
||||||
log.Sugar.Infof("[ListenTrc20Job] Listening to address: %s", address.Address)
|
ticker := time.NewTicker(5 * time.Second)
|
||||||
|
defer ticker.Stop()
|
||||||
wg.Add(1)
|
for range ticker.C {
|
||||||
go service.Trc20CallBack(address.Address, &wg)
|
w, err := data.GetAvailableWalletAddressByNetwork(mdb.NetworkTron)
|
||||||
}
|
if err != nil {
|
||||||
wg.Wait()
|
log.Sugar.Warnf("[TRON-WS] refresh wallet addresses: %v", err)
|
||||||
log.Sugar.Debug("[ListenTrc20Job] Job completed")
|
continue
|
||||||
|
}
|
||||||
|
storeTronRecipientsFromWallets(w)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
wsURL := "wss://api.trongrid.io/jsonrpc"
|
||||||
|
query := ethereum.FilterQuery{
|
||||||
|
Addresses: []common.Address{tronUsdtTrc20Contract},
|
||||||
|
Topics: [][]common.Hash{},
|
||||||
|
}
|
||||||
|
runWsLogListener("[TRON-WS]", wsURL, query, func(client *ethclient.Client, vLog types.Log) {
|
||||||
|
if len(vLog.Topics) < 3 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if vLog.Topics[0].String() != transferEventHash.String() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
toAddr := tronHexToBase58(common.HexToAddress(vLog.Topics[2].Hex()))
|
||||||
|
//if !isWatchedTronRecipient(toAddr) {
|
||||||
|
// return
|
||||||
|
//}
|
||||||
|
|
||||||
|
amount := new(big.Int).SetBytes(vLog.Data)
|
||||||
|
if amount.Sign() <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Sugar.Infof("[TRON-WS] Detected TRC20 transfer to %s amount=%s tx=%s", toAddr, amount.String(), vLog.TxHash.Hex())
|
||||||
|
var blockTsMs int64
|
||||||
|
header, err := client.HeaderByNumber(context.Background(), big.NewInt(int64(vLog.BlockNumber)))
|
||||||
|
if err != nil {
|
||||||
|
log.Sugar.Warnf("[TRON-WS] HeaderByNumber block=%d: %v, using local time", vLog.BlockNumber, err)
|
||||||
|
blockTsMs = time.Now().UnixMilli()
|
||||||
|
} else {
|
||||||
|
blockTsMs = int64(header.Time) * 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
service.TryProcessTronTRC20Transfer(toAddr, amount, vLog.TxHash.Hex(), blockTsMs)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func storeTronRecipientsFromWallets(wallets []mdb.WalletAddress) int {
|
||||||
|
m := make(map[string]struct{})
|
||||||
|
for _, w := range wallets {
|
||||||
|
addr := strings.TrimSpace(w.Address)
|
||||||
|
if addr == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m[addr] = struct{}{}
|
||||||
|
}
|
||||||
|
tronWatchedRecipients.Store(&tronRecipientSnapshot{addrs: m})
|
||||||
|
return len(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isWatchedTronRecipient(to string) bool {
|
||||||
|
snap := tronWatchedRecipients.Load()
|
||||||
|
if snap == nil || len(snap.addrs) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_, ok := snap.addrs[strings.TrimSpace(to)]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func tronHexToBase58(addr common.Address) string {
|
||||||
|
raw := append([]byte{tron.PrefixMainnet}, addr.Bytes()...)
|
||||||
|
return tron.EncodeCheck(raw)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user