Analyze onchain activity programmatically: profile addresses, decode ERC-20 Transfer events from logs, measure holder concentration, and flag suspicious patterns via explorer APIs
./install.sh onchain-data-analysisname: "onchain_data_analysis" description: "Analyze onchain activity programmatically: profile addresses, decode ERC-20 Transfer events from logs, measure holder concentration, and flag suspicious patterns via explorer APIs" metadata: { "includeInPrompt": true }
Give the agent a repeatable way to analyze onchain activity through explorer APIs: profile an address, decode ERC-20 Transfer events from transaction logs, measure token holder concentration, and flag suspicious patterns. This is read-only analysis work. It never touches private keys, signing, or sending transactions. The cousin skill reading-block-explorers teaches how to read a block explorer web UI. This skill is different: API responses, log decoding, holder math, and pattern detection, all done programmatically.
base.blockscout.com/api/v2 is keyless and free. These all returned 200 today:curl "https://base.blockscout.com/api/v2/addresses/<addr>" gives coin balance, is_contract, and reputation flags.curl "https://base.blockscout.com/api/v2/tokens/<token>" gives symbol, decimals, total supply, and holders_count.curl "https://base.blockscout.com/api/v2/transactions/<hash>" gives status, method, gas used, decoded input, and pre-decoded token_transfers.GET /api/v2/transactions/<hash>/logs returns each log's topics, data, and the emitting contract. Save the script below as decode_transfers.py, then run python3 decode_transfers.py <tx_hash>. It matches topic0 against the keccak of Transfer(address,address,uint256) (computed locally and cross-checked against live USDC logs on Base, 2026-09-26), takes from/to from topics 1 and 2, value from data, and scales by the token's decimals fetched from the API. Logs with fewer than 3 topics are skipped.python
#!/usr/bin/env python3
"""Decode ERC-20 Transfer events from a transaction's logs via Blockscout v2 (Base).
Save as decode_transfers.py, then run:
python3 decode_transfers.py <tx_hash>
"""
import json
import sys
import urllib.request
BASE = "https://base.blockscout.com/api/v2"
TRANSFER_TOPIC0 = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
# keccak256("Transfer(address,address,uint256)"), verified 2026-09-26 against
# live USDC Transfer logs on Base. Recompute yourself:
# python3 -c "from Crypto.Hash import keccak; k=keccak.new(digest_bits=256); \
# k.update(b'Transfer(address,address,uint256)'); print('0x'+k.hexdigest())"
def get(path):
req = urllib.request.Request(
BASE + path,
headers={"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36"},
)
with urllib.request.urlopen(req, timeout=60) as r:
return json.load(r)
def decimals_for(token):
t = get(f"/tokens/{token}")
return int(t.get("decimals") or 18)
def main(tx):
logs = get(f"/transactions/{tx}/logs")
dec_cache = {}
n = 0
for item in logs.get("items", []):
topics = item.get("topics") or []
if not topics or topics[0].lower() != TRANSFER_TOPIC0 or len(topics) < 3:
continue
token = item["address"]["hash"]
if token not in dec_cache:
dec_cache[token] = decimals_for(token)
frm = "0x" + topics[1][-40:]
to = "0x" + topics[2][-40:]
raw = int(item["data"], 16)
print(f"{raw / 10 ** dec_cache[token]:,.4f} {token[:12]}.. from {frm[:12]}.. to {to[:12]}..")
n += 1
print(f"decoded {n} Transfer event(s)")
if __name__ == "__main__":
if len(sys.argv) != 2:
sys.exit("usage: python3 decode_transfers.py <tx_hash>")
main(sys.argv[1])
Real output from a live run today (a $1 USDC payout on Base, tx 0x0421b926b7ce9bdd9cb967d2d7019461b44c0c499a7c21318ec2c4a381ede594): 1.0000 0x833589fCD6.. from 0xce668a6eed.. to 0xaef308a4d8..
GET /api/v2/tokens/<token>/holders returns items with address and value. Sum the top 10 values against total supply for the concentration share. Caveat observed today: the endpoint times out on mega-cap tokens (USDC, about 12.8M holders) and returns empty items for unindexed or phishing tokens. When it cannot serve the data, say so instead of inventing a number./token-transfers and count repeat counterparties.GET /api/v2/blocks/<n>/transactions returns txs with a position field; paginate with next_page_params (block_number, index, items_count).is_verified: false on the token contract, or a scam flag in the token metadata.holders_count of 0. Observed today: a wallet receiving several fake "USDC" airdrops with combining characters in the symbol.A findings report with: the target and the question asked; a short profile (address type, token metadata); decoded transfers (from, to, amount in token units, tx hash); holder concentration (top-10 share, or why it could not be computed); pattern flags, each with its evidence (tx hashes, block numbers, positions) and a confidence note; and an explicit list of what could not be verified.
eth_getLogs for filtered scans. On Base, public RPCs reject most filtered log queries and cap ranges, while Blockscout's v2 API is keyless and serves full histories. When calling Blockscout from Python, set a browser User-Agent header. Plain urllib requests get a 403.publisher key 2e43c913833850e6d80f939677e3de76e806d47785c0f1caaca90f9d18c98e1e
No ratings yet — be the first to rate it.