---
name: "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 }
---

## Purpose

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.

## Workflow

1. Pick the target: an address, a token contract, or a tx hash. Verify any contract or token address on the explorer before quoting it.

2. Pull the profile. On Base, `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`.

3. Decode Transfer events from a tx's logs. `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..`

4. Measure holder concentration. `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.

5. Look for suspicious patterns. These are signals, not proof:
   - Wash trading: the same token ping-ponging between a small set of addresses at rising prices. Pull each address's `/token-transfers` and count repeat counterparties.
   - Sandwich hints: inside one block, a victim DEX swap bracketed by two swaps from one address (buy just before, sell just after), usually with higher priority fees. `GET /api/v2/blocks/<n>/transactions` returns txs with a `position` field; paginate with `next_page_params` (`block_number`, `index`, `items_count`).
   - Rug signatures: liquidity-removal transfers followed by large sells from the deployer address, a sudden drop in holder count, `is_verified: false` on the token contract, or a scam flag in the token metadata.
   - Phishing airdrops: tokens with lookalike symbols built from unicode homoglyphs and `holders_count` of 0. Observed today: a wallet receiving several fake "USDC" airdrops with combining characters in the symbol.

6. Before reporting, re-check the two or three strongest data points against the live API. Addresses and amounts in the report come from API output, not memory.

## Output Contract

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.

## Operating Rules

- Read-only. Never request, handle, or store private keys, mnemonics, or signing credentials. Never construct, sign, or broadcast a transaction.
- Quote addresses and amounts character for character from API output. Re-verify any contract or token address on the explorer the same day you use it. If you cannot verify it, write "verify on the explorer" instead of quoting it.
- Prefer explorer APIs over RPC `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.
- Treat pattern findings as hypotheses. A sandwich-like ordering or a repeated counterparty is a signal to investigate, not a verdict. Say what would confirm or disprove it.
- Keep request volume modest: fetch a page or two, cache token decimals per run, paginate only as far as the question needs.
- Soften absolutes: "often", "can", "observed on" instead of "always" or "guaranteed".
