mirror of
https://github.com/GMWalletApp/epusdt.git
synced 2026-07-07 10:16:15 +00:00
fix: (update tron logic)
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
|
||||
"github.com/shengdoushi/base58"
|
||||
)
|
||||
@@ -28,3 +30,24 @@ func EncodeCheck(input []byte) string {
|
||||
|
||||
return Encode(inputCheck)
|
||||
}
|
||||
|
||||
// DecodeCheck decodes a Base58Check string and validates its 4-byte checksum.
|
||||
func DecodeCheck(input string) ([]byte, error) {
|
||||
raw, err := base58.Decode(input, base58.BitcoinAlphabet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(raw) < 4 {
|
||||
return nil, fmt.Errorf("base58check payload too short")
|
||||
}
|
||||
|
||||
payload := raw[:len(raw)-4]
|
||||
checksum := raw[len(raw)-4:]
|
||||
|
||||
h0 := sha256.Sum256(payload)
|
||||
h1 := sha256.Sum256(h0[:])
|
||||
if !bytes.Equal(checksum, h1[:4]) {
|
||||
return nil, fmt.Errorf("base58check checksum mismatch")
|
||||
}
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
@@ -36,47 +36,64 @@ func ResolveTronNode() (string, string, error) {
|
||||
return resolveTronNode()
|
||||
}
|
||||
|
||||
func TryProcessTronTRC20Transfer(toAddr string, rawValue *big.Int, txHash string, blockTsMs int64) {
|
||||
func TryProcessTronTRC20Transfer(token mdb.ChainToken, toAddr string, rawValue *big.Int, txHash string, blockTsMs int64) {
|
||||
tokenSym := strings.ToUpper(strings.TrimSpace(token.Symbol))
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Sugar.Errorf("[TRC20][%s] TryProcessTronTRC20Transfer panic: %v", toAddr, err)
|
||||
log.Sugar.Errorf("[TRC20-%s][%s] TryProcessTronTRC20Transfer panic: %v", tokenSym, toAddr, err)
|
||||
}
|
||||
}()
|
||||
|
||||
addr := strings.TrimSpace(toAddr)
|
||||
if addr == "" || rawValue == nil || rawValue.Sign() <= 0 {
|
||||
if tokenSym == "" || addr == "" || rawValue == nil || rawValue.Sign() <= 0 {
|
||||
return
|
||||
}
|
||||
decimals := token.Decimals
|
||||
if decimals < 0 {
|
||||
decimals = 0
|
||||
}
|
||||
|
||||
decimalQuant := decimal.NewFromBigInt(rawValue, 0)
|
||||
amount := math.MustParsePrecFloat64(decimalQuant.Div(decimal.NewFromInt(1_000_000)).InexactFloat64(), 2)
|
||||
amount := math.MustParsePrecFloat64(decimalQuant.Div(decimal.New(1, int32(decimals))).InexactFloat64(), 2)
|
||||
if amount <= 0 {
|
||||
return
|
||||
}
|
||||
if token.MinAmount > 0 && amount < token.MinAmount {
|
||||
log.Sugar.Debugf("[TRC20-%s][%s] skip below min amount hash=%s amount=%.2f min=%.2f", tokenSym, addr, txHash, amount, token.MinAmount)
|
||||
return
|
||||
}
|
||||
|
||||
tradeID, err := data.GetTradeIdByWalletAddressAndAmountAndToken(mdb.NetworkTron, addr, "USDT", amount)
|
||||
tradeID, err := data.GetTradeIdByWalletAddressAndAmountAndToken(mdb.NetworkTron, addr, tokenSym, amount)
|
||||
if err != nil {
|
||||
log.Sugar.Warnf("[TRC20][%s] lock lookup: %v", addr, err)
|
||||
log.Sugar.Warnf("[TRC20-%s][%s] lock lookup: %v", tokenSym, addr, err)
|
||||
return
|
||||
}
|
||||
if tradeID == "" {
|
||||
log.Sugar.Debugf("[TRC20][%s] skip unmatched tx hash=%s amount=%.2f", addr, txHash, amount)
|
||||
log.Sugar.Debugf("[TRC20-%s][%s] skip unmatched tx hash=%s amount=%.2f", tokenSym, addr, txHash, amount)
|
||||
return
|
||||
}
|
||||
|
||||
order, err := data.GetOrderInfoByTradeId(tradeID)
|
||||
if err != nil {
|
||||
log.Sugar.Warnf("[TRC20][%s] load order: %v", addr, err)
|
||||
log.Sugar.Warnf("[TRC20-%s][%s] load order: %v", tokenSym, addr, err)
|
||||
return
|
||||
}
|
||||
if strings.ToLower(strings.TrimSpace(order.Network)) != mdb.NetworkTron {
|
||||
log.Sugar.Warnf("[TRC20-%s][%s] skip trade_id=%s network=%q", tokenSym, addr, tradeID, order.Network)
|
||||
return
|
||||
}
|
||||
if strings.ToUpper(strings.TrimSpace(order.Token)) != tokenSym {
|
||||
log.Sugar.Warnf("[TRC20-%s][%s] skip trade_id=%s token mismatch order=%s", tokenSym, addr, tradeID, order.Token)
|
||||
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())
|
||||
log.Sugar.Warnf("[TRC20-%s][%s] skip tx %s because block time %d is before order create time %d", tokenSym, addr, txHash, blockTsMs, order.CreatedAt.TimestampMilli())
|
||||
return
|
||||
}
|
||||
|
||||
req := &request.OrderProcessingRequest{
|
||||
ReceiveAddress: addr,
|
||||
Token: "USDT",
|
||||
Token: tokenSym,
|
||||
Network: mdb.NetworkTron,
|
||||
TradeId: tradeID,
|
||||
Amount: amount,
|
||||
@@ -85,15 +102,15 @@ func TryProcessTronTRC20Transfer(toAddr string, rawValue *big.Int, txHash string
|
||||
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)
|
||||
log.Sugar.Infof("[TRC20-%s][%s] skip resolved transfer trade_id=%s hash=%s err=%v", tokenSym, addr, tradeID, txHash, err)
|
||||
return
|
||||
}
|
||||
log.Sugar.Errorf("[TRC20][%s] OrderProcessing trade_id=%s hash=%s: %v", addr, tradeID, txHash, err)
|
||||
log.Sugar.Errorf("[TRC20-%s][%s] OrderProcessing trade_id=%s hash=%s: %v", tokenSym, addr, tradeID, txHash, err)
|
||||
return
|
||||
}
|
||||
|
||||
sendPaymentNotification(order)
|
||||
log.Sugar.Infof("[TRC20][%s] payment processed trade_id=%s hash=%s", addr, tradeID, txHash)
|
||||
log.Sugar.Infof("[TRC20-%s][%s] payment processed trade_id=%s hash=%s", tokenSym, addr, tradeID, txHash)
|
||||
}
|
||||
|
||||
func TryProcessTronTRXTransfer(toAddr string, rawSun int64, txHash string, blockTsMs int64) {
|
||||
|
||||
@@ -12,13 +12,13 @@ import (
|
||||
"time"
|
||||
|
||||
tron "github.com/GMWalletApp/epusdt/crypto"
|
||||
"github.com/GMWalletApp/epusdt/model/data"
|
||||
"github.com/GMWalletApp/epusdt/model/mdb"
|
||||
"github.com/GMWalletApp/epusdt/model/service"
|
||||
"github.com/GMWalletApp/epusdt/util/log"
|
||||
)
|
||||
|
||||
const (
|
||||
// USDT 合约地址 (TRC20 主网)
|
||||
USDTContractHex = "41a614f803b6fd780986a42c78ec9c7f77e6ded13c" // Base58: TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t
|
||||
// transfer(address,uint256) 方法签名前4字节
|
||||
TransferMethodID = "a9059cbb"
|
||||
|
||||
@@ -46,6 +46,31 @@ func HexToTronAddress(hexAddr string) (string, error) {
|
||||
return tron.EncodeCheck(raw), nil
|
||||
}
|
||||
|
||||
func TronAddressToHex(addr string) (string, error) {
|
||||
addr = strings.TrimSpace(addr)
|
||||
if addr == "" {
|
||||
return "", fmt.Errorf("地址为空")
|
||||
}
|
||||
raw, err := tron.DecodeCheck(addr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(raw) != 21 {
|
||||
return "", fmt.Errorf("地址长度非法: %d bytes", len(raw))
|
||||
}
|
||||
if raw[0] != 0x41 {
|
||||
return "", fmt.Errorf("非法 TRON 主网地址前缀: 0x%x", raw[0])
|
||||
}
|
||||
return strings.ToLower(hex.EncodeToString(raw)), nil
|
||||
}
|
||||
|
||||
func normalizeTronHexAddress(hexAddr string) string {
|
||||
hexAddr = strings.TrimSpace(hexAddr)
|
||||
hexAddr = strings.TrimPrefix(hexAddr, "0x")
|
||||
hexAddr = strings.TrimPrefix(hexAddr, "0X")
|
||||
return strings.ToLower(hexAddr)
|
||||
}
|
||||
|
||||
type BlockHeader struct {
|
||||
RawData struct {
|
||||
Number int64 `json:"number"`
|
||||
@@ -89,12 +114,13 @@ type Block struct {
|
||||
Transactions []Transaction `json:"transactions"`
|
||||
}
|
||||
|
||||
type USDTTransfer struct {
|
||||
type TRC20Transfer struct {
|
||||
TxID string
|
||||
From string
|
||||
To string
|
||||
Raw *big.Int // 原始数值(6 decimals)
|
||||
Raw *big.Int
|
||||
Status string
|
||||
Token mdb.ChainToken
|
||||
}
|
||||
|
||||
type TRXTransfer struct {
|
||||
@@ -156,7 +182,33 @@ func GetBlockByNum(baseURL string, apiKey string, num int64) (*Block, error) {
|
||||
return &block, json.Unmarshal(b, &block)
|
||||
}
|
||||
|
||||
func parseUSDTTransfer(tx Transaction) *USDTTransfer {
|
||||
func loadTronTRC20TokenMap() map[string]mdb.ChainToken {
|
||||
tokens, err := data.ListEnabledChainTokensByNetwork(mdb.NetworkTron)
|
||||
if err != nil {
|
||||
log.Sugar.Warnf("[TRON-BLOCK] load chain_tokens: %v", err)
|
||||
return nil
|
||||
}
|
||||
tokenMap := make(map[string]mdb.ChainToken)
|
||||
for _, token := range tokens {
|
||||
symbol := strings.ToUpper(strings.TrimSpace(token.Symbol))
|
||||
contractAddress := strings.TrimSpace(token.ContractAddress)
|
||||
if symbol == "TRX" || contractAddress == "" {
|
||||
continue
|
||||
}
|
||||
contractHex, err := TronAddressToHex(contractAddress)
|
||||
if err != nil {
|
||||
log.Sugar.Warnf("[TRON-BLOCK] skip invalid TRC20 contract symbol=%s address=%s err=%v", symbol, contractAddress, err)
|
||||
continue
|
||||
}
|
||||
tokenMap[contractHex] = token
|
||||
}
|
||||
return tokenMap
|
||||
}
|
||||
|
||||
func parseTRC20Transfer(tx Transaction, tokenMap map[string]mdb.ChainToken) *TRC20Transfer {
|
||||
if len(tokenMap) == 0 {
|
||||
return nil
|
||||
}
|
||||
if len(tx.RawData.Contract) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -170,9 +222,9 @@ func parseUSDTTransfer(tx Transaction) *USDTTransfer {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 检查是否是 USDT 合约
|
||||
contractHex := strings.ToLower(strings.TrimPrefix(val.ContractAddress, "0x"))
|
||||
if contractHex != USDTContractHex {
|
||||
contractHex := normalizeTronHexAddress(val.ContractAddress)
|
||||
token, ok := tokenMap[contractHex]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -213,12 +265,13 @@ func parseUSDTTransfer(tx Transaction) *USDTTransfer {
|
||||
status = tx.Ret[0].ContractRet
|
||||
}
|
||||
|
||||
return &USDTTransfer{
|
||||
return &TRC20Transfer{
|
||||
TxID: tx.TxID,
|
||||
From: fromAddr,
|
||||
To: toAddr,
|
||||
Raw: amountBig,
|
||||
Status: status,
|
||||
Token: token,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,14 +315,14 @@ func parseTRXTransfer(tx Transaction) *TRXTransfer {
|
||||
}
|
||||
}
|
||||
|
||||
func processBlock(block *Block) {
|
||||
func processBlock(block *Block, tokenMap map[string]mdb.ChainToken) {
|
||||
blockTsMs := block.BlockHeader.RawData.Timestamp
|
||||
for _, tx := range block.Transactions {
|
||||
if t := parseUSDTTransfer(tx); t != nil {
|
||||
if t := parseTRC20Transfer(tx, tokenMap); t != nil {
|
||||
if t.Status != "SUCCESS" {
|
||||
continue
|
||||
}
|
||||
service.TryProcessTronTRC20Transfer(t.To, t.Raw, t.TxID, blockTsMs)
|
||||
service.TryProcessTronTRC20Transfer(t.Token, t.To, t.Raw, t.TxID, blockTsMs)
|
||||
continue
|
||||
}
|
||||
if t := parseTRXTransfer(tx); t != nil {
|
||||
@@ -287,7 +340,7 @@ type Scanner struct {
|
||||
lastBlock int64
|
||||
// 统计
|
||||
totalBlocks int64
|
||||
totalUSDTTxs int64
|
||||
totalTRC20Txs int64
|
||||
totalTRXTxs int64
|
||||
}
|
||||
|
||||
@@ -314,7 +367,7 @@ func (s *Scanner) Init() error {
|
||||
}
|
||||
|
||||
func (s *Scanner) Run() {
|
||||
log.Sugar.Info("[TRON-BLOCK] start scanning (USDT TRC20 + TRX)")
|
||||
log.Sugar.Info("[TRON-BLOCK] start scanning (TRC20 + TRX)")
|
||||
ticker := time.NewTicker(PollInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
@@ -324,7 +377,7 @@ func (s *Scanner) Run() {
|
||||
for {
|
||||
select {
|
||||
case <-statTicker.C:
|
||||
log.Sugar.Infof("[TRON-BLOCK] stats blocks=%d usdt=%d trx=%d", s.totalBlocks, s.totalUSDTTxs, s.totalTRXTxs)
|
||||
log.Sugar.Infof("[TRON-BLOCK] stats blocks=%d trc20=%d trx=%d", s.totalBlocks, s.totalTRC20Txs, s.totalTRXTxs)
|
||||
case <-ticker.C:
|
||||
s.poll()
|
||||
}
|
||||
@@ -342,6 +395,7 @@ func (s *Scanner) poll() {
|
||||
return
|
||||
}
|
||||
|
||||
tokenMap := loadTronTRC20TokenMap()
|
||||
for num := s.lastBlock + 1; num <= latestNum; num++ {
|
||||
var block *Block
|
||||
if num == latestNum {
|
||||
@@ -354,13 +408,13 @@ func (s *Scanner) poll() {
|
||||
}
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
processBlock(block)
|
||||
processBlock(block, tokenMap)
|
||||
s.lastBlock = num
|
||||
s.totalBlocks++
|
||||
|
||||
for _, tx := range block.Transactions {
|
||||
if parseUSDTTransfer(tx) != nil {
|
||||
s.totalUSDTTxs++
|
||||
if parseTRC20Transfer(tx, tokenMap) != nil {
|
||||
s.totalTRC20Txs++
|
||||
} else if parseTRXTransfer(tx) != nil {
|
||||
s.totalTRXTxs++
|
||||
}
|
||||
@@ -369,10 +423,15 @@ func (s *Scanner) poll() {
|
||||
}
|
||||
|
||||
func StartTronBlockScannerListener() {
|
||||
for {
|
||||
scanner := NewScanner()
|
||||
if err := scanner.Init(); err != nil {
|
||||
log.Sugar.Errorf("[TRON-BLOCK] init: %v", err)
|
||||
return
|
||||
log.Sugar.Errorf("[TRON-BLOCK] init: %v, retrying...", err)
|
||||
time.Sleep(10 * time.Second)
|
||||
continue
|
||||
}
|
||||
scanner.Run()
|
||||
log.Sugar.Warn("[TRON-BLOCK] scanner stopped, restarting...")
|
||||
time.Sleep(3 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package task
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestTronAddressToHexConvertsBase58Contract(t *testing.T) {
|
||||
got, err := TronAddressToHex("TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t")
|
||||
if err != nil {
|
||||
t.Fatalf("TronAddressToHex(): %v", err)
|
||||
}
|
||||
|
||||
const want = "41a614f803b6fd780986a42c78ec9c7f77e6ded13c"
|
||||
if got != want {
|
||||
t.Fatalf("TronAddressToHex() = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeTronHexAddress(t *testing.T) {
|
||||
got := normalizeTronHexAddress("0X41A614F803B6FD780986A42C78EC9C7F77E6DED13C")
|
||||
const want = "41a614f803b6fd780986a42c78ec9c7f77e6ded13c"
|
||||
if got != want {
|
||||
t.Fatalf("normalizeTronHexAddress() = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user