erc20activity

package module
v0.0.0-...-26e5b11 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 15, 2025 License: MIT Imports: 8 Imported by: 0

README

ERC20 Activity Tracker

A drop-in, concurrent-safe Go service that tells you which tokens are “alive” on-chain right now—without hammering your node.


Why is this useful?

Challenge How the tracker helps
Discovering fresh tokens — Traditional token lists are static; brand-new contracts don’t show up for hours (or ever). As soon as a contract emits its first Transfer event, the tracker records it in memory—no manual curation required.
Avoiding noisy polling — Re-querying every block for every token burns RPC calls and slows your app. Uses a bloom-filter pre-check to skip blocks that cannot contain ERC-20 transfers, then fetches logs only when needed.
Concurrent access — Dashboards, bots, and alerting daemons all want the same data. ActiveTokens() is read-locked (sync.RWMutex) so thousands of goroutines can query the list without blocking each other.
Graceful resource cleanup — Long-running services leak goroutines if shutdown logic is sloppy. Every worker (blockProcessor, pruner) respects the parent context.Context; cancelling the context stops them cleanly.
Stale-token buildup — Dead projects clutter analytics and UI over time. A background pruner removes tokens that haven’t moved in N minutes (configurable).
Integrations & testing — Tight coupling to one client (e.g., ethclient) makes mocking hard. All external dependencies are function types (LogFetcher, BloomTestFunc) injected at startup—mock or swap at will.

Use cases include:

  • Portfolio dashboards that surface “hot” assets automatically
  • Trading/arbitrage bots that filter for tokens with recent flow
  • Whale-alert or analytics feeds that trigger on first activity

Features

  • Real-time activity tracking – listens to the live block stream, flags every contract that fires a Transfer.
  • Bloom-filter shortcut – skips blocks that cannot contain ERC-20 transfers before any RPC call is made, reducing load on your node.
  • Thread-safe & race-free – proven with go test -race.
  • Configurable “freshness” window – treat tokens as stale after X minutes, prune every Y minutes.
  • Pluggable logging – drop in any slog.Handler or custom Logger implementation.
  • Graceful shutdown – one context.CancelFunc stops the world without leaks.

Installation

go get github.com/your-repo/erc20activity   # replace with your actual module path

Quick Start

Below is a minimal example that wires the tracker into a running program. Replace the mock implementations with calls to your own Ethereum client (e.g., ethclient.Client from go-ethereum).

package main

import (
    "context"
    "fmt"
    "log"
    "math/big"
    "time"

    "github.com/ethereum/go-ethereum/core/types"
    "github.com/your-repo/erc20activity" // update with your module path
)

// -----------------------------------------------------------------
// Mock plumbing – swap these with real blockchain calls in production.
// -----------------------------------------------------------------

var newBlockEventer = make(chan *types.Block) // incoming blocks

func logFetcher(ctx context.Context, blockNumber uint64) ([]types.Log, error) {
    // return ethClient.FilterLogs(ctx, ethereum.FilterQuery{FromBlock: bn, ToBlock: bn})
    fmt.Printf("Fetching logs for block %d\n", blockNumber)
    return nil, nil
}

func bloomFilterTest(bloom types.Bloom) bool {
    // return types.BloomLookup(bloom, erc20activity.ERC20TransferTopic)
    return true
}

// -----------------------------------------------------------------
// Main
// -----------------------------------------------------------------

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

    cfg := &erc20activity.Config{
        NewBlockEventer:      newBlockEventer,
        LogFetcher:           logFetcher,
        BloomFilterTest:      bloomFilterTest,
        TokenStaleDuration:   15 * time.Minute,
        ExpiryCheckFrequency: 5 * time.Minute,
        Logger:               log.Default(),
    }

    tracker, err := erc20activity.NewTracker(ctx, cfg)
    if err != nil {
        log.Fatalf("tracker init: %v", err)
    }

    // Simulate blocks
    go func() {
        for i := uint64(0); ; i++ {
            select {
            case newBlockEventer <- types.NewBlockWithHeader(&types.Header{Number: big.NewInt(int64(i))}):
                time.Sleep(10 * time.Second)
            case <-ctx.Done():
                return
            }
        }
    }()

    // Dump active tokens every 30 s
    for {
        select {
        case <-time.After(30 * time.Second):
            for _, t := range tracker.ActiveTokens() {
                fmt.Println(t.Hex())
            }
        case <-ctx.Done():
            return
        }
    }
}

Configuration

Field Type Required Description
NewBlockEventer <-chan *types.Block ✔︎ Feed of newly mined blocks.
LogFetcher func(context.Context, uint64) ([]types.Log, error) ✔︎ Fetches logs for a specific block.
BloomFilterTest func(types.Bloom) bool ✔︎ Cheap bloom check to skip log fetch if no Transfer topics present.
TokenStaleDuration time.Duration Token removed after this period without activity.
ExpiryCheckFrequency time.Duration How often the pruner runs; 0 disables pruning.
Logger Logger interface Defaults to a silent logger when nil.

How it works under the hood

┌ newBlockEventer (chan *Block) ┐
            │
            ▼
┌────── blockProcessor (1 goroutine) ──────┐
│  • bloom pre-filter                      │
│  • fetch logs only when necessary        │
└──────────────────────────────────────────┘
            │ writes
            ▼
┌────── lastSeen map (protected by RWMutex) ──────┐
│ tokenAddr → lastActivityTimestamp              │
└────────────────────────────────────────────────┘
        ▲                 │
        │ reads           ▼
ActiveTokens()       pruner (every N min)
(many callers)        – purge stale entries

Testing

go test -v -race ./...

Contributing

Pull requests and issues are warmly welcome. Please include:

  1. A clear description of the change or bug.
  2. Unit tests where reasonable.
  3. go vet, go fmt, and go test -race passing locally.

License

MIT

Documentation

Overview

Package erc20activity provides a standalone service to track the activity of ERC20 tokens.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrNilNewBlockEventer is returned when a Tracker is created with a nil NewBlockEventer.
	ErrNilNewBlockEventer = errors.New("NewBlockEventer cannot be nil")
	// ErrNilLogFetcher is returned when a Tracker is created with a nil LogFetcher.
	ErrNilLogFetcher = errors.New("LogFetcher cannot be nil")
	// ErrNilBloomFilterTest is returned when a Tracker is created with a nil BloomFilterTest.
	ErrNilBloomFilterTest = errors.New("BloomFilterTest cannot be nil")
)
View Source
var ERC20TransferTopic = common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef")

ERC20TransferTopic is the signature for the ERC20 Transfer event.

Functions

This section is empty.

Types

type BloomTestFunc

type BloomTestFunc func(bloom types.Bloom) bool

BloomTestFunc defines the function signature for testing if a block's bloom filter contains topics of interest.

type Config

type Config struct {
	NewBlockEventer      <-chan *types.Block
	LogFetcher           LogFetcher
	BloomFilterTest      BloomTestFunc
	ExpiryCheckFrequency time.Duration
	TokenStaleDuration   time.Duration
	Logger               Logger
}

Config holds all the dependencies and settings for creating a new Tracker.

type LogFetcher

type LogFetcher func(ctx context.Context, blockNumber uint64) ([]types.Log, error)

LogFetcher defines the function signature for fetching logs for a specific block number.

type Logger

type Logger interface {
	Debug(msg string, args ...any)
	Info(msg string, args ...any)
	Warn(msg string, args ...any)
	Error(msg string, args ...any)
}

Logger defines a standard interface for structured, leveled logging.

type Tracker

type Tracker struct {
	// contains filtered or unexported fields
}

Tracker monitors ERC20 transfer events to maintain a list of recently active tokens.

func NewTracker

func NewTracker(ctx context.Context, cfg *Config) (*Tracker, error)

NewTracker creates and starts a new ERC20 activity tracker.

func (*Tracker) ActiveTokens

func (t *Tracker) ActiveTokens() []common.Address

ActiveTokens returns a slice of all token addresses currently considered active.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL