On-Chain Analytics
Track wallets, monitor whale transactions, and watch gas prices in real time using Etherscan's free API.
Getting Your Free Etherscan Key
Etherscan is the leading block explorer for Ethereum. Its free API tier gives you powerful access to wallet balances, transaction history, gas prices, and ERC-20 token data without breaking the bank or hitting strict rate limits.
Step-by-Step Setup
Register at etherscan.io — Visit https://etherscan.io/ and create a free account.
Navigate to My Account › API Keys — Click your profile icon → "My Account" → "API Keys" tab.
Create a New API Key — Click "+ Add" and name your key (e.g., "Personal Monitor").
Store Your Key Safely — Copy the API key and save it in your environment: export ETHERSCAN_KEY="your_api_key_here"
Free Tier Limits
- Rate: 5 requests per second
- Daily Limit: 100,000 calls per day
- Cost: $0 (free forever)
Basic Setup Code
import os
import requests
ETHERSCAN_KEY = os.getenv("ETHERSCAN_KEY", "YourApiKeyToken")
BASE = "https://api.etherscan.io/api"
def eth_get(module, action, **params):
"""Base Etherscan API call."""
resp = requests.get(BASE, params={
"module": module,
"action": action,
"apikey": ETHERSCAN_KEY,
**params
}, timeout=10)
resp.raise_for_status()
data = resp.json()
if data["status"] != "1":
raise ValueError(f"Etherscan error: {data.get('message', 'Unknown')}")
return data["result"]
# Test: ETH balance of Ethereum Foundation wallet
ETHFOUND = "0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe"
wei = int(eth_get("account", "balance", address=ETHFOUND, tag="latest"))
eth = wei / 1e18
print(f"ETH Foundation balance: {eth:,.4f} ETH")
Wallet Balance Tracker
Monitor a list of wallets in real time. Track the Ethereum Foundation, major exchanges, whale wallets, or your own holdings. Batch requests let you check up to 20 wallets in a single API call.
Why Monitor Balances?
- Whale Activity: Watch large holders to anticipate market moves
- Exchange Health: Track if exchanges are accumulating or dumping ETH
- Portfolio Health: Monitor your own wallets and important addresses
- Risk Assessment: Spot unusual balance changes that might indicate hacks or transfers
Code Example: Watch Multiple Wallets
from datetime import datetime
WATCHED_WALLETS = {
"ETH Foundation": "0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe",
"Vitalik.eth": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
"Binance Hot": "0x28C6c06298d514Db089934071355E5743bf21d60",
}
def watch_balances(wallets: dict, alert_threshold_eth: float = 1000.0):
"""Check balances for a set of labeled wallets."""
# Batch request: up to 20 addresses at once
addrs = ",".join(wallets.values())
results = eth_get("account", "balancemulti", address=addrs, tag="latest")
print(f"\n=== Wallet Balances | {datetime.now().strftime('%Y-%m-%d %H:%M')} ===")
label_map = {v.lower(): k for k, v in wallets.items()}
for item in results:
addr = item["account"]
eth = int(item["balance"]) / 1e18
label = label_map.get(addr.lower(), addr[:10] + "…")
flag = " 🚨 LARGE BALANCE" if eth > alert_threshold_eth else ""
print(f" {label:<20} {eth:>12,.4f} ETH{flag}")
watch_balances(WATCHED_WALLETS)
Output Example
=== Wallet Balances | 2026-03-28 14:32 ===
ETH Foundation 2,145,223.1234 ETH 🚨 LARGE BALANCE
Vitalik.eth 47,392.5678 ETH 🚨 LARGE BALANCE
Binance Hot 563,128.4321 ETH 🚨 LARGE BALANCE
Whale Transaction Monitor
Large ETH transfers ("whale" transactions) often signal important market movements. Monitor public whale wallets and alert when you spot significant outflows or inflows.
What to Monitor
- Binance Hot Wallet: Major exchange movements indicate potential selling/buying pressure
- Known Whales: Watch private addresses linked to major holders
- Staking Pools: Monitor Lido, Rocket Pool, and other liquid staking contracts
- Treasury Wallets: Projects moving holdings signal intent
Whale Monitor Code
import time
def get_recent_txns(address: str, block_count: int = 100) -> list:
"""Fetch recent normal transactions for a wallet."""
txns = eth_get("account", "txlist",
address=address,
startblock=0,
endblock=99999999,
page=1,
offset=block_count,
sort="desc"
)
return txns
def whale_monitor(min_eth: float = 500.0, poll_seconds: int = 300):
"""
Monitor public large wallets for big outflows.
Flags any single tx above min_eth.
"""
WHALE_WALLETS = [
"0x28C6c06298d514Db089934071355E5743bf21d60", # Binance Hot
"0x21a31Ee1afC51d94C2efcCaa2092aD1028285549", # Binance Cold
]
seen_hashes = set()
print(f"🐋 Whale monitor started — alerting on txns > {min_eth} ETH")
while True:
for addr in WHALE_WALLETS:
txns = get_recent_txns(addr, block_count=20)
for tx in txns:
if tx["hash"] in seen_hashes:
continue
seen_hashes.add(tx["hash"])
eth_val = int(tx["value"]) / 1e18
if eth_val >= min_eth:
direction = "OUT" if tx["from"].lower() == addr.lower() else "IN"
print(f"🚨 WHALE {direction}: {eth_val:,.2f} ETH")
print(f" From: {tx['from'][:20]}…")
print(f" To: {tx['to'][:20]}…")
print(f" Hash: {tx['hash'][:20]}…\n")
time.sleep(poll_seconds)
whale_monitor(min_eth=500)
Alert Thresholds
Adjust min_eth based on what matters to you:
- 100 ETH: Tracks moderate whale activity, higher noise
- 500 ETH: Catches significant moves, good balance
- 1,000+ ETH: Only major moves, lower noise
Gas Price Monitor
Ethereum transaction fees fluctuate based on network demand. Monitor gas prices in real time and alert yourself when conditions are favorable for transacting.
Understanding Gas Prices
Etherscan provides three suggested gas levels (all in Gwei):
- Safe: Slower confirmation, lower cost — good for non-urgent txs
- Standard: Balanced speed and cost — recommended for most txs
- Fast: Quick confirmation, higher cost — for urgent needs
- Base Fee: Minimum required by the network, part of EIP-1559
Gas Price Fetcher
def get_gas_prices() -> dict:
"""
Returns safe/proposed/fast gas prices in Gwei.
"""
data = eth_get("gastracker", "gasoracle")
return {
"safe": int(data["SafeGasPrice"]),
"standard": int(data["ProposeGasPrice"]),
"fast": int(data["FastGasPrice"]),
"base_fee": float(data.get("suggestBaseFee", 0)),
}
def gas_alert_loop(target_gwei: int = 15, check_interval: int = 60):
"""Alert when gas drops to target level."""
import smtplib
from email.mime.text import MIMEText
print(f"⛽ Gas monitor: alerting when safe gas ≤ {target_gwei} Gwei")
alerted = False
while True:
gas = get_gas_prices()
print(f" Gas: safe={gas['safe']} | std={gas['standard']} | fast={gas['fast']} Gwei | "
f"base={gas['base_fee']:.2f}")
if gas["safe"] <= target_gwei and not alerted:
print(f"🟢 LOW GAS ALERT: {gas['safe']} Gwei — good time to transact!")
alerted = True
elif gas["safe"] > target_gwei + 5:
alerted = False # Reset after gas rises
time.sleep(check_interval)
gas_alert_loop(target_gwei=20)
Practical Use Cases
- Batch Transfers: Wait for gas < 15 Gwei to move multiple tokens
- DeFi Interactions: Monitor before approving new contracts or swapping
- Staking: Deposit when gas is low to minimize costs on large amounts
- Scheduled Automation: Run transactions only when gas meets your criteria
ERC-20 Token Holdings
See what tokens a wallet holds by analyzing transfer history. While Etherscan doesn't provide real-time balance snapshots for ERC-20 tokens in the free API, you can infer holdings from the transaction history.
Limitations & Alternatives
- Free Etherscan API: Transfer history only — infers holdings via in/out flows
- Better For Real-Time: Use Moralis, Alchemy, or QuickNode for live token balances
- Good For: Historical analysis, trend tracking, and transaction audits
Token Transfer History Code
def get_token_transfers(address: str, contract_address: str = None) -> list:
"""Fetch ERC-20 token transfer events for a wallet."""
params = dict(address=address, page=1, offset=50, sort="desc")
if contract_address:
params["contractaddress"] = contract_address
return eth_get("account", "tokentx", **params)
def get_token_balances_via_transfers(address: str) -> dict:
"""
Infer current token holdings from transfer history.
Note: Use Moralis/Alchemy for real-time balances.
This approach works with free Etherscan API.
"""
txns = get_token_transfers(address)
holdings = {}
for tx in txns:
symbol = tx["tokenSymbol"]
decimals = int(tx["tokenDecimal"])
value = int(tx["value"]) / (10 ** decimals)
if symbol not in holdings:
holdings[symbol] = {"in": 0.0, "out": 0.0, "contract": tx["contractAddress"]}
if tx["to"].lower() == address.lower():
holdings[symbol]["in"] += value
else:
holdings[symbol]["out"] += value
print(f"\nToken flow for {address[:10]}…:")
for sym, data in sorted(holdings.items()):
net = data["in"] - data["out"]
print(f" {sym:<12} net: {net:>15,.4f}")
return holdings
vitalik = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
get_token_balances_via_transfers(vitalik)
Sample Output
Token flow for 0xd8dA6BF…:
USDC net: 5,234.5000
DAI net: 12,100.0000
USDT net: 500.2500
UNI net: 1,050.0000
SHIB net: 100,000,000.0000
Etherscan API Reference
Here's a quick lookup table for the most useful free Etherscan endpoints. Full docs available at docs.etherscan.io.
| Module | Action | Description | Example Params |
|---|---|---|---|
| account | balance | Single wallet ETH balance (Wei) | address, tag="latest" |
| account | balancemulti | Batch wallet balances (up to 20) | address (comma-separated), tag="latest" |
| account | txlist | Normal transaction history | address, startblock, endblock, sort |
| account | tokentx | ERC-20 transfer history | address, contractaddress (optional), page, offset |
| gastracker | gasoracle | Safe/standard/fast gas prices (Gwei) | None |
| stats | ethprice | Current ETH/USD price | None |
| block | getblocknobytime | Block number at timestamp | timestamp, closest |
API Response Format
All Etherscan API responses follow this standard JSON structure:
{
"status": "1", // "1" = success, "0" = error
"message": "OK", // "OK" on success, error details on failure
"result": [...] // Data array or object
}
Best Practices & Tips
Rate Limiting & Efficiency
- Batch Requests: Use
balancemultito check up to 20 wallets in one call - Cache Results: Don't fetch the same data twice in a loop; cache for 60-300 seconds
- Respect 5 req/sec: Add delays between calls if monitoring multiple addresses in sequence
- Monitor Your Usage: Check your Etherscan dashboard to avoid hitting 100k daily limit unexpectedly
Data Accuracy
- Use tag="latest": Always specify the block tag; default behavior varies
- Handle Reorgs: Etherscan data is finalized; older blocks are safe
- Decimal Precision: Always divide token values by
10 ** decimals - Timeout Protection: Set reasonable timeouts (10 seconds) and retry failed requests
Security & Privacy
- Keep API Key Secret: Store in environment variables, never commit to git
- Use HTTPS Only: Etherscan API requires HTTPS — never send keys over HTTP
- Monitor Access: Review your API activity in Etherscan dashboard monthly
- Rotate Keys: Generate new keys periodically and revoke old ones
Error Handling
def eth_get_safe(module, action, max_retries=3, **params):
"""Robust Etherscan call with retries and error handling."""
for attempt in range(max_retries):
try:
return eth_get(module, action, **params)
except requests.exceptions.Timeout:
if attempt < max_retries - 1:
time.sleep(2 ** attempt) # Exponential backoff
continue
raise ValueError("API timeout after retries")
except ValueError as e:
if "Max rate limit reached" in str(e):
time.sleep(1) # Rate limit; wait before retry
continue
raise # Other errors, re-raise immediately
raise RuntimeError("Unexpected retry failure")
What's Next?
Now that you can monitor on-chain activity, the next step is diving deeper into DeFi protocol data. Learn how to fetch liquidity pools, token prices, yield farming APYs, and more from chains like Aave, Uniswap, and Compound.
Recommended Projects
- Portfolio Tracker: Build a real-time ETH/ERC-20 holdings monitor with alerts
- Whale Dashboard: Create a web app showing top whales and recent large txns
- Gas Alert Bot: Set up a Telegram/Discord bot that DMs you when gas is cheap
- Transaction Watcher: Monitor specific addresses and log all txns to a database