Skip to content

Repository files navigation

HawkEye Balance Aggregator

High-performance multi-chain stablecoin balance checker built with Go and Redis

Go Version Redis License

🚀 Overview

A fast stateless API service that aggregates stablecoin balances (USDC, USDT, USDS) across multiple blockchain networks with intelligent Redis caching and concurrent processing.

Supported Chains

  • Ethereum (ETH) - Full EVM support
  • Polygon (MATIC) - Full EVM support
  • Base - Full EVM support
  • Aptos - Native integration
  • Solana - Native integration

Supported Stablecoins

  • USDC (USD Coin)
  • USDT (Tether)
  • USDS (Sky USD)

✨ Key Features

  • 🔥 Multi-Chain Support: Query balances across 5 blockchains simultaneously
  • 🎯 Flexible Filtering: Filter by specific chains and/or tokens
  • 💾 Smart Caching: Redis-powered caching with configurable TTL
  • ⚡ Concurrent Processing: Go routines and worker pools for optimal performance
  • 🔄 Address Type Detection: Automatic detection of EVM, Aptos, and Solana addresses
  • 📊 Batch Processing: Handle multiple addresses in a single request
  • 🛡️ Input Validation: Comprehensive validation for addresses, chains, and tokens
  • 📈 Performance Metrics: Response time tracking and cache hit rate monitoring
  • 🐳 Docker Ready: Complete Docker Compose setup included

🚀 Quick Start

Prerequisites

  • Go: 1.25 or higher
  • Redis: 7.0 or higher (or use Docker)
  • Docker & Docker Compose: (Optional, for containerized setup)
  • RPC API Keys:
    • Alchemy for EVM chains (Ethereum, Polygon, Base)
    • QuickNode/Helius for Solana
    • Geommi for Aptos

📦 Installation & Running

Option 1: Docker (Recommended)

The easiest way to get started. Docker Compose will handle both Redis and the API service.

1. Clone the repository

git clone https://github.com/ProfSaz/HawkEye.git
cd HawkEye

2. Configure environment

cp .env.example .env
# Edit .env with your RPC endpoints and API keys

3. Start with Docker Compose

# Build and start all services
make docker-up

# Or manually
docker-compose up -d

The API will be available at http://localhost:8080

4. Check service health

curl http://localhost:8080/health

5. Stop services

make docker-down
# Or manually
docker-compose down

Option 2: Local Development

Run the service locally without Docker.

1. Clone the repository

git clone https://github.com/ProfSaz/HawkEye.git
cd HawkEye

2. Install dependencies

make deps
# Or manually
go mod download

3. Configure environment

cp .env.example .env
# Edit .env with your RPC endpoints

4. Start Redis (Required)

# Option A: Using Redis directly
redis-server

# Option B: Using Docker for Redis only
docker run -d -p 6379:6379 redis:7-alpine

5. Run the service

# Using Makefile
make start

# Or manually
go run cmd/api/main.go

The API will be available at http://localhost:8080


🛠️ Development Commands

The project includes a Makefile for common tasks:

# Show all available commands
make help

# Development
make start          # Build and run the application
make test           # Run all tests with coverage
make test-unit      # Run unit tests only
make bench          # Run benchmarks
make fmt            # Format code
make lint           # Run linter

# Docker
make docker-build   # Build Docker image
make docker-up      # Start all services
make docker-down    # Stop all services

# Utilities
make clean          # Remove build artifacts
make deps           # Install/update dependencies
make tree           # Show project structure

📡 API Endpoints

Single Address Balance Lookup

GET /api/v1/balance/{address}

Fetch stablecoin balances for a single address with optional chain and token filtering.

Query Parameters

Parameter Type Description Example
chain string Comma-separated list of chains to query ethereum,polygon
token string Comma-separated list of tokens to query usdc,usdt

Examples

1. Get all balances (all compatible chains and tokens)

curl http://localhost:8080/api/v1/balance/0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238

Response:

{
  "address": "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
  "timestamp": "2024-12-30T10:30:00Z",
  "total_usd": 37.31,
  "chains": {
    "ethereum": {
      "usdc": "0.00",
      "usdt": "2.31",
      "usds": "0.00"
    },
    "polygon": {
      "usdc": "0.00",
      "usdt": "0.00",
      "usds": "0.00"
    },
    "base": {
      "usdc": "35.00",
      "usdt": "0.00",
      "usds": "0.00"
    }
  },
  "metadata": {
    "cache_hit": true,
    "response_time_ms": 2,
    "compatible_chain": ["ethereum", "polygon", "base"]
  }
}

2. Get only USDC balances across all chains

curl "http://localhost:8080/api/v1/balance/0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238?token=usdc"

3. Get USDC and USDT on Ethereum only

curl "http://localhost:8080/api/v1/balance/0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238?chain=ethereum&token=usdc,usdt"

4. Get balances on multiple chains

curl "http://localhost:8080/api/v1/balance/0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238?chain=ethereum,base"

Batch Address Lookup

POST /api/v1/balance/batch

Fetch stablecoin balances for multiple addresses with optional filtering.

Request Body:

{
  "addresses": [
    "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
    "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
    "0x84b1675891d370d5de8f169031f9c3116d7add256ecf50a4bc71e3135ddba6e0"
  ],
  "options": {
    "chains": ["ethereum", "polygon", "aptos"],
    "tokens": ["usdc", "usdt"]
  }
}

Response:

{
  "total_addresses": 3,
  "successful": 3,
  "failed": 0,
  "total_value_usd": 118404520.12,
  "results": [
    {
      "address": "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
      "total_usd": 37.31,
      "chains": {
        "ethereum": {
          "usdc": "0.00",
          "usdt": "2.31"
        },
        "polygon": {
          "usdc": "0.00",
          "usdt": "0.00"
        }
      }
    },
    {
      "address": "0x84b1675891d370d5de8f169031f9c3116d7add256ecf50a4bc71e3135ddba6e0",
      "total_usd": 118404482.81,
      "chains": {
        "aptos": {
          "usdc": "12835426.51",
          "usdt": "105569056.30"
        }
      }
    }
  ],
  "performance": {
    "total_time_ms": 1,
    "cache_hit_rate": "100.0%",
    "rpc_calls": 0,
    "redis_hits": 30
  }
}

Health Check

GET /health

Check service health and Redis connectivity.

Response:

{
  "status": "healthy",
  "redis": "connected",
  "timestamp": "2024-12-30T10:30:00Z"
}

🎯 Address Type Detection & Chain Compatibility

The service automatically detects address types and only queries compatible chains:

Address Format Type Compatible Chains
0x + exactly 40 hex chars EVM ethereum, polygon, base
0x + 1-64 hex chars (not 40) Aptos aptos
Base58, 32-44 characters Solana solana

Examples:

EVM Address:

# Automatically queries Ethereum, Polygon, and Base only
curl http://localhost:8080/api/v1/balance/0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238

Aptos Address:

# Automatically queries only Aptos
curl http://localhost:8080/api/v1/balance/0x84b1675891d370d5de8f169031f9c3116d7add256ecf50a4bc71e3135ddba6e0

Solana Address:

# Automatically queries only Solana
curl http://localhost:8080/api/v1/balance/DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK

⚠️ Error Handling

Invalid Chain Name

Request:

curl "http://localhost:8080/api/v1/balance/0x1c7D...?chain=bitcoin"

Response (400):

{
  "error": "Bad Request",
  "message": "invalid chain: 'bitcoin'. Valid chains are: ethereum, polygon, base, aptos, solana",
  "code": 400
}

Incompatible Chain for Address Type

Request:

# Trying to query Solana with an EVM address
curl "http://localhost:8080/api/v1/balance/0x1c7D...?chain=solana"

Response (400):

{
  "error": "Bad Request",
  "message": "chain 'solana' is not compatible with evm address. Compatible chains: ethereum, polygon, base",
  "code": 400
}

Invalid Token Name

Request:

curl "http://localhost:8080/api/v1/balance/0x1c7D...?token=dai"

Response (400):

{
  "error": "Bad Request",
  "message": "invalid token: 'dai'. Valid tokens are: usdc, usdt, usds",
  "code": 400
}

⚙️ Configuration

Configuration is managed via environment variables. Copy .env.example to .env and fill in your values.

Required Environment Variables

# Server Configuration
PORT=8080
ENV=development

# Redis Configuration
REDIS_HOST=localhost      # Use 'redis' for Docker
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_DB=0
CACHE_TTL_SECONDS=300

# Worker Pool
MAX_WORKERS=50
WORKER_QUEUE_SIZE=1000

# RPC Endpoints
ETHEREUM_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY
POLYGON_RPC_URL=https://polygon-mainnet.g.alchemy.com/v2/YOUR_KEY
BASE_RPC_URL=https://base-mainnet.g.alchemy.com/v2/YOUR_KEY
APTOS_RPC_URL=https://fullnode.mainnet.aptoslabs.com/v1
SOLANA_RPC_URL=https://api.mainnet-beta.solana.com

# Token Contract Addresses
ETH_USDC_ADDRESS=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
ETH_USDT_ADDRESS=0xdAC17F958D2ee523a2206206994597C13D831ec7
ETH_USDS_ADDRESS=0xBcca60bB61934080951369a648Fb03DF4F96263C
POLYGON_USDC_ADDRESS=0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174
POLYGON_USDT_ADDRESS=0xc2132D05D31c914a87C6611C10748AEb04B58e8F
BASE_USDC_ADDRESS=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913

Note: When running with Docker Compose, set REDIS_HOST=redis in your .env file. For local development, use REDIS_HOST=localhost.


🐳 Docker Configuration

Docker Compose Services

services:
  redis:
    - Redis 7 Alpine
    - Persistent data storage
    - Health checks enabled
    - Port: 6379
    
  api:
    - Multi-stage build
    - Health checks enabled
    - Auto-restart on failure
    - Port: 8080

Docker Commands

# Build the Docker image
make docker-build

# Start all services (Redis + API)
make docker-up

# View logs
docker-compose logs -f api

# Stop all services
make docker-down

# Restart services
docker-compose restart

# View service status
docker-compose ps

🧪 Testing

Run Tests

# All tests with coverage
make test

# Unit tests only
make test-unit

# Benchmarks
make bench

Test Coverage

go test ./... -coverprofile=coverage.out
go tool cover -html=coverage.out

📊 Performance Characteristics

Single Address Queries

Scenario Response Time Description
Cache Hit ~2-15ms Data retrieved from Redis
Cache Miss (1 chain) ~150-250ms Single RPC call
Cache Miss (3 chains) ~300-400ms 3 concurrent RPC calls
Filtered Query ~100-200ms Fewer RPC calls needed

Batch Queries

Addresses Cache Hit Rate Response Time
5 100% ~2-10ms
50 90% ~50-100ms
500 90% ~200-400ms
5000 90% ~1-2s

Cache Performance

  • Hit Rate: 85-95% in typical usage
  • TTL: 5 minutes (configurable)
  • Key Pattern: balance:{chain}:{address}:{token}
  • 80% Threshold: Cache considered valid if 80%+ keys are present

🏗️ Project Structure

HawkEye/
├── cmd/
│   └── api/
│       └── main.go                 # Application entry point
│
├── internal/
│   ├── api/
│   │   ├── handlers/
│   │   │   ├── balance.go          # Single address handler (with filtering)
│   │   │   ├── batch.go            # Batch address handler
│   │   │   ├── health.go           # Health check
│   │   │   └── metrics.go          # Metrics endpoint
│   │   ├── middleware/
│   │   │   ├── cors.go             # CORS middleware
│   │   │   ├── logging.go          # Request logging
│   │   │   └── rateLimit.go        # Rate limiting
│   │   └── router.go               # HTTP router setup
│   │
│   ├── cache/
│   │   ├── cache.go                # Cache interface
│   │   └── redis.go                # Redis implementation
│   │
│   ├── config/
│   │   └── config.go               # Configuration management
│   │
│   ├── fetcher/
│   │   ├── fetcher.go              # Main balance fetcher
│   │   ├── worker.go               # Worker pool implementation
│   │   ├── types.go                # Common types
│   │   └── chains/
│   │       ├── ethereum.go         # Ethereum fetcher
│   │       ├── polygon.go          # Polygon fetcher
│   │       ├── base.go             # Base fetcher
│   │       ├── aptos.go            # Aptos fetcher
│   │       └── solana.go           # Solana fetcher
│   │
│   ├── models/
│   │   ├── cache.go                # Cache models
│   │   ├── errors.go               # Error types
│   │   ├── request.go              # Request models
│   │   └── response.go             # Response models
│   │
│   └── rpc/
│       ├── client.go               # RPC client pool
│       ├── ethereum.go             # EVM RPC client
│       ├── aptos.go                # Aptos RPC client
│       ├── solana.go               # Solana RPC client
│       └── manager.go              # RPC manager
│
├── pkg/
│   ├── tokens/
│   │   └── decimal.go              # Decimal handling
│   └── utils/
│       ├── addrValidator.go        # Address validators
│       └── errors.go               # Error utilities
│
├── deployments/
│   └── docker/
│       └── Dockerfile              # Multi-stage Dockerfile
│
├── tests/
│   └── unit/
│       ├── address_test.go
│       ├── cache_test.go
│       ├── fetcher_test.go
│       └── rpc_test.go
│
├── docker-compose.yml              # Docker Compose configuration
├── Makefile                        # Build automation
├── go.mod                          # Go module definition
└── README.md                       # This file

🔑 Key Implementation Details

1. Address Type Detection

// Automatically detects address type
addressType := utils.DetectAddressType(address)
// Returns: AddressTypeEVM, AddressTypeAptos, AddressTypeSolana, or AddressTypeUnknown

// Get compatible chains for address type
chains := utils.GetCompatibleChains(addressType)
// For EVM: ["ethereum", "polygon", "base"]
// For Aptos: ["aptos"]
// For Solana: ["solana"]

2. Worker Pool Concurrency

// Creates a worker pool with configurable size
pool := NewWorkerPool(maxWorkers, queueSize)
pool.Start(fetchJobHandler)

// Submit jobs concurrently
pool.Submit(FetchJob{Chain: "ethereum", Address: addr, Token: "usdc"})

// Collect results
result := <-pool.Results()

3. Intelligent Caching

// Cache key pattern
key := fmt.Sprintf("balance:%s:%s:%s", chain, address, token)

// Batch cache retrieval
cachedData := cache.MGet(ctx, cacheKeys)

// 80% threshold for cache hit
if len(cachedData) >= int(0.8 * float64(len(cacheKeys))) {
    return cachedBalance, true
}

4. Filtered Fetching

// Only fetch requested tokens and chains
if shouldIncludeToken(token, requestedTokens) && 
   shouldIncludeChain(chain, requestedChains) &&
   utils.IsChainCompatible(addressType, chain) {
    // Fetch balance
}

🤝 Contributing

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

📝 License

This project is licensed under the MIT License - see the LICENSE file for details.


🙏 Acknowledgments


📧 Contact

GitHub: @profsaz Project: HawkEye Twitter: ProfSaz


About

High performance multi-chain stablecoin balance checker built with Go and Redis

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages