This module powers:
- Aid logic and APIs
- Verification APIs
- On-chain anchoring integrations
From the repo root:
pnpm install
pnpm --filter backend run start:devBy default the server listens on PORT (see .env.example).
Create app/backend/.env from app/backend/.env.example:
cp app/backend/.env.example app/backend/.envThen edit .env with your specific values. See .env.example for detailed inline comments and local development defaults.
All environment variables are documented in .env.example with inline comments, examples, and notes on when each is required.
| Variable | Description | Default | Required |
|---|---|---|---|
| Server Configuration | |||
PORT |
Port the NestJS server listens on | 3001 |
No |
NODE_ENV |
Node environment (development, production, test) |
development |
No |
| Database | |||
DATABASE_URL |
PostgreSQL connection string for Prisma | postgresql://postgres:postgres@localhost:5432/soter?schema=public |
Yes |
| Blockchain (Stellar/Soroban) | |||
STELLAR_RPC_URL |
Stellar RPC endpoint for Soroban interactions | https://soroban-testnet.stellar.org |
Yes |
STELLAR_NETWORK_PASSPHRASE |
Network passphrase (auto-detected if not set) | Auto-detected | No |
SOROBAN_CONTRACT_ID |
Deployed AidEscrow contract ID | None | No* |
| AI & Verification | |||
OPENAI_API_KEY |
OpenAI API key for server-side verification | Empty (disabled) | No** |
VERIFICATION_MODE |
Verification mode: client-side or server-side |
client-side |
No |
OPENAI_MODEL |
OpenAI model to use | gpt-3.5-turbo |
No |
| CORS | |||
CORS_ORIGINS |
Comma-separated allowed origins (defaults only in dev/test) | http://localhost:3000,http://localhost:3001 |
No |
CORS_ALLOW_CREDENTIALS |
Allow CORS credentials (cookies/authorization headers) | false |
No |
| Queue & Cache | |||
REDIS_URL |
Redis connection URL for BullMQ | redis://localhost:6379 |
No*** |
QUEUE_ENABLED |
Enable background job queues | false |
No |
| Security | |||
JWT_SECRET |
Secret for JWT token signing | Auto-generated | No |
JWT_EXPIRES_IN |
JWT token expiration time | 7d |
No |
| Rate Limiting | |||
API_RATE_LIMIT |
Max requests per minute per IP | 100 |
No |
THROTTLE_TTL |
Rate limit window (milliseconds) | 60000 |
No |
THROTTLE_ENABLED |
Enable request throttling | true |
No |
| Monitoring | |||
METRICS_ENABLED |
Enable Prometheus metrics at /metrics |
false |
No |
LOG_LEVEL |
Logging level (debug, info, warn, error) |
debug |
No |
SENTRY_DSN |
Sentry DSN for error tracking | None | No |
| Feature Flags | |||
SWAGGER_ENABLED |
Enable API docs at /api/docs |
true |
No |
API_VERSIONING_ENABLED |
Enable API versioning | true |
No |
* Required for blockchain interactions
** Required only if VERIFICATION_MODE=server-side
*** Required only if QUEUE_ENABLED=true
The default .env.example values work out of the box for local development:
- Uses local PostgreSQL with default credentials
- Points to Stellar testnet
- Client-side verification (no OpenAI key needed)
- Queues disabled (no Redis needed)
- Full logging and Swagger enabled
For production deployments, update these critical variables:
NODE_ENV=productionDATABASE_URL- Use secure credentials and connection poolingSTELLAR_RPC_URL- Switch to mainnet if deploying liveJWT_SECRET- Generate withopenssl rand -base64 32CORS_ORIGINS- Set to your actual frontend domain(s)METRICS_ENABLED=true- Enable for monitoringSWAGGER_ENABLED=false- Disable public API docsLOG_LEVEL=info- Reduce log verbosity
Database connection fails:
- Ensure PostgreSQL is running:
pg_isready - Verify credentials in
DATABASE_URL - Check database exists:
psql -l
Stellar RPC errors:
- Verify network connectivity to RPC endpoint
- Check if using correct network (testnet vs mainnet)
- Ensure you have testnet XLM from Stellar Laboratory
OpenAI verification not working:
- Verify
OPENAI_API_KEYis set correctly - Check API key has credits: https://platform.openai.com/usage
- Ensure
VERIFICATION_MODE=server-side
Queue/Redis errors:
- Only relevant if
QUEUE_ENABLED=true - Ensure Redis is running:
redis-cli ping - Verify
REDIS_URLconnection string
Prisma schema lives in prisma/schema.prisma.
Run migrations:
pnpm --filter backend prisma:generate
pnpm --filter backend prisma:migrateGET /health
Example:
curl -s http://localhost:3001/healthRun from repo root:
pnpm --filter backend lint
pnpm --filter backend testThis section provides operational procedures for common tasks and incident response.
View pending verifications:
curl -H "Authorization: Bearer $JWT_TOKEN" \
http://localhost:3001/api/v1/verification-inbox?status=pending_reviewApprove a verification:
curl -X POST \
-H "Authorization: Bearer $JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"nextStepMessage": "Verification approved. Proceed to disbursement."}' \
http://localhost:3001/api/v1/verification-inbox/{id}/approveReject a verification:
curl -X POST \
-H "Authorization: Bearer $JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"rejectionReason": "Document appears fraudulent", "nextStepMessage": "Please resubmit with valid documentation"}' \
http://localhost:3001/api/v1/verification-inbox/{id}/rejectRequest resubmission:
curl -X POST \
-H "Authorization: Bearer $JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"rejectionReason": "Document expired", "nextStepMessage": "Please submit a current government-issued ID"}' \
http://localhost:3001/api/v1/verification-inbox/{id}/request-resubmissionTrigger a backfill for missing ledger ranges:
curl -X POST \
-H "Authorization: Bearer $ADMIN_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"startLedger": 1000, "endLedger": 2000, "batchSize": 100}' \
http://localhost:3001/api/v1/admin/ledger/backfillCheck backfill job status:
curl -H "Authorization: Bearer $ADMIN_JWT_TOKEN" \
http://localhost:3001/api/v1/admin/ledger/backfill/{jobId}Notes:
- Backfill is idempotent - can be run repeatedly without duplicating data
- Uses
batchSizeto control memory usage during processing - Job status includes processed count and total count
Trigger reconciliation to detect discrepancies:
curl -X POST \
-H "Authorization: Bearer $ADMIN_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"startLedger": 1000, "endLedger": 2000, "thresholdPercent": 5}' \
http://localhost:3001/api/v1/admin/ledger/reconcileCheck reconciliation report:
curl -H "Authorization: Bearer $ADMIN_JWT_TOKEN" \
http://localhost:3001/api/v1/admin/ledger/reconcile/{jobId}Interpreting results:
actionable: true- High severity discrepancies or many medium severity issues- Discrepancy types:
missing,amount_mismatch,count_mismatch - Severity levels:
low,medium,high
View Prometheus metrics:
curl http://localhost:3001/metricsKey metrics to monitor:
http_requests_total- Total HTTP requests by method, route, status codehttp_request_duration_seconds- Request latency distributionerror_rate_total- Error count across all systemsingestion_lag_seconds- Time between event creation and processingwebhook_retries_total- Webhook delivery retry countjobs_processed_total/jobs_failed_total- Background job success/failure ratesonchain_operations_total- On-chain operation counts by status
Structured logging fields:
request_id- Unique identifier for each request (from X-Request-ID header)user_id- User identifier from JWT tokenroute- HTTP method and path (e.g., "GET /api/v1/health")duration_ms- Request processing time in millisecondscorrelationId- Tracks async operations across services
High error rate detected:
- Check
error_rate_totalmetrics breakdown by error type - Review logs for error patterns using
request_idcorrelation - If on-chain failures: check Stellar RPC endpoint status
- If webhook failures: verify external service availability
Ingestion lag increasing:
- Monitor
ingestion_lag_secondsgauge - Check queue depth:
curl http://localhost:3001/api/v1/jobs/status - If lag > 60s: trigger backfill for affected ledger ranges
- Run reconciliation to identify missing data
Webhook delivery failures:
- Check
webhook_retries_totalby reason - Verify external service endpoints are accessible
- Check authentication credentials for external services
- Review webhook payload sizes (may exceed limits)
Database performance issues:
- Monitor
db_query_duration_secondshistogram - Check connection pool metrics
- Run
EXPLAIN ANALYZEon slow queries - Consider adding indexes for frequently queried fields
Verify security headers are present:
curl -I http://localhost:3001/api/v1/healthExpected headers in production:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preloadX-Content-Type-Options: nosniffX-Frame-Options: DENYContent-Security-Policy(with strict directives)X-Permitted-Cross-Domain-Policies: noneReferrer-Policy: strict-origin-when-cross-origin
Verify CORS configuration:
curl -H "Origin: https://yourdomain.com" \
-H "Access-Control-Request-Method: POST" \
-X OPTIONS http://localhost:3001/api/v1/healthExpected response headers:
Access-Control-Allow-Origin: https://yourdomain.com(or configured origin)Access-Control-Allow-Methods(based on request)
See app/backend/CONTRIBUTING.md.