DEV Community

Cover image for PlantChain: Because "Trust Me, I Planted a Tree" Was Never Going to Scale
Anshu Mandal
Anshu Mandal Subscriber

Posted on

PlantChain: Because "Trust Me, I Planted a Tree" Was Never Going to Scale

DEV Weekend Challenge: Earth Day

This is a submission for the DEV Weekend Challenge: Earth Day Edition

What I Built (And Why Planting Trees Needed a Trust Problem Solved)

Here is the thing about tree planting initiatives: everyone loves them, nobody trusts them. You donate to a reforestation campaign, get a nice certificate, and then... hope for the best? There is no way to verify that a tree actually went into the ground, let alone that someone did not just photograph the same oak from three different angles.

Enter PlantChain -- a community reforestation registry where every planting is AI-verified, publicly visible, and (eventually) recorded on-chain. Think of it as a blockchain of trust for trees, minus the blockchain part. For now.

The pitch is simple: snap a photo of your freshly planted tree, drop a pin, and let an AI agent decide if you are telling the truth. No committees, no paperwork, no "we'll get back to you in 6-8 weeks."

Demo

Live app: PlantChain on Vercel

GitHub:

plantchain-new

This project was created with Better-T-Stack, a modern TypeScript stack that combines React, TanStack Router, Convex, and more.

Features

  • TypeScript - For type safety and improved developer experience
  • TanStack Router - File-based routing with full type safety
  • TailwindCSS - Utility-first CSS for rapid UI development
  • Shared UI package - shadcn/ui primitives live in packages/ui
  • Convex - Reactive backend-as-a-service platform
  • Authentication - Better-Auth
  • Turborepo - Optimized monorepo build system

Getting Started

First, install the dependencies:

pnpm install
Enter fullscreen mode Exit fullscreen mode

Convex Setup

This project uses Convex as a backend. You'll need to set up Convex before running the app:

pnpm run dev:setup
Enter fullscreen mode Exit fullscreen mode

Follow the prompts to create a new Convex project and connect it to your application.

Copy environment variables from packages/backend/.env.local to apps/*/.env.

Then, run the development server:

pnpm run dev
Enter fullscreen mode Exit fullscreen mode

Open http://localhost:5173 in your browser to see the web application Your app will connect to the Convex cloud backend…

How it works in 30 seconds:

  1. Sign up (email + password, or Google sign-in)
  2. Hit "Log a Planting" -- pick your species, snap a photo, let the browser grab your GPS coordinates
  3. Submit. Within seconds, Gemini 2.5 Flash analyzes your photo and either verifies or rejects the submission
  4. Your planting appears in the public registry with a verification badge

No human reviewer in the loop. The AI agent handles everything from "is this actually a tree?" to "did someone already submit this exact spot?"

How I Built It

The Stack

Frontend:  React 19 + Vite + TanStack Router (file-based routing)
Backend:   Convex (real-time database + serverless functions)
Auth:      Better Auth via @convex-dev/better-auth
AI:        Gemini 3 Flash via @ai-sdk/google (Vercel AI SDK)
UI:        shadcn/ui + Tailwind CSS
Monorepo:  pnpm + Turborepo
Enter fullscreen mode Exit fullscreen mode

Four packages in a pnpm monorepo: apps/web for the frontend, packages/backend for Convex, packages/ui for shared components, and packages/config for TypeScript config.

The Verification Flow (The Fun Part)

This is where PlantChain gets interesting. When you submit a planting, here is what happens behind the scenes:

User submits photo + species + GPS coords
        |
        v
plantings.submit mutation
  -> inserts record with status: "pending"
  -> immediately schedules verification action
        |
        v
verification.verify (internalAction, runs in Node.js)
  -> fetches photo URL from Convex storage
  -> queries for existing verified plantings within 5 meters
  -> sends photo + context to Gemini 2.5 Flash
  -> parses structured JSON response
  -> updates planting to "verified" or "rejected"
Enter fullscreen mode Exit fullscreen mode

The AI prompt is deliberately opinionated. It checks three things:

  1. Does the photo show a real tree or sapling that has been planted? (Not a houseplant. Not a stock photo. Not your cat.)
  2. Are the GPS coordinates valid? (Not 0,0. Not the middle of the ocean.)
  3. Is this likely a genuine new planting?

Here is the actual verification code that talks to Gemini:

const result = await generateText({
  model: google("gemini-2.5-flash"),
  messages: [
    {
      role: "user",
      content: photoUrl
        ? [
            { type: "image", image: new URL(photoUrl) },
            { type: "text", text: prompt },
          ]
        : [{ type: "text", text: prompt }],
    },
  ],
});
Enter fullscreen mode Exit fullscreen mode

Gemini responds with structured JSON -- passed, reason, and optionally tips for the planter. If it passes but there is already a verified planting within 5 meters, the system overrides the AI and flags it as a duplicate. Trust the math over the model.

Duplicate Detection: Surprisingly Tricky

Detecting duplicates sounds easy until you remember that GPS coordinates are floating point numbers and the Earth is round. The findNearby query converts a 5-meter radius into latitude/longitude deltas, accounting for the cosine correction at different latitudes:

const degPerMeter = 1 / 111320;
const latDelta = radiusMeters * degPerMeter;
const lngDelta =
  (radiusMeters * degPerMeter) / Math.cos((latitude * Math.PI) / 180);
Enter fullscreen mode Exit fullscreen mode

It is not a full Haversine calculation -- that would be overkill for a 5-meter radius where the curvature of the Earth is basically a rounding error. But the longitude correction matters. Without it, duplicate detection gets progressively worse as you move away from the equator. A 5-meter radius in Ecuador would be a 3-meter radius in Norway. Trees deserve equal treatment regardless of latitude.

Auth: Better Auth + Convex

Authentication uses Better Auth through the @convex-dev/better-auth Convex component. Email/password plus Google OAuth. The integration is clean -- authComponent.safeGetAuthUser(ctx) in any mutation or query gives you the authenticated user or null. No middleware chains, no session juggling.

The submit mutation guards itself:

const authUser = await authComponent.safeGetAuthUser(ctx);
if (!authUser) throw new Error("Not authenticated");
Enter fullscreen mode Exit fullscreen mode

Simple. If you are not logged in, you do not get to claim you planted a tree. Seems fair.

The Frontend: Nature-Inspired, Not Nature-Cliche

I wanted the UI to feel like a forest without looking like a national park brochure. The color palette is built around deep greens (forest, forest-mid), a warm gold accent, and a soft sprout green for highlights. The hero section sets the tone:

Every tree planted, forever verified

The feed page renders plantings as cards with the photo, species, location, planter name, and a verification badge. Verified plantings get a green badge, rejected ones get red, pending gets amber. Each card also shows the AI's reasoning -- "Photo shows a healthy young oak sapling planted in soil" or "Image appears to be a screenshot, not a photograph."

The submit form uses the browser's Geolocation API to auto-detect coordinates (with a manual fallback), supports camera capture on mobile via capture="environment", and gives you a species picker with 16 common options plus a custom field.

Auth0: Giving the AI Agent an Identity

Here is a detail that is easy to overlook: when an AI agent verifies your tree planting, who is that agent? In PlantChain, the verification agent is not anonymous -- it has an identity, and that identity comes from Auth0.

The verification action pulls its agent ID from Auth0 client credentials:

const agentId = process.env.AUTH0_AGENT_CLIENT_ID ?? "local-dev-agent";
Enter fullscreen mode Exit fullscreen mode

Every verification result is stamped with this agentId, so you can trace exactly which agent made the call. Right now this is the foundation layer -- the agent authenticates via Auth0 client credentials, and that identity gets recorded alongside every verification decision.

The roadmap here is exciting. Auth0 for AI Agents opens the door to:

  • Multi-agent verification -- different agents with different specialties (one for tropical species, one for temperate) each with their own Auth0 identity and audit trail
  • Token Vault integration -- if PlantChain ever needs to verify against external APIs (satellite imagery, weather data at planting time), Auth0 Token Vault handles the OAuth dance for third-party services without us managing refresh logic
  • Fine-grained permissions -- scoping what each agent can do (verify only, verify + reject, admin override) through Auth0 roles and permissions

The @convex-dev/agent component is already registered in the Convex config, wired up and waiting. The Auth0 env vars (AUTH0_DOMAIN, AUTH0_AGENT_CLIENT_ID, AUTH0_AGENT_CLIENT_SECRET, AUTH0_AUDIENCE) are optional today -- the system gracefully falls back to "local-dev-agent" -- but when they are set, every verification carries a cryptographically verifiable agent identity. That matters when you are building a trust registry.

Solana: On-Chain Receipts for Verified Plantings

You might have noticed the solanaTxSignature field sitting in the schema:

solanaTxSignature: v.optional(v.string()),
Enter fullscreen mode Exit fullscreen mode

That is not dead code. That is the next piece of the puzzle.

The plan: once a planting is verified by the AI agent, PlantChain will record a transaction on Solana -- a permanent, tamper-proof receipt that says "this tree was planted at these coordinates, verified by this agent, at this time." Not an NFT. Not a token. Just an immutable log entry on a public ledger.

Why Solana? Speed and cost. Recording a planting receipt should cost fractions of a cent and confirm in under a second. Solana's transaction fees make it practical to record every single verified planting without the economics falling apart. Nobody wants to pay $5 in gas fees to prove they planted a sapling.

The architecture will look like this:

AI verifies planting -> status: "verified"
        |
        v
Scheduled Solana action
  -> serialize planting data (species, coords, timestamp, agentId)
  -> submit transaction to Solana
  -> store tx signature back on the planting record
        |
        v
solanaTxSignature: "5K7x...abc"
  -> anyone can verify on Solana Explorer
Enter fullscreen mode Exit fullscreen mode

The beauty is that the verification and the on-chain recording are decoupled. The AI agent does its job, the planting gets verified in the app immediately, and the Solana transaction happens asynchronously via Convex's scheduler. Users do not wait for blockchain confirmation to see their planting go green.

Combined with Auth0 agent identity, this creates a full chain of custody: who verified it (Auth0 agent ID), what was verified (photo + coordinates + species), and where the proof lives (Solana transaction). That is the kind of transparency that turns "we planted 10,000 trees" from a marketing claim into a verifiable fact.

Why Convex Was the Right Call

Convex gave me three things that would have been painful to build from scratch:

  1. Real-time queries. The feed updates live. Submit a planting in one tab, watch it appear in another. No polling, no WebSocket plumbing.
  2. Scheduled functions. ctx.scheduler.runAfter(0, internal.verification.verify, { plantingId }) fires the AI verification immediately after insert. No queue infrastructure, no cron jobs.
  3. File storage. Photo upload goes straight to Convex storage via a generated upload URL. No S3 buckets, no signed URLs, no CORS headaches.

The entire backend is five files: schema.ts, plantings.ts, verification.ts, auth.ts, and http.ts. That is the whole thing. A schema, some queries and mutations, an AI action, and auth config.

What is Next

The foundation is laid -- AI verification works, Auth0 agent identity is wired in, and the Solana schema field is ready. The immediate next steps:

  1. Solana integration goes live. The solanaTxSignature field is waiting. Once the on-chain recording action ships, every verified planting gets a permanent receipt on Solana Explorer.
  2. Auth0 Token Vault for external data. Satellite imagery APIs, weather services at planting time, soil databases -- Auth0 Token Vault will manage the OAuth tokens so the verification agent can cross-reference submissions against real-world data.
  3. Multi-agent verification. Different Auth0-authenticated agents specializing in different regions or species, each with their own identity and audit trail.

Beyond the trust infrastructure: leaderboards, planter profiles, species-level stats, and a map view so you can watch the global canopy grow in real time.

The Earth Day Connection

PlantChain exists because trust is the bottleneck in environmental action. People want to plant trees. Organizations want to fund tree planting. But the gap between "we planted 10,000 trees" and "here is verifiable proof of 10,000 trees" is enormous.

AI verification is not perfect -- Gemini can be fooled, GPS can be spoofed, and a determined bad actor will always find a way. But it raises the bar from "trust me bro" to "here is a photo, here are coordinates, an AI agent with a verifiable Auth0 identity confirmed this looks legit, and the receipt lives on Solana forever." That is a meaningful step.

Every tree planted, forever verified. Happy Earth Day.


Built with: React 19, Convex, Gemini 3 Flash, Auth0 (agent identity), Solana (on-chain receipts), Better Auth, TanStack Router, shadcn/ui, Tailwind CSS, Turborepo

Connect: GitHub @prime399

Top comments (0)