137 lines
4.2 KiB
Go
137 lines
4.2 KiB
Go
package track2
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"math/big"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/core/types"
|
|
"github.com/ethereum/go-ethereum/ethclient"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
// TokenIndexer indexes ERC-20 token transfers for Track 2
|
|
type TokenIndexer struct {
|
|
db *pgxpool.Pool
|
|
client *ethclient.Client
|
|
chainID int
|
|
}
|
|
|
|
// NewTokenIndexer creates a new token indexer
|
|
func NewTokenIndexer(db *pgxpool.Pool, client *ethclient.Client, chainID int) *TokenIndexer {
|
|
return &TokenIndexer{
|
|
db: db,
|
|
client: client,
|
|
chainID: chainID,
|
|
}
|
|
}
|
|
|
|
// ERC20TransferEventSignature is the signature for ERC-20 Transfer event
|
|
const ERC20TransferEventSignature = "Transfer(address,address,uint256)"
|
|
|
|
// IndexTokenTransfers indexes token transfers from a transaction receipt
|
|
func (ti *TokenIndexer) IndexTokenTransfers(ctx context.Context, receipt *types.Receipt, blockNumber uint64, blockHash common.Hash, timestamp time.Time) error {
|
|
// Parse Transfer event signature
|
|
transferEventSig := common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef") // keccak256("Transfer(address,address,uint256)")
|
|
|
|
for _, log := range receipt.Logs {
|
|
// Check if this is a Transfer event
|
|
if len(log.Topics) != 3 || log.Topics[0] != transferEventSig {
|
|
continue
|
|
}
|
|
|
|
// Extract token contract, from, to, and value
|
|
tokenContract := log.Address.Hex()
|
|
from := common.BytesToAddress(log.Topics[1].Bytes()).Hex()
|
|
to := common.BytesToAddress(log.Topics[2].Bytes()).Hex()
|
|
|
|
// Decode value from data
|
|
value := new(big.Int).SetBytes(log.Data)
|
|
|
|
// Insert token transfer
|
|
insertQuery := `
|
|
INSERT INTO token_transfers (
|
|
chain_id, transaction_hash, log_index, block_number, block_hash,
|
|
timestamp, token_contract, from_address, to_address, value
|
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
ON CONFLICT (chain_id, transaction_hash, log_index) DO NOTHING
|
|
`
|
|
|
|
_, err := ti.db.Exec(ctx, insertQuery,
|
|
ti.chainID,
|
|
receipt.TxHash.Hex(),
|
|
log.Index,
|
|
blockNumber,
|
|
blockHash.Hex(),
|
|
timestamp,
|
|
tokenContract,
|
|
from,
|
|
to,
|
|
value.String(),
|
|
)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to insert token transfer: %w", err)
|
|
}
|
|
|
|
// Update token balances
|
|
ti.updateTokenBalances(ctx, tokenContract, from, to, value)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// updateTokenBalances updates token balances for addresses
|
|
func (ti *TokenIndexer) updateTokenBalances(ctx context.Context, tokenContract, from, to string, value *big.Int) {
|
|
// Decrease from balance
|
|
if from != "" && from != "0x0000000000000000000000000000000000000000" {
|
|
updateFromQuery := `
|
|
INSERT INTO token_balances (address, token_contract, chain_id, balance, last_updated_timestamp)
|
|
VALUES ($1, $2, $3, 0, NOW())
|
|
ON CONFLICT (address, token_contract, chain_id) DO UPDATE SET
|
|
balance = GREATEST(0, token_balances.balance - $4::numeric),
|
|
last_updated_timestamp = NOW(),
|
|
updated_at = NOW()
|
|
`
|
|
ti.db.Exec(ctx, updateFromQuery, strings.ToLower(from), strings.ToLower(tokenContract), ti.chainID, value.String())
|
|
}
|
|
|
|
// Increase to balance
|
|
if to != "" && to != "0x0000000000000000000000000000000000000000" {
|
|
updateToQuery := `
|
|
INSERT INTO token_balances (address, token_contract, chain_id, balance, last_updated_timestamp)
|
|
VALUES ($1, $2, $3, $4, NOW())
|
|
ON CONFLICT (address, token_contract, chain_id) DO UPDATE SET
|
|
balance = token_balances.balance + $4::numeric,
|
|
last_updated_timestamp = NOW(),
|
|
updated_at = NOW()
|
|
`
|
|
ti.db.Exec(ctx, updateToQuery, strings.ToLower(to), strings.ToLower(tokenContract), ti.chainID, value.String())
|
|
}
|
|
}
|
|
|
|
// IndexBlockTokenTransfers indexes all token transfers in a block
|
|
func (ti *TokenIndexer) IndexBlockTokenTransfers(ctx context.Context, blockNumber uint64) error {
|
|
block, err := ti.client.BlockByNumber(ctx, big.NewInt(int64(blockNumber)))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get block: %w", err)
|
|
}
|
|
|
|
for _, tx := range block.Transactions() {
|
|
receipt, err := ti.client.TransactionReceipt(ctx, tx.Hash())
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
if err := ti.IndexTokenTransfers(ctx, receipt, blockNumber, block.Hash(), time.Unix(int64(block.Time()), 0)); err != nil {
|
|
fmt.Printf("Failed to index token transfers for tx %s: %v\n", tx.Hash().Hex(), err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|