A Blockchain Education Mini App built for MiniPay on Celo.
Road App is a card-based learning game where players progress through three "phases" that teach the core security concepts of self-custody on Celo (anti-phishing, seed backup, smart-contract safety, L2 scaling, fee abstraction). Each phase ends with a boss battle that, when defeated, mints an on-chain NFT card as proof of learning.
Built with Next.js 14, TypeScript, Hardhat + Viem, Wagmi/RainbowKit, Tailwind and
Turborepo. Designed to satisfy MiniPay's submission requirements (single chain,
stablecoin-only display, no CELO/"crypto"/"gas" wording, fee abstraction-ready).
- Live Mini App: road-app.tajada.workers.dev
- Hackathon Post (X): MedellinBlock on X
All three contracts are live on Celo Mainnet and verified. You can inspect them on Celoscan or Blockscout.
| Contract | Address | Purpose |
|---|---|---|
| RoadAppNFTCards | 0x3e3adfd56091511e2e572e40cbb424d3bda54ca6 |
ERC-721 collection of 10 educational cards. Soulbound (non-transferable). Metadata 100% on-chain via data:application/json;base64,… so MiniPay / MetaMask / OpenSea render it without IPFS gateways. Exposes a gas-efficient getOwnedCards(player) for mobile RPC. |
| RoadAppGameState | 0xc977c3c1c48c0a24eff78c866f7fed7bfe5a1ff5 |
Player state machine + anti-cheat. Uses EIP-712 typed signatures with per-player nonces and deadline to gate recordBossDefeat and verifySeedPhraseBackup, blocking replays cross-chain and after restarts. Maintains a paginated on-chain leaderboard so the frontend never has to scan logs on Forno. |
| RoadAppDeckManager | 0x0233dad6871a563e052cc3a3681264f8e0ce37cb |
Stores each player's active battle deck (2–10 NFT cards). Validates ownership against RoadAppNFTCards on save and provides a cheap validateDeck(player) view for the battle screen. Because cards are soulbound, a saved deck can never become silently invalid. |
Security posture (audited against Celopedia
security-patterns): Ownable2Step on every contract · EIP-712 + nonce + deadline · OZ v5 ERC721Enumerable for O(balance) reads · soulbound_updatehook · checks-effects-interactions · custom errors instead ofrequire(string)· noblock.timestamp-based randomness · no function ispayable(fee abstraction handles gas in USDC/USDT/USDm).
If you fork this project, set these in apps/web/.env.local:
NEXT_PUBLIC_CHAIN_ID=42220
NEXT_PUBLIC_NFT_CARDS_ADDRESS=0x3e3adfd56091511e2e572e40cbb424d3bda54ca6
NEXT_PUBLIC_GAME_STATE_ADDRESS=0xc977c3c1c48c0a24eff78c866f7fed7bfe5a1ff5
NEXT_PUBLIC_DECK_MANAGER_ADDRESS=0x0233dad6871a563e052cc3a3681264f8e0ce37cb
CELO_RPC_URL=https://forno.celo.org-
Install dependencies:
pnpm install
-
Start the development server:
pnpm dev
-
Open http://localhost:3000 in your browser.
To test inside MiniPay on a real phone, expose your local server with ngrok and paste the HTTPS URL into MiniPay's "Open URL" developer flow.
This is a monorepo managed by Turborepo:
apps/web— Next.js 14 App Router frontend (MiniPay-ready)apps/contracts— Hardhat workspace with the 3 Solidity contracts, tests and deploy scriptsscripts/copy-abis.js— Copies compiled ABIs fromapps/contracts/artifactsintoapps/web/src/lib/abiafter each compile
pnpm dev— Start the Next.js dev serverpnpm build— Build all packages and appspnpm lint— Lint everythingpnpm type-check— TypeScript type-check
pnpm contracts:compile— Compile Solidity + sync ABIs to the web apppnpm contracts:test— Run the Hardhat + Viem test suitepnpm contracts:deploy— Deploy to local Hardhat networkpnpm contracts:deploy:celo-sepolia— Deploy to Celo Sepolia Testnet (chainId11142220)pnpm contracts:deploy:celo— Deploy to Celo Mainnet (chainId42220)
The web app is deployed to Cloudflare Workers via @opennextjs/cloudflare.
Because this is a Turborepo monorepo, Cloudflare Workers Builds runs wrangler deploy
from the repo root and would normally fail with
"Wrangler application detection logic has been run in the root of a workspace".
To make it work transparently we keep two Wrangler configs:
apps/web/wrangler.jsonc— used for local dev (pnpm preview,pnpm deploy).wrangler.jsonc(repo root) — used by Cloudflare's CI. It just pointsmainandassetsto the OpenNext output insideapps/web/.open-next/, sowrangler deployfinds a valid project at the root.
Recommended Cloudflare Workers build settings:
Locally you can run:
pnpm build:cloudflare # builds apps/web with OpenNext into apps/web/.open-next/
pnpm preview # wrangler dev (local Worker preview, uses apps/web/wrangler.jsonc)
pnpm deploy # publishes to Cloudflare Workers
⚠️ If you changename,compatibility_dateorcompatibility_flagsinapps/web/wrangler.jsonc, mirror the change in the rootwrangler.jsonc.
- Framework: Next.js 14 (App Router) + React 18
- Language: TypeScript
- Styling: Tailwind CSS + shadcn/ui
- Smart Contracts: Solidity
^0.8.20, OpenZeppelin v5, Hardhat, Viem - Wallet: Wagmi + RainbowKit (auto-detects MiniPay's injected provider)
- Monorepo: Turborepo + PNPM workspaces
