CLI and Node.js library for scanning a single EVM address across 12 chains in parallel. Native balance, common stablecoin holdings, and current block height — all in one report, in under 2 seconds.
npx wallet-tracker-multichain 0x1234567890123456789012345678901234567890Or clone + run:
git clone https://github.com/swiftnodes/wallet-tracker-multichain
cd wallet-tracker-multichain
cp .env.example .env # add your SwiftNodes API key
npm install
node src/cli.js 0xVitalik...Sample output:
Wallet: 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
Generated: 2026-05-13T15:42:08.221Z
Ethereum 7.234861 ETH block 22512303 150.00 USDC, 5000.00 USDT
Base 0.024100 ETH block 45895872 82.50 USDC
Arbitrum One 1.502000 ETH block 339214772 0.00 USDC
Optimism 0.001000 ETH block 144812301 -
Polygon 0.000000 POL block 85871730 -
BNB Smart Chain 0.000000 BNB block 97836271 10.00 USDT
Avalanche 0.000000 AVAX block 49213810 -
Scroll 0.000000 ETH block 4948120 -
Linea 0.000000 ETH block 7912331 -
zkSync Era 0.000000 ETH block 75112304 -
Blast 0.000000 ETH block 12891244 -
Mantle 0.000000 MNT block 80123104 -
Held assets on 4 chain(s).
- ✅ 12 chains scanned in parallel — Ethereum, Base, Arbitrum, Optimism, Polygon, BSC, Avalanche, Scroll, Linea, zkSync, Blast, Mantle
- ✅ Native + stablecoin balances for each chain (configurable)
- ✅ Sub-second per chain — true parallelism via
Promise.all - ✅ JSON output mode for piping into other tools
- ✅
--hide-emptyflag to focus on chains with actual holdings - ✅ Library importable — use
scanWallet(addr)directly in your code - ✅ No API keys for chains besides SwiftNodes — one key, all 12 chains
- ✅ Adapts cleanly to ENS (resolve before passing in)
# Human-readable (default)
wallet-tracker 0x...
# Hide chains with no holdings
wallet-tracker 0x... --hide-empty
# JSON output (pipe to jq, save to file, etc.)
wallet-tracker 0x... --json > scan.jsonimport { scanWallet } from "wallet-tracker-multichain";
const result = await scanWallet("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045");
console.log(`Scanned ${result.perChain.length} chains`);
for (const c of result.perChain) {
if (c.error) continue;
console.log(`${c.chain}: ${c.nativeBalance} ${c.nativeSymbol}`);
}The returned shape:
{
address: string,
generatedAt: string, // ISO timestamp
perChain: Array<{
chain: string, // e.g. "Base"
slug: string, // SwiftNodes slug e.g. "base"
nativeSymbol: string, // e.g. "ETH"
blockNumber: bigint | null,
nativeBalance: string | null, // formatted to native decimals
tokens: Array<{ address, symbol, decimals, balance }>,
error: string | null
}>
}Visit swiftnodes.io, connect a wallet (no email, no KYC), grab your key.
cp .env.example .env
# edit .env, set SWIFTNODES_API_KEY=sn_...npm install
node src/cli.js 0xYourAddressEdit src/chains.js:
import { sonic, mantle, polygonZkEvm } from "viem/chains";
export const CHAINS = [
// ... existing entries ...
{ viem: sonic, slug: "sonic", stables: { USDC: "0x..." } },
{ viem: mantle, slug: "mantle", stables: { USDT: "0x..." } },
];The slug must match SwiftNodes' chain slug — see swiftnodes.io/docs/supported-chains for the full list of 75+ chains.
Each chain's stables map can hold any ERC-20 contract, not just stablecoins:
{
viem: base,
slug: "base",
stables: {
USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
USDbC: "0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA",
DAI: "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb",
cbETH: "0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22",
},
}The script just calls balanceOf(yourAddress) on each, filters out zero balances, formats by decimals.
Each chain runs independently — there's no sequential dependency. The runtime fires off 12 simultaneous JSON-RPC connections (one per chain), each making 2-5 batched eth_call and eth_getBalance requests. Total wall time = the slowest chain's response, not the sum.
With SwiftNodes (which load-balances upstream nodes per chain), typical wall time for 12 chains is 800-1500ms. Most of that is network latency to the slowest chain's RPC.
This script makes 12 × 5 = ~60 RPC requests per scan. Most providers either:
- Don't support 12 of these chains in one account (Alchemy/Infura are stronger on 5-7 chains)
- Charge per-request (this scan would cost real money on usage-billed providers)
- Don't support some of the newer chains (Blast, Scroll, zkSync Era)
SwiftNodes' flat-rate pricing means scanning 60 requests costs the same as 6 — you scan as much as you need within your tier. And the same key works across all chains.
- Personal portfolio check — quick CLI sanity check of your own holdings
- Wallet investigations — see where a suspicious address has been moving
- Onchain dashboard backend — wire
scanWallet()into a server - Token-claim automation — find chains where you have eligible balance
- Tax prep / accounting — feed
--jsonoutput into your tax tool - Multi-chain DeFi position monitoring — periodic snapshots
- Only checks pre-configured tokens per chain. For full ERC-20 discovery, you'd need an indexer or to use a "get token balances" API (we keep it lean here on purpose — read-only RPC only, no third-party dependencies)
- No NFT balances — same reason
- No DeFi position parsing — Aave deposits, Uniswap V3 positions, etc. would need protocol-specific logic
- No price/USD conversion — would require a price feed (Coingecko, Chainlink, etc.)
PRs welcome for any of the above.
MIT