Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Query crypto token market rankings and performance data via Binance Skills Hub for AI agents.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
scripts/cli.mjs
1#!/usr/bin/env node2// crypto-market-rank CLI — self-contained, zero-dep, Node >= 223// Usage: node cli.mjs <command> '<json_params>'4//5// Commands:6// social-hype GET social buzz leaderboard (sentiment + summary)7// token-rank POST unified rank (Trending / TopSearch / Alpha / Stock)8// smart-money-inflow POST token rank by smart money net inflow9// meme-rank GET top meme tokens from Pulse launchpad (BSC only)10// address-pnl-rank GET top trader PnL leaderboard11//1213// ---- inline HTTP helper (self-contained, zero dependency) ----14const TIMEOUT_MS = 10_000;15const UA = { 'Accept-Encoding': 'identity', 'User-Agent': 'binance-web3/3.0 (Skill)' };1617const qs = (p) => Object.entries(p)18.filter(([, v]) => v != null)19.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)20.join('&');2122async function call({ url, method = 'GET', body, headers = {} }) {23const ctrl = new AbortController();24const timer = setTimeout(() => ctrl.abort(), TIMEOUT_MS);25const opts = { method, headers: { ...UA, ...headers }, signal: ctrl.signal };26if (method === 'POST') { opts.headers['content-type'] = 'application/json'; opts.body = JSON.stringify(body || {}); }27let res;28try { res = await fetch(url, opts); }29catch { clearTimeout(timer); throw Object.assign(new Error('Network request failed'), { exitCode: 3 }); }30clearTimeout(timer);31const data = await res.json();32if (res.status >= 400) throw Object.assign(new Error(`HTTP ${res.status}`), { exitCode: 1, body: data });33return data;34}3536// ---- per-command supported chains (client-side fail-fast) ----37const CHAINS = {38'social-hype': new Set(['56', '8453', 'CT_501']),39'token-rank': new Set(['56', '8453', 'CT_501', '1']),40'smart-money-inflow': new Set(['56', 'CT_501', '8453']),41'meme-rank': new Set(['56']),42'address-pnl-rank': new Set(['56', 'CT_501', '8453', '1']),43};4445function validateChainId(cmd, chainId) {46const allowed = CHAINS[cmd];47if (!allowed) return;48const id = String(chainId ?? '');49if (!allowed.has(id)) {50const supported = [...allowed].map((c) => `"${c}"`).join(', ');51throw Object.assign(52new Error(`${cmd}: unsupported chainId "${chainId}". Supported: ${supported}`),53{ exitCode: 1 },54);55}56}5758// ---- commands: (params) => { url, method?, body?, headers? } ----59const COMMANDS = {60'social-hype': (p) => {61validateChainId('social-hype', p.chainId);62return {63url: `https://web3.binance.com/bapi/defi/v1/public/wallet-direct/buw/wallet/market/token/pulse/social/hype/rank/leaderboard/ai?${qs(p)}`,64};65},66'token-rank': (p) => {67validateChainId('token-rank', p.chainId);68const TRENDING_ALPHA_DEFAULTS = {69countMin: 10,70launchTimeMin: 15,71liquidityMin: 5000,72uniqueTraderMin: 10,73volumeMin: 10000,74};75const TRENDING_DEFAULTS = {76tagFilter: [1, 2, 3],77};7879const rankType = p.rankType ?? 10;80let body = { ...p };8182if (rankType === 10) {83// Trending: apply both shared + trending-only defaults (caller params take precedence)84body = { ...TRENDING_ALPHA_DEFAULTS, ...TRENDING_DEFAULTS, ...p };85} else if (rankType === 20) {86// Alpha: apply only shared defaults (caller params take precedence)87body = { ...TRENDING_ALPHA_DEFAULTS, ...p };88}8990return {91url: 'https://web3.binance.com/bapi/defi/v1/public/wallet-direct/buw/wallet/market/token/pulse/unified/rank/list/ai',92method: 'POST',93body,94};95},96'smart-money-inflow': (p) => {97validateChainId('smart-money-inflow', p.chainId);98return {99url: 'https://web3.binance.com/bapi/defi/v1/public/wallet-direct/tracker/wallet/token/inflow/rank/query/ai',100method: 'POST',101body: { ...p, tagType: p.tagType ?? 2 },102};103},104'meme-rank': (p) => {105validateChainId('meme-rank', p.chainId);106return {107url: `https://web3.binance.com/bapi/defi/v1/public/wallet-direct/buw/wallet/market/token/pulse/exclusive/rank/list/ai?${qs(p)}`,108};109},110'address-pnl-rank': (p) => {111validateChainId('address-pnl-rank', p.chainId);112return {113url: `https://web3.binance.com/bapi/defi/v1/public/wallet-direct/market/leaderboard/query/ai?${qs(p)}`,114};115},116};117118// ---- exports (for unit testing; direct execution still works — see dispatch below) ----119export { COMMANDS, call, qs, UA, TIMEOUT_MS, CHAINS, validateChainId };120121// ---- CLI dispatch (only runs when executed directly, not when imported) ----122if (import.meta.url === `file://${process.argv[1]}`) {123const [cmd, paramsStr] = process.argv.slice(2);124125if (!cmd || cmd === '--help' || cmd === '-h') {126console.log("Usage: node cli.mjs <command> '<json_params>'\n\nCommands:");127for (const name of Object.keys(COMMANDS)) console.log(` ${name}`);128process.exit(0);129}130131const builder = COMMANDS[cmd];132if (!builder) { console.error(`Unknown command: ${cmd}\nRun with --help to see available commands.`); process.exit(1); }133134let params = {};135if (paramsStr) {136try { params = JSON.parse(paramsStr); }137catch { console.error('Invalid JSON params'); process.exit(1); }138}139140try {141const result = await call(builder(params));142console.log(JSON.stringify(result, null, 2));143} catch (err) {144console.error(err.message);145if (err.body) console.log(JSON.stringify(err.body, null, 2));146process.exit(err.exitCode || 1);147}148}149