Managing crypto across multiple chains is a hassle — different explorers, different UIs, scattered balances. In this tutorial, we will build a command-line multi-chain portfolio tracker that aggregates your ETH, ERC-20 token, and native coin balances across Ethereum, Polygon, and Arbitrum in one unified view.
🧰 What You Will Learn
- Querying native balances and ERC-20 token balances with
web3.py - Working with multiple RPC providers (Infura, Alchemy, public endpoints)
- Aggregating data from multiple chains into a single CLI dashboard
- Handling rate limits and fallback providers gracefully
📦 Prerequisites
pip install web3 rich tabulate python-dotenv
-
web3— Ethereum interaction -
rich— Beautiful CLI tables -
python-dotenv— Manage RPC URLs securely
🔧 Step 1: Configure RPC Endpoints
Create a .env file:
ETH_RPC=https://mainnet.infura.io/v3/YOUR_KEY
POLYGON_RPC=https://polygon-rpc.com
ARBITRUM_RPC=https://arb1.arbitrum.io/rpc
🐍 Step 2: The Core Tracker Script
from web3 import Web3
from rich.console import Console
from rich.table import Table
import json, os
from dotenv import load_dotenv
load_dotenv()
console = Console()
CHAINS = {
"Ethereum": Web3(Web3.HTTPProvider(os.getenv("ETH_RPC"))),
"Polygon": Web3(Web3.HTTPProvider(os.getenv("POLYGON_RPC"))),
"Arbitrum": Web3(Web3.HTTPProvider(os.getenv("ARBITRUM_RPC"))),
}
ERC20_ABI = json.loads('[{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"type":"function"}]')
TOKENS = {
"USDC": {
"ethereum": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"polygon": "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
"arbitrum": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
},
"USDT": {
"ethereum": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
"polygon": "0xc2132D05D31c914a87C6611C10748AEb04B58e8F",
}
}
def get_native_balance(w3, address):
balance_wei = w3.eth.get_balance(address)
return w3.from_wei(balance_wei, "ether")
def get_token_balance(w3, token_address, address):
contract = w3.eth.contract(address=token_address, abi=ERC20_ABI)
symbol = contract.functions.symbol().call()
decimals = contract.functions.decimals().call()
balance = contract.functions.balanceOf(address).call()
return symbol, balance / (10 ** decimals)
def track_portfolio(addresses):
table = Table(title="Multi-Chain Portfolio")
table.add_column("Chain", style="cyan")
table.add_column("Address", style="magenta")
table.add_column("Asset", style="green")
table.add_column("Balance", justify="right")
for chain_name, w3 in CHAINS.items():
for addr in addresses:
try:
native = get_native_balance(w3, addr)
table.add_row(chain_name, addr[:10] + "...", "Native", f"{native:.4f}")
for token_name, chain_tokens in TOKENS.items():
if chain_name.lower() in chain_tokens:
symbol, bal = get_token_balance(w3, chain_tokens[chain_name.lower()], addr)
table.add_row("", "", symbol, f"{bal:.2f}")
except Exception as e:
table.add_row(chain_name, addr[:10] + "...", "ERROR", str(e)[:30])
console.print(table)
if __name__ == "__main__":
WALLETS = [
"0x18da907cb9d981bc798acb87ac27b03a2dc3cbb7",
]
track_portfolio(WALLETS)
🚀 Step 3: Run It
python portfolio_tracker.py
You will see a formatted table showing native coin and token balances across all configured chains.
🔐 Security Notes
- Never commit your
.envfile - Use read-only RPC keys
- For production, consider using The Graph or Dune Analytics for heavy queries
🧩 Extending the Tracker
- Add NFT detection using OpenSea API
- Integrate price feeds from CoinGecko for USD values
- Export to CSV for spreadsheet analysis
- Add Telegram bot alerts for balance changes
📂 Source Code
Full project on GitHub: github.com/Byaigo
Like this article? Drop a ❤️ and follow for more Python + Web3 content!
ETH Tip Jar: 0x18da907cb9d981bc798acb87ac27b03a2dc3cbb7

Top comments (0)