Files
assetx/webapp-back/holders/swap_scanner.go
default 2ee4553b71 init: 初始化 AssetX 项目仓库
包含 webapp(Next.js 用户端)、webapp-back(Go 后端)、
antdesign(管理后台)、landingpage(营销落地页)、
数据库 SQL 和配置文件。
2026-03-27 11:26:43 +00:00

100 lines
3.1 KiB
Go

package holders
import (
"context"
"log"
"math/big"
"strings"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
db "github.com/gothinkster/golang-gin-realworld-example-app/common"
"github.com/gothinkster/golang-gin-realworld-example-app/models"
)
// Swap(address account, address tokenIn, address tokenOut, uint256 amountIn, uint256 amountOut, uint256 feeBasisPoints)
// account/tokenIn/tokenOut are indexed → topics[1..3]
// amountIn/amountOut/feeBasisPoints are non-indexed → data[0..95]
var swapEventTopic = crypto.Keccak256Hash([]byte("Swap(address,address,address,uint256,uint256,uint256)"))
// scanSwapEvents scans Swap events from all YT vaults and upserts into yt_swap_records.
func (s *Scanner) scanSwapEvents(ctx context.Context, fromBlock, toBlock uint64) error {
if toBlock < fromBlock {
return nil
}
database := db.GetDB()
totalNew := 0
// Cache block timestamps to avoid repeated RPC calls for the same block
blockTimeCache := make(map[uint64]time.Time)
for _, vault := range s.config.YTVaults {
log.Printf(" [SwapScanner] %s blocks %d→%d", vault.Name, fromBlock, toBlock)
logs, err := s.queryBlockRange(ctx, common.HexToAddress(vault.Address), swapEventTopic, fromBlock, toBlock)
if err != nil {
log.Printf(" [SwapScanner] Error scanning %s: %v", vault.Name, err)
continue
}
for _, l := range logs {
// Validate topics and data length
if len(l.Topics) < 4 || len(l.Data) < 64 {
continue
}
account := common.BytesToAddress(l.Topics[1].Bytes()).Hex()
tokenIn := common.BytesToAddress(l.Topics[2].Bytes()).Hex()
tokenOut := common.BytesToAddress(l.Topics[3].Bytes()).Hex()
amountIn := new(big.Int).SetBytes(l.Data[0:32])
amountOut := new(big.Int).SetBytes(l.Data[32:64])
// Get block time (cached per block number)
blockTime, ok := blockTimeCache[l.BlockNumber]
if !ok {
blk, err := s.client.BlockByNumber(ctx, big.NewInt(int64(l.BlockNumber)))
if err == nil {
blockTime = time.Unix(int64(blk.Time()), 0)
blockTimeCache[l.BlockNumber] = blockTime
}
}
record := models.YTSwapRecord{
TxHash: l.TxHash.Hex(),
LogIndex: uint(l.Index),
ChainID: s.config.ChainID,
BlockNumber: l.BlockNumber,
BlockTime: blockTime,
VaultAddr: vault.Address,
Account: account,
TokenIn: strings.ToLower(tokenIn),
TokenOut: strings.ToLower(tokenOut),
AmountIn: amountIn.String(),
AmountOut: amountOut.String(),
}
// Skip if already exists (idempotent)
var existing models.YTSwapRecord
res := database.Where("tx_hash = ? AND log_index = ?", record.TxHash, record.LogIndex).First(&existing)
if res.Error == nil {
continue
}
if err := database.Create(&record).Error; err != nil {
if !strings.Contains(err.Error(), "Duplicate") {
log.Printf(" [SwapScanner] Save failed %s:%d: %v", record.TxHash, record.LogIndex, err)
}
continue
}
totalNew++
}
}
if totalNew > 0 {
log.Printf(" [SwapScanner] Saved %d new swap records", totalNew)
}
return nil
}