# ERC-20 Token Due Diligence — Holders, Contract Risks, Liquidity (`yadroo/base-token-intel`) Actor

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.

- **URL**: https://apify.com/yadroo/base-token-intel.md
- **Developed by:** [Samat Makatov](https://apify.com/yadroo) (community)
- **Categories:** Developer tools, AI, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.50 / 1,000 result items

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

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** — `flags` such as `single_holder_over_50pct`, `thin_liquidity_under_10k`, `owner_can_change_fees`, `meme_token_style_controls`.
- **Whale watching** — `minTransferUsd: 100000` returns 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 with `likelySpam: 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)**

```json
{ "tokens": ["0x940181a94A35A4569E4529A3CDfB74e38FD98631"], "chain": "base", "holdersLimit": 100, "transfersLimit": 20 }
```

**2. Whale transfers ≥ $50k for PEPE (Ethereum)**

```json
{ "tokens": ["0x6982508145454Ce325dDbE47a25d4ec3d2311933"], "chain": "ethereum", "holdersLimit": 0, "transfersLimit": 50, "minTransferUsd": 50000, "transferScanHours": 72, "includeContract": false, "includeDexPairs": false }
```

**3. Holder snapshot: top 500 holders with labels**

```json
{ "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**

```json
{ "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**

```json
{ "mode": "search", "queries": ["aerodrome", "virtual"], "chain": "base", "searchLimit": 5, "searchTokenType": "ERC-20" }
```

### Output

One item per token (trimmed real example, AERO on Base):

```json
{
  "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

```bash
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"}'
```

```js
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();
```

```python
from apify_client import ApifyClient
client = 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 its `Retry-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: null` and a warning that says so, and the status message counts them ("1 with warnings"). Use `holdersLimit: 0` for 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 `largestHolderLabel` and `contractShareInTop` before 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 — every `share` and 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: null` and the `unverified_contract` flag.
- **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 with `transfersLimit: 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 latest `transferScanPages` pages and filtering them itself. On USDT-class tokens 500 transfers are under a minute, so the output then says `method: client-scan`, `stopReason: pageCap`, and adds a warning with the time actually covered. Always read `transferScan.coveredFrom` before 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 `fields` only) 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](https://apify.com/yadroo/wallet-intel) · [dexscreener-tokens](https://apify.com/yadroo/dexscreener-tokens) · [ens-resolver](https://apify.com/yadroo/ens-resolver) · [coingecko-markets](https://apify.com/yadroo/coingecko-markets) · [defillama-protocols](https://apify.com/yadroo/defillama-protocols)

# Actor input Schema

## `mode` (type: `string`):

`tokens` = full due-diligence item per contract address in `tokens`. `search` = find token contracts by name/symbol (`queries`) and return lightweight summary items (address, holders, price) — use it to discover the right contract address before a `tokens` run.

## `tokens` (type: `array`):

ERC-20 (also ERC-721/1155) contract addresses for mode=tokens. One dataset item (and one charge) per token found on the chosen chain. Letter case does not matter and an address listed twice is analyzed once. Values that are not 0x addresses, and addresses that are not a token on this chain, are not dataset items and are not charged: they are listed in the SUMMARY record (`errors`, `notFound`).

## `queries` (type: `array`):

Token names or symbols for mode=search, e.g. \["aerodrome", "USDC"].

## `searchLimit` (type: `integer`):

mode=search only. Every search hit is one dataset item, charged at the same per-item price as an analyzed token, so keep this small. The first N tokens Blockscout returns for each query are kept.

## `searchTokenType` (type: `string`):

mode=search only: restrict results to one token standard.

## `chain` (type: `string`):

Blockscout-hosted chain, one key from the list (README → Reference → Chains). The platform refuses any other value (for example eth or mainnet) before the run starts. DexScreener market data is available on every chain in the list except Gnosis, Mode and the two testnets (there `market` stays null and `dexPairs` empty, with a warning).

## `holdersLimit` (type: `integer`):

How many top holders to include (0 = skip holders and concentration metrics). The explorer cannot list the holders of the very largest tokens (USDC, WETH class: millions of holders) in time; such rows come with an empty list and a warning, so use 0 for them.

## `transfersLimit` (type: `integer`):

How many most recent transfers to include (0 = skip). With a `minTransfer*` filter: how many matching (large) transfers to return. How far back the actor looks is set by transferScanPages / transferScanHours and reported in `transferScan`.

## `minTransferAmount` (type: `number`):

Keep only transfers of at least this many tokens (whale watching). 0 = no filter.

## `minTransferUsd` (type: `number`):

Keep only transfers worth at least this much USD at the explorer's current price. 0 = no filter. Ignored (with a warning) when the token has no price. Converted to a token amount and filtered server-side by the explorer, so matches come from hours or days back, not only from the latest few hundred transfers.

## `transferScanPages` (type: `integer`):

Upper bound on transfer pages (50 transfers each) read per token when a minTransfer\* filter or a time window is set. Large-transfer filters run server-side (Blockscout advanced filters), so each page holds only matching transfers. If the explorer can't filter (NFTs, or the endpoint is unavailable), the actor reads the latest pages and filters them itself; on busy tokens like USDT or PEPE, 10 pages cover only minutes to an hour. The time range actually covered is reported in `transferScan`.

## `transferScanHours` (type: `number`):

Only consider transfers from the last N hours, e.g. 24 = large transfers in the last day. The scan stops at the window start or at transferScanPages, whichever comes first. 0 = no time limit: newest first until transfersLimit matches are found.

## `includeContract` (type: `boolean`):

Verification status, compiler, license, proxy/implementations, creator, creation tx, explorer tags and an ABI capability scan (mint, pause, blacklist, fee/max-tx setters, upgradeability, meme-token style controls).

## `includeDexPairs` (type: `boolean`):

Pools for the token from DexScreener: liquidity, 24h volume, buys/sells, price change, pair age, websites and socials — plus a market summary used by the risk flags.

## `dexPairsLimit` (type: `integer`):

Pairs kept in `dexPairs` (sorted by liquidity). The market summary always uses all pairs.

## `fields` (type: `array`):

Keep only these top-level fields, in this order. address, chain, symbol, found and 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 and SUMMARY, and the run is refused when none of the names exists in this mode's rows. Example: \["holdersCount","holderConcentration","flags"].

## `requestDelayMs` (type: `integer`):

Minimum gap between two requests to the same host (the Blockscout explorer, DexScreener). Values below 250 are raised to 250: both sources publish a limit of 300 requests per minute per IP. At most 2 requests per host wait for an answer at once. An HTTP 429 answer pauses that host for the time it asks (Retry-After; without one 10 s, doubling for 429s in a row) and slows the pace to it. Raise this if the status message reports rate limiting.

## Actor input object example

```json
{
  "mode": "tokens",
  "tokens": [
    "0x940181a94A35A4569E4529A3CDfB74e38FD98631"
  ],
  "queries": [],
  "searchLimit": 10,
  "searchTokenType": "all",
  "chain": "base",
  "holdersLimit": 50,
  "transfersLimit": 20,
  "minTransferAmount": 0,
  "minTransferUsd": 0,
  "transferScanPages": 10,
  "transferScanHours": 0,
  "includeContract": true,
  "includeDexPairs": true,
  "dexPairsLimit": 5,
  "fields": [],
  "requestDelayMs": 250
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `summary` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "tokens": [
        "0x940181a94A35A4569E4529A3CDfB74e38FD98631"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("yadroo/base-token-intel").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "tokens": ["0x940181a94A35A4569E4529A3CDfB74e38FD98631"] }

# Run the Actor and wait for it to finish
run = client.actor("yadroo/base-token-intel").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "tokens": [
    "0x940181a94A35A4569E4529A3CDfB74e38FD98631"
  ]
}' |
apify call yadroo/base-token-intel --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,yadroo/base-token-intel"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/YzQMZgME4dU9Hb9fw/builds/mgeBJKuIFl9z8YWYx/openapi.json
