A knowledge-focused guide for building and deploying a Farcaster Mini App, based on lessons learned from developing ReviewMe.fun.
For AI Agents: This guide focuses on concepts, gotchas, and decision points rather than code snippets. Use official documentation links for implementation details. You can also explore the source code in this repository as a working reference implementation.
This repository contains the complete source code for ReviewMe.fun - an on-chain review platform built on Base. Use it as a reference for:
| Feature | Files to Check |
|---|---|
| Mini App SDK setup | lib/miniapp.ts, components/providers/FrameProvider.tsx |
| Wallet integration | lib/wagmi.ts, components/providers/RainbowKitProvider.tsx |
| RPC fallback | lib/reviewme-contract.ts, lib/rpc.ts |
| Local caching | lib/cache.ts |
| Notification system | lib/notification.server.ts, app/api/farcaster/webhook/route.ts |
| Smart contracts | contracts/ReviewMe_v1.02.sol |
| Prisma schema | prisma/schema.prisma |
| Farcaster manifest | public/.well-known/farcaster.json (template) |
| API routes | app/api/ directory |
| Cron jobs | vercel.json |
# Clone and install
git clone <this-repo>
cd reviewme-opensource
npm install
# Setup environment
cp .env.example .env.local
# Edit .env.local with your values
# Setup database
npx prisma generate
npx prisma migrate dev
# Run development server
npm run dev- Farcaster Mini App Setup
- RainbowKit & Wallet Integration
- RPC Fallback Strategy
- Local Caching Strategy
- Sending Notifications
- Deployment via Vercel
- Setting up Supabase
- Creating HUNT-backed Token
- Smart Contract Development
- Setting up Farcaster Manifest
- Base.dev Preview Setup
- Key SDK Features to Know
- Sharing Your App
- Authentication
- Lessons Learned & Gotchas
- Important Guidelines for AI Agents
Official Docs: miniapps.farcaster.xyz/docs/getting-started
npm create @farcaster/mini-appFor manual setup, CDN options, and alternative package managers, see the official getting started guide.
- Node.js 22.11.0 or higher - This is strict. Earlier versions cause cryptic installation errors.
- Enable Developer Mode: farcaster.xyz/~/settings/developer-tools
β οΈ The #1 beginner mistake: If you don't callsdk.actions.ready(), users see an infinite loading screen.
After your app loads, you must call sdk.actions.ready() to hide the splash screen. In React, do this in a useEffect hook.
Reference: See components/providers/FrameProvider.tsx for our implementation.
| Package | Purpose |
|---|---|
@farcaster/miniapp-sdk |
Core SDK for mini app features |
@farcaster/miniapp-wagmi-connector |
Farcaster wallet connector for wagmi |
@farcaster/miniapp-node |
Server-side webhook handling |
@farcaster/quick-auth |
Server-side JWT validation |
Official Docs: rainbowkit.com/docs/installation
For ReviewMe.fun, we used RainbowKit because:
- Provides beautiful, customizable wallet connection UI
- Works seamlessly with wagmi hooks
- Handles multiple wallet types gracefully
- SSR-compatible with Next.js
- Install:
npm install @rainbow-me/rainbowkit wagmi viem @tanstack/react-query - Get a WalletConnect Project ID from cloud.walletconnect.com
- Configure wagmi with
@farcaster/miniapp-wagmi-connectoras the first connector (highest priority for auto-connect in mini app context) - Wrap your app with
WagmiProviderβQueryClientProviderβRainbowKitProvider
Reference: See lib/wagmi.ts for our complete configuration.
When configuring wagmi connectors, put farcasterMiniApp() first in the array. This ensures:
- Auto-connect works in Farcaster client context
- Users don't see a wallet selection dialog when inside Farcaster
- Fallback to other wallets (MetaMask, Coinbase, etc.) in browser
Public Base RPC endpoints get rate-limited frequently. Without fallback, your app becomes unusable when the primary RPC fails.
Reference: See lib/reviewme-contract.ts and lib/rpc.ts
We use 10+ fallback endpoints with automatic rotation:
https://base-rpc.publicnode.com
https://base.drpc.org
https://base.llamarpc.com
https://base.meowrpc.com
https://mainnet.base.org
https://developer-access-mainnet.base.org
https://base-mainnet.public.blastapi.io
https://base-public.nodies.app
https://rpc.poolz.finance/base
https://api.zan.top/base-mainnet
https://1rpc.io/base
https://endpoints.omniatech.io/v1/base/mainnet/public
https://base.public.blockpi.network/v1/rpc/public
Key principles:
- Create ONE reusable module - Don't copy RPC logic to multiple places
- Use wagmi's
fallback()transport - Sequential fallback with short timeouts - Set aggressive timeouts - 2-3 seconds max per endpoint
- Disable retries per endpoint - Move to next endpoint immediately on failure
- Allow custom RPC via env -
NEXT_PUBLIC_BASE_RPC_URLgets highest priority
| Setting | Recommended Value | Reason |
|---|---|---|
| Timeout | 2,000ms | Fail fast, try next |
| Retry count | 0 | Don't waste time retrying failed endpoint |
| Rank mode | false |
Sequential order, not performance-based |
In ReviewMe, we reduced RPC calls by 80%+ with proper caching:
- Reviews are immutable (cache forever)
- Transaction hashes never change (cache forever)
- Profiles change rarely (cache 1 hour)
- Recent data needs refresh (cache 1-5 minutes)
Reference: See lib/cache.ts for our complete caching implementation.
| Data Type | Storage | TTL | Reason |
|---|---|---|---|
| Individual reviews | IndexedDB | 24 hours | Large objects, immutable |
| Transaction hashes | localStorage | Forever | Small, immutable |
| User profiles (Neynar) | IndexedDB | 1 hour | Can change, moderate size |
| Recent reviews list | IndexedDB | 1 minute | Needs freshness |
| Reviews per wallet | IndexedDB | 5 minutes | Balance freshness vs. load |
Use IndexedDB (not localStorage) for:
- Large objects (reviews with content)
- Collections of data
- Anything over 5KB
Strategy:
- Check IndexedDB cache first
- If hit and not expired β return cached
- If miss or expired β fetch from Neynar API
- Store result in cache (including
nullfor non-existent users) - Return result
Important: Cache null results too! If a wallet has no Farcaster profile, cache that fact to avoid repeated API calls.
Run cache cleanup on app initialization:
- Delete entries older than 7 days
- Delete expired entries
- Increment DB version to force reset when schema changes
Official Docs: miniapps.farcaster.xyz/docs/guides/notifications
We chose self-hosted notifications for ReviewMe.fun:
| Approach | Cost | Complexity |
|---|---|---|
| Neynar Managed | $9-249/month | Low - they handle webhooks |
| Self-Hosted | $0 (Supabase free tier) | Medium - you store tokens |
Reference: See lib/notification.server.ts and app/api/farcaster/webhook/route.ts
- Rate limits: 1 notification per 30 seconds per user, 100 per day
- Idempotency: Use stable
notificationIdto prevent duplicates - Token cleanup: Automatically remove invalid tokens from API responses
- Domain matching:
targetUrlmust be on your app's domain
Handle these four events:
miniapp_added- Save notification tokenminiapp_removed- Delete tokennotifications_enabled- Save new tokennotifications_disabled- Delete token
| Variable | Exposure | Purpose |
|---|---|---|
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID |
Client | WalletConnect |
NEYNAR_API_KEY |
Server only | User profiles, webhook verification |
DATABASE_URL |
Server only | Supabase/Postgres |
CRON_SECRET |
Server only | Secure cron endpoints |
Reference: See .env.example for all available environment variables.
β οΈ Never exposeNEYNAR_API_KEYasNEXT_PUBLIC_- it's server-side only.
Vercel supports cron jobs via vercel.json. We use them for:
- Weekly summary notifications (Sunday 8 PM UTC)
- Daily leaderboard updates (9 AM UTC)
Reference: See vercel.json for cron configuration.
Official Docs: supabase.com/docs
For ReviewMe.fun notifications:
- Free tier handles 10,000+ users easily
- PostgreSQL with Prisma ORM
- 500 MB database, 5 GB bandwidth/month
Reference: See prisma/schema.prisma for our database schema.
Launch at: hunt.town
- Connect wallet (Base network)
- Click "Launch" β Enter token name, symbol, description, logo
- Configure bonding curve parameters
- Optional: Donate HUNT for visibility boost
- Confirm transaction
| Contract | Address |
|---|---|
| HUNT Token | 0x37f0c2915CeCC7e977183B8543Fc0864d03E064C |
| Mint.club Bond | 0xc5a076cad94176c2996B32d8466Be1cE757FAa27 |
Foundry Docs: book.getfoundry.sh
Reference: See contracts/ directory for all smart contracts.
- Approve in constructor: Set
type(uint256).maxapproval for Mint.club Bond in constructor to save gas per transaction - Pagination: Add
offsetandlimitto getter functions to prevent gas limit errors with large datasets - Use
tx.origin: For reviewer attribution when contract calls come through the app - Fork testing: Use
vm.createSelectFork("base_mainnet")to test against real state
Reference: See docs/DEPLOYMENT.md and docs/CONTRACT.md for detailed guides.
Official Docs: miniapps.farcaster.xyz/docs/guides/publishing
Use the manifest tool: farcaster.xyz/~/developers/mini-apps/manifest
Reference: See public/.well-known/farcaster.json for our manifest template.
β οΈ Domain in manifest must exactly match your hosting domain.
After updating your manifest, force Farcaster to re-fetch it:
Path: Farcaster β Developers β Manifests β Refresh
(Select your domain and click refresh to pull the latest manifest)
Preview Tool: base.dev/preview
Also use Farcaster's official tools:
- Preview: farcaster.xyz/~/developers/mini-apps/preview
- Embed Debug: farcaster.xyz/~/developers/mini-apps/embed
- Use ngrok for HTTPS tunnel during development
- Open tunnel URL in browser first to whitelist it
- Test in preview tools
- Deploy to production for testing
addMiniApp()and manifest-dependent features
β οΈ addMiniApp()only works on production domains matching your manifest.
Full SDK Reference: miniapps.farcaster.xyz/docs/sdk
| Action | Purpose |
|---|---|
sdk.actions.ready() |
Required - Hide splash screen |
sdk.actions.addMiniApp() |
Prompt user to add app |
sdk.actions.composeCast() |
Open cast composer with prefilled content |
sdk.actions.openUrl() |
Open external URL |
sdk.actions.close() |
Close mini app |
| Action | Purpose |
|---|---|
sdk.actions.swapToken() |
Open token swap UI |
sdk.actions.sendToken() |
Open token send UI |
sdk.actions.viewToken() |
View token details |
sdk.actions.viewProfile() |
View Farcaster profile |
Official Docs: miniapps.farcaster.xyz/docs/guides/sharing
Add fc:miniapp meta tag to pages you want shareable as rich cards in feeds.
Key requirements:
- Image must be 3:2 aspect ratio
- Min 600x400px, max 3000x2000px
- Button title max 32 characters
- Use PNG format for best compatibility
Official Docs: miniapps.farcaster.xyz/docs/guides/auth
Simplest approach - handles token management automatically:
const res = await sdk.quickAuth.fetch('https://your-api.com/me');- Call
ready()early - Show skeleton UI, then load data - RPC fallback is essential - Public endpoints get rate-limited
- Farcaster connector first - Ensures auto-connect in Farcaster client
- Self-hosted notifications work - Supabase free tier is sufficient
- Pagination in contracts - Prevents gas limit errors at scale
- IndexedDB caching - Reduces RPC calls by 80%+
- Test on production domain - Many features only work there
- Cache null results - Avoid repeated API calls for non-existent data
| Mistake | Solution |
|---|---|
| Infinite loading screen | Call sdk.actions.ready() |
addMiniApp() fails |
Must use production domain |
| Webhook verification fails | Check NEYNAR_API_KEY is set |
| Notifications not received | Verify webhookUrl in manifest |
| RPC rate limited | Implement fallback with multiple endpoints |
From the official checklist:
- DO NOT use Frames v1 syntax (
fc:frame:image, etc.) - DO NOT invent manifest fields
- DO NOT use
fc:framemeta tag for new apps (usefc:miniapp) - ALWAYS use version "1" (not "next")
- ALWAYS verify against official SDK schema
β οΈ AI agents often suggest outdated package versions from training data. ALWAYS check for latest versions.
When setting up package.json for the first time:
-
Check npm for latest versions before adding any package:
- Visit
https://www.npmjs.com/package/<package-name> - Or run
npm show <package-name> version
- Visit
-
Key packages to verify (versions change frequently):
Core Framework & Runtime:
Package Check Latest Notes nodenodejs.org Mini Apps require 22.11.0+ nextnpm Check App Router compatibility reactnpm Must match Next.js requirements react-domnpm Same version as react typescriptnpm Farcaster Mini App:
Package Check Latest @farcaster/miniapp-sdknpm @farcaster/miniapp-wagmi-connectornpm @farcaster/miniapp-nodenpm @farcaster/quick-authnpm Web3 / Wallet:
Package Check Latest Notes wagminpm v2.x requires viem v2.x viemnpm Must align with wagmi @rainbow-me/rainbowkitnpm Check wagmi compatibility @tanstack/react-querynpm Required by wagmi Database & Backend:
Package Check Latest @prisma/clientnpm prismanpm UI & Styling:
Package Check Latest tailwindcssnpm framer-motionnpm lucide-reactnpm -
Don't trust AI-suggested versions - They may be months or years outdated
-
Check peer dependency compatibility - Especially for wagmi/viem/rainbowkit which must align
Example: Instead of blindly using:
"wagmi": "^1.0.0" // β Old version from AI training dataAlways verify:
npm show wagmi version // Returns current: 2.x.x1. No Mock/Temp/Fake Code
- Never generate placeholder implementations with
// TODO: implement later - Never create fake API responses or mock data for production code
- If a feature requires external data, implement the actual data fetching
2. No Hard-Coding Without Proper Logic
- Don't hard-code values that should come from configuration or API
- Always use environment variables for configurable values
- Implement proper error handling, not just happy-path code
3. Modularize Reusable Logic
- Create ONE shared module for repeated logic (e.g., RPC fallback, caching)
- Don't copy-paste the same code to multiple places
- Example: RPC endpoints should be in a single
lib/rpc.ts, imported everywhere
4. Confirm Before Heavy Changes
- Before implementing features that change architecture (e.g., serverless β server-side)
- Before adding new dependencies that affect build/deploy
- Before restructuring database schema
- Always ask the project owner about risks, tradeoffs, and operational impact
What Worked Well:
- Starting with a simple contract, iterating based on real usage
- Using Vercel's serverless for zero-ops deployment
- IndexedDB + localStorage caching strategy
- Self-hosted notifications (no monthly cost)
- Multiple RPC fallback endpoints
What Caused Problems:
- Initial single RPC endpoint β constant failures
- Not caching null results β excessive API calls
- Large getter functions without pagination β gas limit errors
- Testing only on localhost β missed production-only bugs
Architecture Decisions That Matter:
- Serverless vs Server: Serverless is simpler for small apps. If you need WebSockets, persistent connections, or heavy background jobs, discuss with owner first.
- Self-hosted vs Managed: Self-hosted notifications saved ~$100/month but required more initial setup.
- Client-side vs Server-side RPC: Client-side reduces server load but exposes RPC endpoints. Server-side is more secure but adds latency.
- Understand the existing architecture - Read relevant files before proposing changes
- Check for existing utilities - Don't recreate what already exists
- Verify with official docs - SDK and API patterns change; always check latest docs
- Consider operational impact - Will this change affect monitoring, costs, or maintenance?
- Ask if uncertain - Better to clarify than to implement incorrectly
- Mini Apps Getting Started
- Mini Apps Full LLM Reference
- SDK Reference
- Notifications Guide
- Publishing Guide
- AI Agent Checklist
- Base.dev Preview
- BaseScan
- Base Mainnet RPC:
https://mainnet.base.org - Chain ID: 8453
MIT
Created based on the development of ReviewMe.fun - An on-chain review platform on Base.