How I built a CLI agent that runs a full background check on any crypto token and rates it 1–10 in under 200 lines of TypeScript.
The Idea
Every day, thousands of tokens launch onchain. Most are scams, rugs, or honeypots. Before you ape into any token, you need answers:
- Has the dev rugged before?
- Is the supply bundled/sniped?
- Are the top 10 holders controlling too much?
- What's the cluster concentration risk?
I wanted a single command that answers all of these — an agent you run in your terminal that gives you a clear 1–10 "vibe score" before you trade.
Why OnChainOS?
OnChainOS by OKX exposes a rich set of Market APIs that provide exactly this data:
- Token advanced-info — risk control level, dev reputation, bundle/sniper holdings
- Cluster overview — holder concentration, rug pull probability, new wallet percentage
- Price info — market cap, liquidity, holders, 24h trading volume
- Token Dev Info (pump.fun) — how many tokens this dev created vs rugged
- Token Bundle Info — bundler detection for newly launched tokens
- Similar tokens — other tokens by the same dev
All of this is accessible via a single REST API with HMAC-SHA256 authentication.
Architecture
┌──────────────┐ ┌──────────────────────────────────────────┐
│ index.ts │────▶│ client.ts │
│ CLI entry │ │ Promise.all (6 parallel OnChainOS API │
│ (args/help) │ │ calls with HMAC-SHA256 auth) │
└──────────────┘ └──────────────────┬───────────────────────┘
▼
┌──────────────────────┐
│ scorer.ts │
│ Weighted 9-metric │
│ algorithm → 1-10 │
└──────────┬───────────┘
▼
┌──────────────────────┐
│ display.ts │
│ picocolors + bars + │
│ emoji verdict │
└──────────────────────┘
The Scoring Algorithm
The score is computed from 9 weighted metrics. Each metric is scored 1–10 using thresholds derived from common DeFi security heuristics:
// Weight distribution
const METRICS = [
{ name: "Risk Level", weight: 20, source: "riskControlLevel" },
{ name: "Dev Reputation", weight: 20, source: "rugCount / totalTokens" },
{ name: "Bundled Supply", weight: 10, source: "bundleHoldingPercent" },
{ name: "Sniper Holdings", weight: 10, source: "sniperHoldingPercent" },
{ name: "Concentration", weight: 10, source: "cluster level" },
{ name: "Rug Pull Prob", weight: 10, source: "rugPullPercent" },
{ name: "Top 10 Holders", weight: 10, source: "top10HoldPercent" },
{ name: "Dev Holdings", weight: 5, source: "devHoldingPercent" },
{ name: "New Wallet %", weight: 5, source: "holderNewAddressPercent" },
];
For each metric, we define thresholds that determine the score:
// Example: Top 10 Holder % scoring
const top10Thresholds: [number, number][] = [
[10, 10], // ≤10% → perfect score
[20, 8], // ≤20% → good
[30, 6], // ≤30% → moderate
[50, 4], // ≤50% → concerning
[70, 2], // ≤70% → bad
]; // >70% → 0
The final score is (weightedSum / totalWeight) * 10.
HMAC-SHA256 Authentication
The OnChainOS API uses a custom HMAC-SHA256 signing scheme. Each request needs:
export function getHeaders(auth, method, requestPath, bodyOrQuery) {
const timestamp = new Date().toISOString().slice(0, -5) + "Z";
const message = timestamp + method + requestPath + bodyOrQuery;
const signature = crypto.createHmac("sha256", auth.secretKey)
.update(message).digest("base64");
return {
"OK-ACCESS-KEY": auth.apiKey,
"OK-ACCESS-SIGN": signature,
"OK-ACCESS-TIMESTAMP": timestamp,
"OK-ACCESS-PASSPHRASE": auth.passphrase,
"OK-ACCESS-PROJECT": auth.projectId,
};
}
Key gotcha: the timestamp format must match the official JS example — without milliseconds (.slice(0, -5) + "Z").
Parallel API Calls
All 6 endpoint calls fire in parallel using Promise.all, making the total check take only as long as the slowest endpoint (~300-500ms):
const [priceInfo, advancedInfo, clusterInfo] = await Promise.all([
postJson("dex/market/price-info", [...]);
getJson("dex/market/token/advanced-info", {...});
getJson("dex/market/token/cluster/overview", {...});
]);
For meme-token chains (Solana, BSC), we additionally query memepump-specific endpoints for dev reputation and bundle detection.
CLI Output
The display module uses picocolors (a 2KB zero-dep alternative to chalk) for color-coded output:
- 🟢 Green (score ≥ 8) — "Solid vibes"
- 🟡 Yellow (score ≥ 6) — "Decent, some caution"
- 🟠 Orange (score ≥ 4) — "Risky"
- 🔴 Red (score < 4) — "High risk"
Each metric gets a visual bar:
Risk Level: LOW ████████░░ 8/10
Top 10 Holders: 9.59% ██████████ 10/10
What I Learned
-
Parallelism matters —
Promise.allover 6 endpoints cut response time from ~1.5s to ~400ms - HMAC gotchas — timestamp format discrepancies between the docs examples caused auth failures. Stick with the code examples, not the prose.
- Not all endpoints work everywhere — memepump endpoints only work on Solana/BSC/TRON. Handle gracefully.
- TypeScript pays off — 10+ API response interfaces caught 3 field name typos before runtime
- Picocolors > Chalk — for CLI tools, 2KB vs 20KB makes a real difference
Ideas to Extend
- WebSocket mode — subscribe to the OnChainOS signal channel for real-time alerts on new tokens, then auto-score them
- Auto-trade agent — combine the vibe checker with the OnChainOS DEX Swap API: score ≥ 7? Execute a buy
- Telegram bot — wrap the CLI in a bot for on-the-go token research
- Dashboard — log scores to SQLite and build a Svelte dashboard showing trends over time
Try It Yourself
git clone https://github.com/harishkotra/token-vibe.git
cd token-vibe
npm install
cp .env.example .env
# Fill in your OKX API keys
npx tsx src/index.ts -t 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 -c 1
Get your free API keys at OKX Developer Portal.
Code and more: https://www.dailybuild.xyz/project/165-token-vibe
Top comments (0)