skills/etherscan-api/SKILL.md
This skill should be used when the user asks to "check ETH balance", "query ERC-20 balance", "get wallet balance", "check token holdings", "fetch NFT transfers", "ERC-721 transfer history", "ERC-1155 transfer history", "find first funding transaction", "trace fund origin", "who funded this address", "query Etherscan", or mentions Etherscan API, blockchain balance queries, NFT transfer history, multi-chain balance lookups, or wallet provenance tracing.
npx skillsauth add paulrberg/agent-skills etherscan-apiInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Query blockchain data using Etherscan's unified API V2. This skill covers:
fundedby with a 2-call free-tier fallback)chainid parameterScope: Read-only account queries. For other Etherscan API features, consult the fallback documentation.
Before making any API call, verify the ETHERSCAN_API_KEY environment variable is set:
if [ -z "$ETHERSCAN_API_KEY" ]; then
echo "Error: ETHERSCAN_API_KEY environment variable is not set."
echo "Get a free API key at: https://etherscan.io/myapikey"
exit 1
fi
If the environment variable is missing, inform the user and halt execution.
Run the detection helper once per session and cache the result. It maps getapilimit → plan tier and probes a Base balance call to disambiguate Free from Lite:
./scripts/detect-plan.sh
Output (key=value lines):
plan=lite
credit_limit=100000
credits_used=4
credits_available=99996
limit_interval=daily
interval_expiry=14:38:10
pro_endpoints=false
paid_chains=true
plan is one of free, lite, standard, advanced, professional, pro_plus, enterprise, unknown. Two boolean fields gate behavior:
paid_chains=true — paid-only chains (Base, OP, Avalanche, BNB) are queryable. True for Lite and all higher tiers.pro_endpoints=true — PRO-only actions (addresstokenbalance, balancehistory, tokenholderlist, fundedby, daily-stats endpoints, etc.) are callable. True for Standard and higher; false on Lite.Manual detection (if the script is unavailable):
curl -s "https://api.etherscan.io/v2/api?chainid=1&module=getapilimit&action=getapilimit&apikey=$ETHERSCAN_API_KEY"
# → {"status":"1","message":"OK","result":{"creditsUsed":1,"creditsAvailable":99999,"creditLimit":100000,"limitInterval":"daily","intervalExpiryTimespan":"07:20:05"}}
| creditLimit | Plan | Paid-only chains | PRO endpoints |
| ------------- | ------------ | ---------------- | ------------- |
| 100,000 | Free or Lite | Probe to confirm | No |
| 200,000 | Standard | Yes | Yes |
| 500,000 | Advanced | Yes | Yes |
| 1,000,000 | Professional | Yes | Yes |
| 1,500,000 | Pro Plus | Yes | Yes |
| > 1,500,000 | Enterprise | Yes | Yes |
Free and Lite both report creditLimit: 100000. Lite ($49/mo) raises rate-limit-per-second (5 vs 3) and unlocks every supported chain (Base, OP, Avalanche, BNB), but does not add PRO endpoints — those start at Standard. To disambiguate, attempt a paid-chain balance call (e.g., chainid=8453): status=1 → Lite, status=0 → Free. To probe PRO instead, the failure response is "Sorry, it looks like you are trying to access an API Pro endpoint.".
getapilimit itself consumes 1 credit (plus 1 more for the paid-chain probe), so do not re-run mid-session.
Do not default to Ethereum Mainnet. Always infer the chain from the user's prompt before making any API call.
If the user references an EVM chain that Etherscan API V2 does not cover (e.g., a niche L2 or appchain not in ./references/chains.md), do not halt. Fall back to direct RPC calls against the chain's default public RPC:
evm-chains skill to get the default public RPC, chain ID, native currency symbol, and explorer URL.eth_getBalance, eth_getLogs, eth_getTransactionByHash) against that RPC using curl or the cast CLI from the cli-cast skill.If the user references a non-EVM chain (e.g., Solana, Bitcoin, Cosmos), inform them — no RPC fallback applies:
The chain "[chain name]" is not supported by Etherscan API V2.
Etherscan supports EVM-compatible chains only. For the full list, see:
https://docs.etherscan.io/supported-chains
For the complete list of Etherscan-supported chains and their IDs, see ./references/chains.md.
All requests use the unified V2 endpoint:
https://api.etherscan.io/v2/api
The chainid parameter determines which blockchain to query.
Query native ETH (or native token) balance for an address.
| Parameter | Required | Default | Description |
| --------- | -------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| chainid | No | 1 | Chain ID (see chains.md) |
| module | Yes | - | Set to account |
| action | Yes | - | Set to balance |
| address | Yes | - | Wallet address (supports up to 20 comma-separated) |
| tag | No | latest | latest or hex block number. On free/Lite, only the last 128 blocks are queryable; older history needs the balancehistory PRO endpoint (Standard+) |
| apikey | Yes | - | API key from $ETHERSCAN_API_KEY |
curl -s "https://api.etherscan.io/v2/api?chainid=1&module=account&action=balance&address=0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe&tag=latest&apikey=$ETHERSCAN_API_KEY"
curl -s "https://api.etherscan.io/v2/api?chainid=1&module=account&action=balancemulti&address=0xaddress1,0xaddress2,0xaddress3&tag=latest&apikey=$ETHERSCAN_API_KEY"
Single address:
{
"status": "1",
"message": "OK",
"result": "172774397764084972158218"
}
Multi-address:
{
"status": "1",
"message": "OK",
"result": [
{"account": "0xaddress1", "balance": "1000000000000000000"},
{"account": "0xaddress2", "balance": "2500000000000000000"}
]
}
Query ERC-20 token balance for an address.
| Parameter | Required | Default | Description |
| ----------------- | -------- | -------- | --------------------------------- |
| chainid | No | 1 | Chain ID (see chains.md) |
| module | Yes | - | Set to account |
| action | Yes | - | Set to tokenbalance |
| contractaddress | Yes | - | ERC-20 token contract address |
| address | Yes | - | Wallet address to query |
| tag | No | latest | Block tag |
| apikey | Yes | - | API key from $ETHERSCAN_API_KEY |
curl -s "https://api.etherscan.io/v2/api?chainid=1&module=account&action=tokenbalance&contractaddress=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48&address=0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe&tag=latest&apikey=$ETHERSCAN_API_KEY"
{
"status": "1",
"message": "OK",
"result": "135499000000"
}
tokenbalance returns the balance for one ERC-20 contract at a time. To list every token an address holds:
| Action | Returns |
| ------------------------ | ---------------------------------------------------------- |
| addresstokenbalance | All ERC-20 holdings (token, quantity, decimals, USD price) |
| addresstokennftbalance | All ERC-721 collection holdings and counts |
Use only when pro_endpoints=true from plan detection. Both require Standard plan or higher and are throttled to 2 calls/second regardless of tier.
curl -s "https://api.etherscan.io/v2/api?chainid=1&module=account&action=addresstokenbalance&address=0x...&page=1&offset=100&apikey=$ETHERSCAN_API_KEY"
When pro_endpoints=false, fall back to looping tokenbalance over a known token contract list.
Query an address's transaction history. Five actions are available under module=account:
| Action | Returns |
| ---------------- | ------------------------------------------ |
| txlist | Normal (external) transactions |
| txlistinternal | Internal transactions (contract-initiated) |
| tokentx | ERC-20 token transfer events |
| tokennfttx | ERC-721 (NFT) token transfer events |
| token1155tx | ERC-1155 token transfer events |
| Parameter | Required | Default | Description |
| ----------------- | -------- | ----------- | ------------------------------------------------------------ |
| chainid | No | 1 | Chain ID (see chains.md) |
| module | Yes | - | Set to account |
| action | Yes | - | One of the actions above |
| address | Yes | - | Wallet address |
| contractaddress | No | - | Token contract filter (tokentx/tokennfttx/token1155tx) |
| startblock | No | 0 | Starting block number |
| endblock | No | 999999999 | Ending block number |
| page | No | 1 | Page number for pagination |
| offset | No | 100 | Results per page (see free-tier limit note below) |
| sort | No | asc | asc or desc by block number |
| apikey | Yes | - | API key from $ETHERSCAN_API_KEY |
Pagination cap by plan (effective July 1, 2026):
offsetmaximum is1000for free-tier accounts and10000for paid tiers (Lite included) ontxlist,txlistinternal,tokentx,tokennfttx,token1155tx, and other list endpoints. Whenplan=free, paginate in batches ≤ 1,000.
curl -s "https://api.etherscan.io/v2/api?chainid=1&module=account&action=txlist&address=0x8877bcb2223682048baDD5b09b7eE5a8FA2F3424&startblock=0&endblock=999999999&page=1&offset=100&sort=desc&apikey=$ETHERSCAN_API_KEY"
result is an array of transaction objects. Each contains a Unix timeStamp (seconds, as a string) and chain-specific fields (hash, from, to, value, gasUsed, etc.).
{
"status": "1",
"message": "OK",
"result": [
{
"blockNumber": "18000000",
"timeStamp": "1693526400",
"hash": "0x...",
"from": "0x...",
"to": "0x...",
"value": "1000000000000000000",
"gasUsed": "21000"
}
]
}
timeStamp is a Unix epoch in seconds. Always produce timezone-aware UTC datetimes.
from datetime import datetime, timezone
dt = datetime.fromtimestamp(int(tx["timeStamp"]), tz=timezone.utc)
Do not use datetime.utcfromtimestamp() — it returns a naive datetime and is deprecated in Python 3.12+.
# Shell equivalent (GNU date)
date -u -d "@1693526400" --iso-8601=seconds
# macOS / BSD date
date -u -r 1693526400 +"%Y-%m-%dT%H:%M:%SZ"
Fetch historical ERC-721 or ERC-1155 transfers for an address. Both actions share the parameter table in the previous section; pass contractaddress to filter by collection. Pagination caps (1,000 free / 10,000 paid) and the startblock/endblock/page/offset/sort semantics are identical to txlist.
tokennfttx)curl -s "https://api.etherscan.io/v2/api?chainid=1&module=account&action=tokennfttx&address=0x6975be450864c02b4613023c2152ee0743572325&contractaddress=0x06012c8cf97bead5deae237070f9587f8e7a266d&startblock=0&endblock=999999999&page=1&offset=100&sort=asc&apikey=$ETHERSCAN_API_KEY"
Response entry (one per Transfer event involving the address):
{
"blockNumber": "4708120",
"timeStamp": "1512907118",
"hash": "0x031e6968...",
"nonce": "0",
"blockHash": "0x4be19c27...",
"from": "0xb1690c08e213a35ed9bab7b318de14420fb57d8c",
"contractAddress": "0x06012c8cf97bead5deae237070f9587f8e7a266d",
"to": "0x6975be450864c02b4613023c2152ee0743572325",
"tokenID": "202106",
"tokenName": "CryptoKitties",
"tokenSymbol": "CK",
"tokenDecimal": "0",
"transactionIndex": "81",
"gas": "158820",
"gasPrice": "40000000000",
"gasUsed": "60508",
"cumulativeGasUsed": "4880352",
"input": "deprecated",
"methodId": "0x454a2ab3",
"functionName": "bid(uint256 _tokenId)",
"confirmations": "18759540"
}
NFT-specific fields: contractAddress (collection), tokenID (per-NFT identifier), tokenName, tokenSymbol, tokenDecimal (always "0" for ERC-721).
token1155tx)Same parameter shape — swap action=token1155tx. ERC-1155 differs from ERC-721 in two response fields:
tokenValue (string) — quantity transferred for this tokenID. Required because ERC-1155 is semi-fungible; a single transfer can move N copies of one ID. Not present in ERC-721 responses.tokenDecimal is omitted (ERC-1155 has no decimals concept).{
"blockNumber": "...",
"timeStamp": "...",
"hash": "...",
"from": "...",
"to": "...",
"contractAddress": "0x76be3b62873462d2142405439777e971754e8e77",
"tokenID": "10371",
"tokenValue": "1",
"tokenName": "...",
"tokenSymbol": "...",
"...": "(other tx-level fields identical to tokennfttx)"
}
TransferBatch events (multiple IDs in one tx) appear as multiple result entries sharing the same hash — one per (tokenID, tokenValue) pair. Group by hash to reconstruct the batch.
contractaddress=<collection>. The API filters server-side; omit to fetch transfers across all collections.result[].tokenID == <id> client-side. For high-volume collections, narrow with startblock/endblock first.from/to:
from == 0x0000...0000 → mintto == 0x0000...0000 → burnStandard list-endpoint pricing — 1 credit per call, same rate-limit tier as txlist. Not a PRO endpoint; available on Free and Lite for all supported chains (paid-chain restriction still applies to Base/OP/Avalanche/BNB).
Identify the earliest transaction that sent native value to an address — useful for fund-origin tracing, provenance, or compliance checks. Cost is 1 API call (PRO) or 2 API calls (fallback).
fundedby (PRO endpoint)Returns the address, tx hash, block, timestamp, and value of the transaction that first funded an EOA. Single call, structured response.
| Parameter | Required | Default | Description |
| --------- | -------- | ------- | ----------------------------------- |
| chainid | No | 1 | Chain ID (see chains.md) |
| module | Yes | - | Set to account |
| action | Yes | - | Set to fundedby |
| address | Yes | - | EOA address (contracts unsupported) |
| apikey | Yes | - | API key from $ETHERSCAN_API_KEY |
curl -s "https://api.etherscan.io/v2/api?chainid=1&module=account&action=fundedby&address=0x4838B106FCe9647Bdf1E7877BF73cE8B0BAD5f97&apikey=$ETHERSCAN_API_KEY"
Response:
{
"status": "1",
"message": "OK",
"result": {
"block": 53708500,
"timeStamp": "1708349932",
"fundingAddress": "0x6969174fd72466430a46e18234d0b530c9fd5f49",
"fundingTxn": "0xbc0ca4a67eb1555920552246409626cd60df01314dd2bcdb99718b506d9c9946",
"value": "1000000000000000"
}
}
Requirements & limits:
pro_endpoints=true from plan detection).When pro_endpoints=false (free/Lite) or the address is a contract, scan both transaction lists ascending and pick the earliest qualifying incoming entry. Two API calls per address.
# Earliest normal txs involving the address
curl -s "https://api.etherscan.io/v2/api?chainid=1&module=account&action=txlist&address=0x...&startblock=0&endblock=999999999&page=1&offset=10&sort=asc&apikey=$ETHERSCAN_API_KEY"
# Earliest internal txs involving the address
curl -s "https://api.etherscan.io/v2/api?chainid=1&module=account&action=txlistinternal&address=0x...&startblock=0&endblock=999999999&page=1&offset=10&sort=asc&apikey=$ETHERSCAN_API_KEY"
For each response, pick the first entry where all of the following hold:
to.toLowerCase() == address.toLowerCase() — incoming, not outgoing.value (in wei) is greater than 0 — actual funding, not a zero-value call.isError == "0" (omit this filter for internal txs, which use isError differently or not at all).The funding tx is whichever match has the lower blockNumber; break ties by transactionIndex (normal txs) or by list order (internal txs).
Why both lists: An address may be funded externally (normal tx) or internally (contract sent ETH — common for CEX withdrawals routed through proxy/router contracts, contract deployments with non-zero msg.value, or SELFDESTRUCT refunds). Checking only txlist will miss internally-funded addresses.
Why offset=10, not 1: A txlist query returns every tx involving the address, including outgoing ones. The very first entry is occasionally outgoing (e.g., the address was internally pre-funded), so fetch a small window and scan for the first incoming match.
Edge cases:
offset=100 and page=1, or paginate further. In practice, > 10 outgoing-before-incoming is exceedingly rare.fundedby and this fallback only consider native value. If the address was bootstrapped with ERC-20 transfers alone (rare for EOAs since gas is needed), repeat the fallback against tokentx.Specify the chainid parameter to query different blockchains.
| Chain | Chain ID |
| ------------ | -------- |
| Ethereum | 1 |
| Polygon | 137 |
| Arbitrum One | 42161 |
| Linea | 59144 |
| Blast | 81457 |
| Unichain | 130 |
| Mantle | 5000 |
curl -s "https://api.etherscan.io/v2/api?chainid=137&module=account&action=balance&address=0x...&tag=latest&apikey=$ETHERSCAN_API_KEY"
For the complete list of supported chains, see ./references/chains.md.
API responses return balances in the smallest unit (wei for ETH, smallest decimals for tokens).
Divide by 10^18:
# Using bc for precision
echo "scale=18; 172774397764084972158218 / 1000000000000000000" | bc
# Result: 172774.397764084972158218
Divide by 10^decimals (typically 18, but varies per token):
| Token | Decimals | | ----------- | -------- | | Most tokens | 18 | | USDC, USDT | 6 | | WBTC | 8 |
# USDC example (6 decimals)
echo "scale=6; 135499000000 / 1000000" | bc
# Result: 135499.000000
Default behavior: Present results in a Markdown table:
| Address | Balance (ETH) | Chain |
|---------|---------------|-------|
| 0xde0B...7BAe | 172,774.40 | Ethereum |
| 0xabc1...2def | 50.25 | Polygon |
User preference: If the user requests a specific format (JSON, CSV, plain text, etc.), use that format instead. Do not generate a Markdown table when the user specifies an alternative output format.
Decisions in this section depend on the cached output of ./scripts/detect-plan.sh.
Four chain families (8 chains total, mainnet + testnet) require any paid Etherscan plan. Lite ($49/mo) is sufficient — it grants access to every supported chain at the same 100,000 daily-credit limit as Free. Data endpoints (balance, txlist, logs, etc.) fail only when plan=free (i.e., paid_chains=false):
| Chain | Chain ID |
| ----------------- | ---------- |
| Base Mainnet | 8453 |
| Base Sepolia | 84532 |
| OP Mainnet | 10 |
| OP Sepolia | 11155420 |
| Avalanche C-Chain | 43114 |
| Avalanche Fuji | 43113 |
| BNB Smart Chain | 56 |
| BNB Testnet | 97 |
Exception: module=contract endpoints (getsourcecode, getabi, etc.) work on all chains for every plan including free. The paid-plan requirement applies only to data endpoints.
If paid_chains=false (i.e., plan=free) and the user requests a data query on the chains above, halt and inform them upgrading to Lite or higher is required.
When pro_endpoints=true, the following actions become available (non-exhaustive — see https://docs.etherscan.io/api-pro/api-pro for the full list):
| Module | Action(s) | Use case |
| ------------ | ----------------------------------------------------------------------------- | -------------------------------------------------------- |
| account | addresstokenbalance, addresstokennftbalance, balancehistory, fundedby | Full holdings, historical balances, first-funding lookup |
| token | tokenholderlist, tokeninfo, tokensupplyhistory, tokenbalancehistory | Token analytics |
| block | dailyavgblocksize, dailyblkcount, dailyblockrewards, etc. | Daily block stats |
| stats | dailytxnfee, dailynewaddress, dailynetutilization, etc. | Network-wide daily metrics |
| gastracker | dailyavggaslimit, dailygasused, dailyavggasprice | Daily gas metrics |
When pro_endpoints=false (free or Lite), prefer the non-PRO equivalents listed in this skill or fall back to per-token loops.
All other supported chains — Ethereum, Polygon, Arbitrum One, Linea, Blast, Mantle, Unichain, Gnosis, Celo, Fraxtal, Moonbeam, Moonriver, opBNB, Sonic, Sei, Monad, Berachain, Abstract, ApeChain, World, Katana, HyperEVM, MegaETH, Memecore, Plasma, Stable, Taiko, BitTorrent, XDC, and their testnets — are available on every plan including Free. On Lite and higher, the paid-only chains above also become available.
See ./references/chains.md for the full list with chain IDs.
| Status | Message | Cause |
| ------ | ------------------------ | ------------------------------- |
| 0 | NOTOK | Invalid API key or rate limited |
| 0 | Invalid address format | Malformed address |
| 0 | No transactions found | Address has no activity |
| Plan | Calls/second | Daily calls | | ------------ | ------------ | ----------- | | Free | 3 | 100,000 | | Lite | 5 | 100,000 | | Standard | 10 | 200,000 | | Advanced | 20 | 500,000 | | Professional | 30 | 1,000,000 | | Pro Plus | 30 | 1,500,000 | | Enterprise | custom | unmetered |
PRO endpoints (addresstokenbalance, etc.) are throttled to 2 calls/second regardless of tier. See https://docs.etherscan.io/resources/rate-limits for the authoritative schedule.
If rate limited, wait briefly and retry.
./references/chains.md - Complete list of supported chains with chain IDs./scripts/detect-plan.sh - Plan-tier detection helper (run once per session)For use cases not covered by this skill (transaction history, contract verification, gas estimates, etc.), fetch the AI-friendly documentation:
https://docs.etherscan.io/llms.txt
Use WebFetch to retrieve this documentation for extended API capabilities.
testing
Use ONLY to check or update the project-scoped agent skills installed under .agents/skills so they match the current state of the repo. Do not trigger for creating, finding, or installing skills, or for README/AGENTS.md updates.
testing
Use when CSV, TSV, or Excel (.xlsx) is the primary input/output: inspect, clean, transform, dedupe, merge, validate, convert, recalc formulas, or create/fix spreadsheets. Do not trigger when tabular data is incidental.
development
This skill should be used when the user asks to consult Codex/GPT for planning or code review, or wants a second opinion on code. Trigger phrases include "ask Codex", "consult Codex", "second opinion on code", "consult the oracle". NOT for implementation tasks.
databases
This skill should be used when the user asks to create a monochrome technical diagram, schematic, or systems/architecture diagram in black-and-white line-art style. Trigger phrases include "create a diagram", "monochrome diagram", "systems diagram", "draw a schematic".