celo-donation: The Farcaster Mini App That Turns a Donation Button Into a Full Stack

A Next.js starter that wires discovery, payments, uploads, and identity into one onchain social app, with the hard parts hidden in the plumbing.

8 min read • View on GitHub • More from sodofi

A Farcaster feed card opens like a mechanical panel, with the donation button sitting on top of a dense machine of metadata, readiness signals, payment rails, and analytics wires. It explains that the visible UI is only the surface of a much larger launch and identity stack.
The donation button is real, but it only works because the launch machinery underneath is already in place.
Key Takeaways

The donation button is the bait, not the story

The first trick in celo-donation is that the donation button is almost beside the point. The repo reads like a tiny fundraising app, but it behaves like a complete Farcaster Mini App template, with discovery metadata, SDK readiness, payment rails, uploads, and analytics all wired together. The visible UI is the least interesting layer.

That matters because mini apps fail in the seams. If the manifest is wrong, the app is hard to discover. If the SDK handshake is late, the shell never feels native. If payments, uploads, and identity tracking live in separate mental buckets, the whole thing degrades into a browser tab with a wallet button.

The launch path is the real product

A Farcaster mini app is not just a page. It is a sequence of discovery, activation, and value flow that has to work in order.

The repo's opening move is the right one: make the Farcaster client recognize the app before you worry about the donation form. The critical pieces live in src/app/.well-known/farcaster.json, the page metadata, and the use-miniapp-sdk.ts hook that calls sdk.actions.ready() as soon as the view is live. In other words, the app earns its seat inside Farcaster before it asks for money.

useEffect(() => {
  let mounted = true;

  async function boot() {
    await sdk.actions.ready();
    if (mounted) setReady(true);
  }

  boot();

  const offAdded = sdk.events.on("miniAppAdded", () => setPinned(true));
  const offNotifs = sdk.events.on("notificationsEnabled", () => setNotifications(true));

  return () => {
    mounted = false;
    offAdded();
    offNotifs();
  };
}, []);

The repo even marks the metadata section as a danger zone, and that warning is deserved. Mini-app discovery can fail on a single malformed meta block, so the template treats launchability as a first-class feature, not a build step to check later.

Why the donation flow feels simple on purpose

Once the shell is live, the donation experience is almost austere. The template keeps the choice set tiny, usually preset amounts like 1, 2, 5, or 10 USDC, then hands the transfer to Daimo Pay. That constraint is the design decision. It avoids the usual crypto-dapp clutter of address fields, chain confusion, and modal sprawl.

A close-up of a compact payment console with four raised donation buttons, one being pressed as a thin path lights up toward a small receipt and thank-you marker. It explains how the template turns a payment into a single, legible state change instead of a messy sequence of dialogs.
The template keeps the payment path narrow so the handoff feels like one action, not five.

The post-payment state matters just as much as the payment. A clean thank-you screen turns a successful transfer into a complete interaction, which is rare in web3 demos. The repo treats that as product work, not decoration.

const [status, setStatus] = useState<"idle" | "paying" | "thank-you">("idle");

async function donate(amount: number) {
  setStatus("paying");
  await pay(amount);
  setStatus("thank-you");
}

Identity-aware analytics makes the template useful

The smartest non-payment choice is the analytics layer. In src/app/providers.tsx, the app looks for the Farcaster FID and aliases the session in PostHog, which lets a founder see repeat behavior without inventing a separate identity system. That is a small implementation detail with a big product effect: anonymous traffic stops being disposable.

The same pattern makes the template easier to extend. If you add uploads with Supabase, or another onchain action later, the event trail stays attached to the same social identity layer. That is what turns a demo into a surface you can iterate on.

What this template replaces

Project typeWhat it teachesWhat it leaves outIdentity modelBest use case
Generic Next.js web3 starterHow to scaffold a dappMini-app discovery, Farcaster readiness, analytics, uploadsWallet firstStarting from zero
Celo crowdfunding tutorialHow to build a simple crowdfunding flow on CeloFarcaster behavior and social identityWallet firstLearning onchain transfers
Bare Farcaster mini-app integrationHow to launch inside FarcasterPayments, uploads, and product analyticsFarcaster aware but thinSpec compliance
celo-donationHow to ship a Farcaster-native donation templateDeep domain logic for a single campaignFID linked and session awareBuilding reusable social onchain apps

So the repo's value is not that it does one thing well. It gives you the default architecture for a Farcaster-native app that can discover, launch, transact, and remember users without splitting those concerns across different starters.

Why Celo still matters

Celo is the rail, not the thesis. It gives the template a practical payment target and a low-friction stablecoin story, but the broader idea is platform design: onchain social apps work better when the social client, the payment flow, and the analytics layer are designed together. That is why this repo feels less like a donation example and more like a blueprint.