Imported from argenestel/memeos- (
ui/AGENTS.md). Install upstream withnpx skills add argenestel/memeos- --skill ui. Copyright stays with the author.
This is NOT the Next.js you know
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in node_modules/next/dist/docs/ before writing any code. Heed deprecation notices.
MemeOS — UI Project Context
What this project is
MemeOS is a live AI persona feed for crypto meme coin launches on Four.meme. The UI polls real token data, streams AI hot takes per persona via a local LLM proxy, caches all takes in Supabase, and lets users one-click buy via Four.meme. Wallet connection is handled by RainbowKit + wagmi.
Stack
- Framework: Next.js 16.2.4 (App Router, Turbopack)
- Language: TypeScript
- Styling: Tailwind CSS v4 +
tw-animate-css+ custom CSS variables inapp/globals.css - Component library: shadcn/ui (style:
base-nova, aliases via@/components/ui/) - Icons: lucide-react
- Animation: framer-motion (AnimatePresence + motion.div on cards)
- AI SDK: Vercel AI SDK v6 (
ai,@ai-sdk/openai,@ai-sdk/react) - Database: Supabase (Postgres) via
postgrespackage (direct SQL, no ORM) - Theme: next-themes (default: dark, suppressHydrationWarning on html)
- Wallet: RainbowKit + wagmi + @tanstack/react-query, chain: BSC only
- Utilities: clsx, tailwind-merge (via
@/lib/utils), zod (for AI structured output)
AI / LLM setup
- Local OpenAI-compatible endpoint:
http://localhost:8317/v1 - API key:
your-api-key-1 - Model:
gemini-3.1-flash-lite-preview - Server-side streaming via
streamObjectwith Zod schema →result.toTextStreamResponse() - Client-side streaming via
experimental_useObject as useObjectfrom@ai-sdk/react maxOutputTokensis the correct param name (NOTmaxTokens) in this SDK version- Each persona has a deeply polarized system prompt with forced opinion direction and strict behavioral rules so they never agree with each other
Supabase database
- Connection:
postgresql://postgres:RatEEvq%23fx17ay404GwA@db.ugkucjbnwgohmmhdebri.supabase.co:5432/postgres - Client:
lib/db.tsexports apostgresinstance withssl: 'require' - Table:
token_analysesidUUID PRIMARY KEY DEFAULT gen_random_uuid()token_addressTEXT NOT NULLpersonaTEXT NOT NULLtakeTEXT NOT NULLcreated_atTIMESTAMP WITH TIME ZONE DEFAULT now()- UNIQUE(token_address, persona) — prevents duplicate takes
- Init script:
scripts/init_db.tscreates the table
Caching flow
TokenCardmounts → callsGET /api/analysis?address=...&persona=...- If cached take exists → display instantly (no AI call, no streaming)
- If no cache → call
POST /api/completionwith{ persona, prompt, tokenAddress } - On stream finish,
/api/completionsaves the take to Supabase viaonFinishcallback - Next time the same token+persona combo is viewed, it loads from cache
Four.meme API
- Token list:
POST https://four.meme/meme-api/v1/public/token/search- Body:
{ pageIndex, pageSize, type: "NEW"|"HOT", listType: "ADV" }
- Body:
- Token detail:
GET https://four.meme/meme-api/v1/private/token/get/v2?address=0x... - Proxied via Next.js API routes:
GET /api/tokens?page=1&size=40&type=NEWGET /api/token?address=0x...GET /api/market-stats(trending, rugged, fear/greed)GET /api/analysis?address=0x...&persona=degen
- Helpers in
lib/fourmeme.ts:formatCap,formatIncrease,formatProgress,imgUrl
File structure
ui/
├── app/
│ ├── api/
│ │ ├── analysis/route.ts # GET: check Supabase for cached take
│ │ ├── completion/route.ts # POST: stream AI take + save to Supabase on finish
│ │ ├── market-stats/route.ts# GET: trending, rugged, fear/greed index
│ │ ├── token/route.ts # GET: proxies four.meme token detail
│ │ └── tokens/route.ts # GET: proxies four.meme token list search
│ ├── components/
│ │ ├── Feed.tsx # Polls /api/tokens every 30s, renders TokenCard per token
│ │ ├── Header.tsx # Top nav, MemeOS brand, RainbowKit ConnectButton
│ │ ├── Sidebar.tsx # Persona selector + track record + trending/rugged (fetched from /api/market-stats)
│ │ └── ThemeProvider.tsx # next-themes wrapper (client component)
│ ├── globals.css # Tailwind v4, tw-animate-css, CSS vars, keyframe animations
│ ├── layout.tsx # Root layout, Geist fonts, ThemeProvider
│ └── page.tsx # "use client", persona state, WagmiProvider, RainbowKitProvider, StatsPanel
├── components/
│ └── ui/ # shadcn/ui: button.tsx, card.tsx, badge.tsx
├── lib/
│ ├── db.ts # postgres client (Supabase, ssl: require)
│ ├── fourmeme.ts # Four.meme fetch helpers + type definitions
│ └── utils.ts # cn() utility (clsx + tailwind-merge)
├── scripts/
│ └── init_db.ts # Run once: creates token_analyses table
├── .env.local # DATABASE_URL for Supabase
├── components.json # shadcn config
└── package.json
Personas (deeply polarized)
| ID | Name | Emoji | Forced Opinion Direction |
|---|---|---|---|
| degen | The Degen | 🦍 | ALWAYS buys. Every metric is bullish. Uses degenerate slang. |
| doomer | The Doomer | 📉 | ALWAYS hates it. Picks apart every red flag. Bitter and sarcastic. |
| moonboy | Moonboy | 🚀 | ALWAYS predicts 1000x. Rockets, caps, delusion. Never sees risk. |
| boomer | The Boomer | 👴 | ALWAYS compares to stocks. Confused, asks about dividends, accidentally insightful. |
Default active persona: "degen" (set in page.tsx useState)
Data flow
page.tsxholdsactivePersonastate, passes down toFeedandSidebarFeedpolls/api/tokensevery 30s, deduplicates bytokenId, caps at 40 cards- Each
TokenCardon mount: a. Checks/api/analysisfor a cached take in Supabase b. If cached → display instantly, no AI call c. If not cached → callsPOST /api/completionwith{ persona, prompt, tokenAddress } /api/completionstreams AI take viastreamObject+ Zod{ take: string }- On finish,
/api/completionsaves the take totoken_analysesin Supabase - Sidebar fetches
/api/market-statsfor trending tokens, rugged tokens, fear/greed index
Key conventions
- All client components start with
"use client"directive - shadcn components imported via
@/components/ui/...(NOT./ui/...) experimental_useObject as useObjectfrom@ai-sdk/react(NOTuseObjectdirectly, NOTuseCompletion)streamObjectwith Zod schema for structured AI output (NOTstreamText)maxOutputTokensis correct (NOTmaxTokens) in AI SDK v6result.toTextStreamResponse()for streaming (NOTtoDataStreamResponse)- CSS variables from
globals.cssused inline (var(--orange),var(--green), etc.) - Images from four.meme use
imgUrl()helper to prefixhttps://static.four.meme - npm installs require
--legacy-peer-depsdue to rainbowkit/wagmi version conflict postgrespackage for Supabase (NOT Prisma, NOT supabase-js)
Known issues fixed
toDataStreamResponse()→ usetoTextStreamResponse()maxTokens→ usemaxOutputTokensai/react→ use@ai-sdk/reactuseObject→ useexperimental_useObject as useObjectfrom@ai-sdk/reactstreamText→ usestreamObjectfor structured JSON output- shadcn components in
components/ui/notapp/components/ui/ tw-animate-cssmust be installed:npm install tw-animate-css- Feed default persona was
"all"→ changed to"degen"to always pass a valid persona to AI