CRIUS Docs
API status Get API key
Build

Build a Telegram token scanner bot for Robinhood Chain

Build a Telegram bot that scans Robinhood Chain tokens: price, liquidity, holders, taxes and a security score from two Crius API calls, with real examples.

Updated 3 min read
Short answer

When a message contains a token address, call GET /v1/robinhood/token/{address} (price, market cap, liquidity, holders, launchpad) and GET /v1/robinhood/token/{address}/security (score, risk, flags, taxes, honeypot) in parallel and reply with a summary; for a name, call /search first and let the user pick. Two credits per scan, and both calls work without a key.

A Telegram token scanner is a bot that answers “is this token OK?” in a chat: someone pastes a Robinhood Chain token address, and the bot replies with its price, market cap, liquidity, holders, launchpad, taxes and a security verdict. With the Crius API that’s two calls per scan, the token profile and security checks, plus search when someone types a name instead of an address. All three are on the anonymous plan, so the bot runs without a key until it gets busy.

Architecture

  1. Telegram side. Long-poll getUpdates from the Telegram Bot API (no webhook or server needed) and reply with sendMessage. Create the bot with @BotFather to get its token.

  2. Parse the message. An address (0x and 40 hex characters) is scanned directly. A word after /scan is a name: search for it and reply with the matches.

  3. Scan. Call the token profile and security in parallel, then format one message. Cache results for a minute so ten people pasting the same token in a group cost one scan.

  4. Optional feed. A second process listens to launches:all on the stream and posts new tokens that pass the scan to a channel.

Endpoints used: GET/token/{address} GET/token/{address}/security GET/search

Names are not unique: search, then pick

Three tokens called Housecoin

A user types /scan housecoin. Which token do they mean?

GET/v1/robinhood/search?q=housecoin&limit=3
curl "https://api.crius.sh/v1/robinhood/search?q=housecoin&limit=3"
Real response 200 Captured Oct 28, 2026 · 73 lines (arrays trimmed)
{
"data": [
{
"address": "0x578dfaf61257ea702b8834bb037740a6368947bd",
"label": null,
"launchpad": null,
"launchpad_name": null,
"links": {
"discord": null,
"farcaster": null,
"github": null,
"telegram": null,
"twitter": null,
"website": null
},
"liquidity_usd": 22978.7,
"logo": null,
"name": "Housecoin",
"price_usd": 0.0000846805,
"symbol": "HOUSECOIN",
"type": "token"
},
{
"address": "0x34c21f10ea7ec052ed878d157f40fb4160730b24",
"label": null,
"launchpad": "pons",
"launchpad_name": "Pons",
"links": {
"discord": null,
"farcaster": null,
"github": null,
"telegram": null,
"twitter": "https://x.com/aryonserwin/status/2093942789784637480",
"website": "https://fs.blog/knowledge-project-podcast/vlad-tenev/?utm_source=chatgpt.com"
},
"liquidity_usd": 4978.47,
"logo": "https://api.crius.sh/v1/robinhood/logo/0x34c21f10ea7ec052ed878d157f40fb4160730b24.png?size=128",
"name": "Housecoin",
"price_usd": 0.00000374137,
"symbol": "housecoin",
"type": "token"
},
{
"address": "0xc19b982b971642aa6cc5346f5a15cd0ccf4626cc",
"label": null,
"launchpad": "pons",
"launchpad_name": "Pons",
"links": {
"discord": null,
"farcaster": null,
"github": null,
"telegram": null,
"twitter": "https://x.com/coinbase/status/2093710406695125093",
"website": "https://www.ponsfamily.com/launchpad/0x992f10d2c751ec875d6a2834a70f730d030a25f9"
},
"liquidity_usd": 3876.33,
"logo": "https://api.crius.sh/v1/robinhood/logo/0xc19b982b971642aa6cc5346f5a15cd0ccf4626cc.png?size=128",
"name": "Housecoin",
"price_usd": 0.00000218996,
"symbol": "Housecoin",
"type": "token"
}
],
"meta": {
"block": 100410183,
"block_age_ms": 555,
"block_ts": 1793225424000,
"chain": "robinhood",
"finalized_block": 100401329,
"lag_ms": 9,
"safe_block": 100405028
}
}

What this tells you

  • Three different tokens share the name: 0x578dfaf61257ea702b8834bb037740a6368947bd (no launchpad, liquidity_usd 22,978.7), and two Pons tokens, 0x34c21f10ea7ec052ed878d157f40fb4160730b24 (4,978.47) and 0xc19b982b971642aa6cc5346f5a15cd0ccf4626cc (3,876.33).
  • Their symbols differ only in case (HOUSECOIN, housecoin, Housecoin), and their links point to unrelated pages. A bot that scans “the first result” would be wrong as often as right.
  • Reply with every match: name, launchpad_name, liquidity and the full address, and let the user choose. type=token restricts search to tokens; limit goes up to 20.
Search reference Runs from your browser on the anonymous plan Data looks wrong? Report it

The scan: profile plus security

Housecoin's security: unlocked liquidity in one wallet

The user picks 0x578dfaf61257ea702b8834bb037740a6368947bd. What should the bot warn about?

GET/v1/robinhood/token/0x578dfaf61257ea702b8834bb037740a6368947bd/security
curl "https://api.crius.sh/v1/robinhood/token/0x578dfaf61257ea702b8834bb037740a6368947bd/security"
Real response 200 Captured Oct 28, 2026 · 119 lines (arrays trimmed)
{
"data": {
"contract": {
"blacklist": false,
"can_change_limits": false,
"can_change_tax": false,
"cooldown": false,
"implementation": null,
"is_proxy": false,
"mintable": false,
"owner": null,
"ownership": "none",
"pausable": false,
"proxy_kind": null,
"self_destruct": false,
"trading_switch": false,
"upgradeable": false,
"verified": "none"
},
"flags": [
"lp_unlocked",
"unverified",
"lp_single_wallet"
],
"honeypot": {
"is_honeypot": false,
"reason": null,
"selective": false
},
"hook": null,
"known_asset": false,
"launch": {
"flags": [
"lp_single_wallet"
],
"lp_top_wallet": {
"address": "0x52c6e90a0928df4d6e4a6164ee0c7851144b1454",
"liquidity_usd": 23239.6,
"pct": 100
},
"sniper_supply_pct": 0.0000145583,
"sniper_top_wallet_pct": 0.0000145583,
"sniper_wallets": 1,
"sniper_window_blocks": 10,
"uniform_holders": null,
"wallet_farm": null
},
"limits": {
"max_tx": "1000000000",
"max_tx_getter": "maxTxAmount()",
"max_tx_pct": 100,
"max_wallet": "1000000000",
"max_wallet_getter": "maxWalletAmount()",
"max_wallet_pct": 100
},
"liquidity": {
"status": "unlocked",
"summary": {
"burned_pct": 0,
"locked_pct": 0,
"next_unlock_ts": null,
"protocol_fee_pct": 0,
"unlocked_in_wallets_pct": 100,
"weighting": "by pool liquidity_usd"
}
},
"primary_dex": "uniswap_v4",
"risk": "medium",
"score": 50,
"tax": {
"buy_pct": 0,
"confidence": "high",
"realized": {
"buy_pct": 0,
"by_wallet_class": {
"dev_sell_pct": null,
"others_sell_pct": 0
},
"last_sample_ts": 1793225424000,
"max_sell_pct_seen": 0,
"samples": {
"buys": 53,
"sells": 48
},
"sell_pct": 0,
"window": "30d"
},
"sell_pct": 0,
"simulated": null,
"source": "realized",
"transfer_pct": null
},
"tax_history": [
{
"from_block": 100408848,
"pct": 0,
"side": "buy"
},
{
"from_block": 100408910,
"pct": 0,
"side": "sell"
}
],
"tax_history_total": 2,
"tax_mode": "static",
"token": "0x578dfaf61257ea702b8834bb037740a6368947bd"
},
"meta": {
"block": 100410199,
"block_age_ms": 1166,
"block_ts": 1793225425000,
"chain": "robinhood",
"finalized_block": 100401329,
"lag_ms": 97,
"safe_block": 100405028,
"simulation": "no V2 WETH pair: realized tax only"
}
}

What this tells you

  • score: 50, risk: "medium", flags: lp_unlocked, unverified, lp_single_wallet.
  • launch.lp_top_wallet is 0x52c6e90a0928df4d6e4a6164ee0c7851144b1454 with pct: 100 of the liquidity (liquidity_usd 23,239.6), and liquidity.status is unlocked: one wallet can pull the whole pool. That’s the line to put first in the reply.
  • No tax: tax.buy_pct and tax.sell_pct are 0 over 53 buys and 48 sells (tax.realized.samples, confidence: "high"), and honeypot.is_honeypot is false.
  • meta.simulation says "no V2 WETH pair: realized tax only": the taxes come from real trades, not a simulation.
  • contract.ownership: "none", with no mint, pause or blacklist: the contract itself isn’t the risk here.
Security checks reference Runs from your browser on the anonymous plan Data looks wrong? Report it

Minutes later: the pool is empty

The same token, scanned again twelve minutes after the security check above. This is what the lp_single_wallet warning was about.

Housecoin after its liquidity was pulled

Someone pastes 0x578dfaf61257ea702b8834bb037740a6368947bd again. What does the profile show now?

GET/v1/robinhood/token/0x578dfaf61257ea702b8834bb037740a6368947bd
curl "https://api.crius.sh/v1/robinhood/token/0x578dfaf61257ea702b8834bb037740a6368947bd"
Real response 200 Captured Oct 28, 2026 · 267 lines (arrays trimmed)
{
"data": {
"address": "0x578dfaf61257ea702b8834bb037740a6368947bd",
"admin_flags": null,
"bundles": {
"see": "/v1/robinhood/token/0x578dfaf61257ea702b8834bb037740a6368947bd/bundles"
},
"burned": 771605584,
"chain_id": 4663,
"circulating_supply": 228394416,
"creation": {
"attribution": "unattributed_factory",
"block": 100408825,
"deployed_via": "factory",
"deployer": "0x52c6e90a0928df4d6e4a6164ee0c7851144b1454",
"factory": "0x1cbaf24d53fe930fce8eff149fa797d2611da149",
"source": "trace",
"tx": "0xba98b7479b2bf5896021262176c4c92f149df0750f93a75b2fcc83869d10d51a"
},
"decimals": 18,
"dexscreener": {
"status": "checking"
},
"first_seen_block": 100408825,
"history": "complete",
"holders": {
"burned_pct": 77.1606,
"count": 166,
"dev_holding": null,
"dev_pct": null,
"locked_pct": 0,
"top10_pct": 16.4376
},
"launch": null,
"liquidity": {
"see": "/v1/robinhood/token/0x578dfaf61257ea702b8834bb037740a6368947bd/liquidity",
"status": "no_liquidity",
"summary": {
"burned_pct": null,
"locked_pct": null,
"next_unlock_ts": null,
"protocol_fee_pct": null,
"unlocked_in_wallets_pct": null,
"weighting": "by pool liquidity_usd"
}
},
"locked": 0,
"market": {
"fdv_usd": null,
"liquidity_usd": 0,
"market_cap_usd": null,
"pools_count": 1,
"quote_liquidity_usd": 0
},
"metadata": {
"banner": null,
"description": null,
"links": {
"discord": null,
"farcaster": null,
"github": null,
"telegram": null,
"twitter": null,
"website": null
},
"logo": null,
"logo_original": null,
"socials": {
"discord": null,
"farcaster": null,
"github": null,
"telegram": null,
"twitter": null,
"website": null
},
"source": null,
"sources": {
"links": null,
"logo": null
}
},
"name": "Housecoin",
"price": {
"ath_market_cap_usd": 21314.9,
"ath_scope": "all_time",
"ath_ts": 1793225465000,
"ath_usd": 0.0000933247,
"change_pct": {
"1h": 271.103,
"1m": 0,
"1s": 0,
"24h": 271.103,
"30s": 0,
"4h": 271.103,
"5m": 0,
"6h": 271.103
},
"confidence": null,
"dex": null,
"eth": null,
"liquidity_usd": null,
"quote": null,
"quote_liquidity_usd": null,
"source_pool": null,
"thin_liquidity": null,
"usd": null
},
"security": {
"see": "/v1/robinhood/token/0x578dfaf61257ea702b8834bb037740a6368947bd/security"
},
"standard": "erc20",
"stats": {
"all_time": {
"buy_volume_usd": 17761,
"buys": 70,
"first_trade_ts": 1793225291000,
"sell_volume_usd": 11036.9,
"sells": 62,
"since": "chain_start",
"trades": 132,
"volume_usd": 28797.9
},
"avg_trade_usd": {
"1h": 218.166,
"1m": null,
"1s": null,
"24h": 218.166,
"30s": null,
"4h": 218.166,
"5m": null,
"6h": 218.166
},
"buy_volume_usd": {
"1h": 17761,
"1m": 0,
"1s": 0,
"24h": 17761,
"30s": 0,
"4h": 17761,
"5m": 0,
"6h": 17761
},
"buyers": {
"1h": 52,
"1m": 0,
"1s": 0,
"24h": 52,
"30s": 0,
"4h": 52,
"5m": 0,
"6h": 52
},
"buys": {
"1h": 70,
"1m": 0,
"1s": 0,
"24h": 70,
"30s": 0,
"4h": 70,
"5m": 0,
"6h": 70
},
"last_trade_ts": 1793225465000,
"sell_volume_usd": {
"1h": 11036.9,
"1m": 0,
"1s": 0,
"24h": 11036.9,
"30s": 0,
"4h": 11036.9,
"5m": 0,
"6h": 11036.9
},
"sellers": {
"1h": 31,
"1m": 0,
"1s": 0,
"24h": 31,
"30s": 0,
"4h": 31,
"5m": 0,
"6h": 31
},
"sells": {
"1h": 62,
"1m": 0,
"1s": 0,
"24h": 62,
"30s": 0,
"4h": 62,
"5m": 0,
"6h": 62
},
"traders": {
"1h": 52,
"1m": 0,
"1s": 0,
"24h": 52,
"30s": 0,
"4h": 52,
"5m": 0,
"6h": 52
},
"trades": {
"1h": 132,
"1m": 0,
"1s": 0,
"24h": 132,
"30s": 0,
"4h": 132,
"5m": 0,
"6h": 132
},
"txns": {
"1h": 132,
"1m": 0,
"1s": 0,
"24h": 132,
"30s": 0,
"4h": 132,
"5m": 0,
"6h": 132
},
"volume_eth": {
"1h": 12.2259,
"1m": 0,
"1s": 0,
"24h": 12.2259,
"30s": 0,
"4h": 12.2259,
"5m": 0,
"6h": 12.2259
},
"volume_usd": {
"1h": 28797.9,
"1m": 0,
"1s": 0,
"24h": 28797.9,
"30s": 0,
"4h": 28797.9,
"5m": 0,
"6h": 28797.9
}
},
"symbol": "HOUSECOIN",
"total_supply": 1000000000,
"type": "generic",
"ui_multiplier": null,
"verification": {
"compiler": null,
"contract_name": null,
"implementation": null,
"is_proxy": false,
"sourcify": "none"
}
},
"meta": {
"block": 100417059,
"block_age_ms": 1310,
"block_ts": 1793226138000,
"chain": "robinhood",
"finalized_block": 100408671,
"lag_ms": 100,
"pending": [],
"safe_block": 100412483
}
}

What this tells you

  • market.liquidity_usd is 0, price.usd is null and liquidity.status is no_liquidity: the token’s one pool (market.pools_count: 1) has nothing left to trade against.
  • No trade happened after the all-time high: price.ath_ts and stats.last_trade_ts are both 1793225465000 (22:11:05 UTC), less than a minute after the security check.
  • creation.deployer is 0x52c6e90a0928df4d6e4a6164ee0c7851144b1454, the same wallet the security check reported as holding 100% of the liquidity (launch.lp_top_wallet).
  • holders.count is still 166: the holders keep their tokens, but there’s no market to sell them in. creation.attribution: "unattributed_factory" and launch: null: no launchpad, so no launch locker either.
Token profile reference Runs from your browser on the anonymous plan Data looks wrong? Report it

The bot

Node.js 22+, no dependencies. Set TELEGRAM_TOKEN from @BotFather; CRIUS_API_KEY is optional.

// scanner-bot.mjs: TELEGRAM_TOKEN=... node scanner-bot.mjs
const TG = `https://api.telegram.org/bot${process.env.TELEGRAM_TOKEN}`;
const API = "https://api.crius.sh/v1/robinhood";
const HEADERS = process.env.CRIUS_API_KEY ? { "x-api-key": process.env.CRIUS_API_KEY } : {};
const cache = new Map(); // address -> { at, text }
const esc = (s) => String(s ?? "").replace(/[&<>]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;" })[c]); // names are user-made
const usd = (n) => (n == null ? "?" : n >= 1000 ? `$${Math.round(n).toLocaleString("en-US")}` : `$${Number(n).toPrecision(4)}`);
async function get(path, tries = 3) {
const res = await fetch(`${API}${path}`, { headers: HEADERS });
if (res.status === 429 && tries > 1) {
await new Promise((r) => setTimeout(r, Number(res.headers.get("retry-after") ?? 1) * 1000)); // rate limited: wait, retry
return get(path, tries - 1);
}
return (await res.json()).data;
}
async function scan(address) {
const hit = cache.get(address);
if (hit && Date.now() - hit.at < 60_000) return hit.text;
const [t, s] = await Promise.all([get(`/token/${address}`), get(`/token/${address}/security`)]);
if (!t) return "Token not found on Robinhood Chain.";
const warn = { critical: "DANGER: honeypot or scam", high: "HIGH RISK", medium: "Medium risk", low: "Low risk" }[s?.risk] ?? "Risk unknown";
const lines = [
`<b>${esc(t.name)}</b> (${esc(t.symbol)})`,
`<code>${address}</code>`,
`Price ${usd(t.price?.usd)} · 24h ${t.price?.change_pct?.["24h"]?.toFixed(1) ?? "?"}%`,
`Market cap ${usd(t.market?.market_cap_usd)} · Liquidity ${usd(t.market?.liquidity_usd)}`,
`Holders ${t.holders?.count ?? "?"} · Top 10 hold ${t.holders?.top10_pct?.toFixed(1) ?? "?"}%`,
t.launch ? `Launchpad ${esc(t.launch.launchpad_name)} · ${t.launch.status}` : "No launchpad",
"",
`${warn} · score ${s?.score ?? "?"}/100`,
s?.flags?.includes("lp_single_wallet") ? "One wallet holds the unlocked liquidity and can pull it at any time." : null,
`Tax buy ${s?.tax?.buy_pct ?? "?"}% · sell ${s?.tax?.sell_pct ?? "?"}% · LP ${s?.liquidity?.status ?? "?"}`,
s?.flags?.length ? `Flags: ${s.flags.join(", ")}` : "No flags",
];
const text = lines.filter((l) => l != null).join("\n");
cache.set(address, { at: Date.now(), text });
return text;
}
async function search(q) {
const hits = (await get(`/search?q=${encodeURIComponent(q)}&type=token&limit=5`)) ?? [];
if (!hits.length) return "No token with that name.";
return ["Which one? Send the address:", ...hits.map((h) => `${esc(h.name)} (${esc(h.symbol)}) · ${esc(h.launchpad_name ?? "no launchpad")} · liq ${usd(h.liquidity_usd)}\n<code>${h.address}</code>`)].join("\n\n");
}
async function reply(chatId, text) {
await fetch(`${TG}/sendMessage`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ chat_id: chatId, text, parse_mode: "HTML", link_preview_options: { is_disabled: true } }),
});
}
let offset = 0;
for (;;) {
try {
const { result = [] } = await (await fetch(`${TG}/getUpdates?timeout=30&offset=${offset}`)).json();
for (const u of result) {
offset = u.update_id + 1;
const text = u.message?.text ?? "";
const addr = text.match(/0x[0-9a-fA-F]{40}/)?.[0]?.toLowerCase();
const name = text.match(/^\/scan(?:@\w+)?\s+(.+)/)?.[1];
if (addr) await reply(u.message.chat.id, await scan(addr));
else if (name) await reply(u.message.chat.id, await search(name.trim()));
}
} catch (e) {
console.error(e);
await new Promise((r) => setTimeout(r, 3000));
}
}

What it costs

Operation Calls Credits
Scan an address /token/{address} + /token/{address}/security 2
Look up a name /search 1
Repeat scan within a minute cached 0
New-launch feed 1 stream connection + 2 per posted token 1 + 2 each

Without a key, scans are limited to 2 requests per second per IP, which a small group won’t reach. With a Free key, 1,000,000 credits cover about 500,000 scans a month. See Plans & limits.

Frequently asked questions

Do I need a Crius API key for a Telegram scanner?

Not to start. Token profile, security and search are on the anonymous plan at 2 requests per second per IP. A busy group needs more: a Free key gives 10 requests per second and 1,000,000 credits a month, about 500,000 scans.

How do I stop users scanning the wrong token with the same name?

Treat names as a search, never as an identity. /search returns every token with that name or symbol; show each one's address, launchpad and liquidity_usd, and scan only after the user sends or taps an address.

Can the bot post new launches to a channel automatically?

Yes. Subscribe to launches:all on the WebSocket stream (Free key with the stream scope), wait a minute, run the same scan and post tokens that pass. The new tokens guide has the stream client.

Does this work for Discord too?

The Crius part is the same: two calls per scan. Only the chat side changes: replace getUpdates and sendMessage with your Discord library's message events and replies.

Get your free Crius API key

Every example on this page works with a free key: higher rate limits than the anonymous plan, holders, wallets and the WebSocket stream. No card needed.

  • Robinhood Chain (4663) only, from our own archive nodes
  • REST + WebSocket
  • Pay on-chain or by card