Docs
How SI is built, the public API it exposes, and how to run it for free.
Architecture
One Next.js application. Data retrieval, rules, explanation and presentation are separate modules:
lib/providers: DEX Screener, Solana RPC and EVM RPC adapters (Robinhood Chain, Ethereum, Base, BNB), each with timeouts and typed errors.lib/analysis/evm.ts: ABI decoding and bytecode inspection for owner, proxy, mint, pause, blacklist, fee and trading-switch functions.lib/analysis/normalize.ts: turns raw provider payloads into facts. Pure and unit tested.lib/analysis/rules.ts: deterministic rules with published thresholds. Pure and unit tested.lib/analysis/verdict.ts: the built-in voice.llm.tsis an optional, guarded rewrite.lib/analysis/scan.ts: orchestration, streaming progress, caching and in-flight de-duplication.lib/arena: moderation, claim detection and checking.lib/store: JSON file persistence and the in-process event bus behind the live feed.
Credentials never reach the browser. The browser only talks to this app’s own API routes.
Public API
Scan a token (streaming)
GET /api/scan?address=<token>&chain=robinhood|solana|ethereum|base|bnb[&force=1]
Content-Type: application/x-ndjson
{"type":"step","id":"market","status":"done","detail":"3 pair(s) indexed","ms":412}
...
{"type":"report","cached":false,"report":{ ... }}Which chain is this 0x address on?
GET /api/detect?address=0x… → { chain: "base" | null, reason: "pairs" | "code" | "known" | "ambiguous" | "none" | "unreachable", options: [...], candidates: [...] }Asks DEX Screener for indexed pairs and each EVM chain’s RPC for contract code, in parallel. Pairs win (highest liquidity), then code on exactly one chain. Code on several chains is ambiguous; nothing found is none; no source answering is unreachable. In those cases chain is null and the visitor picks: SI never falls back to a default chain. /api/scan and the Arena run the same lookup when a 0x address arrives without a chain, and refuse (422) rather than guess.
Latest report
GET /api/report/<token>?chain=<chain> → { report, history, scanCount, rank }SI Score and the ranked board
report.score → { points: 0..100, tier: "fortified" | "solid" | "watchlist" | "fragile" | "hazard", confidence, checks, deductions: [...], cappedBy }
GET /api/rank?chain=<chain>&limit=50 → { rows: [{ address, chain, symbol, points, tier, liquidityUsd, scannedAt, ... }], total, mode }The SI Score starts at 100 and loses points for every finding by severity (critical 55, high 22, medium 9, low 4, no data 2) and 7 for each core check without data. It can never say more than the posture label: a critical finding caps it at 24, high at 44, elevated at 64, moderate at 84; fewer than half the checks with data caps it at 59. Tiers: 85+ FORTIFIED, 65+ SOLID, 45+ WATCHLIST, 25+ FRAGILE, below HAZARD. It measures what SI could see. It is not a return prediction and never a buy or sell call. The board ranks real scans only.
Arena
GET /api/arena?limit=20&before=<iso>
POST /api/arena { "text": "ownership is renounced", "address": "0x…", "chain": "robinhood" }
GET /api/waitlist { count }
POST /api/waitlist { "handle": "@you", "wallet": "0x…" }
GET /api/arena/<id>Live feed
GET /api/live?limit=50&type=signal (JSON)
GET /api/live/stream (Server-Sent Events, event: feed)Share cards
GET /api/card/scan/<chain>/<token> 1200×630 PNG
GET /api/card/arena/<id> 1200×630 PNGOperator endpoints
GET /api/health
GET /api/cron/observe Authorization: Bearer $CRON_SECRET
GET /api/admin/moderate Authorization: Bearer $ADMIN_TOKEN
POST /api/admin/moderate { "id": "...", "status": "hidden" }
POST /api/admin/corrections { "subject": "...", "note": "..." }
GET /api/admin/waitlist?format=csvRate limits apply per IP: scans and challenges are throttled to protect the free upstream APIs.
Configuration
| Variable | Purpose | Default |
|---|---|---|
NEXT_PUBLIC_SITE_URL | Public URL, used for share links and cards | http://localhost:3000 |
SOLANA_RPC_URL | Your own Solana RPC endpoint (optional) | public mainnet RPC |
ROBINHOOD_RPC_URL | Your own Robinhood Chain RPC (optional) | rpc.mainnet.chain.robinhood.com |
ETHEREUM_RPC_URL | Your own Ethereum RPC (optional) | ethereum-rpc.publicnode.com |
BLOCKSCOUT_URL_ROBINHOOD / _ETHEREUM / _BASE / _BNB | Keyless Blockscout explorer per chain: largest holders and holder counts | Robinhood, Ethereum and Base instances; none for BNB |
ETHERSCAN_API_KEY | Etherscan API V2 key, server-only: verified source, deployer, deployment time, recent transfers | none (explorer checks off) |
ETHERSCAN_CHAINS | Chains to ask Etherscan about (Robinhood Chain is robin.etherscan.io). A chain the plan does not cover is skipped after one refusal | ethereum,robinhood,base |
BASE_RPC_URL | Your own Base RPC (optional) | mainnet.base.org |
BNB_RPC_URL | Your own BNB Chain RPC (optional) | bsc-dataseed.binance.org |
HELIUS_API_KEY | Free Helius key: faster RPC and token metadata (optional) | none |
LLM_API_URL / LLM_MODEL / LLM_API_KEY | Any OpenAI-compatible endpoint, e.g. local Ollama (optional) | built-in rules engine |
SCAN_CACHE_SECONDS | Reuse a scan for this long | 60 |
DATA_DIR | Where the JSON store lives. Keep it outside the app and deploy folders | ~/agent-si-data |
WATCHER_INTERVAL_MINUTES | Scan newly listed tokens on a timer (0 = off) | 0 |
CRON_SECRET | Protects /api/cron/observe | none (endpoint disabled) |
NEXT_PUBLIC_X_HANDLE, NEXT_PUBLIC_TELEGRAM_URL, NEXT_PUBLIC_DISCORD_URL, NEXT_PUBLIC_GITHUB_URL, NEXT_PUBLIC_DEXSCREENER_URL, NEXT_PUBLIC_CONTACT_EMAIL | The social bar. Unset channels show as "soon" | none |
ADMIN_TOKEN | Protects moderation and corrections endpoints | none (endpoints disabled) |
SI_DEMO | Serve labelled fixtures instead of live data | false |
NEXT_PUBLIC_TOKEN_ADDRESS | Official contract once deployed | empty = not deployed |
NEXT_PUBLIC_TOKEN_VENUE_URL / _NAME | Official trading venue; the Buy button appears only when set | none (no Buy button) |
NEXT_PUBLIC_X_HANDLE | X account for Follow buttons and post tags | none |
NEXT_PUBLIC_HELMET_GLB_URL | Production 3D helmet (e.g. /models/agent-si-helmet.glb); HIGH tier only, falls back to the 2.5D helmet | none (2.5D helmet) |
Run it
npm install
npm run build
npm start # http://localhost:3000On a VPS, run one instance with PM2 in fork mode so the live feed and store share a process, and put Nginx in front with proxy_buffering off for /api/live/stream and /api/scan. The README in the repository has the full config.