Decentralized Fundraising & Campaign Management API
Built on NestJS Β· Powered by Stellar Β· Backed by PostgreSQL
MilestoneX is a decentralized fundraising platform API that enables transparent, blockchain-verified campaign creation, donation processing, and milestone-based fund releases on the Stellar network. Designed for NGOs, community organizers, and individual fundraisers who demand trustless accountability.
- π Stellar Wallet Authentication β Sign-in with Stellar key-based challenge-response
- π Campaign Lifecycle Management β Draft β Approval β Active β Completion
- π° Blockchain-Verified Donations β On-chain transaction tracking and confirmation
- π― Milestone-Based Fund Releases β Smart contract-governed milestone unlocking
- π Real-Time Notifications β WebSocket + email notifications for all campaign events
- π Analytics & Export β Campaign stats, donation history, CSV exports
- π‘οΈ Admin Dashboard β User management, KYC verification, dispute resolution
- π Audit Trail β Immutable audit logs for compliance and transparency
| Layer | Technology |
|---|---|
| Runtime | Node.js + TypeScript |
| Framework | NestJS (Express adapter) |
| Database | PostgreSQL + Prisma ORM |
| Cache / Queue | Redis + Bull |
| Blockchain | Stellar SDK + Soroban Smart Contracts |
| Real-Time | Socket.IO WebSockets |
| Monitoring | Sentry error tracking |
| Nodemailer (SMTP) | |
| API Docs | Swagger / OpenAPI |
- Node.js >= 18
- PostgreSQL >= 14
- Redis >= 6
- npm or yarn
# Clone the repository
git clone <repository-url>
cd milestonex-api
# Install dependencies
npm install
# Configure environment
cp .env.example .env
# Edit .env with your database, Redis, and Stellar configurationUpdate the .env file with your credentials:
# Database
DATABASE_URL="postgresql://user:password@localhost:5432/milestonex?schema=public"
# Redis
REDIS_URL="redis://localhost:6379"
# JWT
JWT_SECRET=your-secure-secret-here
# Email (optional β console fallback in development)
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=your-user
SMTP_PASS=your-password
EMAIL_FROM=noreply@milestonex.io# Generate Prisma client
npx prisma generate
# Run migrations
npx prisma migrate dev --name init
# (Optional) Open Prisma Studio
npx prisma studio# Development (hot reload)
npm run start:dev
# Production
npm run build
npm run start:prod
# Debug mode
npm run start:debugThe API will be available at http://localhost:3000 and Swagger docs at http://localhost:3000/api/docs.
# Unit tests
npm run test
# E2E tests
npm run test:e2e
# Test coverage
npm run test:cov| Module | Description | Endpoints |
|---|---|---|
| Auth | Stellar wallet challenge-response auth | /auth/* |
| Users | Profile, KYC, notification preferences | /users/* |
| Campaigns | CRUD, stats, fund release requests | /campaigns/* |
| Donations | Donation creation, history, admin tips | /donations/* |
| Milestones | Campaign milestone tracking | /milestones/* |
| Contracts | Soroban smart contract management | /contracts/* |
| Notifications | WebSocket gateway + REST endpoints | /notifications/* |
| Admin | User moderation, campaign suspension | /admin/* |
| Health | Health checks (DB, Redis) | /health |
| API Keys | Programmatic API key management | /api-keys/* |
API keys are created and revoked via /api-keys/* (JWT-authenticated) and
authenticate programmatic requests through the X-API-Key header.
| Endpoint | Method | Scope honored | Description |
|---|---|---|---|
/users/me |
GET |
read |
Returns the key owner's full profile. |
GET /users/me accepts either a Bearer JWT or an X-API-Key header.
For API-key requests, ScopeGuard validates the scope stored on the key
(default read) against the endpoint's honored scopes; a key whose scope is
not honored receives 403 Forbidden, and a revoked key (isActive: false)
receives 401 Unauthorized. Every other endpoint remains JWT-only.
src/
βββ admin/ # Admin dashboard & moderation
βββ api-keys/ # API key management
βββ audit/ # Audit log entities
βββ auth/ # Stellar wallet authentication
βββ campaigns/ # Campaign CRUD & lifecycle
βββ common/ # Shared guards, decorators, middleware
βββ contracts/ # Soroban smart contract services
βββ donations/ # Donation processing & admin tips
βββ health/ # Health check endpoints
βββ milestones/ # Milestone tracking & fund release
βββ notifications/ # Email, WebSocket, notification prefs
βββ platform/ # Platform tip processing
βββ prisma/ # Prisma ORM service & module
βββ queue/ # Bull queue configuration
βββ redis/ # Redis module
βββ stellar/ # Stellar SDK, Soroban, event services
βββ throttler/ # Rate limiting
βββ users/ # User profiles, KYC, exports
βββ app.controller.ts # Root controller
βββ app.module.ts # Root module
βββ app.service.ts # Root service
βββ main.ts # Application bootstrap & Swagger
All donation CSV exports (GET /users/me/donations/export and the async queue variant) include the following columns:
| Column | Notes |
|---|---|
| Campaign | Campaign title at time of export |
| Amount | On-chain amount in the native asset |
| Asset | Asset code (e.g. XLM, USDC) |
| Date | ISO date of the donation (YYYY-MM-DD) |
| Tx Hash | Stellar transaction hash for independent verification |
USD Equivalent column is intentionally absent. A hardcoded
0.00placeholder was previously exported under this heading β a medium-severity finding (#15) because downstream consumers (tax tools, accounting software, partner integrations) could silently trust an incorrect value. The column will be reinstated once a verified price-oracle integration (Stellar Horizon order-book snapshots, CoinGecko, or a self-hosted oracle) is in place. Until then, please cross-reference on-chain amounts with your preferred exchange-rate source.
A campaign can accept multiple assets (acceptedAssets: native XLM and/or issued
assets such as USDC:<issuer>), so raised totals are never collapsed into a single
mixed-unit number.
| Field | Shape | Meaning |
|---|---|---|
raisedByAsset |
Record<string, string> |
Per-asset raised totals. Keys are XLM (native) or CODE:ISSUER (issued); values are decimal strings. |
raisedAmount |
decimal string | The native-XLM (base asset) portion only. Powers mostFunded browse sorting. |
progressPercentage |
number (0β100) | Native-XLM raised Γ· goalAmount (XLM-denominated), capped at 100. |
GET /campaigns/:id/stats returns raisedByAsset alongside the native-XLM scalar
fields. GET /campaigns/:id/contract-balance reports on-chain balances per asset
and never overwrites stored totals.
Fiat conversion is intentionally out of scope. Without a price-oracle integration, heterogeneous assets cannot be converted into a single monetary value. Clients should render
raisedByAssetper asset. A future price oracle can feed these per-asset amounts into a USD-equivalent summary without another schema change.
All configuration is provided via environment variables. Copy .env.example to .env and fill in the values.
| Variable | Required | Default | Description |
|---|---|---|---|
DATABASE_URL |
β | β | PostgreSQL connection string |
REDIS_URL |
β | β | Redis connection URL |
JWT_SECRET |
β | β | Secret key for signing JWT access tokens |
JWT_EXPIRY |
β | 15m |
JWT token expiry duration |
PORT |
β | 3000 |
HTTP port the server listens on |
NODE_ENV |
β | development |
Runtime environment (development, production, test) |
ADMIN_WALLETS |
β | β | Comma-separated list of Stellar wallet addresses granted the ADMIN role on login |
STELLAR_HORIZON_URL |
β | https://horizon-testnet.stellar.org |
Stellar Horizon API endpoint |
SMTP_HOST |
β | β | SMTP server hostname for email delivery |
SMTP_PORT |
β | 587 |
SMTP port |
SMTP_USER |
β | β | SMTP authentication username |
SMTP_PASS |
β | β | SMTP authentication password |
EMAIL_FROM |
β | noreply@milestonex.io |
Sender address used in outgoing emails |
SENTRY_DSN |
β | β | Sentry DSN for error tracking (disabled if unset) |
Security note: Never commit real secrets to source control. Use a secrets manager or CI environment variable injection for production deployments.
For production deployment:
# Build the application
npm run build
# Run with Node.js
node dist/main
# Or use a process manager
pm2 start dist/main.js --name milestonex-apiUNLICENSED β Proprietary. All rights reserved.