The chat widget routes model calls through SiloRail. Everything else degrades gracefully (defaults, no-op rate limiter). This document walks each piece from minimum-to-talk to fully wired.
| Var | Where | Required for |
|---|---|---|
SILORAIL_WALLET_KEY |
local + prod | unattended SiloRail signing |
SILORAIL_GATEWAY_URL |
local + prod | optional gateway override |
SILORAIL_MODEL |
local + prod | optional model override |
SILORAIL_PER_CALL_MAX_MICRO_USD |
local + prod | optional per-call spend cap |
SILORAIL_SESSION_MAX_MICRO_USD |
local + prod | optional per-instance spend cap |
EDGE_CONFIG |
local + prod | runtime config reads (else: defaults) |
EDGE_CONFIG_ID |
local + prod | admin writes (auto-parsed from above) |
VERCEL_API_TOKEN |
local + prod | admin saves config from /admin/chat |
VERCEL_TEAM_ID |
local + prod | only if project lives in a team |
ADMIN_PASSWORD |
prod only | HTTP Basic Auth on /admin/* |
Pull whatever you've already set on Vercel down into .env.local:
vercel link
vercel env pull .env.localThe route calls SiloRail from src/app/api/chat/route.ts through
@silorail/sdk. The browser still posts to /api/chat; only the server-side
completion path changes.
Minimum local/testnet setup:
SILORAIL_WALLET_KEY=0x...
SILORAIL_GATEWAY_URL=https://testnet.silorail.com
SILORAIL_MODEL=openrouter/free
SILORAIL_PER_CALL_MAX_MICRO_USD=50000
SILORAIL_SESSION_MAX_MICRO_USD=500000If SILORAIL_GATEWAY_URL is unset, the SDK uses the public testnet gateway.
Testnet is limited to free models. openrouter/free lets SiloRail route to an
available free model instead of pinning the chat to one upstream provider's
shared quota. SILORAIL_MODEL, when set, overrides the model from Edge Config
as well as the code default. For paid production traffic, set:
SILORAIL_GATEWAY_URL=https://mainnet.silorail.com
SILORAIL_MODEL=deepseek/deepseek-v4-proThe SDK can create a local key file automatically, but Vercel preview and
production deployments must set SILORAIL_WALLET_KEY. The route refuses to
fall back to ~/.silorail/key on Vercel because that filesystem is not a stable
place for a spending key. Fund that wallet with the USDC required by the
selected SiloRail network. The budget env vars are advisory client-side caps in
micro-USDC (50000 = $0.05). The signed x402 authorization still remains the
gateway-enforced upper bound.
This stores the editable chat key (system prompt, model, refusal text, etc.).
If unset, the chat uses DEFAULT_CONFIG from src/lib/ai/config.ts and the
admin page shows a "writes are not wired up" banner.
# In the Vercel dashboard:
# Storage → Edge Config → Create.
# Then link it to this project (Project → Settings → Storage → Connect).
# Or via CLI:
vercel link
vercel env pull .env.local # picks up EDGE_CONFIG automatically once linkedAfter linking, .env.local will contain something like:
EDGE_CONFIG=https://edge-config.vercel.com/ecfg_xxx?token=yyy
The admin save action parses the ecfg_xxx ID out of this string, so you
don't need to set EDGE_CONFIG_ID separately. Set it only if you want to
override the parsed value.
The first time you save from /admin/chat, the action upserts the key. You can
seed it manually too:
curl -X PATCH "https://api.vercel.com/v1/edge-config/$EDGE_CONFIG_ID/items" \
-H "Authorization: Bearer $VERCEL_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"items":[{"operation":"upsert","key":"chat","value":{"model":"openrouter/free"}}]}'The Edge Config SDK is read-only; writes go through the REST API.
-
Vercel → Account Settings → Tokens → Create.
-
Scope it to this project (least privilege).
-
Add to
.env.localand to the Vercel env (Project → Settings → Environment Variables) for Preview + Production:VERCEL_API_TOKEN=... # Only if the project lives under a team: VERCEL_TEAM_ID=team_xxxxxxxxxx
Without this, the /admin/chat form will validate input but the save fails
with a clear "writes not wired up" message.
src/lib/ai/ratelimit.ts is an in-memory token bucket (10 req/min/IP, burst
10). State lives in a Map on the module — Fluid Compute reuses function
instances, so the counter persists across requests on the same instance. No
env vars, no external service.
Trade-off: each instance has its own counter, so if Vercel scales out to N
instances under burst, the effective limit is 10/min × N. At this traffic
level (low, bursty around announcements) that's fine. If abuse becomes real,
swap in a shared store — sign up for Upstash directly at upstash.com (free
tier: 500k commands/month), paste UPSTASH_REDIS_REST_URL and
UPSTASH_REDIS_REST_TOKEN into the project env vars, and revert
ratelimit.ts to use Ratelimit.tokenBucket from @upstash/ratelimit.
The right next defense for a public endpoint is Vercel BotID (free, GA
since 2025-06) — it blocks scrapers/headless bots at the edge before they
hit /api/chat. Add it before the chat goes public.
src/middleware.ts gates /admin/:path* with HTTP Basic Auth. It reads
ADMIN_PASSWORD from env and prompts the browser with the built-in basic
auth dialog. Username is ignored; password is compared in constant time.
Why not Vercel Deployment Protection? On Hobby/Pro plans, Deployment Protection only gates the entire deployment — there's no per-path scoping. Gating the whole site would auth-wall the public chat widget itself, so we do the gate in code instead.
Set the password as a sensitive env var in Vercel:
vercel env add ADMIN_PASSWORD production --value '<your-password>' --yes
vercel env add ADMIN_PASSWORD preview '' --value '<your-password>' --yesBehavior:
- Production with
ADMIN_PASSWORDset → browser prompts for credentials. - Production with
ADMIN_PASSWORDunset → middleware fails closed; every/admin/*request gets a 401. A forgotten env var won't accidentally expose the admin. - Local dev → middleware fails open if the env is missing, so
localhost:3000/admin/chatworks without setting a password.
Verify in an incognito window:
https://<your-prod>/→ loads (public site unaffected)https://<your-prod>/admin/chat→ browser auth prompt → correct password lets you in, wrong password loops the prompt
content/mips/*.md ships with placeholder stubs. Replace the TODO(author)
sections with the canonical spec text for each MIP.
The whole directory is concatenated into the system prompt on every request. Watch the size — at roughly 20k tokens the cost-per-request math (see CLAUDE.md → Cost controls) starts to apply. Beyond ~30k tokens, switch to the keyword pre-filter strategy noted there before adding more files.
A quick estimate:
# rough token count (1 token ≈ 4 chars)
wc -c content/mips/*.md | tail -1 | awk '{print int($1/4)}'After steps 1–4, with the dev server running:
pnpm dev- Open the home page. The "Ask about MIPs" pill should appear bottom-right.
- Open it, type "What is MIP-3?", press Enter. You should get a streamed answer.
- Visit
/admin/chat. The form should render with the current config (or defaults if Edge Config isn't seeded). Change the system prompt, hit Save, confirm "Saved." appears. - Send another chat — the new system prompt should take effect on the next request (no deploy needed).
If step 2 says the SiloRail wallet is not configured, set
SILORAIL_WALLET_KEY for that deployment and redeploy. If it fails with a
payment or budget error, check the wallet balance, gateway URL, selected model,
and budget env vars. If it streams a refusal, the knowledge bundle is empty or
doesn't mention the topic.