Skip to content

Latest commit

 

History

History
355 lines (267 loc) · 10.5 KB

File metadata and controls

355 lines (267 loc) · 10.5 KB

YieldVault Backend API

Express.js backend server for YieldVault Stellar RWA platform with rate limiting and health monitoring.

Features

  • Health Check Endpoint (/health) - Real-time service health status
  • Readiness Endpoint (/ready) - Dependency status for deployment orchestration
  • Rate Limiting - Per-IP and per-API-key rate limiting to prevent abuse
  • Dependency Monitoring - Checks for cache and Stellar RPC availability
  • Admin Audit Logs - Tracks privileged admin actions via /admin/audit-logs
  • Event Replay System - Recovers from polling gaps by replaying missed on-chain events
  • Background Job Dashboard - Monitoring views at /admin/jobs/dashboard and /admin/jobs/dashboard/view
  • Prisma Runtime Tuning - Configurable pooling and query timeouts
  • Error Handling - Consistent JSON error responses
  • TypeScript - Full type safety with TypeScript

Quick Start

Setup

# Install dependencies
npm install

# Create environment file
cp .env.example .env

# Create/update the local Prisma database
npx prisma migrate dev

Development

# Start development server with auto-reload
npm run dev

The server will start on http://localhost:3000.

For the default local workflow, PostgreSQL and Redis are optional:

  • Prisma uses the SQLite datasource in prisma/schema.prisma when DATABASE_URL is not set.
  • Redis-backed features fall back to in-memory behavior when REDIS_URL is not configured.

Minimum local environment values:

PORT=3000
NODE_ENV=development
STELLAR_RPC_URL=https://soroban-testnet.stellar.org
STELLAR_NETWORK_PASSPHRASE=Test SDF Network ; September 2015
VAULT_CONTRACT_ID=

For the full monorepo bootstrap order, see docs/LOCAL_DEVELOPMENT_QUICKSTART.md.

Production

# Build TypeScript
npm run build

# Start production server
npm start

Configuration

Rate limiting and other settings are configurable via environment variables:

Variable Default Description
PORT 3000 Server port
NODE_ENV development Environment mode
RATE_LIMIT_WINDOW_MS 900000 Global rate limit window (15 min)
RATE_LIMIT_MAX_REQUESTS 100 Global requests per window
API_RATE_LIMIT_WINDOW_MS 60000 API rate limit window (1 min)
API_RATE_LIMIT_MAX_REQUESTS 30 API requests per window
RATE_LIMIT_AUTH_MAX 5 Auth tier request limit per window (1 min)
RATE_LIMIT_AUTH_WINDOW_MS 60000 Auth tier window duration in ms
DEPOSITS_RATE_LIMIT_MAX 10 Deposit/transfer tier request limit per window (1 min)
DEPOSITS_RATE_LIMIT_WINDOW_MS 60000 Deposit/transfer tier window duration in ms
STELLAR_RPC_URL https://soroban-testnet.stellar.org Stellar RPC endpoint
DATABASE_URL local PostgreSQL URL Primary PostgreSQL connection string (required in production)
DATABASE_REPLICA_URL primary database Optional read-replica connection string
DATABASE_POOL_SIZE 10 Maximum connections per PostgreSQL pool
PRISMA_POOL_MAX 10 Prisma connection pool max size
PRISMA_POOL_TIMEOUT_MS 10000 Prisma pool wait timeout in ms
PRISMA_QUERY_TIMEOUT_MS 5000 Max Prisma query time in ms
ADMIN_AUDIT_LOG_STORAGE hybrid Audit log storage mode (memory, prisma, hybrid)
EVENT_POLL_INTERVAL_MS 10000 Event polling interval (10 seconds)
EVENT_REPLAY_BATCH_SIZE 100 Batch size for event replay (ledgers per batch)

API Endpoints

Health Check

GET /health

Returns service health status with dependency checks.

Response (200 OK):

{
  "status": "healthy",
  "timestamp": "2026-03-26T10:30:00.000Z",
  "uptime": 3600.5,
  "environment": "development",
  "checks": {
    "api": "up",
    "cache": "up",
    "stellarRpc": "up"
  }
}

Readiness Check

GET /ready

Returns service readiness state. Checks all critical dependencies before reporting ready.

Response (200 OK - Ready):

{
  "ready": true,
  "timestamp": "2026-03-26T10:30:00.000Z",
  "dependencies": {
    "cache": true,
    "stellarRpc": true
  }
}

Admin Audit Logs

GET /admin/audit-logs
Authorization: ApiKey <admin-key>

Returns recent admin activities with optional filters: action, actor, statusCode, and limit.

Signed wallet actions (nonce + replay protection)

Protected routes (POST /api/v1/auth/login, vault deposits/withdrawals) accept single-use server nonces:

  1. POST /api/v1/auth/nonce with { walletAddress, action }{ nonce, message, expiresAt, expiresIn }
  2. Sign message with the wallet (Ed25519 in production, HMAC in dev/test)
  3. Submit the action with { walletAddress, nonce, signature, ... }
Error HTTP code
Missing nonce/signature 400 SIGNED_ACTION_REQUIRED
Unknown/mismatched nonce 401 NONCE_NOT_FOUND / NONCE_ACTION_MISMATCH
Expired nonce 401 NONCE_EXPIRED
Reused nonce 401 NONCE_REPLAY
Bad signature 401 SIGNATURE_INVALID
Active nonce cap reached 429 NONCE_LIMIT_EXCEEDED

Configure via WALLET_NONCE_ENFORCEMENT (strict in production) and WALLET_SIGNATURE_MODE (stellar | hmac).

Nonce allocation and consumption are atomic. With REDIS_URL configured, the backend enforces single-use consumption and the per-wallet active nonce cap across all replicas. Without Redis those guarantees apply only within one backend process, so multi-instance production deployments must configure the shared Redis store.

Admin API Key RBAC

All /admin/* routes require Authorization: ApiKey <key>. Keys are assigned one of four roles (least → most privileged):

Role Capabilities
viewer Read-only admin endpoints (metrics, audit logs, config snapshots)
operator Viewer + operational writes (maintenance, cache, allowlist, webhooks, jobs, exports)
admin Operator + privileged webhook url/secret updates and API key lifecycle
super-admin Admin + impersonation, global idempotency flush, minting super-admin keys

Forbidden requests return 403 with requiredPermission in the JSON body. Maintenance and webhook PATCH bodies are validated so privileged parameters (enabled, url, secret, etc.) require the matching permission tier.

Background Job Dashboard

GET /admin/jobs/dashboard
GET /admin/jobs/dashboard/view
Authorization: ApiKey <admin-key>

Exposes dead-letter metrics, recurring failures, job runtime telemetry, and health status.

Response (503 Unavailable - Not Ready):

{
  "ready": false,
  "timestamp": "2026-03-26T10:30:00.000Z",
  "dependencies": {
    "cache": false,
    "stellarRpc": false
  }
}

Rate Limit Exceeded

Status: 429 Too Many Requests
{
  "error": "Too many requests",
  "status": 429,
  "message": "Rate limit exceeded. Please try again later.",
  "retryAfter": 1711432200000
}

Rate Limiting

Global Rate Limiting

Applied to all requests except /health and /ready:

  • Window: 15 minutes (configurable)
  • Max: 100 requests per window (configurable)
  • Per: IP address

API Endpoint Rate Limiting

Stricter limits for API endpoints (e.g., /api/vault/summary):

  • Window: 1 minute (configurable)
  • Max: 30 requests per window (configurable)
  • Per: API key (from x-api-key header) or IP address

Testing

# Apply native PostgreSQL migrations
npm run db:migrate

# Verify applied migration checksums and required tables
npm run db:check-drift

# Run all tests
npm test

# Run tests in watch mode
npm test -- --watch

# Run with coverage
npm test -- --coverage

API Contract Schema Snapshots (Issue #711)

Committed JSON snapshots under schema-snapshots/ describe the response shape of critical public endpoints. CI fails when a required field is removed or changes type.

Guarded endpoints: GET /health, GET /ready, GET /api/v1/vault/summary, GET /api/v1/transactions

# Verify snapshots are backward-compatible (CI)
npm run snapshots:check

# Regenerate after an intentional breaking API change
npm run snapshots:write

When bumping snapshots intentionally:

  1. Update the Zod schema in src/apiContractSnapshots.ts
  2. Run npm run snapshots:write and commit schema-snapshots/*.json
  3. Align OpenAPI annotations and run npm run generate:openapi

Wallet Alias Persistence

Wallet alias identity groups are persisted in Prisma and loaded into the walletAliasService cache on startup. New aliases registered through auth or /api/v1/wallet-aliases/link survive backend restarts and continue to resolve for referral attribution.

Environments that relied on the previous in-memory-only alias registry do not have a durable source to backfill from after a restart. If those aliases matter, export or re-register the live mappings before deploying/restarting; otherwise the persistent tables will start empty and only new registrations will be preserved.

Issues Addressed

Issue #145: Rate Limiting

  • ✅ Global rate limiting per IP
  • ✅ Per-user/API-key rate limiting
  • ✅ Configurable via environment variables
  • ✅ Clear 429 responses with retry information
  • ✅ Tests included for rate limiting behavior

Issue #148: Health & Readiness Endpoints

  • /health endpoint for service health
  • /ready endpoint for deployment readiness
  • ✅ Dependency health checks (cache, RPC)
  • ✅ CI smoke test setup via npm scripts
  • ✅ Consistent response formats

CI/CD Integration

Smoke Test (CI Pipeline)

# Build and start server
npm run test:smoke

# The server will start in background, ready for health checks
# Call: curl http://localhost:3000/health
# Call: curl http://localhost:3000/ready

Docker Deployment

Example Dockerfile:

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY dist ./dist
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD node -e "require('http').get('http://localhost:3000/health', (r) => r.statusCode === 200 ? process.exit(0) : process.exit(1))"
CMD ["npm", "start"]

Monitoring

Headers returned in responses:

  • RateLimit-Limit - Request limit
  • RateLimit-Remaining - Requests remaining
  • RateLimit-Reset - Reset timestamp

Example:

RateLimit-Limit: 100
RateLimit-Remaining: 95
RateLimit-Reset: 1711432200

License

MIT