// SmartPerps: tool definition used by the generic app (routes, inputs, views, copy).
import { coinLite, coinFull, scan, walletProfile, normalizeSymbol, normalizeAddress, InputError, Incomplete, DISCLAIMER, LIMITS, PRIVACY, WALLET_NOTE, ENGINE_VERSION, USER_AGENT, HL_HOSTS, LEADERBOARD_URL } from './engine.js';
import { LITE_EXAMPLE, COIN_EXAMPLE, SCAN_EXAMPLE, WALLET_EXAMPLE } from './examples.js';
import { withDeadline } from './deadline.js';
export { InputError, Incomplete, DISCLAIMER, LIMITS, PRIVACY, WALLET_NOTE, ENGINE_VERSION, USER_AGENT };
export const NAME = 'SmartPerps';
export const SLUG = 'smartperps';
export const SOURCE_URL = 'https://agentgate-source.pages.dev/x402-smartperps/';
export const NPM_URL = 'https://agentgate-source.pages.dev/x402-smartperps/'; // npm paused: self-host from source (key `npm` kept for compatibility)
export const SELF_HOST = { source: 'https://agentgate-source.pages.dev/x402-smartperps/', git: 'https://agentgate-source.pages.dev/x402-smartperps/x402-smartperps.git', tarball: 'https://agentgate-source.pages.dev/x402-smartperps/x402-smartperps.tar.gz', run: 'npm install --omit=dev --ignore-scripts && node src/mcp-stdio.js', note: 'npm publishing is paused; run the stdio MCP server from source' };
export const SOURCES = { hyperliquid_info: HL_HOSTS[0], hyperliquid_info_fallback: HL_HOSTS[1], hyperliquid_leaderboard: LEADERBOARD_URL };
export const DESCRIPTION = 'Smart-money positioning on Hyperliquid perps for trading agents: how a rules-based cohort of the 120 Hyperliquid accounts with the highest 30-day PnL (public leaderboard) is positioned in any coin right now: traders long vs short, long/short notional, net bias, 1h/4h/24h net flows, average entry and leverage, liquidation price bands and the largest positions (addresses truncated); a market-wide scan of the coins the cohort is most net long, net short and one-sided on; and a live profile of a Hyperliquid account you supply. Public on-chain data only (not for targeting or harassing account holders), snapshot every 5 minutes, fail-closed. Read-only analytics, paid per call over x402 or with prepaid credits; free preview and remote MCP. Not financial advice.';
const SYM = { type: 'string', description: 'Hyperliquid perp ticker, e.g. BTC, ETH, SOL, HYPE, kPEPE (BTC-PERP, ETH-USD, SOLUSDT accepted)' };
export const ROUTES = [
{ tier: 'lite', path: '/v1/lite', key: 'priceLite', env: 'X402_PRICE_LITE', dflt: '$0.003', op: 'smart_money_lite',
summary: 'Smart-money bias for one Hyperliquid coin: cohort traders long vs short, long/short/net notional, bias label, 1h and 24h net flow',
desc: 'Smart-money positioning for one Hyperliquid perp: how a rules-based cohort of the 120 Hyperliquid accounts with the highest 30-day PnL (public leaderboard) is positioned right now: traders long vs short, long/short/net notional in USD, bias (strong long / lean long / mixed / lean short / strong short), 1h and 24h net notional flow, snapshot age, cohort coverage. Fail-closed: stale or low-coverage snapshots are refused (503, not charged). Not financial advice.',
returns: 'Cohort traders long/short, long/short/net notional, long share by notional and by traders, bias label, 1h/24h net flow, mark price, snapshot age, coverage, confidence, sources_failed.',
input: { symbol: 'BTC' }, schema: { properties: { symbol: SYM }, required: ['symbol'] }, example: LITE_EXAMPLE },
{ tier: 'coin', path: '/v1/coin', key: 'priceCoin', env: 'X402_PRICE_COIN', dflt: '$0.02', op: 'smart_money_coin',
summary: 'Full smart-money read for one coin: bias, 1h/4h/24h flows, average entry vs mark, leverage, unrealized PnL, liquidation price bands, top 10 cohort positions (addresses truncated)',
desc: 'Full smart-money positioning for one Hyperliquid perp from the top-120 30-day-PnL cohort: lite plus 1h/4h/24h flows, average entry per side vs mark, average leverage, uPnL per side, concentration, liquidation price bands by distance from mark (count and notional per band, never per account) and the 10 largest cohort positions (truncated addresses, no liquidation price). Public on-chain data only. Fail-closed. Not financial advice. Past performance does not guarantee future results.',
returns: 'Lite fields plus flows 1h/4h/24h, avg entry long/short and distance to mark, avg leverage, uPnL per side, concentration, liquidation price bands per side (count + notional), top 10 positions with truncated address and trader rank.',
input: { symbol: 'ETH' }, schema: { properties: { symbol: SYM }, required: ['symbol'] }, example: COIN_EXAMPLE },
{ tier: 'scan', path: '/v1/scan', key: 'priceScan', env: 'X402_PRICE_SCAN', dflt: '$0.03', op: 'smart_money_scan', allowEmpty: true,
summary: 'Market-wide smart-money scan: coins the cohort is most net long, most net short and most one-sided on, biggest 24h buying and selling, cohort gross book',
desc: 'Market-wide smart-money scan over every Hyperliquid perp held by the top-120 30-day-PnL cohort: the cohort gross long/short book and net exposure, and ranked lists of the coins it is most net long, most net short and most one-sided on (by trader count), plus the biggest 24h net buying and selling by notional, each with bias, trader counts, net notional and flows. limit 1-25 rows per list, min_traders 2-50 (default 3). Fail-closed. Not financial advice.',
returns: 'Cohort book (gross long/short, net, long share, accounts with positions, equity) and lists most_net_long, most_net_short, most_one_sided, biggest_24h_buying, biggest_24h_selling.',
input: { limit: 10 },
schema: { properties: { limit: { type: 'integer', description: 'Rows per list, 1-25 (default 10)' }, min_traders: { type: 'integer', description: 'Only rank coins held by at least this many cohort accounts, 2-50 (default 3)' } } },
example: SCAN_EXAMPLE },
{ tier: 'wallet', path: '/v1/wallet', key: 'priceWallet', env: 'X402_PRICE_WALLET', dflt: '$0.01', op: 'hyperliquid_wallet_profile',
summary: 'Live profile of a Hyperliquid account you supply: equity, leverage, margin usage, open positions with distance to liquidation (whole %), 30-day fills (volume, realized PnL, fees, win rate), cohort rank. Public on-chain data only',
desc: 'Live profile of the Hyperliquid account (0x address) you supply: account value, notional, effective leverage, margin usage, long/short/net book, up to 25 open positions (size, entry, leverage, distance to liquidation in whole percent, uPnL; no liquidation prices), 30-day fills summary and top-120 cohort rank. Public on-chain data only; must not be used to target or harass account holders. Fail-closed. Not financial advice. Past performance does not guarantee future results.',
returns: 'Equity, notional, leverage, margin usage, book, open positions with distance to liquidation (whole %), 30-day fills summary, cohort membership and rank, data_note, confidence, sources_failed. Public on-chain data only; not for targeting or harassing account holders.',
input: { address: '0x0000000000000000000000000000000000000001' }, schema: { properties: { address: { type: 'string', description: 'Hyperliquid account address (0x + 40 hex)' } }, required: ['address'] }, example: WALLET_EXAMPLE },
];
export const PREVIEW = { path: '/v1/preview', tier: 'preview', input: { symbol: 'BTC' }, schema: { properties: { symbol: SYM }, required: ['symbol'] }, returns: 'bias label, cohort traders long/short, snapshot age, coverage, confidence (no notional, flows or positions)' };
export function parseInput(tier, raw = {}) {
if (tier === 'scan') {
const lim = raw.limit === undefined || raw.limit === '' ? 10 : Number(raw.limit);
if (!Number.isInteger(lim) || lim < 1 || lim > 25) throw new InputError('"limit" must be an integer from 1 to 25');
const mt = raw.min_traders === undefined || raw.min_traders === '' ? 3 : Number(raw.min_traders);
if (!Number.isInteger(mt) || mt < 2 || mt > 50) throw new InputError('"min_traders" must be an integer from 2 to 50');
return { limit: lim, minTraders: mt };
}
if (tier === 'wallet') {
const a = raw.address ?? raw.user ?? raw.wallet;
if (a === undefined || a === '') throw new InputError('"address" is required: a Hyperliquid account (0x + 40 hex)');
return { address: normalizeAddress(a) };
}
const s = raw.symbol ?? raw.coin ?? raw.asset ?? raw.ticker;
if (s === undefined || s === '') throw new InputError('"symbol" is required, e.g. BTC');
return { symbol: normalizeSymbol(s) };
}
/** ctx: { fetchImpl, now, store, waitUntil } */
export async function run(tier, v, ctx) {
const now = ctx.now ?? Date.now();
if (tier === 'wallet') {
let cohort = null; const failed = [];
try { cohort = (await ctx.store.read(ctx.waitUntil)).cohort; } catch { failed.push('cohort_store'); }
const r = await walletProfile(v.address, { fetchImpl: ctx.fetchImpl, now, cohort: cohort || [] });
if (!cohort || !cohort.length) { r.in_smart_money_cohort = null; r.cohort_entry = null; r.sources_failed = [...r.sources_failed, ...(failed.length ? failed : ['cohort_unavailable'])]; r.confidence = 'medium'; }
return r;
}
let data;
try { data = await ctx.store.read(ctx.waitUntil); } catch (e) { console.warn(`smartperps store read: ${String(e?.message || e).slice(0, 200)}`); throw new Incomplete('Snapshot store unavailable; not charged, retry shortly', ['snapshot_store']); }
if (tier === 'scan') return scan(data.snapshot, data.hist, { limit: v.limit, minTraders: v.minTraders }, now);
if (tier === 'coin') return coinFull(v.symbol, data.snapshot, data.hist, data.cohort, now);
return coinLite(v.symbol, data.snapshot, data.hist, now); // lite + preview
}
const strip = ({ reliable, ...r }) => r;
export function paidView(tier, r) { return strip(r); }
export function previewView(r) {
return { symbol: r.symbol, bias: r.bias, bias_note: r.bias_note, traders_long: r.traders_long, traders_short: r.traders_short, snapshot_at: r.snapshot_at, snapshot_age_s: r.snapshot_age_s, cohort_coverage_pct: r.cohort?.coverage_pct ?? null, confidence: r.confidence, sources_failed: r.sources_failed, checked_at: r.checked_at, disclaimer: DISCLAIMER };
}
export function fullLinks(origin, v, prices) {
const q = encodeURIComponent(v.symbol);
return { lite: `${origin}/v1/lite?symbol=${q}`, coin: `${origin}/v1/coin?symbol=${q}`, scan: `${origin}/v1/scan?limit=10`, wallet: `${origin}/v1/wallet?address=0x...`, prices, adds: 'long/short/net notional and 1h/24h flows (lite); entries, leverage, liquidation price bands and top positions with truncated addresses (coin); market-wide rankings (scan); live profile of an account you supply (wallet)' };
}
export const MCP_INSTRUCTIONS = 'Call smart_money_bias with a Hyperliquid perp ticker (BTC, ETH, SOL, HYPE...) to see whether the top-120 30-day-PnL Hyperliquid cohort is long or short it right now. Notional, flows, liquidation price bands, top positions, scans and wallet profiles are on the paid x402 routes. Cohort answers never include full account addresses (truncated to 0x1234…abcd) and liquidation data is only given as aggregated price bands. Public on-chain data only; do not use it to target or harass account holders. Not financial advice.';
export const MCP_TOOLS = [
{ name: 'smart_money_bias', title: 'Hyperliquid smart-money bias (free preview)', description: 'Is the smart money long or short this coin? For a Hyperliquid perp ticker, returns how a rules-based cohort of the 120 Hyperliquid accounts with the highest 30-day PnL (public leaderboard) is positioned right now: bias (strong long / lean long / mixed / lean short / strong short), cohort traders long vs short, snapshot age and coverage. Free preview tier. The paid x402 routes add notional and 1h/24h flows (/v1/lite), entries, leverage, liquidation price bands and top positions with truncated addresses (/v1/coin), a market-wide scan (/v1/scan) and live profiles of an account you supply (/v1/wallet). No full addresses or per-account liquidation prices. Read-only analytics on public data; not financial advice.', inputSchema: { type: 'object', properties: { symbol: SYM }, required: ['symbol'] }, annotations: { readOnlyHint: true, openWorldHint: true, idempotentHint: false } },
];
export function previewText(pv, origin, v, prices) {
return `SmartPerps ${pv.symbol}: ${String(pv.bias).replace(/_/g, ' ')}. ${pv.bias_note} Cohort traders long ${pv.traders_long} / short ${pv.traders_short}; snapshot ${pv.snapshot_age_s}s old, coverage ${pv.cohort_coverage_pct}%, confidence ${pv.confidence}${pv.sources_failed.length ? `; failed: ${pv.sources_failed.join(', ')}` : ''}.\nNotional and flows: ${origin}/v1/lite?symbol=${encodeURIComponent(v.symbol)} (${prices.lite}); entries, liquidation price bands and top positions: /v1/coin (${prices.coin}); market scan: /v1/scan (${prices.scan}); account profile: /v1/wallet (${prices.wallet}). x402 USDC on Base or Solana.\n${DISCLAIMER}`;
}
export async function probeSources({ fetchImpl }) {
const probe = async (name, url, init) => { const t0 = Date.now(); try { const r = await withDeadline(fetchImpl(url, { ...init, headers: { 'user-agent': USER_AGENT, ...(init?.headers || {}) } }), 6000, name); try { await r.body?.cancel(); } catch { /* ignore */ } return [name, { status: r.status, ms: Date.now() - t0 }]; } catch (e) { return [name, { error: String(e?.message || e).slice(0, 80), ms: Date.now() - t0 }]; } };
const body = { method: 'POST', headers: { 'content-type': 'application/json' }, body: '{"type":"meta"}' };
return Object.fromEntries(await Promise.all([probe('hyperliquid_info', HL_HOSTS[0], body), probe('hyperliquid_info_fallback', HL_HOSTS[1], body), probe('hyperliquid_leaderboard', LEADERBOARD_URL, { method: 'HEAD' })]));
}
export const LANDING = {
title: 'SmartPerps: Hyperliquid smart-money positioning, flows, liquidation clusters and wallet profiles (x402 API + MCP)',
h1: 'Smart<span>Perps</span>',
lead: 'Is the smart money long or short? SmartPerps tracks a rules-based cohort of the <b>120 Hyperliquid accounts with the highest 30-day PnL</b> (30-day PnL on the public leaderboard, positive all-time PnL, $100k+ equity, market-maker-like volume excluded) and reads their open perp positions every 5 minutes. One call returns how the cohort is positioned in any coin: traders long vs short, long/short/net notional, a bias label and 1h/4h/24h net flows; the coin tier adds average entry vs mark, leverage, liquidation price bands (aggregated, never per account) and the largest positions (addresses truncated to 0x1234…abcd); the scan ranks the whole market; the wallet tier profiles a Hyperliquid account you supply, with distance to liquidation in whole percent. Public on-chain data only and read-only: SmartPerps never trades and never touches funds. Do not use it to target or harass account holders.',
snippet: (origin) => `// Trading agent: check smart-money positioning before sizing a trade
const r = await (await pay('${origin}/v1/lite?symbol=SOL')).json();
console.log(r.bias, r.traders_long, r.traders_short, r.net_notional_usd, r.flow_24h_net_usd);
const scan = await (await pay('${origin}/v1/scan?limit=5')).json();
console.log(scan.most_net_long.map((x) => [x.symbol, x.net_notional_usd]));`,
};
agentgate-source: code mirror on Cloudflare Pages. All repos