Tech
Architecture
The stack, the data sources, where state lives and what runs on a schedule.
Stack
| Layer | What Animr uses |
|---|---|
| App | Next.js 16 (App Router, Cache Components), React 19, Tailwind CSS 4 |
| Chain access | viem and wagmi on Monad mainnet (chain 143), TanStack Query |
| Login | Privy (wallets, email, Google, X, Discord, embedded wallets) when NEXT_PUBLIC_PRIVY_APP_ID is set; otherwise a plain browser-wallet menu |
| AI | Vercel AI SDK with an OpenAI-compatible provider for the 0G Compute Router (default model glm-5.3), zod schemas |
| State | Upstash Redis, required in production: the Vercel Marketplace's KV_REST_API_URL/TOKEN pair, or UPSTASH_REDIS_REST_URL/TOKEN. A local file store in .data/ on a laptop. |
| Contracts | Solidity 0.8 with Foundry; ERC-6551 registry v0.3.1 and Tokenbound V3 as already deployed |
| Market data | GeckoTerminal (hourly candles) and DexScreener (live price); OpenSea API for /market listings |
| Hosting | Vercel (Hobby: daily crons that fire anywhere within their hour) plus an external hourly pulse |
Data sources
- Traits:
data/chogs.json, a snapshot of all 1,969 tokens' on-chain metadata (IPFS), made byscripts/snapshot-chogs.mts. - Candles: hourly CHOG/WMON OHLCV from GeckoTerminal, with the history committed in
data/chog-ohlcv-1h.json. A 10-minute cache per instance, a shared recent copy in Redis, one refresher at a time and backoff that honoursRetry-After. - Live price: DexScreener and GeckoTerminal, cached 30 seconds, for agents.
- Chain reads: the public Monad RPC (or
NEXT_PUBLIC_RPC_URL). - Brand safety:
data/brand-safety.jsonclassifies all 327 distinct trait values.
Where state lives
Bells, rides, fan cards, Versus picks, the heist's counters and budget, agents and their decisions, rate limits and opt-outs live in Redis under animr:<namespace>:…. The namespace is ANIMR_ENV, else VERCEL_ENV (production, preview, development), else dev, so one database can serve several deployments safely. Nothing about a visitor needs a wallet: fans are anonymous httpOnly cookies.
What only production does
Redis is required in production. Without a Redis pair, state falls back to one instance's /tmp and is wiped on cold starts: Bells, rides and heist counters don't persist, and the heist fails closed because its daily 0G budget can't be enforced (the House Chog naps). On Vercel, connect Upstash Redis from the Marketplace.
Only the production deployment broadcasts. Guarded trades and trophy mints are sent only when ANIMR_ENV or VERCEL_ENV is production (lib/runtime-env.ts). A laptop or a preview that happens to hold the operator key and the contract addresses refuses to act on mainnet for its own Bells or agents.
What runs on a schedule
| When (UTC) | What | Trigger |
|---|---|---|
| 15:00 | Vercel Cron rings today's Bell; on Hobby it fires anywhere from 15:00 to 15:59 | Vercel Cron → GET /api/bell/cron |
| 15:01 | The pulse rings today's Bell after the 60-second ring lag (claimed agents think once) | External pulse, 1,11 * * * * → GET /api/bell/cron |
| 15:11 | The pulse settles yesterday's Bell (horizon plus 10 minutes' grace) and mints its Best call and Most rekt trophies | External pulse → GET /api/bell/cron |
| 16:00 | Vercel Cron settles yesterday's Bell if the pulse hasn't | Vercel Cron → GET /api/bell/cron |
| Every hour, :01 and :11 | Catch up any ring or settlement that is due | External pulse → GET /api/bell/cron |
| Every hour, :30 | Every awake agent thinks once; on-chain agents decide only here and at the ring | External pulse, 30 * * * * → GET /api/agent/tick |
| After settlement | The Oracle Purse is sent from the House key and recorded | A scheduled job the team runs: scripts/send-oracle-purse.mts |
| On request | A settled Bell's recap is written once, on its first request | GET /api/bell/recap/[day] |
| On visit | Bell pages catch up anything due | after() hook on the Bell read routes |
The pulse schedules are production's, on an external scheduler; it sends CRON_SECRET as a Bearer header, never in a URL. Both routes are idempotent and hold a lock, so overlapping triggers are safe.
AI on 0G Compute
Every LLM call goes to the 0G Compute Router (https://router-api.0g.ai/v1 by default) with ZG_API_KEY. Three features use it: the agent brain, the heist defender and Ask this Chog (plus Bell recaps). Each has a deterministic fallback when 0G is not configured or fails: the genome's rules, a template defender and template answers. Spend is bounded by the per-feature caps described on their pages.
Share cards and the Mini App
Chogs, Bells, the League, replays and Versus bouts have opengraph-image share cards rendered from the collection's own art. A Farcaster Mini App embed opens the Daily Bell inside Farcaster clients. Animr uses no AI-generated Chog art.
The header and login
The header is a game HUD. One glyph per section (Your Chogs, League, Versus, Daily Bell, Rob this Chog, Guard) with small live badges from real data: the Daily Bell's countdown (to the ride window's close while a live Bell is open, else to the next ring), today's heist attempts, and how many Chogs the logged-in wallet holds. Badges load after hydration and pause while the tab is hidden. Below 1100px the links and the wallet move into a hamburger panel, and the League's and the market's filter chips wrap instead of scrolling sideways.
Log in is Privy when NEXT_PUBLIC_PRIVY_APP_ID is set: a wallet, an email code, Google, X or Discord, with an embedded wallet for users who have none; the active wallet is bridged into wagmi. Unset, the header shows a plain browser-wallet menu.
