Decentralized Disaster Verification Platform
AI-powered satellite imagery analysis · Human consensus voting · On-chain proof via Solana
Nadir is a platform that combines satellite imagery, crowd-sourced human verification, and blockchain consensus to produce immutable, trustworthy disaster damage assessments.
The pipeline works like this:
Satellite Imagery (Maxar Open Data)
↓
AI Uncertainty Scoring
↓
Human Volunteer Voting (Damage / No Damage / Unsure)
↓
Consensus Engine (N-of-M threshold)
↓
On-Chain Anchoring (Solana via Anchor/Seahorse)
↓
Reputation SBTs (Metaplex Core Soulbound Tokens)
↓
First Responder Dispatch (Command Center)
- Architecture
- Prerequisites
- Quick Start
- Environment Variables
- Running with Docker
- Database Setup
- Solana / On-Chain Setup
- Project Structure
- Features & Pages
- API Reference
- Demo Walkthrough
- Satellite Data Sources
- Deployment
┌──────────────────────────────────────────────────────────────┐
│ FRONTEND (Next.js 16) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────────┐ │
│ │Dashboard │ │Satellite │ │Missions │ │ Responder │ │
│ │(Mapbox) │ │Map View │ │& Scanner │ │ Cmd Center │ │
│ └──────────┘ └──────────┘ └──────────┘ └─────────────┘ │
├──────────────────────────────────────────────────────────────┤
│ API LAYER (Next.js Routes) │
│ /api/tiles · /api/vote · /api/mosaic · /api/satellite │
│ /api/nonce · /api/user · /api/admin · /api/actions │
├──────────────────────────────────────────────────────────────┤
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────────┐ │
│ │PostgreSQL│ │TITiler │ │Solana │ │ Metaplex │ │
│ │(Prisma) │ │(COG Svr) │ │(Anchor) │ │ Core (SBT) │ │
│ └──────────┘ └──────────┘ └──────────┘ └─────────────┘ │
└──────────────────────────────────────────────────────────────┘
| Layer | Technology | Purpose |
|---|---|---|
| Frontend | React 19, Mapbox GL JS, Tailwind CSS 4 | Interactive satellite map, voting UI, dashboards |
| Auth | Privy + better-auth | Email/social login with embedded Solana wallet |
| Database | PostgreSQL + Prisma ORM | Users, tiles, votes, sessions |
| Tile Server | TITiler (self-hosted) | Dynamic rendering of Cloud-Optimized GeoTIFFs |
| Blockchain | Solana (Anchor / Seahorse) | Consensus anchoring, damage verification proofs |
| NFTs | Metaplex Core | Soulbound reputation tokens for volunteers |
| Infrastructure | Docker, DigitalOcean | Containerized deployment |
| Tool | Version | Install |
|---|---|---|
| Node.js | ≥ 20 | nodejs.org |
| pnpm | ≥ 9 | npm install -g pnpm |
| PostgreSQL | ≥ 15 | postgresql.org or Docker |
| Docker | Latest | docker.com (optional, for TITiler + production) |
| Solana CLI | ≥ 1.18 | sh -c "$(curl -sSfL https://release.solana.com/stable/install)" |
| Anchor CLI | ≥ 0.32 | cargo install --git https://github.com/coral-xyz/anchor avm --locked |
Optional (for on-chain programs):
- Rust —
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh - Seahorse —
cargo install seahorse-lang
# 1. Clone the repo
git clone https://github.com/YOUR_ORG/nadir.git
cd nadir
# 2. Install dependencies
pnpm install
# 3. Set up environment variables
cp .env.example .env
# Edit .env and fill in the required values (see table below)
# 4. Set up the database
# Option A: Local PostgreSQL
createdb nadir
pnpm prisma migrate dev
# Option B: Docker PostgreSQL (included in docker-compose)
docker compose up postgres-db -d
pnpm prisma migrate dev
# 5. Generate the Prisma client
pnpm prisma generate
# 6. Start the dev server
pnpm devOpen http://localhost:3000 to see the dashboard.
Create a .env file from the example:
cp .env.example .env| Variable | Required | Description |
|---|---|---|
DATABASE_URL |
✅ | PostgreSQL connection string (pooled) |
DIRECT_URL |
✅ | PostgreSQL direct connection (for migrations) |
NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN |
✅ | Mapbox GL JS token — get one here |
NEXT_PUBLIC_PRIVY_APP_ID |
✅ | Privy authentication app ID — privy.io |
SOLANA_RPC_URL |
⬡ | Solana RPC endpoint (defaults to http://127.0.0.1:8899) |
ADMIN_KEYPAIR_SECRET |
⬡ | JSON array of 64 bytes for the admin Solana keypair |
HELIUS_API_KEY |
⬡ | Helius RPC/webhook API key |
AUTHORITY_PRIVATE_KEY |
⬡ | Base58 private key for on-chain authority |
NEXT_PUBLIC_NADIR_MINT_ADDRESS |
⬡ | SPL token mint address |
WEBHOOK_SECRET |
⬡ | Secret for satellite webhook verification |
NEXT_PUBLIC_TITILER_URL |
⬡ | TITiler endpoint (defaults to http://localhost:8080) |
COG_CDN_URL |
⬡ | CDN URL for mirrored Cloud-Optimized GeoTIFFs |
CONSENSUS_THRESHOLD |
⬡ | Votes to reach consensus (default: 5) |
CONSENSUS_QUORUM |
⬡ | Total votes required before evaluation (default: 7) |
✅ = Required for the app to run · ⬡ = Optional / has default
The docker-compose.yml includes all services:
# Start everything (app + TITiler + PostgreSQL)
docker compose up -d --build
# Start only TITiler (for local dev with satellite imagery)
docker compose up titiler -d
# Start only PostgreSQL
docker compose up postgres-db -d
# View logs
docker compose logs -f web
# Shut down
docker compose down| Service | Port | Description |
|---|---|---|
web |
3000 | Next.js production server |
titiler |
8080 | TITiler COG tile server |
postgres-db |
5433 | PostgreSQL 15 |
The schema is managed by Prisma. Key models:
| Model | Purpose |
|---|---|
User |
Volunteers, rescuers, admins (with solanaWallet, reputationScore, role) |
Tile |
Satellite tiles with beforeUrl, afterUrl, aiUncertaintyScore, status |
Vote |
User votes on tiles (DAMAGE_FOUND / NO_DAMAGE / UNSURE) |
Session |
Auth sessions via better-auth |
# Run migrations
pnpm prisma migrate dev
# Open Prisma Studio (visual DB browser)
pnpm prisma studio
# Reset database
pnpm prisma migrate reset# Start a local Solana validator
solana-test-validator
# In a new terminal, configure CLI for local
solana config set --url http://127.0.0.1:8899
# Airdrop SOL for testing
solana airdrop 5cd nadir-consensus
# Build
seahorse build # Compile Python → Rust
anchor build # Compile Rust → BPF
# Deploy to local validator
anchor deploy
# Or deploy to Devnet
solana config set --url devnet
solana airdrop 2
anchor deployNadir uses Metaplex Core to mint frozen (soulbound) reputation NFTs:
- Minted after a user casts ≥ 5 evaluated votes
- Score (0–100) reflects voting accuracy against consensus outcomes
- Automatically updated as more votes are cast
- Frozen via
FreezeDelegateplugin — non-transferable
nadir/
├── src/
│ ├── app/ # Next.js App Router pages
│ │ ├── page.tsx # Landing → Dashboard
│ │ ├── dashboard/ # Dashboard view
│ │ ├── map/ # Satellite map explorer
│ │ ├── missions/ # Mission listing & details
│ │ ├── scan/ # Tile scanner (swipe voting)
│ │ ├── review/ # Tile review (detailed)
│ │ ├── responder/ # First responder command center
│ │ │ ├── page.tsx # Tactical heatmap with dispatch
│ │ │ ├── analytics/ # Responder analytics
│ │ │ ├── missions/ # Responder mission view
│ │ │ └── settings/ # Responder settings
│ │ ├── login/ # Sign in page
│ │ ├── signup/ # Registration
│ │ ├── profile/ # User profile & wallet
│ │ └── api/ # API routes (see API Reference)
│ ├── components/
│ │ ├── DashboardClient.tsx # Main dashboard (873 lines!)
│ │ ├── MapView.tsx # Mapbox map with mosaic overlays
│ │ ├── HeatMap.tsx # Canvas-based global heatmap
│ │ ├── Sidebar.tsx # Tile listing & filtering
│ │ ├── TileDetailPanel.tsx # Tile inspection + voting
│ │ ├── tile-scanner.tsx # Swipe-style tile verification
│ │ ├── Header.tsx # App header with stats & auth
│ │ └── ui/ # shadcn/ui components
│ ├── lib/
│ │ ├── consensus.ts # Core consensus engine + Solana anchoring
│ │ ├── reputation.ts # Reputation scoring + SBT minting
│ │ ├── umi.ts # Metaplex Core SBT operations
│ │ ├── solana.ts # Solana connection + config
│ │ ├── nonce.ts # Durable nonce accounts for offline tx
│ │ ├── satellite/ # Multi-provider satellite imagery
│ │ │ ├── types.ts # Provider contracts
│ │ │ ├── maxar.ts # Maxar Open Data
│ │ │ ├── sentinel2.ts # Sentinel-2 (medium-res)
│ │ │ ├── planet.ts # Planet Labs
│ │ │ └── umbra.ts # Umbra SAR
│ │ ├── prisma.ts # Prisma client singleton
│ │ └── auth.ts # better-auth setup
│ └── data/ # Mock/seed data
├── prisma/
│ └── schema.prisma # Database schema
├── nadir-consensus/ # Anchor/Seahorse on-chain programs
├── disaster_relief/ # Seahorse disaster relief program
├── contracts/ # Additional smart contracts
├── scripts/
│ ├── prerender-tiles.sh # Pre-render tiles to CDN
│ └── mirror-cogs.sh # Mirror COG files to DigitalOcean
├── docker-compose.yml # All services
├── Dockerfile # Multi-stage Next.js build
├── titiler.Dockerfile # TITiler tile server
└── public/ # Static assets
The main command center. Displays an interactive Mapbox satellite map with:
- 50+ disaster events from the Maxar Open Data catalog (hurricanes, earthquakes, floods, wildfires, volcanoes, landslides)
- Mosaic tile overlays rendered via TITiler from Cloud-Optimized GeoTIFFs
- Tile sidebar with filtering by status (Pending / Verified Damage / Verified Safe)
- AI uncertainty scoring for prioritizing tiles that need human review
- Catalog switching between pre/post-disaster imagery passes
- Map style switching between satellite, hybrid, and dark map layers
Dedicated satellite map view with event selection and catalog browsing.
Lists active verification missions with:
- Certainty percentage, milestone progress, escrow balance
- Region and status indicators
- Drill-down to individual mission details
A swipe-style mobile-friendly tile verification interface:
- Before/after satellite image comparison
- Keyboard shortcuts for rapid voting (← / → / ↓)
- Tag-based damage classification (structural, flooding, debris)
- Progress tracking across tile sets
Deep inspection panel for individual tiles:
- Before/after image comparison with slider
- Vote submission (Damage Found / No Damage / Unsure)
- Metadata: coordinates, AI uncertainty score, consensus status
- Solana transaction hash link after consensus anchoring
- Catalog browsing per tile
A tactical interface for first responders:
- Live heatmap of verified damage zones
- 3D tilted map with automated damage point overlays
- Target popups with satellite preview and "Initiate Dispatch" action
- GeoJSON export of all verified damage for GIS tools
- Metric cards: Priority Zones, Active Volunteers
- Hidden header for immersive full-screen experience
User profile with:
- Wallet connection status
- Reputation score and SBT details
- Organization affiliation
- Voting history
- Email/password via better-auth
- Privy embedded wallet (automatic Solana wallet provisioning)
- Solana Wallet Adapter integration (Phantom, Solflare, etc.)
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/tiles |
List all tiles (supports ?status= filter) |
GET |
/api/tiles/[id] |
Get tile by ID |
GET |
/api/tiles/next-priority |
Get next highest-priority tile for review |
GET |
/api/tiles/map-markers |
Get map marker data for all tiles |
GET |
/api/tiles-for-bbox |
Get tiles within a bounding box |
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/vote |
Submit a vote (tileId, decision) |
GET |
/api/user/votes |
Get user's voting history |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/mosaic/[slug] |
Get mosaic metadata for a disaster event |
GET |
/api/mosaic-tiles/[slug]/[...path] |
Proxy mosaic tiles from TITiler |
GET |
/api/satellite/search |
Search satellite imagery catalogs |
GET |
/api/satellite/tile |
Get individual satellite tile |
GET |
/api/satellite/providers |
List available satellite providers |
POST |
/api/satellite/aoi |
Register area of interest for monitoring |
POST |
/api/satellite/webhook |
Receive satellite imagery webhooks |
| Method | Endpoint | Description |
|---|---|---|
GET/POST |
/api/auth/[...all] |
better-auth catch-all handler |
GET |
/api/user/reputation |
Get user's reputation score |
GET |
/api/user/wallet |
Get user's wallet info |
POST |
/api/user/org |
Update user organization |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/nonce |
Get user's durable nonce |
POST |
/api/nonce/create |
Create a durable nonce account |
POST |
/api/nonce/submit |
Submit an offline-signed transaction |
| Method | Endpoint | Description |
|---|---|---|
GET/POST |
/api/actions/verify/[tileId] |
Solana Action for tile verification |
GET/POST |
/api/actions/vote/[tileId] |
Solana Action for voting |
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/admin/mint |
Admin mint operations |
Follow this sequence to showcase the full platform during a demo:
# Terminal 1: Start TITiler for satellite imagery
docker compose up titiler -d
# Terminal 2: Start the Next.js dev server
pnpm dev- Navigate to localhost:3000/signup
- Create an account with email/password
- You'll be redirected to the dashboard with an embedded Solana wallet
- Open localhost:3000 — the main satellite dashboard
- Use the event dropdown in the sidebar to select a disaster event (e.g., "Hurricane Ian", "Wildfires — Los Angeles")
- Watch as the Maxar Open Data mosaic loads over the satellite basemap
- Click individual tile footprints on the map to see before/after comparisons
- Use the filter chips (Pending / Damage / Safe) to focus on specific tile statuses
- Switch map styles between satellite, hybrid, and dark
- Click a pending tile on the map or sidebar
- The Tile Detail Panel slides open on the right
- Inspect the satellite imagery — compare before/after
- Cast your vote: Damage Found, No Damage, or Unsure
- When enough votes accumulate (default: 5/7), consensus is reached
- The result is anchored to Solana — look for the transaction hash in the tile detail
- Navigate to localhost:3000/missions
- Select an active mission
- Click Start Scanning to enter the swipe-style scanner
- Use keyboard shortcuts: ← (No Damage), → (Damage), ↓ (Unsure)
- Tag damage types: structural damage, flooding, debris
- Navigate to localhost:3000/responder
- See the tactical heatmap showing verified damage clusters
- Click individual damage points for a popup with satellite preview
- Click "Initiate Dispatch" in the popup
- Use "Export .GeoJSON Data" to download all verified damage for GIS tools
- Note the immersive full-screen layout (no header)
- Navigate to localhost:3000/profile
- Check your reputation score (accuracy % based on consensus alignment)
- After ≥ 5 votes on finalized tiles, a Soulbound Token is minted to your wallet
- Connect an external wallet via the Solana wallet button in the header
# Check the Solana explorer for anchored consensus transactions
# The tx hash appears in the Tile Detail Panel after consensus is reached
# Check SBT minting
# Use `solana account <asset-id>` to inspect a minted reputation SBTNadir supports a multi-provider architecture:
| Provider | Resolution | Pipeline | Access |
|---|---|---|---|
| Maxar Open Data | 30–50 cm | Human verification | Free (AWS S3 / GitHub) |
| Sentinel-2 | 10 m | AI background scanning | Free (Copernicus) |
| Planet Labs | 3–5 m | Both | API key required |
| Umbra | 25 cm SAR | Human verification | API key required |
Maxar events are sourced from the opengeos/maxar-open-data repository. The catalog includes 50+ disaster events spanning wildfires, hurricanes, earthquakes, floods, cyclones, volcanic eruptions, and landslides from 2015–2025.
- Push to your GitHub repo
- Create a new App on DigitalOcean App Platform
- Set environment variables in the App settings
- Add a managed PostgreSQL database
- Deploy TITiler as a separate service (or on a Droplet)
# Build and run all services
docker compose up -d --build
# The app will be available at http://localhost:3000
# TITiler at http://localhost:8080- Set
NODE_ENV=production - Configure production
DATABASE_URLwith connection pooling - Set Solana RPC to Devnet/Mainnet (not localhost)
- Configure
COG_CDN_URLfor faster tile serving - Add a real
WEBHOOK_SECRET - Run
pnpm prisma migrate deploy(notdev) - Pre-render tiles with
scripts/prerender-tiles.shfor high-traffic events
MIT
Built with 🛰️ by the Nadir team
