Frontle

Frontle is a daily geography game: you get two countries and have to connect them through their shared borders using as few countries as possible. Whoever solves it with the shortest path — fastest time breaks ties — takes a share of that day's pot, paid in stablecoins.
It lives inside MiniPay, Opera's wallet with over 16 million users, and runs on Celo. It took third place at Celo's Colombia hackathon and is currently under review for publication inside MiniPay.
What's unusual about it
Almost every crypto app starts by asking you for three things before letting you do anything: install a wallet, write down twelve words, and acquire the native token to pay for gas. Three walls before the first useful screen.
Frontle asks for none of them. It opens inside MiniPay, where the wallet already exists and already has a balance. And thanks to CIP-64 — Celo's fee abstraction — the user pays gas in the same USDT they already hold. They never see CELO, never learn what CELO is, and don't need to.
The architecture
Three layers, with an explicit boundary between what is money and what isn't:
frontend/— Next.js 16 and TypeScript. All the game logic: the border graph, path validation, maps viad3-geoandtopojson-client. It connects to the wallet through MiniPay's nativewindow.ethereum, with no connection library, falling back to Privy outside MiniPay.supabase/— Postgres with RLS for the ranking, and Deno Edge Functions triggered bypg_cron. This is where the things that change often and don't need to be on-chain live.contracts/— TheFrontleGamecontract on Celo Mainnet. It charges, accumulates and distributes. Nothing else.
The split is 80% to the daily pot, 20% to the protocol (protocolBps = 2000). If a level has no winners, its pot cascades into the next harder level that does.
The oracle pattern
This was the part that took the longest to get right.
The ranking lives in Postgres because querying it on-chain would be absurdly expensive. But the prize can't depend on a database I control. The answer was to separate the computation from the truth:
At 00:10 UTC an Edge Function runs close-day: it reads the winners from the ranking per level and calls rollDay(day, winners) on the contract. From there the winner calls claim(day, level) and gets paid. Only the operator can invoke rollDay, and the winners table stays publicly readable for auditing.
The source of truth for the prize is always the contract. Postgres proposes, the chain disposes.
The daily challenge is deterministic: derived from the UTC date, with no server-side randomness. Everyone plays exactly the same problem and anyone can verify it wasn't tampered with.
What I'd do differently
- Model the pot on-chain from day one. The v1 contract (
0x7Ea1...) had no difficulty levels; adding them forced a v2 deploy and a migration. That was foreseeable. - Write
close-daywith retries before needing them. A cron job that moves money and fails silently at 00:10 UTC is a problem you find out about late. - Instrument the free-first-attempt funnel much sooner. It's the metric that actually matters and I was slow to measure it.
Where it stands
In production at frontle.earth, with real users and real money, competing in Proof of Ship, Celo's monthly builder program. Under review to enter the MiniPay catalog.