High-performance multi-chain stablecoin balance checker built with Go and Redis
A fast stateless API service that aggregates stablecoin balances (USDC, USDT, USDS) across multiple blockchain networks with intelligent Redis caching and concurrent processing.
- Ethereum (ETH) - Full EVM support
- Polygon (MATIC) - Full EVM support
- Base - Full EVM support
- Aptos - Native integration
- Solana - Native integration
- USDC (USD Coin)
- USDT (Tether)
- USDS (Sky USD)
- 🔥 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
- 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
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 HawkEye2. Configure environment
cp .env.example .env
# Edit .env with your RPC endpoints and API keys3. Start with Docker Compose
# Build and start all services
make docker-up
# Or manually
docker-compose up -dThe API will be available at http://localhost:8080
4. Check service health
curl http://localhost:8080/health5. Stop services
make docker-down
# Or manually
docker-compose downRun the service locally without Docker.
1. Clone the repository
git clone https://github.com/ProfSaz/HawkEye.git
cd HawkEye2. Install dependencies
make deps
# Or manually
go mod download3. Configure environment
cp .env.example .env
# Edit .env with your RPC endpoints4. 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-alpine5. Run the service
# Using Makefile
make start
# Or manually
go run cmd/api/main.goThe API will be available at http://localhost:8080
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 structureGET /api/v1/balance/{address}
Fetch stablecoin balances for a single address with optional chain and token filtering.
| 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 |
1. Get all balances (all compatible chains and tokens)
curl http://localhost:8080/api/v1/balance/0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238Response:
{
"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"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
}
}GET /health
Check service health and Redis connectivity.
Response:
{
"status": "healthy",
"redis": "connected",
"timestamp": "2024-12-30T10:30:00Z"
}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 |
EVM Address:
# Automatically queries Ethereum, Polygon, and Base only
curl http://localhost:8080/api/v1/balance/0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238Aptos Address:
# Automatically queries only Aptos
curl http://localhost:8080/api/v1/balance/0x84b1675891d370d5de8f169031f9c3116d7add256ecf50a4bc71e3135ddba6e0Solana Address:
# Automatically queries only Solana
curl http://localhost:8080/api/v1/balance/DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKKRequest:
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
}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
}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 is managed via environment variables. Copy .env.example to .env and fill in your values.
# 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=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913Note: When running with Docker Compose, set
REDIS_HOST=redisin your.envfile. For local development, useREDIS_HOST=localhost.
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# 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# All tests with coverage
make test
# Unit tests only
make test-unit
# Benchmarks
make benchgo test ./... -coverprofile=coverage.out
go tool cover -html=coverage.out| 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 |
| Addresses | Cache Hit Rate | Response Time |
|---|---|---|
| 5 | 100% | ~2-10ms |
| 50 | 90% | ~50-100ms |
| 500 | 90% | ~200-400ms |
| 5000 | 90% | ~1-2s |
- 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
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
// 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"]// 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()// 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
}// Only fetch requested tokens and chains
if shouldIncludeToken(token, requestedTokens) &&
shouldIncludeChain(chain, requestedChains) &&
utils.IsChainCompatible(addressType, chain) {
// Fetch balance
}Contributions are welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
GitHub: @profsaz Project: HawkEye Twitter: ProfSaz