CRIUS
ScreenerNode.jsSecurityTutorial

Build a Robinhood Chain Token Screener with Node.js

Build a Robinhood Chain token screener in Node.js: rank by volume or momentum, filter on liquidity, holders and flow, then check tax, honeypots and LP.

Crius 7 min read

Short answer: pull ranked candidates from GET /v1/robinhood/tokens (trending, top volume, gainers, new, graduating) with server-side liquidity and volume filters, apply your own rules on the activity fields every row carries, then run GET /token/{address}/security on the shortlist to drop honeypots, taxed tokens and pullable liquidity. In our test this narrowed 200 tokens to 14 tradeable candidates in 13 seconds.

This guide builds that screener step by step in Node.js 20+ with no dependencies, explains the fields it filters on and shows where screeners usually go wrong. Every number below came from a live run in October 2026.

The three stages

/tokens?list=… server-side: ranking, min liquidity, min volume, launchpad, quote
│ 200 rows, each with price, liquidity, volume, holders, traders, buys/sells, change
▼
your rules client-side: holder count, flow, momentum, age
│ 15 shortlisted
▼
/token/{address}/security per token, 2 at a time: honeypot, tax, owner powers, LP locks
│
▼
14 candidates

Ranking and coarse filtering happen on the server, where every token’s statistics are already in memory. Your rules run on the rows you get back. Only the shortlist gets the security check, because that endpoint does real work per token.

Stage 1: candidates

Terminal window
curl "https://api.crius.sh/v1/robinhood/tokens?list=top_volume&window=24h&min_liquidity_usd=25000&limit=100" \
-H "x-api-key: $CRIUS_API_KEY"
Parameter Values
list trending (default), top_volume, top_gainers, top_losers, new, bonding, graduating, graduated
window 1s, 30s, 1m, 5m, 1h, 4h, 6h, 24h (ranking window for trending, volume and movers)
min_liquidity_usd, min_volume_usd server-side floors
launchpad e.g. pons, flap
quote eth, usdg, …
created_after_ts Unix ms, for “new in the last hour” screens
limit, cursor up to 100 per page; meta.next_cursor for the next page
include_flagged include tokens flagged as scams or hidden by moderation (excluded by default)

trending scores tokens by unique traders weighted by the logarithm of volume, so a token with many small traders outranks one whale churning volume. graduating lists bonding-curve tokens past 50% of their curve, closest to graduation first.

Each row carries what a screener needs, with every activity field broken down by window:

{
"address": "0xc9675fe679c7d4e1b94416f66d61c96127eb964a",
"symbol": "GIGATENEV",
"price_usd": 0.00119234,
"market_cap_usd": 1192338,
"liquidity_usd": 146654,
"holders": 10219,
"volume_usd": { "5m": 3961, "1h": 55243.7, "6h": 334474, "24h": 1240296 },
"change_pct": { "5m": 2.54, "1h": 10.51, "6h": 7.31, "24h": 8.37 },
"traders": { "1h": 384, "24h": 6057 },
"buyers": { "24h": 4508 }, "sellers": { "24h": 4713 },
"buys": { "24h": 10644 }, "sells": { "24h": 10332 },
"launchpad": null, "progress_pct": null, "quote": "ETH",
"created_ts": 1791568845600
}

Stage 2: your rules

const API = "https://api.crius.sh/v1/robinhood";
const KEY = process.env.CRIUS_API_KEY;
async function get(path) {
for (let i = 0; i < 5; i++) {
const r = await fetch(`${API}${path}`, { headers: { "x-api-key": KEY } });
if (r.status === 429 || r.status === 503) { // rate limited or busy: wait as told
await new Promise((s) => setTimeout(s, Number(r.headers.get("retry-after") ?? 2) * 1000));
continue;
}
if (!r.ok) throw new Error(`${path}: ${r.status}`);
return r.json();
}
throw new Error(`${path}: retries exhausted`);
}
// Two pages of candidates.
const rows = [];
let cursor = "";
for (let page = 0; page < 2; page++) {
const r = await get(`/tokens?list=top_volume&window=24h&min_liquidity_usd=25000&limit=100${cursor ? `&cursor=${cursor}` : ""}`);
rows.push(...r.data);
if (!r.meta.next_cursor) break;
cursor = r.meta.next_cursor;
}
const shortlist = rows
.filter((t) => t.holders >= 200) // not a handful of wallets
.filter((t) => t.traders["24h"] >= 50) // real participation
.filter((t) => t.buyers["24h"] / Math.max(1, t.sellers["24h"]) >= 0.8) // not a one-way exit
.filter((t) => (t.change_pct["24h"] ?? 0) > -40) // not collapsing
.slice(0, 15);

Why these rules:

  • Holders and traders separate tokens with an audience from tokens a few wallets trade back and forth. Volume alone is easy to inflate; distinct traders are much harder.
  • Buyer-to-seller ratio catches distribution: when far more wallets are selling than buying, the price is usually being held up by a few large buys.
  • A momentum floor keeps tokens in free fall out of a “top volume” list, where panic selling ranks them highly.

Stage 3: security on the shortlist

const results = [];
for (let i = 0; i < shortlist.length; i += 2) { // two at a time: security is a heavy endpoint
const batch = await Promise.all(shortlist.slice(i, i + 2).map((t) => get(`/token/${t.address}/security`)));
batch.forEach((s, j) => results.push({ t: shortlist[i + j], s: s.data }));
}
const passed = results.filter(({ s }) =>
!s.honeypot?.is_honeypot &&
(s.tax?.sell_pct ?? 0) <= 5 &&
s.risk !== "high");
console.table(passed.slice(0, 8).map(({ t, s }) => ({
symbol: t.symbol, price_usd: t.price_usd, vol_24h: Math.round(t.volume_usd["24h"]),
liq: Math.round(t.liquidity_usd), holders: t.holders, chg_24h: t.change_pct["24h"]?.toFixed(1),
risk: s.risk, sell_tax: s.tax?.sell_pct, flags: (s.flags ?? []).join(" "),
})));

The security response is detailed; these are the fields a screener should act on:

Field Meaning
honeypot.is_honeypot Sells revert or are blocked. Never list these.
tax.buy_pct, tax.sell_pct Tax measured from real trades (tax.source: "realized") or simulated when there aren’t enough trades yet.
contract.mintable, pausable, blacklist, can_change_tax Powers the owner still holds.
liquidity.status burned, locked or unlocked, with percentages per pool.
flags Short codes: lp_unlocked, hook_can_block_swaps, unverified, ownership_active …
risk, score An overall rating and a 0–100 score.

Our run, trimmed:

200 candidates → 15 shortlisted → 14 passed security
symbol price_usd vol_24h liq holders chg_24h risk sell_tax flags
UHI 0.0275856 16301698 508832 8445 -14.5 low 0
AI 0.0551436 8869592 2429155 57727 -32.0 low 0 ownership_active
SPY 762.602 6121669 13356096 70614 -0.7 low null mintable pausable upgradeable_proxy
CRCL 84.7201 3817375 3625793 12702 -2.5 low null mintable pausable upgradeable_proxy
HMCAST 0.00000452166 2714979 160185 1339 1743.3 medium 0 lp_unlocked hook_can_block_swaps unverified
CONDUCTOR 0.0000017233 2608844 68742 814 1546.3 medium 0 lp_unlocked unverified

Two things stand out:

  • Tokenized stocks carry “scary” flags legitimately. SPY and CRCL are mintable, pausable and upgradeable because their issuer must be able to mint against custody, freeze for compliance and upgrade. A screener for memecoins should treat these flags as red; a screener that includes stocks should whitelist known issuers (the known_asset field marks them) rather than exclude them.
  • hook_can_block_swaps matters on Uniswap v4. A v4 pool’s hook can be written to block sells. Treat that flag as a reason for manual review, not an automatic pass.

Common screener mistakes

Trusting volume alone. Volume is the easiest number to inflate. Pair every volume filter with distinct traders and holders, as Stage 2 does; a token with $1 million of volume and 30 traders is a different thing from one with $1 million and 3,000.

Filtering on liquidity that can leave. liquidity_usd says how much is in the pools now, not whether it can be pulled. Check liquidity.status in the security response: unlocked liquidity in one wallet can disappear in a single transaction.

Judging a new token on 24-hour windows. A token launched two hours ago has no meaningful 24-hour change. For new tokens, rank on 5m and 1h windows and use created_after_ts to keep the list to recent launches.

Ignoring the quote asset. A token quoted in another volatile token inherits that token’s moves. The quote field tells you what each row trades against; most screeners do best restricting to ETH, WETH and USDG quotes.

Hammering heavy endpoints. Running security checks for every candidate in parallel gets you 503 responses. Shortlist first, check two or three at a time, and cache results.

Running it on a schedule

Run the screener every few minutes and diff the results: tokens entering the list are your alerts. Two practical points:

  • Cache security results. A token’s contract powers rarely change; its tax and LP status can. Re-check a token at most every 10–15 minutes, and immediately when the tax:<token> or lp:<token> stream reports a change.
  • Use the stream for “new”. For a new-token screener, subscribe to launches:all and pools:new on the WebSocket API instead of polling list=new, then run stages 2 and 3 on each arrival.

Adding depth with batch calls

To show more columns without a request per token, use POST /tokens/batch with up to 100 addresses and a fields list:

Terminal window
curl -X POST "https://api.crius.sh/v1/robinhood/tokens/batch" \
-H "x-api-key: $CRIUS_API_KEY" -H "content-type: application/json" \
-d '{"tokens":["0xc9675fe679c7d4e1b94416f66d61c96127eb964a"],"fields":["symbol","price.usd","holders.count","holders.top10_pct","market.pools_count"]}'

The full profile includes holder concentration (holders.top10_pct), creation details, launchpad data and all-time statistics. Security isn’t inlined in batch responses; it points to the security endpoint, which you call for the shortlist only.

Frequently asked questions

How much does a screener run cost? Two /tokens pages are 2 credits and 15 security checks are 15 credits, so 17 credits per run. Every 5 minutes, that’s about 4,900 credits a day.

Can I screen tokens still on a bonding curve? Yes: use list=bonding or list=graduating and filter on progress_pct. Bonding-curve tokens have no DEX pool yet, so judge them on holders, traders and the creator’s history.

Why are some tokens missing from the list? Tokens flagged as scams or hidden by moderation are excluded by default; pass include_flagged=true to see them. Tokens with no trades in the ranking window don’t appear in trending or movers lists.

Does the screener work without an API key? The /tokens lists and /security work anonymously at 2 requests per second, enough for experiments. A free key raises that to 10 per second with 1,000,000 credits a month.

Next steps

Questions go to support@crius.sh.

Build with Crius

Robinhood Chain data in one API

Free plan with 1M credits a month. Your key in under a minute.

Related APIs

More from the blog