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 }