package tokens import ( "context" "fmt" "math/big" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/jackc/pgx/v5/pgxpool" ) // Extractor extracts token transfers from transaction logs type Extractor struct { db *pgxpool.Pool chainID int } // NewExtractor creates a new token extractor func NewExtractor(db *pgxpool.Pool, chainID int) *Extractor { return &Extractor{ db: db, chainID: chainID, } } // ERC20 Transfer event signature: Transfer(address,address,uint256) var ERC20TransferSignature = common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef") // ERC721 Transfer event signature: Transfer(address,address,uint256) var ERC721TransferSignature = ERC20TransferSignature // ERC1155 TransferSingle event signature: TransferSingle(address,address,address,uint256,uint256) var ERC1155TransferSingleSignature = common.HexToHash("0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62") // ExtractTokenTransfers extracts token transfers from logs func (e *Extractor) ExtractTokenTransfers(ctx context.Context, txHash common.Hash, blockNumber int64, logs []types.Log) error { for i, log := range logs { // Check for ERC20/ERC721 Transfer if len(log.Topics) == 3 && log.Topics[0] == ERC20TransferSignature { if err := e.extractERC20Transfer(ctx, txHash, blockNumber, i, log); err != nil { return fmt.Errorf("failed to extract ERC20 transfer: %w", err) } } // Check for ERC1155 TransferSingle if len(log.Topics) == 4 && log.Topics[0] == ERC1155TransferSingleSignature { if err := e.extractERC1155Transfer(ctx, txHash, blockNumber, i, log); err != nil { return fmt.Errorf("failed to extract ERC1155 transfer: %w", err) } } } return nil } // extractERC20Transfer extracts ERC20 transfer func (e *Extractor) extractERC20Transfer(ctx context.Context, txHash common.Hash, blockNumber int64, logIndex int, log types.Log) error { if len(log.Topics) != 3 || len(log.Data) != 32 { return fmt.Errorf("invalid ERC20 transfer log") } from := common.BytesToAddress(log.Topics[1].Bytes()) to := common.BytesToAddress(log.Topics[2].Bytes()) amount := new(big.Int).SetBytes(log.Data) // Determine token type (ERC20 or ERC721) tokenType := "ERC20" if len(log.Data) == 0 { tokenType = "ERC721" } query := ` INSERT INTO token_transfers ( chain_id, transaction_hash, block_number, log_index, token_address, token_type, from_address, to_address, amount ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) ON CONFLICT (chain_id, transaction_hash, log_index) DO NOTHING ` _, err := e.db.Exec(ctx, query, e.chainID, txHash.Hex(), blockNumber, logIndex, log.Address.Hex(), tokenType, from.Hex(), to.Hex(), amount.String(), ) if err != nil { return err } // Update token holder count return e.updateTokenStats(ctx, log.Address) } // extractERC1155Transfer extracts ERC1155 transfer func (e *Extractor) extractERC1155Transfer(ctx context.Context, txHash common.Hash, blockNumber int64, logIndex int, log types.Log) error { if len(log.Topics) != 4 || len(log.Data) != 64 { return fmt.Errorf("invalid ERC1155 transfer log") } operator := common.BytesToAddress(log.Topics[1].Bytes()) from := common.BytesToAddress(log.Topics[2].Bytes()) to := common.BytesToAddress(log.Topics[3].Bytes()) tokenID := new(big.Int).SetBytes(log.Data[:32]) amount := new(big.Int).SetBytes(log.Data[32:]) query := ` INSERT INTO token_transfers ( chain_id, transaction_hash, block_number, log_index, token_address, token_type, from_address, to_address, amount, token_id, operator ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) ON CONFLICT (chain_id, transaction_hash, log_index) DO NOTHING ` _, err := e.db.Exec(ctx, query, e.chainID, txHash.Hex(), blockNumber, logIndex, log.Address.Hex(), "ERC1155", from.Hex(), to.Hex(), amount.String(), tokenID.String(), operator.Hex(), ) if err != nil { return err } return e.updateTokenStats(ctx, log.Address) } // updateTokenStats updates token statistics func (e *Extractor) updateTokenStats(ctx context.Context, tokenAddress common.Address) error { // Count holders var holderCount int err := e.db.QueryRow(ctx, ` SELECT COUNT(DISTINCT to_address) FROM token_transfers WHERE chain_id = $1 AND token_address = $2 `, e.chainID, tokenAddress.Hex()).Scan(&holderCount) if err != nil { holderCount = 0 } // Count transfers var transferCount int err = e.db.QueryRow(ctx, ` SELECT COUNT(*) FROM token_transfers WHERE chain_id = $1 AND token_address = $2 `, e.chainID, tokenAddress.Hex()).Scan(&transferCount) if err != nil { transferCount = 0 } // Update token query := ` INSERT INTO tokens (chain_id, address, type, holder_count, transfer_count) VALUES ($1, $2, 'ERC20', $3, $4) ON CONFLICT (chain_id, address) DO UPDATE SET holder_count = $3, transfer_count = $4, updated_at = NOW() ` _, err = e.db.Exec(ctx, query, e.chainID, tokenAddress.Hex(), holderCount, transferCount) return err }