The EcoTask API server β proof verification, task management, and Stellar oracle.
A Node.js/Express backend that bridges the real world and the blockchain β processing proof submissions, coordinating validators, and triggering on-chain rewards.
ecotask-backend is the off-chain infrastructure layer of EcoTask. It acts as the trusted bridge between user actions in the mobile app and the Stellar smart contracts that release rewards.
The backend is responsible for:
- ποΈ Task management β Creating, listing, and expiring tasks
- πΈ Proof intake β Receiving photo + GPS submissions from the mobile app
- π Verification coordination β Routing proofs to community validators or automated checks
- βοΈ Stellar oracle β Submitting verified results to the
reward-enginesmart contract - π Analytics β Tracking impact metrics (trees planted, plastic collected, COβ offset)
- π Notifications β Alerting users when their proof is verified and reward is sent
| Module | What It Does |
|---|---|
| ποΈ Task API | CRUD for tasks; filter by location, type, reward, status |
| π€ Proof API | Accept photo uploads, extract GPS metadata, pin to IPFS |
| π Verification Engine | Queue-based system routing proofs to validators |
| βοΈ Stellar Oracle | Signs and submits reward transactions to Soroban contracts |
| π€ User API | Profile, wallet linking, impact history |
| π Analytics API | Aggregated platform & user impact statistics |
| π Auth | JWT-based auth with Stellar wallet signature verification |
| Layer | Technology | Why |
|---|---|---|
| Runtime | Node.js 20 | Fast, async-first, huge ecosystem |
| Framework | Express 4 | Lightweight, flexible REST APIs |
| Database | PostgreSQL 15 | Reliable relational data for tasks & users |
| ORM | Prisma | Type-safe DB queries with easy migrations |
| Queue | BullMQ + Redis | Async proof verification job queue |
| File Storage | IPFS (via Web3.Storage) | Decentralised, permanent proof storage |
| Blockchain | Stellar SDK (JS) | Submit transactions to reward-engine contract |
| Auth | JWT + Stellar keypair | Wallet-based authentication |
| Validation | Zod | Runtime schema validation |
| Testing | Jest + Supertest | Unit & integration tests |
| Docs | Swagger / OpenAPI | Auto-generated API documentation |
ecotask-backend/
βββ src/
β βββ routes/ # Express route definitions
β β βββ tasks.ts # GET/POST /tasks
β β βββ proofs.ts # POST /proofs
β β βββ users.ts # GET/PUT /users
β β βββ auth.ts # POST /auth/login, /auth/verify
β β βββ notifications.ts # GET /notifications
β β βββ analytics.ts # GET /analytics
β β
β βββ controllers/ # Route handler logic
β β βββ taskController.ts
β β βββ proofController.ts
β β βββ userController.ts
β β βββ authController.ts
β β βββ analyticsController.ts
β β
β βββ services/ # Core business logic
β β βββ verificationService.ts # Proof review & scoring
β β βββ stellarService.ts # Stellar SDK + oracle calls
β β βββ ipfsService.ts # Upload proofs to IPFS
β β βββ notificationService.ts # Push notification dispatch
β β βββ geoService.ts # Location validation & distance
β β
β βββ middleware/ # Express middleware
β β βββ auth.ts # JWT verification
β β βββ rateLimit.ts # Request throttling
β β βββ upload.ts # Multer file upload config
β β βββ errorHandler.ts # Global error handling
β β
β βββ models/ # Prisma schema types & helpers
β β βββ task.ts
β β βββ proof.ts
β β βββ user.ts
β β
β βββ workers/ # BullMQ background jobs
β β βββ verificationWorker.ts # Process proof verification queue
β β βββ rewardWorker.ts # Trigger Stellar reward payouts
β β
β βββ utils/ # Shared helpers
β β βββ logger.ts # Structured logging
β β βββ stellarUtils.ts # Key formatting & signing helpers
β β βββ ipfsUtils.ts # CID formatting & gateway URLs
β β
β βββ app.ts # Express app setup
β
βββ prisma/
β βββ schema.prisma # Database schema
β βββ migrations/ # DB migration history
β
βββ tests/
β βββ routes/ # Route integration tests
β βββ services/ # Service unit tests
β
βββ config/
β βββ default.ts # Default config values
β βββ production.ts # Production overrides
β
βββ .env.example
βββ Dockerfile
βββ docker-compose.yml
βββ package.json
- Node.js >= 20
- PostgreSQL 15
- Redis (for BullMQ job queue)
- A Stellar testnet account (funded via Friendbot)
# 1. Clone the repo
git clone https://github.com/ecotask-network/ecotask-backend.git
cd ecotask-backend
# 2. Install dependencies
npm install
# 3. Set up environment variables
cp .env.example .env
# Fill in your database URL, Stellar keys, IPFS token, etc.
# 4. Run database migrations
npx prisma migrate dev
# 5. Seed the database with sample tasks
npm run db:seed
# 6. Start the development server
npm run devdocker-compose.yml runs just the supporting services β PostgreSQL and Redis.
The API itself runs on your host:
# 1. Start Postgres and Redis in the background
docker-compose up -d
# 2. Install, configure, migrate and run the API
npm install
cp .env.example .env # fill in DATABASE_URL, JWT_SECRET, Stellar keys, etc.
npx prisma migrate dev
npm run dev
# API available at http://localhost:3000# Server
PORT=3000
NODE_ENV=development
CORS_ORIGIN=*
LOG_LEVEL=info
# Database
DATABASE_URL=postgresql://user:password@localhost:5432/ecotask
# Redis
REDIS_URL=redis://localhost:6379
# Queue retention
PROOF_VERIFICATION_QUEUE_COMPLETED_RETENTION_COUNT=1000
PROOF_VERIFICATION_QUEUE_FAILED_RETENTION_SECONDS=604800
REWARD_PAYOUT_QUEUE_COMPLETED_RETENTION_COUNT=1000
REWARD_PAYOUT_QUEUE_FAILED_RETENTION_SECONDS=604800
NOTIFICATION_DISPATCH_QUEUE_COMPLETED_RETENTION_COUNT=1000
NOTIFICATION_DISPATCH_QUEUE_FAILED_RETENTION_SECONDS=604800
# Background jobs
EXPIRY_SWEEP_INTERVAL_MS=900000
# Stellar
STELLAR_NETWORK=testnet
STELLAR_ORACLE_SECRET_KEY=YOUR_ORACLE_SECRET_KEY
REWARD_ENGINE_CONTRACT_ID=YOUR_CONTRACT_ID
# IPFS
WEB3_STORAGE_TOKEN=YOUR_WEB3_STORAGE_TOKEN
# Auth
JWT_SECRET=your_jwt_secret_here
JWT_EXPIRES_IN=7d
# Rate limits (per-user)
PROOF_RATE_LIMIT_WINDOW_MS=3600000
PROOF_RATE_LIMIT_MAX=20
CLAIM_RATE_LIMIT_WINDOW_MS=3600000
CLAIM_RATE_LIMIT_MAX=50
# Notifications
NOTIFICATION_WEBHOOK_TIMEOUT_MS=5000
NOTIFICATION_EMAIL_FROM=EcoTask <[email protected]>
# Community validation
VALIDATOR_ASSIGNMENT_COUNT=3
VALIDATOR_QUORUM_REQUIRED=2Completed jobs are capped independently for each BullMQ queue. Failed jobs remain available for investigation for the configured number of seconds and are then removed as newer failed jobs finish.
To apply the same policy to jobs accumulated before retention was enabled:
npm run build
npm run queues:cleanupTo run the isolated Redis load check and confirm queue keys and memory remain bounded:
npm run build
npm run queues:verify-retentionProduction startup rejects missing JWT secrets and all development or documentation placeholders stored in this repository. This denial list does not measure secret strength; generate a fresh random secret for every deployment.
The endpoint reference below is the authoritative API documentation for the
current version. The API is versionless for now; once stable, breaking changes
will be gated behind /v1 and /v2 prefixes.
GET /api/tasks # List tasks (filter by type, location, status, reward)
GET /api/tasks/:id # Get single task details
POST /api/tasks # Create a task (admin/sponsor only)
PUT /api/tasks/:id # Update task (admin only)
DELETE /api/tasks/:id # Delete/expire a task
POST /api/tasks/:id/claim # Claim a task (24h claim window)
DELETE /api/tasks/:id/claim # Release a claim
GET /api/tasks/:id/claims # List active claims for a task
Tasks accept an optional maxCompletions capacity; once that many proofs are
approved the task is auto-marked COMPLETED. Overdue ACTIVE tasks are
flipped to EXPIRED by a background sweeper (EXPIRY_SWEEP_INTERVAL_MS).
Claims are enforced. Submitting a proof requires a valid, unexpired claim
on the task: POST /api/proofs returns 403 when the submitter holds no
active claim, and a claim whose expiresAt has passed is rejected at submit
time even before the background sweeper marks it expired. Each proof is
stored with the claimId of the claim it was submitted under. (Proofs
created before this rule was introduced are grandfathered: their claimId
is NULL and they remain valid.)
POST /api/proofs # Submit proof (photo + GPS + task_id)
GET /api/proofs/:id # Get proof status
GET /api/proofs/user/:id # Get all proofs by a user
GET /api/proofs/review # List pending proofs; ?reviewReason=no_validators filters escalations (admin)
POST /api/proofs/:id/review # Approve/reject an inconclusive proof (admin)
Proofs that the auto-verifier cannot decide are left for an admin to review via
POST /api/proofs/:id/review, which resolves the verdict, notifies the user,
and enqueues the reward payout when approved. If no community validator can be
assigned, the verification worker immediately moves the proof back to the existing
PENDING admin-reviewable state and records a manual-review marker. Admins can
list only those proofs with
GET /api/proofs/review?reviewReason=no_validators.
Every uploaded photo is hashed (SHA-256) and its EXIF metadata is extracted on
submission. The auto-verifier uses this data for real checks instead of the old
placeholder photo_quality scoring:
photo_qualityβ at least one photo meets the minimum resolution (480Γ480)photo_recencyβ at least one photo carries an EXIF capture timestamp within the last 7 days (blocks stale/stock images)photo_not_duplicateβ no photo may reuse a hash already submitted on another proof; reuse is treated as fraud and immediately rejects the proof
These checks complement the existing GPS-radius and task-expiry checks.
Beyond the global express-rate-limit throttles, sensitive endpoints apply
per-user (or per-IP for anonymous callers) limits backed by Redis:
| Endpoint | Default limit | Env vars |
|---|---|---|
POST /api/proofs |
20 / hour | PROOF_RATE_LIMIT_WINDOW_MS, PROOF_RATE_LIMIT_MAX |
POST/DELETE /:id/claim |
50 / hour | CLAIM_RATE_LIMIT_WINDOW_MS, CLAIM_RATE_LIMIT_MAX |
The limiter reports RateLimit-* headers and Retry-After on 429s, and
fails open if Redis is unreachable so infrastructure hiccups never block
legitimate traffic.
POST /api/auth/login # Authenticate with Stellar wallet signature
GET /api/users/:id # Get user profile & stats
PUT /api/users/:id # Update profile
GET /api/users/:id/impact # Get impact history (trees, plastic, COβ)
GET /api/notifications # Paginated inbox for the logged-in user
GET /api/notifications/unread-count
POST /api/notifications/preferences # Set email + webhook delivery channels
POST /api/notifications/:id/read # Mark one notification as read
POST /api/notifications/read-all # Mark all as read
Beyond the in-app inbox, notifications are pushed to outbound channels. Set
email and/or webhookUrl via POST /api/notifications/preferences and a
background worker dispatches each new notification to the configured channels:
- Webhook β a JSON payload is POSTed to the user's
webhookUrl(NOTIFICATION_WEBHOOK_TIMEOUT_MSguards the request) - Email β dispatched through a mock transport that logs the message; swap in an SMTP provider to go live
Each notification tracks which channel it went through (channel), whether it
was delivered (deliveredAt) and any delivery failure (deliveryError).
GET /api/analytics/platform # Global platform impact stats
GET /api/analytics/trends # Daily approved proofs & rewards series
GET /api/leaderboard # Top contributors (proofs + total reward)
GET /api/audit # Query audit logs (user, resource, paginated)
Proofs that the auto-checks can't resolve are routed to community validators
instead of always landing in the admin queue. Every submission is coordinated by
validatorService:
Auto-check inconclusive
β
βΌ
assignValidators(): picks `VALIDATOR_ASSIGNMENT_COUNT` least-loaded
validators (lowest reviewCount), excluding the proof's submitter
β
βΌ
Validators cast verdicts via POST /api/validator/reviews/:proofId
β
βΌ
resolveQuorum(): first verdict to reach `VALIDATOR_QUORUM_REQUIRED`
votes finalizes the proof β then notifies, completes the task and pays
β
βΌ
Split votes with no quorum β escalated to admin review
Validator reputation is rewarded: agreeing with the final quorum verdict earns
+1 validatorReputation, dissenting costs -1. Admin endpoints manage the
roster:
GET /api/validators # List validators by reputation (admin)
POST /api/validators/:userId/activate # Promote a user to validator (admin)
POST /api/validators/:userId/deactivate # Demote back to user (admin)
GET /api/validator/reviews # My assigned, undecided reviews (validator)
POST /api/validator/reviews/:proofId # Cast verdict: approved | rejected
User submits proof
β
βΌ
Proof saved to DB (status: pending)
β
βΌ
Photo + GPS pinned to IPFS
β
βΌ
Job added to BullMQ verification queue
β
βΌ
verificationWorker picks up job
β
βββ Auto-checks (GPS in task zone? Photo contains relevant content?)
β
βββ Community validator review (if auto-check inconclusive)
β
βΌ
Proof approved / rejected
β
ββββββββ΄βββββββ
βΌ βΌ
Rejected Approved
(notify) β
βΌ
rewardWorker triggers
Stellar oracle call
β
βΌ
ECO tokens / USDC sent
to user's Stellar wallet
β
βΌ
User notified β
Inconclusive proofs are routed to community validators for quorum review (see Community validation); only split votes with no quorum fall through to the admin review queue (
GET /api/proofs/review, resolved viaPOST /api/proofs/:id/review), which re-enters the flow at "Proof approved / rejected".
# Run all tests
npm test
# Run with coverage report
npm run test:coverage
# Run integration tests only
npm run test:integrationWhere the project is today and where it's headed. Priorities may shift based on contributor and community feedback β see the discussions and issues for the lively plan.
- Stellar wallet-based auth (challenge β signature β JWT)
- Task CRUD, geo-bounded listing, claims, capacity limits (
maxCompletions), and automatic expiry - Proof intake: photo upload, EXIF GPS extraction, IPFS pinning
- Verification pipeline: auto-checks (GPS radius, photos, task expiry) with confidence scoring, plus admin review for inconclusive proofs
- Reward payouts via the Stellar SDK (mock mode for local dev)
- DB-backed notification inbox with read/unread tracking
- Notification delivery: webhook + email channels with delivery tracking
- Per-user Redis-backed rate limits on proof submissions and task claims
- Photo analysis: SHA-256 hashing, resolution/recency checks, duplicate detection
- Community validator program: quorum voting, reputation, and fair assignment
- Platform analytics, daily trends, and a rewards-enriched leaderboard
- Audit logging of mutating admin/API actions
- Integrating the
reward-engineSoroban contract (the oracle secret and contract ID are configured, but payouts currently use a direct payment op) - Swap the mock email transport for a real SMTP provider
- API versioning, pagination hypermedia, and generated OpenAPI docs
- Impact reporting standardization (trees, plastic, COβ) with exportable verifiable claims
Backend developers, DevOps engineers, and database architects especially welcome! See CONTRIBUTING.md to get started.
Questions, ideas, or feedback? Reach the team and fellow contributors here:
| Channel | What it's for |
|---|---|
| GitHub Discussions | General questions, feature ideas, project chat |
| GitHub Issues | Bug reports and trackable feature requests |
| EcoTask docs hub | Project-wide documentation and announcements |
Please follow our Code of Conduct in all interactions. Security issues should be reported privately β see SECURITY.md.
MIT β see LICENSE for details.
This is part of the EcoTask Network:
| Repo | Description |
|---|---|
| EcoTask-app | Mobile dApp |
| EcoTask-backend | Node.js API & verification engine |
| EcoTask-contracts | Stellar Soroban smart contracts |
| EcoTask-docs | Documentation hub |
Part of the EcoTask Network β Because the environment deserves an economy.