mirror of
https://github.com/GMWalletApp/epusdt.git
synced 2026-07-07 10:16:15 +00:00
test: bsc pol plasma
This commit is contained in:
@@ -8,6 +8,9 @@ import (
|
||||
func Start() {
|
||||
log.Sugar.Info("[task] Starting task scheduler...")
|
||||
go StartEthereumWebSocketListener()
|
||||
go StartBscWebSocketListener()
|
||||
go StartPolygonWebSocketListener()
|
||||
go StartPlasmaWebSocketListener()
|
||||
|
||||
c := cron.New()
|
||||
// trc20钱包监听
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/big"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/assimon/luuu/model/data"
|
||||
"github.com/assimon/luuu/model/mdb"
|
||||
"github.com/assimon/luuu/model/service"
|
||||
"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"
|
||||
)
|
||||
|
||||
var (
|
||||
// USDT / USDC 合约地址(BSC 主网)
|
||||
bscUsdtContract = common.HexToAddress("0x55d398326f99059fF775485246999027B3197955")
|
||||
bscUsdcContract = common.HexToAddress("0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d")
|
||||
)
|
||||
|
||||
type bscRecipientSnapshot struct {
|
||||
addrs map[string]struct{}
|
||||
}
|
||||
|
||||
var bscWatchedRecipients atomic.Pointer[bscRecipientSnapshot]
|
||||
|
||||
func StartBscWebSocketListener() {
|
||||
wallets, err := data.GetAvailableWalletAddressByNetwork(mdb.NetworkBsc)
|
||||
if err != nil {
|
||||
log.Sugar.Errorf("[BSC-WS] Failed to get wallet addresses: %v", err)
|
||||
return
|
||||
}
|
||||
storeBscRecipientsFromWallets(wallets)
|
||||
go func() {
|
||||
ticker := time.NewTicker(5 * time.Second)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
w, err := data.GetAvailableWalletAddressByNetwork(mdb.NetworkBsc)
|
||||
if err != nil {
|
||||
log.Sugar.Warnf("[BSC-WS] refresh wallet addresses: %v", err)
|
||||
continue
|
||||
}
|
||||
storeBscRecipientsFromWallets(w)
|
||||
}
|
||||
}()
|
||||
wsURL := "wss://bsc.publicnode.com"
|
||||
query := ethereum.FilterQuery{
|
||||
Addresses: []common.Address{
|
||||
bscUsdtContract,
|
||||
bscUsdcContract,
|
||||
},
|
||||
Topics: [][]common.Hash{},
|
||||
}
|
||||
|
||||
runEvmWsLogListener("[BSC-WS]", wsURL, query, func(client *ethclient.Client, vLog types.Log) {
|
||||
if len(vLog.Topics) < 3 {
|
||||
return
|
||||
}
|
||||
|
||||
event := vLog.Topics[0].String()
|
||||
if event != transferEventHash.String() {
|
||||
return
|
||||
}
|
||||
|
||||
amount := new(big.Int).SetBytes(vLog.Data)
|
||||
|
||||
toAddr := common.HexToAddress(vLog.Topics[2].Hex())
|
||||
|
||||
if !isWatchedBscRecipient(toAddr) {
|
||||
return
|
||||
}
|
||||
|
||||
var blockTsMs int64
|
||||
header, err := client.HeaderByNumber(context.Background(), big.NewInt(int64(vLog.BlockNumber)))
|
||||
if err != nil {
|
||||
log.Sugar.Warnf("[BSC-WS] HeaderByNumber block=%d: %v, using local time", vLog.BlockNumber, err)
|
||||
blockTsMs = time.Now().UnixMilli()
|
||||
} else {
|
||||
blockTsMs = int64(header.Time) * 1000
|
||||
}
|
||||
|
||||
service.TryProcessEvmERC20Transfer(mdb.NetworkBsc, vLog.Address, toAddr, amount, vLog.TxHash.Hex(), blockTsMs)
|
||||
})
|
||||
}
|
||||
|
||||
func storeBscRecipientsFromWallets(wallets []mdb.WalletAddress) int {
|
||||
m := make(map[string]struct{})
|
||||
for _, w := range wallets {
|
||||
a := strings.TrimSpace(w.Address)
|
||||
if !common.IsHexAddress(a) {
|
||||
continue
|
||||
}
|
||||
m[strings.ToLower(common.HexToAddress(a).Hex())] = struct{}{}
|
||||
}
|
||||
bscWatchedRecipients.Store(&bscRecipientSnapshot{addrs: m})
|
||||
return len(m)
|
||||
}
|
||||
|
||||
func isWatchedBscRecipient(to common.Address) bool {
|
||||
snap := bscWatchedRecipients.Load()
|
||||
if snap == nil || len(snap.addrs) == 0 {
|
||||
return false
|
||||
}
|
||||
_, ok := snap.addrs[strings.ToLower(to.Hex())]
|
||||
return ok
|
||||
}
|
||||
+29
-64
@@ -2,7 +2,6 @@ package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
@@ -38,7 +37,7 @@ var ethWatchedRecipients atomic.Pointer[ethRecipientSnapshot]
|
||||
func StartEthereumWebSocketListener() {
|
||||
wallets, err := data.GetAvailableWalletAddressByNetwork(mdb.NetworkEthereum)
|
||||
if err != nil {
|
||||
log.Sugar.Fatalf("Failed to get wallet addresses: %v", err)
|
||||
log.Sugar.Errorf("Failed to get wallet addresses: %v", err)
|
||||
return
|
||||
}
|
||||
StoreEthRecipientsFromWallets(wallets)
|
||||
@@ -55,16 +54,6 @@ func StartEthereumWebSocketListener() {
|
||||
}
|
||||
}()
|
||||
wsURL := "wss://ethereum.publicnode.com"
|
||||
|
||||
client, err := ethclient.Dial(wsURL)
|
||||
if err != nil {
|
||||
log.Sugar.Fatal("连接失败:", err)
|
||||
}
|
||||
|
||||
// 创建日志通道
|
||||
logsCh := make(chan types.Log)
|
||||
|
||||
// 订阅条件
|
||||
query := ethereum.FilterQuery{
|
||||
Addresses: []common.Address{
|
||||
usdtContract,
|
||||
@@ -73,59 +62,35 @@ func StartEthereumWebSocketListener() {
|
||||
Topics: [][]common.Hash{},
|
||||
}
|
||||
|
||||
// 订阅日志(核心)
|
||||
sub, err := client.SubscribeFilterLogs(context.Background(), query, logsCh)
|
||||
if err != nil {
|
||||
log.Sugar.Fatal("订阅失败:", err)
|
||||
}
|
||||
|
||||
fmt.Println("🚀 开始监听 USDT / USDC 收款...")
|
||||
|
||||
for {
|
||||
select {
|
||||
case err := <-sub.Err():
|
||||
log.Sugar.Fatal("订阅错误:", err)
|
||||
|
||||
case vLog := <-logsCh:
|
||||
|
||||
// topics:
|
||||
// [0] Transfer event
|
||||
// [1] from
|
||||
// [2] to
|
||||
if len(vLog.Topics) < 3 {
|
||||
continue
|
||||
}
|
||||
|
||||
event := vLog.Topics[0].String()
|
||||
if event != transferEventHash.String() {
|
||||
continue
|
||||
}
|
||||
|
||||
// value from data
|
||||
amount := new(big.Int).SetBytes(vLog.Data)
|
||||
|
||||
|
||||
if event != transferEventHash.String() {
|
||||
continue
|
||||
}
|
||||
toAddr := common.HexToAddress(vLog.Topics[2].Hex())
|
||||
|
||||
if !isWatchedEthRecipient(toAddr) {
|
||||
continue
|
||||
}
|
||||
|
||||
var blockTsMs int64
|
||||
header, err := client.HeaderByNumber(context.Background(), big.NewInt(int64(vLog.BlockNumber)))
|
||||
if err != nil {
|
||||
log.Sugar.Warnf("[ETH-WS] HeaderByNumber block=%d: %v, using local time", vLog.BlockNumber, err)
|
||||
blockTsMs = time.Now().UnixMilli()
|
||||
} else {
|
||||
blockTsMs = int64(header.Time) * 1000
|
||||
}
|
||||
|
||||
service.TryProcessEthereumERC20Transfer(vLog.Address, toAddr, amount, vLog.TxHash.Hex(), blockTsMs)
|
||||
runEvmWsLogListener("[ETH-WS]", wsURL, query, func(client *ethclient.Client, vLog types.Log) {
|
||||
if len(vLog.Topics) < 3 {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
event := vLog.Topics[0].String()
|
||||
if event != transferEventHash.String() {
|
||||
return
|
||||
}
|
||||
|
||||
amount := new(big.Int).SetBytes(vLog.Data)
|
||||
|
||||
toAddr := common.HexToAddress(vLog.Topics[2].Hex())
|
||||
|
||||
if !isWatchedEthRecipient(toAddr) {
|
||||
return
|
||||
}
|
||||
|
||||
var blockTsMs int64
|
||||
header, err := client.HeaderByNumber(context.Background(), big.NewInt(int64(vLog.BlockNumber)))
|
||||
if err != nil {
|
||||
log.Sugar.Warnf("[ETH-WS] HeaderByNumber block=%d: %v, using local time", vLog.BlockNumber, err)
|
||||
blockTsMs = time.Now().UnixMilli()
|
||||
} else {
|
||||
blockTsMs = int64(header.Time) * 1000
|
||||
}
|
||||
|
||||
service.TryProcessEvmERC20Transfer(mdb.NetworkEthereum, vLog.Address, toAddr, amount, vLog.TxHash.Hex(), blockTsMs)
|
||||
})
|
||||
}
|
||||
|
||||
func StoreEthRecipientsFromWallets(wallets []mdb.WalletAddress) int {
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/assimon/luuu/util/log"
|
||||
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
)
|
||||
|
||||
func runEvmWsLogListener(logPrefix, wsURL string, query ethereum.FilterQuery, handleLog func(*ethclient.Client, types.Log)) {
|
||||
const (
|
||||
minBackoff = 2 * time.Second
|
||||
maxBackoff = 60 * time.Second
|
||||
rejoinWait = 3 * time.Second
|
||||
)
|
||||
failWait := minBackoff
|
||||
|
||||
for {
|
||||
client, err := ethclient.Dial(wsURL)
|
||||
if err != nil {
|
||||
log.Sugar.Warnf("%s dial: %v, retry in %s", logPrefix, err, failWait)
|
||||
time.Sleep(failWait)
|
||||
failWait = nextBackoff(failWait, maxBackoff)
|
||||
continue
|
||||
}
|
||||
|
||||
logsCh := make(chan types.Log)
|
||||
sub, err := client.SubscribeFilterLogs(context.Background(), query, logsCh)
|
||||
if err != nil {
|
||||
client.Close()
|
||||
log.Sugar.Warnf("%s subscribe: %v, retry in %s", logPrefix, err, failWait)
|
||||
time.Sleep(failWait)
|
||||
failWait = nextBackoff(failWait, maxBackoff)
|
||||
continue
|
||||
}
|
||||
failWait = minBackoff
|
||||
|
||||
log.Sugar.Infof("%s connected, subscribed to USDT/USDC Transfer logs", logPrefix)
|
||||
|
||||
recvLoop(client, sub, logsCh, logPrefix, handleLog)
|
||||
|
||||
time.Sleep(rejoinWait)
|
||||
}
|
||||
}
|
||||
|
||||
func recvLoop(client *ethclient.Client, sub ethereum.Subscription, logsCh <-chan types.Log, logPrefix string, handleLog func(*ethclient.Client, types.Log)) {
|
||||
defer func() {
|
||||
sub.Unsubscribe()
|
||||
client.Close()
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case err := <-sub.Err():
|
||||
if err != nil {
|
||||
log.Sugar.Warnf("%s subscription error: %v, reconnecting", logPrefix, err)
|
||||
} else {
|
||||
log.Sugar.Warnf("%s subscription closed, reconnecting", logPrefix)
|
||||
}
|
||||
return
|
||||
case vLog, ok := <-logsCh:
|
||||
if !ok {
|
||||
log.Sugar.Warnf("%s log channel closed, reconnecting", logPrefix)
|
||||
return
|
||||
}
|
||||
handleLog(client, vLog)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func nextBackoff(cur, max time.Duration) time.Duration {
|
||||
n := cur * 2
|
||||
if n > max {
|
||||
return max
|
||||
}
|
||||
return n
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/big"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/assimon/luuu/model/data"
|
||||
"github.com/assimon/luuu/model/mdb"
|
||||
"github.com/assimon/luuu/model/service"
|
||||
"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"
|
||||
)
|
||||
|
||||
var (
|
||||
plasmaUsdt0Contract = common.HexToAddress("0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb")
|
||||
)
|
||||
|
||||
type plasmaRecipientSnapshot struct {
|
||||
addrs map[string]struct{}
|
||||
}
|
||||
|
||||
var plasmaWatchedRecipients atomic.Pointer[plasmaRecipientSnapshot]
|
||||
|
||||
func StartPlasmaWebSocketListener() {
|
||||
wallets, err := data.GetAvailableWalletAddressByNetwork(mdb.NetworkPlasma)
|
||||
if err != nil {
|
||||
log.Sugar.Errorf("[PLASMA-WS] Failed to get wallet addresses: %v", err)
|
||||
return
|
||||
}
|
||||
storePlasmaRecipientsFromWallets(wallets)
|
||||
go func() {
|
||||
ticker := time.NewTicker(5 * time.Second)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
w, err := data.GetAvailableWalletAddressByNetwork(mdb.NetworkPlasma)
|
||||
if err != nil {
|
||||
log.Sugar.Warnf("[PLASMA-WS] refresh wallet addresses: %v", err)
|
||||
continue
|
||||
}
|
||||
storePlasmaRecipientsFromWallets(w)
|
||||
}
|
||||
}()
|
||||
// 文档提供 https://rpc.plasma.to;常见实现同主机 wss
|
||||
wsURL := "wss://rpc.plasma.to"
|
||||
query := ethereum.FilterQuery{
|
||||
Addresses: []common.Address{plasmaUsdt0Contract},
|
||||
Topics: [][]common.Hash{},
|
||||
}
|
||||
|
||||
runEvmWsLogListener("[PLASMA-WS]", wsURL, query, func(client *ethclient.Client, vLog types.Log) {
|
||||
if len(vLog.Topics) < 3 {
|
||||
return
|
||||
}
|
||||
|
||||
event := vLog.Topics[0].String()
|
||||
if event != transferEventHash.String() {
|
||||
return
|
||||
}
|
||||
|
||||
amount := new(big.Int).SetBytes(vLog.Data)
|
||||
|
||||
toAddr := common.HexToAddress(vLog.Topics[2].Hex())
|
||||
|
||||
if !isWatchedPlasmaRecipient(toAddr) {
|
||||
return
|
||||
}
|
||||
|
||||
var blockTsMs int64
|
||||
header, err := client.HeaderByNumber(context.Background(), big.NewInt(int64(vLog.BlockNumber)))
|
||||
if err != nil {
|
||||
log.Sugar.Warnf("[PLASMA-WS] HeaderByNumber block=%d: %v, using local time", vLog.BlockNumber, err)
|
||||
blockTsMs = time.Now().UnixMilli()
|
||||
} else {
|
||||
blockTsMs = int64(header.Time) * 1000
|
||||
}
|
||||
|
||||
service.TryProcessEvmERC20Transfer(mdb.NetworkPlasma, vLog.Address, toAddr, amount, vLog.TxHash.Hex(), blockTsMs)
|
||||
})
|
||||
}
|
||||
|
||||
func storePlasmaRecipientsFromWallets(wallets []mdb.WalletAddress) int {
|
||||
m := make(map[string]struct{})
|
||||
for _, w := range wallets {
|
||||
a := strings.TrimSpace(w.Address)
|
||||
if !common.IsHexAddress(a) {
|
||||
continue
|
||||
}
|
||||
m[strings.ToLower(common.HexToAddress(a).Hex())] = struct{}{}
|
||||
}
|
||||
plasmaWatchedRecipients.Store(&plasmaRecipientSnapshot{addrs: m})
|
||||
return len(m)
|
||||
}
|
||||
|
||||
func isWatchedPlasmaRecipient(to common.Address) bool {
|
||||
snap := plasmaWatchedRecipients.Load()
|
||||
if snap == nil || len(snap.addrs) == 0 {
|
||||
return false
|
||||
}
|
||||
_, ok := snap.addrs[strings.ToLower(to.Hex())]
|
||||
return ok
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/big"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/assimon/luuu/model/data"
|
||||
"github.com/assimon/luuu/model/mdb"
|
||||
"github.com/assimon/luuu/model/service"
|
||||
"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"
|
||||
)
|
||||
|
||||
var (
|
||||
// Polygon PoS 主网 USDT / USDC(原生)/ USDC.e(桥接)
|
||||
polygonUsdtContract = common.HexToAddress("0xc2132D05D31c914a87C6611C10748AEb04B58e8F")
|
||||
polygonUsdcContract = common.HexToAddress("0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359")
|
||||
polygonUsdcEContract = common.HexToAddress("0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174")
|
||||
)
|
||||
|
||||
type polygonRecipientSnapshot struct {
|
||||
addrs map[string]struct{}
|
||||
}
|
||||
|
||||
var polygonWatchedRecipients atomic.Pointer[polygonRecipientSnapshot]
|
||||
|
||||
func StartPolygonWebSocketListener() {
|
||||
wallets, err := data.GetAvailableWalletAddressByNetwork(mdb.NetworkPolygon)
|
||||
if err != nil {
|
||||
log.Sugar.Errorf("[POLYGON-WS] Failed to get wallet addresses: %v", err)
|
||||
return
|
||||
}
|
||||
storePolygonRecipientsFromWallets(wallets)
|
||||
go func() {
|
||||
ticker := time.NewTicker(5 * time.Second)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
w, err := data.GetAvailableWalletAddressByNetwork(mdb.NetworkPolygon)
|
||||
if err != nil {
|
||||
log.Sugar.Warnf("[POLYGON-WS] refresh wallet addresses: %v", err)
|
||||
continue
|
||||
}
|
||||
storePolygonRecipientsFromWallets(w)
|
||||
}
|
||||
}()
|
||||
wsURL := "wss://polygon-bor-ws.publicnode.com"
|
||||
query := ethereum.FilterQuery{
|
||||
Addresses: []common.Address{
|
||||
polygonUsdtContract,
|
||||
polygonUsdcContract,
|
||||
polygonUsdcEContract,
|
||||
},
|
||||
Topics: [][]common.Hash{},
|
||||
}
|
||||
|
||||
runEvmWsLogListener("[POLYGON-WS]", wsURL, query, func(client *ethclient.Client, vLog types.Log) {
|
||||
if len(vLog.Topics) < 3 {
|
||||
return
|
||||
}
|
||||
|
||||
event := vLog.Topics[0].String()
|
||||
if event != transferEventHash.String() {
|
||||
return
|
||||
}
|
||||
|
||||
amount := new(big.Int).SetBytes(vLog.Data)
|
||||
|
||||
toAddr := common.HexToAddress(vLog.Topics[2].Hex())
|
||||
|
||||
if !isWatchedPolygonRecipient(toAddr) {
|
||||
return
|
||||
}
|
||||
|
||||
var blockTsMs int64
|
||||
header, err := client.HeaderByNumber(context.Background(), big.NewInt(int64(vLog.BlockNumber)))
|
||||
if err != nil {
|
||||
log.Sugar.Warnf("[POLYGON-WS] HeaderByNumber block=%d: %v, using local time", vLog.BlockNumber, err)
|
||||
blockTsMs = time.Now().UnixMilli()
|
||||
} else {
|
||||
blockTsMs = int64(header.Time) * 1000
|
||||
}
|
||||
|
||||
service.TryProcessEvmERC20Transfer(mdb.NetworkPolygon, vLog.Address, toAddr, amount, vLog.TxHash.Hex(), blockTsMs)
|
||||
})
|
||||
}
|
||||
|
||||
func storePolygonRecipientsFromWallets(wallets []mdb.WalletAddress) int {
|
||||
m := make(map[string]struct{})
|
||||
for _, w := range wallets {
|
||||
a := strings.TrimSpace(w.Address)
|
||||
if !common.IsHexAddress(a) {
|
||||
continue
|
||||
}
|
||||
m[strings.ToLower(common.HexToAddress(a).Hex())] = struct{}{}
|
||||
}
|
||||
polygonWatchedRecipients.Store(&polygonRecipientSnapshot{addrs: m})
|
||||
return len(m)
|
||||
}
|
||||
|
||||
func isWatchedPolygonRecipient(to common.Address) bool {
|
||||
snap := polygonWatchedRecipients.Load()
|
||||
if snap == nil || len(snap.addrs) == 0 {
|
||||
return false
|
||||
}
|
||||
_, ok := snap.addrs[strings.ToLower(to.Hex())]
|
||||
return ok
|
||||
}
|
||||
Reference in New Issue
Block a user