ERC-20 Token Due Diligence — Holders, Contract Risks, Liquidity
Pricing
from $3.50 / 1,000 result items
ERC-20 Token Due Diligence — Holders, Contract Risks, Liquidity
Token intelligence on Base, Ethereum, Arbitrum, Optimism, Polygon and 9 more Blockscout chains: supply, price, market cap, top holders with concentration metrics, verified-contract facts and owner powers (mint/pause/blacklist/fees), whale transfers, DexScreener liquidity and pair age, risk flags.
Pricing
from $3.50 / 1,000 result items
Rating
0.0
(0)
Developer
Samat Makatov
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 days ago
Last modified
Categories
Share
Give it token contract addresses and get one due-diligence JSON per token: supply, price, market cap, top holders with concentration metrics (largest holder, top-10/20/50 share, burned share, contracts vs wallets), verified-contract facts and owner powers read from the ABI (mint, pause, blacklist, fee/max-tx setters, upgradeability, meme-token controls), whale transfers, DexScreener liquidity, volume and pair age, and a deterministic list of risk flags. Works on Base, Ethereum, Arbitrum, OP Mainnet, Polygon, Gnosis, ZKsync, Scroll, Celo, Unichain, Soneium, Mode, Ink, World Chain and two testnets (DexScreener market data on all of them except Gnosis, Mode and the testnets). Also finds contract addresses by token name. No API key, no proxy, no browser.
Built for agents and analysts who must decide "is this token safe to list / accept / hold?" in one call.
Use cases
- Listing & treasury checks — before accepting a token as payment or listing it: verified? owner can mint or blacklist? top-10 own 90 %? liquidity younger than a week?
- Rug-pull triage —
flagssuch assingle_holder_over_50pct,thin_liquidity_under_10k,owner_can_change_fees,meme_token_style_controls. - Whale watching —
minTransferUsd: 100000returns only large transfers with labelled sender/receiver (exchanges, pools). - Holder analytics — top 500 holders with labels and USD value for airdrop planning, competitor research, cap-table-like snapshots.
- Market monitoring — daily run over your watchlist: price, 24h volume, liquidity, buys/sells per DEX pool.
- Contract discovery —
mode: "search"turns "aerodrome" into the right contract address (and shows look-alike spam tokens withlikelySpam: true).
Input
| Field | Type | Default | Notes |
|---|---|---|---|
mode | tokens / search | tokens | tokens = full analysis per contract; search = find tokens by name/symbol. |
tokens | string[] | — | Contract addresses (mode=tokens). Letter case does not matter; an address listed twice is analyzed (and charged) once. Values that are not 0x addresses are skipped and listed in SUMMARY.errors (not charged); a run with no 0x address at all is refused (use mode: "search" to find a token by name). |
queries | string[] | [] | Names/symbols (mode=search). |
searchLimit | int 1–100 | 10 | Results per query. Every search hit is one dataset item, charged like an analyzed token. Queries that differ only in letter case are searched once. |
searchTokenType | all / ERC-20 / ERC-721 / ERC-1155 | all | Search filter. |
chain | enum | base | One key from Reference → Chains (ethereum, not eth): the platform refuses a value outside that list before the run starts. |
holdersLimit | int 0–500 | 50 | Top holders (pages of 50). 0 = skip holders and concentration. |
transfersLimit | int 0–200 | 20 | Recent transfers kept. |
minTransferAmount | number | 0 | Keep only transfers ≥ this many tokens. Filtered server-side by the explorer, so matches can be days old on busy tokens. |
minTransferUsd | number | 0 | Keep only transfers ≥ this USD value (explorer price, converted to a token amount for the server-side filter). Ignored with a warning if the token is unpriced. |
transferScanPages | int 1–100 | 10 | Most transfer pages (50 each) read per token when a size filter or time window is set. |
transferScanHours | number | 0 | Only transfers from the last N hours (e.g. 24). 0 = no time limit. |
includeContract | bool | true | Verification, compiler, license, proxy, creator, tags, ABI capability scan. |
includeDexPairs | bool | true | DexScreener pools + market summary. |
dexPairsLimit | int 1–50 | 5 | Pools kept in dexPairs (by liquidity). |
fields | string[] | [] | Keep only these top-level fields, in this order. address, chain, symbol, found, fetchedAt (and query in mode=search) are always kept and come first unless you list them. Letter case does not matter; an unknown name is left out with a note in the status message ("did you mean …?"), and a run where none of the names exists in that mode's rows is refused before anything is charged. |
requestDelayMs | int 0–5000 | 250 | Minimum gap between two requests to the same host (explorer, DexScreener); values below 250 are raised to 250 (both publish 300 requests/min per IP). At most 2 requests per host wait for an answer at once. |
Original inputs (tokens, chain, holdersLimit, transfersLimit) keep their meaning.
Reference
Chains
chain | Network | Chain ID | DexScreener market data |
|---|---|---|---|
ethereum | Ethereum mainnet | 1 | yes |
base | Base | 8453 | yes |
arbitrum | Arbitrum One | 42161 | yes |
optimism | OP Mainnet | 10 | yes |
polygon | Polygon PoS | 137 | yes |
gnosis | Gnosis Chain | 100 | no (not indexed by DexScreener) |
zksync | ZKsync Era | 324 | yes |
scroll | Scroll | 534352 | yes |
celo | Celo | 42220 | yes |
unichain | Unichain | 130 | yes |
soneium | Soneium | 1868 | yes |
mode | Mode | 34443 | no |
ink | Ink | 57073 | yes |
worldchain | World Chain | 480 | yes |
sepolia, base-sepolia | testnets | 11155111 / 84532 | no |
Contract capabilities (contract.capabilities)
Derived from function names in the verified ABI (null when the contract is not verified).
| Field | True when the ABI has… |
|---|---|
canMint | mint, mintTo, issue, mint* (state-changing) |
canBurnOthers | burnFrom, destroyBlackFunds, forceBurn… |
canPause | pause, unpause, setPaused |
canBlacklist | blacklist*, freeze*, blockAccount, setBots… |
hasOwner | owner, transferOwnership, renounceOwnership |
canSetFees | set*Fee, set*Tax |
hasMaxTxLimits | set*MaxTx, set*MaxWallet… |
hasPermit | EIP-2612 permit |
isUpgradeable | upgradeTo, upgradeToAndCall, implementation |
suspiciousFunctions | openTrading, excludeFromFee, manualSwap, rescueTokens… (meme-token launch tooling) |
Flags (flags[])
| Flag | Meaning |
|---|---|
flagged_scam_by_explorer | Blockscout reputation ≠ ok / is_scam. |
unverified_contract | No verified source. |
upgradeable_or_proxy | Proxy or upgrade functions — logic can change. |
owner_can_mint / owner_can_pause / owner_can_blacklist / owner_can_change_fees / has_max_tx_or_wallet_limits | From the ABI scan. |
meme_token_style_controls | ≥ 3 suspicious launch-tooling functions. |
single_holder_over_50pct / single_holder_over_20pct | Largest non-burn holder share (often a staking/escrow contract — check largestHolderLabel). |
top10_hold_over_80pct | Concentration. |
under_100_holders | Tiny holder base. |
no_dex_liquidity_found / thin_liquidity_under_10k / liquidity_under_1pct_of_mcap | Market depth (skipped for tokens that are mostly quote assets like USDC/WETH). |
pair_younger_than_7d | Newest liquidity. |
no_website_or_socials | DexScreener profile has no links. |
verified_less_than_7d_ago | Fresh verification. |
Flags are a triage checklist, not a verdict.
Examples
1. Vet a token before listing it (Base)
{ "tokens": ["0x940181a94A35A4569E4529A3CDfB74e38FD98631"], "chain": "base", "holdersLimit": 100, "transfersLimit": 20 }
2. Whale transfers ≥ $50k for PEPE (Ethereum)
{ "tokens": ["0x6982508145454Ce325dDbE47a25d4ec3d2311933"], "chain": "ethereum", "holdersLimit": 0, "transfersLimit": 50, "minTransferUsd": 50000, "transferScanHours": 72, "includeContract": false, "includeDexPairs": false }
3. Holder snapshot: top 500 holders with labels
{ "tokens": ["0x…"], "chain": "arbitrum", "holdersLimit": 500, "transfersLimit": 0, "includeContract": false, "includeDexPairs": false, "fields": ["holdersCount", "holderConcentration", "topHolders"] }
4. Daily market monitor over a watchlist, compact output
{ "tokens": ["0x…", "0x…", "0x…"], "chain": "base", "holdersLimit": 10, "transfersLimit": 0, "includeContract": false, "dexPairsLimit": 3, "fields": ["priceUsd", "marketCapUsd", "volume24hUsd", "market", "dexPairs", "flags"] }
5. Find the real contract behind a name
{ "mode": "search", "queries": ["aerodrome", "virtual"], "chain": "base", "searchLimit": 5, "searchTokenType": "ERC-20" }
Output
One item per token (trimmed real example, AERO on Base):
{"address": "0x940181a94A35A4569E4529A3CDfB74e38FD98631", "chain": "base", "chainId": 8453,"explorerUrl": "https://base.blockscout.com/token/0x940181a94A35A4569E4529A3CDfB74e38FD98631","found": true, "name": "Aerodrome Finance", "symbol": "AERO", "type": "ERC-20", "decimals": 18, "reputation": "ok","totalSupply": 1978450301.48, "holdersCount": 833725, "transfersCount": 23933,"priceUsd": 0.566, "marketCapUsd": 559998827.4, "volume24hUsd": 18574709.8, "fdvUsd": 1120244065.1,"contract": {"isVerified": true, "verifiedAt": "2023-12-27T19:32:58Z", "contractName": "Aero", "compiler": "0.8.19+commit.7dd6d404", "license": "none","proxyType": null, "implementations": [], "creator": "0xe83f922C34A1962e9aE9F52B59e18239764f2818", "creationTxHash": "0x5727…", "isScam": false, "tags": [],"capabilities": { "functions": 18, "canMint": true, "canBurnOthers": false, "canPause": false, "canBlacklist": false, "hasOwner": false, "canSetFees": false, "hasMaxTxLimits": false, "hasPermit": true, "isUpgradeable": false, "suspiciousFunctions": [] }},"holderConcentration": { "holdersAnalyzed": 50, "largestHolderShare": 0.5002, "largestHolderLabel": "VotingEscrow", "top10Share": 0.6675, "top20Share": 0.7578, "top50Share": 0.8341, "contractShareInTop": 0.5689, "burnedShare": 0, "eoaHoldersInTop10": 8 },"topHolders": [{ "rank": 1, "address": "0xeBf418Fe2512e7E6bd9b87a8F0f294aCDC67e6B4", "label": "VotingEscrow", "isContract": true, "isBurn": false, "balance": 989616587.86, "share": 0.5002, "usdValue": 560343673.23 }],"transfersScanned": 20, "largeTransferFilter": null,"transferScan": { "method": "latest", "stopReason": "limit", "pagesScanned": 1, "transfersScanned": 20, "matched": 20, "coveredFrom": "2026-09-12T23:41:05Z", "coveredTo": "2026-09-12T23:43:20Z", "coveredHours": 0.04, "windowHours": null, "minAmountTokens": null },"recentTransfers": [{ "hash": "0xa014…", "timestamp": "2026-09-12T23:43:17Z", "from": "0x3B86…", "fromLabel": "CL100-WETH/B3 Pool Gauge", "to": "0x6104…", "toLabel": "ERC1967Proxy", "amount": 0.135, "usdValue": 0.08, "method": "0x62402c0e", "isMint": false, "isBurn": false }],"lastTransferAt": "2026-09-12T23:43:17Z","market": { "pairs": 30, "quotePairs": 12, "isMostlyQuoteAsset": false, "totalLiquidityUsd": 46109898.91, "totalVolume24hUsd": 7177661.31, "bestPriceUsd": 0.5629, "oldestPairAt": "2023-08-28T05:07:27Z", "websites": ["https://aerodrome.finance/docs"], "socials": [{ "type": "twitter", "url": "https://x.com/aerodromefi" }] },"dexPairs": [{ "dexId": "aerodrome", "labels": [], "pairAddress": "0x6cDc…", "url": "https://dexscreener.com/base/0x6cdc…", "quoteSymbol": "USDC", "priceUsd": 0.5629, "liquidityUsd": 33953838.44, "volume24hUsd": 965075, "buys24h": 251, "sells24h": 1654, "change1h": -0.45, "change24h": -0.63, "pairCreatedAt": "2023-09-07T22:50:45Z", "ageDays": 1101 }],"flags": ["owner_can_mint", "single_holder_over_50pct"],"warnings": [],"source": "blockscout+dexscreener", "sourceUrl": "https://base.blockscout.com/api/v2/tokens/0x9401…", "fetchedAt": "2026-09-12T23:43:20.620Z"}
| Field | Description |
|---|---|
found | Always true on dataset items (rows have no error field). Addresses that are not a token on this chain, invalid addresses and lookups that failed are not dataset items (not charged): they are listed in the SUMMARY record (notFound, errors with the reason, notFoundCount, errorCount) and in the run's status message. The run fails only if every input failed. |
name, symbol, type, decimals, icon, reputation | Token metadata; type is ERC-20 / ERC-721 / ERC-1155. |
totalSupply, holdersCount, transfersCount | Supply in token units; explorer counters. |
priceUsd, marketCapUsd, volume24hUsd, fdvUsd | Explorer (CoinGecko-backed) price data; fdvUsd = supply × price. |
contract | Verification facts, proxy/implementations, creator, tags, capabilities (see Reference). null when includeContract:false. |
holderConcentration | Shares of total supply; burn addresses excluded from "largest". null when holders skipped/failed. |
topHolders[] | Rank, address, label, contract/burn flags, balance, share, USD. |
transfersScanned, largeTransferFilter, recentTransfers[], lastTransferAt | Transfers with labelled parties, isMint/isBurn. The Transfers dataset view shows them one row per transfer. |
transferScan | How the transfers were found and which time range was checked: method (server-filter = explorer filtered by size over the whole history, client-scan = latest pages filtered locally, latest = no filter), coveredFrom/coveredTo/coveredHours, pagesScanned, transfersScanned, matched, stopReason (limit = enough matches, window = reached transferScanHours, history = no older transfers, pageCap = hit transferScanPages), minAmountTokens. Also in the SUMMARY record. |
market, dexPairs[] | DexScreener summary (pairs where the token is the base asset) and top pools. |
flags[], warnings[] | Triage flags; parts that could not be read (e.g. the holder list) and notes — the item is still delivered and charged, and the status message counts such items ("1 with warnings"). |
mode: "search" items: query, address, chain, chainId, explorerUrl, found, name, symbol, type, decimals, icon, reputation, totalSupply, holdersCount, priceUsd, marketCapUsd, volume24hUsd, likelySpam, source, sourceUrl, fetchedAt. Search hits without a contract address are left out.
Dataset views: Overview, Risk & concentration, Top holders (one row per holder), Transfers (one row per transfer) and Transfer scan coverage. Every field has a title, description and example in the dataset schema, so agents can read the output shape.
Status message and SUMMARY. Every run ends with one status message, e.g. 3 token(s) saved · 1 with warnings (…) · 1 not found (see SUMMARY), and a SUMMARY record in the run's key-value store: delivered, items, itemsWithWarnings, notFound, errors, notAnalyzed (inputs a stop left out, with notAnalyzedReason), tokens (per token: symbol, transfers kept, transferScan, warnings), notes (corrected or ignored fields, duplicate inputs) and throttledResponses (HTTP 429 answers the actor waited out).
Use it from code / agents
curl -X POST "https://api.apify.com/v2/acts/yadroo~base-token-intel/run-sync-get-dataset-items?token=$APIFY_TOKEN" \-H 'Content-Type: application/json' \-d '{"tokens":["0x940181a94A35A4569E4529A3CDfB74e38FD98631"],"chain":"base"}'
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: process.env.APIFY_TOKEN });const run = await client.actor('yadroo/base-token-intel').call({ tokens: ['0x940181a94A35A4569E4529A3CDfB74e38FD98631'], chain: 'base' });const { items } = await client.dataset(run.defaultDatasetId).listItems();
from apify_client import ApifyClientclient = ApifyClient(token)run = client.actor("yadroo/base-token-intel").call(run_input={"tokens": ["0x940181a94A35A4569E4529A3CDfB74e38FD98631"], "chain": "base"})items = client.dataset(run["defaultDatasetId"]).list_items().items
MCP: add https://mcp.apify.com to your MCP client and call the yadroo/base-token-intel tool with the same JSON.
Pricing
Pay per event: $0.001 per run start + $0.005 per token analyzed (mode=tokens) — search results are charged at the same per-item rate, so keep searchLimit small. Only tokens actually analyzed (and search hits) are charged — invalid addresses, tokens not found on the chain and failed lookups are free and listed in SUMMARY. Vetting 5 tokens costs $0.026; a daily watchlist of 50 tokens costs $0.251 per day.
Store discounts: Bronze −10 %, Silver −20 %, Gold and above −30 % on the per-item price; the start event is the same on every plan; platform usage is included.
Maximum cost per run is respected: the actor works out how many items your limit still pays for, stops starting new tokens when it is reached, and ends with Stopped at your spending limit: N rows delivered (the tokens it did not reach are counted in SUMMARY.notAnalyzed, not charged).
Limits & FAQ
- Rate limits — Blockscout and DexScreener both publish 300 requests/min per IP. The actor keeps well under it per host: one request start per
requestDelayMs(at least 250 ms), at most 2 requests waiting for an answer at once. An HTTP 429 pauses that host for as long as itsRetry-After(or rate-limit reset header) asks — without one 10 s, then 20 s, 40 s, 60 s for 429s in a row — and halves the pace to that host (gap up to 2 s; it eases back as answers come in). A token still refused after 4 tries (about 70 s of pauses) is not delivered and not charged, and neither is a token one of whose parts was refused; after 3 refused tokens in a row the run stops asking, says so in the status message and lists the rest as not analyzed (not charged) — run them again later. 5xx answers and timeouts are retried with backoff. No proxies are used. Keyless Blockscout access can answer 429 well below its published limit: on 02.10.2026 a run from an Apify IP got 429s after about 3 minutes of continuous use (about 80 requests/min). - Giant tokens — the explorer does not return the holder lists of the very largest tokens in time: USDC on Base (13 M holders) failed on every daily run since 18.09 (25 s per try) and also with 45 s on 02.10; WETH on Base (5 M holders) too. Such rows are still delivered and charged, with
topHolders: [],holderConcentration: nulland a warning that says so, and the status message counts them ("1 with warnings"). UseholdersLimit: 0for such tokens: it saves about 50 s per token and the rest of the row (price, supply, holder count, contract, market) is the same. - Capacity — with the defaults one token takes about 5 s (2–20 s; measured 02.10.2026 on 26 Base tokens), plus about 50 s for a token whose holder list times out. A large list is more likely to be cut by the explorer's rate limit (above) than by the 900 s default timeout: about 25–30 tokens per run is a safe size; split longer watchlists into several runs.
- Concentration caveat — the largest holder is often a staking, escrow, bridge or CEX contract; read
largestHolderLabelandcontractShareInTopbefore concluding. When the listed holders (burn and zero addresses aside) together hold more than 101 % of the explorer's total supply — 7 of 26 Base tokens checked on 02.10.2026: vault tokens, bridged DAI, and look-alike tokens that fake balances — everyshareand the concentration shares are left empty with a warning that names both numbers, and no share-based flag is raised. - Capabilities are name-based — a verified ABI is scanned by function names; unusual naming can produce false negatives/positives. Unverified contracts get
capabilities: nulland theunverified_contractflag. - Quote assets — for tokens that are mostly the quote side of pools (USDC, WETH) liquidity flags are suppressed (
isMostlyQuoteAsset). - Whale-filter coverage — size filters (
minTransferAmount/minTransferUsd) use Blockscout's advanced-filter endpoint (amount_from), so the explorer returns only large transfers and the scan reaches back days, not minutes (PEPE ≥ $50k withtransfersLimit: 50: 50 matches going back ~5 h, where scanning the latest 200 transfers found 1). If an explorer rejects that endpoint, or the token is an NFT, the actor falls back to reading the latesttransferScanPagespages and filtering them itself. On USDT-class tokens 500 transfers are under a minute, so the output then saysmethod: client-scan,stopReason: pageCap, and adds a warning with the time actually covered. Always readtransferScan.coveredFrombefore concluding that there were no whale moves. - NFTs — ERC-721/1155 contracts work for supply, holders and transfers (amounts are item counts / token ids).
- Errors — input mistakes (no 0x address, no queries, unknown
fieldsonly) end the run at once with a clear status message, before any request; per-token failures never stop the run. - Run timeout — the actor saves each token as soon as it is analyzed and stops starting new tokens shortly before the run timeout: the run ends SUCCEEDED with
Stopped before the run timeout: N rows saved, and the token in progress is not delivered or charged. A migration or restart of the run never stores or charges a token twice. - Roadmap — honeypot simulation, LP-lock detection, holder growth over time.
Made by Yadroo. Siblings: wallet-intel · dexscreener-tokens · ens-resolver · coingecko-markets · defillama-protocols