verified contracts · live grants · MiniPay checklist · DeFi protocols · one install
▾
🌐What is Celopedia? An AI skill that gives Claude (and any compatible coding agent) deep, real-time knowledge of the entire Celo ecosystem — verified contract addresses, live grant programs, MiniPay submission requirements, DeFi protocol docs, and security patterns — all queryable in natural language, right inside your editor. No more tab-switching. No more copy-pasting addresses from block explorers. Just ask.
1
Install Celopedia in one command
Run this from your project root. It installs the skill for Claude Code, Cursor, and 12 other AI agents simultaneously.
npx skills add celo-org/celopedia-skills
# Installs to Claude Code, Cursor, Cline, Codex, and more# No API key needed — all data is free
💡Once installed, just ask Claude things like "What is the verified USDT address on Celo?" or "What grants can I apply to right now?" — Celopedia answers instantly with live data.
2
150+ verified contract addresses — never guess again
Wrong contract addresses mean lost funds. Celopedia ships with Celo's canonical verified addresses for every major protocol, updated from official docs. Ask Claude directly instead of searching Celoscan:
# Ask Claude (with Celopedia installed):
"What is the Aave V3 Pool address on Celo mainnet?"
→ 0x3E59A31363E2ad014dcbc521C4a0d5757d9f3402
"What USDT adapter do I use for fee abstraction on Celo?"
→ 0x0e2a3e05bc9a16f5292a6170456a710cb89c6f72 (adapter, not the token)
"What is the USDC address on Celo?"
→ 0xceba9300f2b948710d2653dd7b07f33a8b32118c
⚠️Fee abstraction (CIP-64) uses adapter addresses for USDC/USDT — not the token addresses. Using the wrong one silently fails. Celopedia always gives you the right one.
3
Live grant matching — find funding before you build
Celopedia fetches active grant programs from celopg.eco in real time and matches your project to the right ones. Amounts, deadlines, and eligibility criteria — all current, not cached docs from 6 months ago.
# Ask Claude:
"I'm building a no-loss savings game on Celo for MiniPay users.
What grants can I apply to right now, and what's the pitch?"
→ Celopedia fetches live programs, matches your profile,
and drafts the funding angle for each one.
💡Celopedia knows about Proof of Ship, Prezenti, Celo Builder Fund, Divvi, GoodBuilders, and 20+ other programs — including which ones are closing soon.
4
MiniPay submission checklist — built in
Getting listed in MiniPay (14M+ wallets) requires passing a strict technical and UX review. Celopedia has the complete official requirements embedded: copy rules, UI constraints, contract verification standards, and country-level targeting notes.
# Ask Claude:
"Review my app.js for MiniPay compliance issues."
→ Celopedia checks for:
✅ Zero-click connect (no wallet button when isMiniPay)
✅ No personal_sign / eth_signTypedData
✅ Banned copy: "gas" → "network fee", "crypto" → "stablecoin"
✅ Only USDT / USDC / USDm — no CELO shown to users
✅ Low-balance redirect to minipay.opera.com/add_cash
✅ SVG/WebP assets only (no PNG)
✅ PageSpeed 90+ on mobile
5
DeFi protocol reference — Aave, Uniswap, Morpho and more
Celopedia includes integration guides for every major DeFi protocol deployed on Celo. Correct ABIs, pool addresses, token decimals, and Celo-specific gotchas — all in one place.
💡Celopedia also flags Celo-specific security risks: aToken ratio drift, CIP-64 fee abstraction accounting, CELO token duality, and L2 epoch boundary effects — things a generic Solidity auditor will miss.
Before building a feature, ask Celopedia if it already exists. It queries The Grid — a live index of 6,300+ crypto products — and tells you what's deployed on Celo, what exists on other chains but not on Celo, and where the gaps are.
# Ask Claude:
"Is there a no-loss lottery on Celo already?"
"What yield products are live in MiniPay today?"
"What DeFi protocols exist on other EVM chains but not on Celo?"
→ Celopedia queries The Grid in real time and gives you
a competitive landscape in seconds, not hours.
💡Zorrito was built using Celopedia — it's how we confirmed there were no direct competitors on Celo and identified which grants to apply to on day one.
Why Celopedia changes how you build on Celo
✅Never look up a contract address in a browser again
✅Know which grants exist before you finish your MVP
✅Catch MiniPay blockers before submitting for review
✅Avoid Celo-specific bugs that generic tools miss
✅Validate your idea against 6,300+ live products
✅Works in Claude Code, Cursor, Cline, Codex, and more
💡Claude Code is Anthropic's terminal-based AI agent. With the right MCP servers it can read your contracts, call RPC nodes, deploy to Vercel and push to GitHub — all in one session.
1
Install Claude Code
Install globally via npm, then authenticate with your Anthropic account.
npm install -g @anthropic-ai/claude-code
claude # opens interactive session
2
Add project-specific context to CLAUDE.md
Create a CLAUDE.md at the root of your project. Claude reads this on every session — focus it on your contract addresses and conventions. Protocol addresses (USDT, USDC, Aave, etc.) are handled automatically by Celopedia (Module 00) — no need to hardcode them here.
💡With Celopedia installed (Module 00), you can ask "What's the Aave V3 Pool address on Celo?" at any time — verified, correct, no copy-paste from block explorers.
3
Connect the Celo MCP server
MCP (Model Context Protocol) servers extend Claude with new tools. The Celo MCP gives Claude direct RPC access — it can read balances, simulate txs, and decode events without leaving the conversation.
⚠️Never put private keys in mcp_servers.json. Use env vars loaded from your shell profile instead.
4
Set your starting prompt
Paste this at the start of every Claude session to give it full project context:
You are helping me build a MiniPay mini-app on Celo.
Stack: Solidity (contract already deployed), vanilla JS frontend,
Node.js Vercel serverless functions, ethers.js v6.
RPC: https://forno.celo.org | ChainId: 42220
Always use ethers.js v6 syntax (no .connect() on provider directly,
use new ethers.JsonRpcProvider()). Ask before adding dependencies.
💡Never deploy without an automated audit pass. Pashov Audit Group publishes open-source security tools — and Claude can run them and explain every finding.
🧠Two audit layers: Slither + Pashov cover generic Solidity risks (reentrancy, overflow, access control). Celopedia (Module 00) adds the Celo-specific layer on top — aToken ratio drift, CIP-64 fee abstraction accounting, CELO token duality, and L2 epoch effects. Run both.
1
Install Slither (static analyser)
Slither is the industry-standard Solidity static analyser used by Pashov and major audit firms.
pip3 install slither-analyzer
# Then ask Claude:
"Run slither on contracts/MyContract.sol and explain every HIGH and
MEDIUM finding. Suggest a fix for each one."
2
Run the Pashov audit checklist with Claude
Paste this prompt directly into Claude Code — it will read your contract and walk through the Pashov checklist:
Read contracts/MyContract.sol and audit it against the Pashov
security checklist:
1. Reentrancy (CEI pattern, ReentrancyGuard)
2. Integer overflow / underflow (Solidity 0.8+ built-in, SafeMath)
3. Access control (onlyOwner, roles, constructor ownership)
4. Front-running / MEV exposure
5. Oracle manipulation
6. Unchecked external calls
7. Centralization risks
8. Event emissions for all state changes
For each item: PASS / FAIL / N/A with explanation.
3
Generate a unit test suite
After the audit, ask Claude to generate Hardhat tests that cover every vulnerability it found:
"Based on the audit findings, write a full Hardhat test suite in
TypeScript that covers: the happy path, reentrancy attack attempt,
unauthorized access, and edge cases around deposit/withdrawal math.
Use Celo fork (chainId 42220) via hardhat.config.ts."
🚀
Module 03
GitHub + Vercel for automatic deployments
CI/CD · env vars · preview deployments
▾
💡Connect Vercel to GitHub once — every push to main auto-deploys. Every PR gets a preview URL. Claude can push, check build logs, and fix errors in one loop.
1
Project structure for Vercel
Vercel serves frontend/ as static files and api/*.js as serverless functions. No framework needed.
my-app/
├── api/
│ ├── deposit.js # POST → interact with contract
│ └── stats.js # GET → read on-chain data
├── frontend/
│ ├── index.html
│ ├── app.js
│ └── style.css
└── vercel.json
✅• Show amounts in USD, not raw wei • Never show a seed phrase or private key UI • Use font-size: 16px minimum — MiniPay zooms in otherwise • Test on a real Android device, not just desktop Chrome
🧠The full MiniPay submission checklist — copy rules, asset formats, PageSpeed targets, country targeting — is covered by Celopedia in Module 00. Ask Claude to run a compliance review before submitting.
⚡
Module 05
Agents with x402 + MCP + Vercel
on-chain automation · MCP server · x402 payments · cron jobs
▾
💡Build a single autonomous agent like zorrito.app/agent.html — a serverless function that runs daily on Vercel, interacts with your contract, exposes MCP tools, and optionally charges AI callers via x402.
1
Agent wallet — one private key, stored in Vercel env
Your agent is just a wallet — store the private key as a Vercel environment variable and load it at runtime.
Don't await tx.wait() inside crons unless you need the receipt. Submit the tx and return — Celo's sequencing guarantees ordering.
// ✅ fast — submit and return within the 60s cron window
const tx = await contract.feed(agent.address);
res.json({ ok: true, txHash: tx.hash, status: "submitted" });
// ❌ slow — awaiting confirmation blocks for ~5s and can timeout
await tx.wait();
5
x402 payment gate — charge AI agents per API call
Wrap any endpoint with x402 so other AI agents pay USDC on Celo to call your API. No API keys, no subscriptions — pure on-chain micropayments.
const { withPaymentRequired } = require("x402-next"); // or x402-express
module.exports = withPaymentRequired(
async (req, res) => {
// Only runs after payment is verified on-chain
res.json({ ok: true, data: await getPremiumData() });
},
{
amount: "0.01", // $0.01 USDC per call
currency: "USDC",
network: "celo",
receiver: process.env.PAYMENT_ADDRESS,
}
);
💡x402 turns your Vercel function into a monetized AI service. Claude agents, LangChain bots, and any HTTP client that supports EIP-402 can call it autonomously without human approval.
📊
Module 06
Building an on-chain dashboard
ethers.js · event logs · live stats · The Graph subgraph
▾
💡Start with direct RPC calls — fast to build and free to run. When you need historical data, time-series charts, or leaderboards at scale, add a subgraph on The Graph. Zorrito uses both: RPC for live state, The Graph for the scalable analytics dashboard.
1
Batch RPC calls with Promise.all
Read multiple contract values in parallel — don't await them sequentially:
Use queryFilter with block ranges. Celo has ~5s block time so 30 days ≈ 518,400 blocks. Stay under 2,000 blocks per query or use a public indexer endpoint.
// frontend/app.js — fetch and render
async function loadStats() {
const res = await fetch("/api/stats?t=" + Date.now());
const data = await res.json();
document.getElementById("pool").textContent =
"$" + Number(data.poolSize).toFixed(2);
document.getElementById("users").textContent =
data.uniqueUsers.toLocaleString();
}
loadStats();
setInterval(loadStats, 30_000); // refresh every 30s
5
Scale with The Graph — subgraph for complex queries
Once your dashboard needs time-series charts, lifetime totals, or leaderboards, deploy a subgraph. The Graph indexes your contract events into a GraphQL API — queries are O(1) regardless of how many transactions exist. free on Studio
💡Why The Graph? Contract view functions only return current state. The Graph keeps the full history: every deposit ever made, daily volume per day, unique users per week — all pre-aggregated and queryable in milliseconds.
① Define your schema
# schema.graphql — one entity per aggregation you need
type GlobalStats @entity {
id: ID! # always "global"
totalTxCount: BigInt!
totalDepositedUSDT: BigDecimal! # lifetime gross, not net
totalDepositors: Int!
activeDepositors: Int!
}
type DayStat @entity {
id: ID! # "YYYY-MM-DD"
date: String!
txCount: Int!
depositVolume: BigDecimal!
uniqueUsers: Int!
}
② Map events to entities (AssemblyScript)
// src/mappings.ts
export function handleDeposited(event: Deposited): void {
let global = getOrCreateGlobal();
let amount = normaliseUsdt(event.params.amount);
global.totalDepositedUSDT = global.totalDepositedUSDT.plus(amount);
global.totalTxCount = global.totalTxCount + 1;
global.save();
let day = getOrCreateDayStat(getDayId(event.block.timestamp));
day.depositVolume = day.depositVolume.plus(amount);
day.txCount = day.txCount + 1;
day.save();
}
⚠️AssemblyScript gotchas: Use .toI32() not as i32. Use BigDecimal.fromString("0") not BigDecimal.zero(). Avoid parseInt — it doesn't exist. Renaming an entity changes the collection query name, so redeploy as a new version.
💡PostHog is open-source, self-hostable, and has a generous free tier. Use it to understand your real users: where they come from, how many connect their wallet, and your deposit conversion rate.
1
Add the PostHog snippet to every HTML page
Use the project API key (starts with phc_) — not the personal API key (phx_). Wrong key = silent failure, all zeros in dashboard.
⚠️Use countDistinctIf() in HogQL, NOT count(distinct CASE WHEN ...) — the CASE WHEN syntax fails silently in PostHog's ClickHouse backend.
4
Run the PostHog wizard for zero-config setup
npx -y @posthog/wizard@latest
# Auto-detects your framework and injects the snippet# Works with Next.js, Vite, vanilla HTML
🛡️
Module 08
Scaling a public dashboard without exploding your API
CDN cache · CORS · anti-abuse · 2-min refresh
▾
💡The stats dashboard at zorrito.app/stats.html handles 10,000 simultaneous users while sending The Graph just one query every 2 minutes — not 10,000. The trick is layered: CDN cache absorbs the volume, CORS blocks unauthorized callers, and the browser never talks to The Graph directly.
1
Never expose The Graph (or any data source) directly to the browser
The browser calls your Vercel API. Your API calls The Graph. The Graph URL lives in an env var — it never appears in client code.
// ❌ wrong — The Graph URL visible to anyone who opens DevTools
fetch("https://api.studio.thegraph.com/query/.../zorrito/v0.0.2", { ... })
// ✅ right — browser calls your own API, The Graph stays private
fetch("/api/graph-stats")
⚠️If you expose the subgraph URL directly, anyone can run unlimited queries against it — bypassing your cache entirely and exhausting your free-tier quota.
2
CDN cache with stale-while-revalidate
Two headers turn Vercel's global CDN into a shared cache. 10,000 users requesting the same endpoint simultaneously receive the cached response — The Graph only gets queried when the cache expires.
// api/graph-stats.js
res.setHeader("Cache-Control", "s-maxage=120, stale-while-revalidate=240");
// s-maxage=120 → CDN caches for 2 minutes (shared by all users)// stale-while- → after 2 min, CDN serves the old response instantly// revalidate=240 while fetching the fresh one in the background
The math: 10,000 users × 1 req/2min = The Graph receives 1 query per 2 minutes globally — not 10,000.
3
Restrict CORS to your own domain
Setting Access-Control-Allow-Origin: * lets any website call your API from a browser. Restrict it so only your frontend can make cross-origin requests.
Vercel's CDN caches by exact URL. A bot adding ?t=1234 generates a unique URL on every request — each one hits The Graph directly, bypassing the cache entirely.
// api/graph-stats.js — redirect any request with query params
if (Object.keys(req.query).length > 0) {
res.setHeader("Cache-Control", "no-store");
return res.redirect(307, "/api/graph-stats");
}
💡307 (Temporary Redirect) tells the browser to reuse the method (GET) and follow the redirect to the canonical cacheable URL. The browser caches this redirect too, so repeat offenders get stopped earlier.
5
Frontend polling with live countdown
Poll the API every 2 minutes. The stale-while-revalidate header means the user always gets an instant response — no loading spinner, no perceived latency. A live countdown shows when the data was last refreshed.
// frontend/stats.js
const POLL_INTERVAL = 120_000; // 2 minutes
let lastUpdated = null;
async function loadStats() {
const res = await fetch("/api/graph-stats");
const data = await res.json();
lastUpdated = Date.now();
renderDashboard(data);
}
function startCountdown() {
const el = document.getElementById("last-updated");
setInterval(() => {
if (!lastUpdated) return;
const s = Math.floor((Date.now() - lastUpdated) / 1000);
el.textContent = s < 60 ? `Updated ${s}s ago` : `Updated ${Math.floor(s/60)}m ago`;
}, 1000);
}
loadStats();
setInterval(loadStats, POLL_INTERVAL);
startCountdown();
# Building on Celo — Workshop Guide
> A hands-on guide: build a production MiniPay app from scratch using Claude, Vercel, and the Celo stack.
---
## Module 00 — Celopedia: AI-native Celo ecosystem intelligence
Install Celopedia first. It gives Claude verified contract addresses, live grant data, MiniPay submission requirements, DeFi protocol references, and Celo-specific security patterns — all queryable in natural language.
```bash
npx skills add celo-org/celopedia-skills
# Works with Claude Code, Cursor, Cline, Codex, and 10+ more agents
```
### Why Celopedia changes how you build on Celo
- **150+ verified contract addresses** — ask Claude instead of searching Celoscan. Wrong addresses = lost funds.
- **Live grant matching** — fetches active programs from celopg.eco in real time. Proof of Ship, Prezenti, Builder Fund, Divvi, and 20+ more.
- **MiniPay checklist built-in** — the full official submission requirements: copy rules, UI constraints, asset format (WebP/SVG only), PageSpeed targets, country-level targeting.
- **DeFi protocol reference** — Aave V3, Uniswap V3/V4, Morpho Blue, Mento, x402, stCELO — correct ABIs, pool addresses, and Celo-specific gotchas.
- **Ecosystem search** — query 6,300+ products via The Grid to validate your idea before building.
- **Celo-specific security patterns** — aToken ratio drift, CIP-64 fee abstraction accounting, CELO token duality, L2 epoch effects.
### Example prompts (after installing Celopedia)
```
"What is the Aave V3 Pool address on Celo mainnet?"
"What USDT adapter do I use for fee abstraction?"
"What grants can I apply to right now for my MiniPay app?"
"Review my app.js for MiniPay compliance issues."
"Is there a no-loss lottery already live on Celo?"
```
> Learn more: celopedia.celo.org
---
## Module 01 — Setting up Claude with Celo Skills
Claude Code is Anthropic's terminal-based AI agent. With the right MCP servers it can read your contracts, call RPC nodes, deploy to Vercel and push to GitHub — all in one session.
### Step 1 — Install Claude Code
```bash
npm install -g @anthropic-ai/claude-code
claude # opens interactive session
```
### Step 2 — Add project-specific context to CLAUDE.md
Create a `CLAUDE.md` at the root of your project. Claude reads this on every session. Keep it focused on YOUR contract addresses — protocol addresses (USDT, USDC, Aave, etc.) are handled automatically by Celopedia (Module 00).
```markdown
# CLAUDE.md — project-specific context only
## Network
- Chain: Celo Mainnet (chainId 42220)
- RPC: https://forno.celo.org
- Explorer: https://celoscan.io
## My Contracts
- MyContract: 0xYOUR_CONTRACT_ADDRESS
# Protocol addresses (USDT, USDC, Aave, etc.) → ask Celopedia
## Stack
- Frontend: vanilla JS, deployed on Vercel (static)
- Backend: Node.js serverless functions (api/*.js)
- Wallet: MiniPay first, then WalletConnect fallback
```
### Step 3 — Connect the Celo MCP server
```json
// ~/.claude/mcp_servers.json
{
"mcpServers": {
"celo": {
"command": "npx",
"args": ["-y", "@celo/mcp-server"],
"env": { "CELO_RPC": "https://forno.celo.org" }
}
}
}
```
> ⚠️ Never put private keys in mcp_servers.json.
### Step 4 — Starting prompt for every Claude session
```
You are helping me build a MiniPay mini-app on Celo.
Stack: Solidity (contract already deployed), vanilla JS frontend,
Node.js Vercel serverless functions, ethers.js v6.
RPC: https://forno.celo.org | ChainId: 42220
Always use ethers.js v6 syntax (no .connect() on provider directly,
use new ethers.JsonRpcProvider()). Ask before adding dependencies.
```
---
## Module 02 — Smart contract security with Pashov
Never deploy without an automated audit pass.
### Step 1 — Install Slither
```bash
pip3 install slither-analyzer
# Then ask Claude:
# "Run slither on contracts/MyContract.sol and explain every HIGH and
# MEDIUM finding. Suggest a fix for each one."
```
### Step 2 — Pashov audit checklist prompt
Paste into Claude Code:
```
Read contracts/MyContract.sol and audit it against the Pashov security checklist:
1. Reentrancy (CEI pattern, ReentrancyGuard)
2. Integer overflow / underflow (Solidity 0.8+ built-in, SafeMath)
3. Access control (onlyOwner, roles, constructor ownership)
4. Front-running / MEV exposure
5. Oracle manipulation
6. Unchecked external calls
7. Centralization risks
8. Event emissions for all state changes
For each item: PASS / FAIL / N/A with explanation.
```
### Step 3 — Generate a test suite
```
Based on the audit findings, write a full Hardhat test suite in TypeScript
that covers: the happy path, reentrancy attack attempt, unauthorized access,
and edge cases around deposit/withdrawal math.
Use Celo fork (chainId 42220) via hardhat.config.ts.
```
---
## Module 03 — GitHub + Vercel for automatic deployments
### Step 1 — Project structure
```
my-app/
├── api/
│ ├── deposit.js # POST → interact with contract
│ └── stats.js # GET → read on-chain data
├── frontend/
│ ├── index.html
│ ├── app.js
│ └── style.css
└── vercel.json
```
### Step 2 — vercel.json
```json
{
"builds": [
{ "src": "api/*.js", "use": "@vercel/node" },
{ "src": "frontend/**", "use": "@vercel/static" }
],
"routes": [
{ "src": "/api/(.*)", "dest": "api/$1.js" },
{ "src": "/(.*)", "dest": "frontend/$1" }
],
"crons": [
{ "path": "/api/daily-job", "schedule": "0 2 * * *" }
],
"functions": {
"api/daily-job.js": { "maxDuration": 60 }
}
}
```
### Step 3 — Env vars in serverless functions
```js
const privateKey = process.env.MASTER_PRIVATE_KEY;
const rpcUrl = process.env.CELO_RPC || "https://forno.celo.org";
const wallet = new ethers.Wallet(privateKey, new ethers.JsonRpcProvider(rpcUrl));
```
### Step 4 — Deploy loop with Claude
```
Deploy to production: npx vercel deploy --prod --yes
If the build fails, read the error and fix it, then redeploy.
```
---
## Module 04 — Integrating MiniPay hooks
### Step 1 — Detect MiniPay
```js
function isMiniPay() {
return !!(window.ethereum?.isMiniPay);
}
async function connectWallet() {
if (isMiniPay()) {
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
return accounts[0];
}
return await connectViaWalletConnect();
}
```
### Step 2 — Switch to Celo network
```js
const CELO_CHAIN = {
chainId: '0xA4EC',
chainName: 'Celo',
rpcUrls: ['https://forno.celo.org'],
nativeCurrency: { name: 'CELO', symbol: 'CELO', decimals: 18 },
blockExplorerUrls: ['https://celoscan.io'],
};
async function ensureCeloNetwork() {
try {
await window.ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: CELO_CHAIN.chainId }],
});
} catch (err) {
if (err.code === 4902) {
await window.ethereum.request({ method: 'wallet_addEthereumChain', params: [CELO_CHAIN] });
}
}
}
```
### Step 3 — Approve + deposit pattern
```js
async function deposit(amountUSDT) {
const amount = ethers.parseUnits(amountUSDT.toString(), 6);
const allowance = await USDT.allowance(userAddress, CONTRACT_ADDRESS);
if (allowance < amount) {
const approveTx = await USDT.approve(CONTRACT_ADDRESS, amount);
await approveTx.wait();
}
const tx = await contract.deposit(amount);
await tx.wait();
}
```
### Step 4 — MiniPay UX rules
- Show amounts in USD, not raw wei
- Never show a seed phrase or private key UI
- Fit the app in a single scrollable page (no nested modals)
- Use font-size: 16px minimum
- Test on a real Android device
---
## Module 05 — Agents with x402 + MCP + Vercel
### Step 1 — Agent wallet (one private key in Vercel env)
```js
// api/agent.js
const { ethers } = require("ethers");
const provider = new ethers.JsonRpcProvider(process.env.CELO_RPC || "https://forno.celo.org");
const agent = new ethers.Wallet(process.env.AGENT_PRIVATE_KEY, provider);
module.exports = async (req, res) => {
const contract = new ethers.Contract(CONTRACT_ADDRESS, ABI, agent);
const tx = await contract.feed(agent.address);
res.json({ ok: true, txHash: tx.hash });
};
```
### Step 2 — Schedule with Vercel cron
```json
{
"crons": [
{ "path": "/api/agent", "schedule": "50 5 * * *" }
],
"functions": {
"api/agent.js": { "maxDuration": 60 }
}
}
```
> ⚠️ Schedule before your contract's daily deadline, not at midnight.
### Step 3 — MCP server so Claude can control the agent
```js
// api/mcp.js
const { McpServer } = require("@modelcontextprotocol/sdk/server/mcp.js");
const { StreamableHTTPServerTransport } = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
const server = new McpServer({ name: "my-app-agent", version: "1.0.0" });
server.tool("get_fox_status", "Read fox state for a wallet", {
wallet: { type: "string" }
}, async ({ wallet }) => {
const status = await contract.getFoxStatus(wallet);
return { content: [{ type: "text", text: JSON.stringify(status) }] };
});
server.tool("feed_fox", "Feed the fox (costs 1 USDT)", {}, async () => {
const tx = await contract.feed(agent.address);
await tx.wait();
return { content: [{ type: "text", text: `Fed! tx: ${tx.hash}` }] };
});
module.exports = async (req, res) => {
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
res.on("close", () => transport.close());
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
};
```
Connect to Claude Desktop:
```json
{
"mcpServers": {
"my-app": { "url": "https://your-app.vercel.app/api/mcp" }
}
}
```
### Step 4 — Fire-and-forget for cron efficiency
```js
// ✅ fast — submit and return within the 60s cron window
const tx = await contract.feed(agent.address);
res.json({ ok: true, txHash: tx.hash, status: "submitted" });
// ❌ slow — awaiting confirmation blocks for ~5s and can timeout
await tx.wait();
```
### Step 5 — x402 payment gate
```js
const { withPaymentRequired } = require("x402-next");
module.exports = withPaymentRequired(
async (req, res) => { res.json({ ok: true, data: await getPremiumData() }); },
{
amount: "0.01",
currency: "USDC",
network: "celo",
receiver: process.env.PAYMENT_ADDRESS,
}
);
```
> x402 turns your endpoint into a monetized AI service — Claude agents pay USDC on Celo per call, no API keys needed.
---
## Module 06 — Building an on-chain dashboard + The Graph
Start with direct RPC calls — fast to build and free to run. When you need historical data, time-series charts, or leaderboards at scale, add a subgraph on The Graph.
### Step 1 — Batch RPC calls
```js
const [poolSize, totalUsers, myBalance] = await Promise.all([
contract.getTotalDeposited(),
contract.getUniqueDepositors(),
contract.getUserDeposit(userAddress),
]);
```
### Step 2 — Read historical events
```js
const toBlock = await provider.getBlockNumber();
const fromBlock = toBlock - 518400; // ~30 days on Celo
const events = await contract.queryFilter(
contract.filters.Deposited(),
fromBlock, toBlock
);
```
### Step 3 — Cache in serverless function
```js
module.exports = async (req, res) => {
res.setHeader("Cache-Control", "s-maxage=60, stale-while-revalidate=120");
res.setHeader("Access-Control-Allow-Origin", "*");
res.json({ ok: true, ...(await fetchOnChainData()) });
};
```
### Step 4 — Frontend auto-refresh
```js
async function loadStats() {
const data = await fetch("/api/stats?t=" + Date.now()).then(r => r.json());
document.getElementById("pool").textContent = "$" + Number(data.poolSize).toFixed(2);
}
loadStats();
setInterval(loadStats, 30_000);
```
### Step 5 — Scale with The Graph subgraph
Contract view functions only return current state. The Graph indexes every event into a GraphQL API — O(1) queries for lifetime totals, daily time-series, and leaderboards no matter how many transactions exist. Graph Studio is free.
**① Define your schema**
```graphql
# schema.graphql
type GlobalStats @entity {
id: ID! # always "global"
totalTxCount: BigInt!
totalDepositedUSDT: BigDecimal! # lifetime gross, not net balance
totalDepositors: Int!
activeDepositors: Int!
}
type DayStat @entity {
id: ID! # "YYYY-MM-DD"
date: String!
txCount: Int!
depositVolume: BigDecimal!
uniqueUsers: Int!
}
```
**② Map events in AssemblyScript**
```typescript
// src/mappings.ts
export function handleDeposited(event: Deposited): void {
let global = getOrCreateGlobal();
let amount = normaliseUsdt(event.params.amount); // divide by 1_000_000
global.totalDepositedUSDT = global.totalDepositedUSDT.plus(amount);
global.totalTxCount = global.totalTxCount + 1;
global.save();
let day = getOrCreateDayStat(getDayId(event.block.timestamp));
day.depositVolume = day.depositVolume.plus(amount);
day.txCount = day.txCount + 1;
day.save();
}
```
**③ Deploy to Graph Studio, query from Vercel**
```js
// api/graph-stats.js
const ENDPOINT = "https://api.studio.thegraph.com/query/YOUR_ID/your-app/v0.0.1";
module.exports = async (req, res) => {
res.setHeader("Cache-Control", "s-maxage=300, stale-while-revalidate=600");
const r = await fetch(ENDPOINT, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query: `{
globalStats(id: "global") {
totalTxCount totalDepositedUSDT totalDepositors activeDepositors
}
dayStats(first: 30, orderBy: date, orderDirection: desc) {
date txCount depositVolume uniqueUsers
}
}` }),
});
const { data } = await r.json();
res.json({ ok: true, source: "the-graph", ...data });
};
```
> ⚠️ AssemblyScript gotchas: Use `.toI32()` not `as i32`. Use `BigDecimal.fromString("0")` not `BigDecimal.zero()`. Avoid `parseInt` — it doesn't exist. Renaming an entity changes the collection query name, so redeploy as a new version.
---
## Module 07 — Tracking off-chain data with PostHog
### Step 1 — Add PostHog snippet (use phc_ key, NOT phx_)
```html
<script>
posthog.init('phc_YOUR_PROJECT_KEY', {
api_host: 'https://us.i.posthog.com',
person_profiles: 'always',
autocapture: true,
});
</script>
```
### Step 2 — Custom wallet events
```js
posthog.identify(userAddress);
posthog.capture('wallet_connected', { wallet_type: isMiniPay() ? 'minipay' : 'walletconnect' });
posthog.capture('deposit_completed', { amount_usdt: amount });
posthog.capture('withdraw_completed', { amount_usdt: amount });
```
### Step 3 — Server-side proxy for HogQL queries
```js
// api/analytics.js
module.exports = async (req, res) => {
res.setHeader("Cache-Control", "s-maxage=300");
const r = await fetch(
`https://us.i.posthog.com/api/projects/${process.env.PH_PROJECT_ID}/query/`,
{
method: "POST",
headers: { "Authorization": `Bearer ${process.env.PH_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({
query: {
kind: "HogQLQuery",
// ✅ Use countDistinctIf — CASE WHEN fails silently in HogQL
query: `SELECT countDistinctIf(distinct_id, event = '$pageview') as visitors,
countDistinctIf(distinct_id, event = 'wallet_connected') as connected
FROM events WHERE timestamp >= now() - interval 30 day`
}
})
}
);
res.json({ ok: true, results: (await r.json()).results });
};
```
### Step 4 — Zero-config setup wizard
```bash
npx -y @posthog/wizard@latest
```
---
*Built with ❤️ on Celo · Powered by Claude*