Backend API for StellarSettle, an invoice-financing platform that settles on Stellar and uses Soroban contracts for escrow and payment distribution.
- Stellar wallet challenge authentication and short-lived JWT access tokens
- Seller invoice creation, document upload, KYC-gated publishing, and lifecycle tracking
- Redis caching layer for read-heavy invoice listing and detail routes with automated mutation invalidation and
X-Cacheobservability headers - HMAC-SHA256 signature verification middleware for authenticating third-party webhook payloads
- Public marketplace discovery with cursor pagination, filtering, and sorting
- Fractional investments with idempotent payment verification and reconciliation
- Settlement orchestration, notifications, webhooks, health checks, and Prometheus metrics
- Hardened structured logging with correlation IDs, sensitive data redaction, and cycle-safe serialization
- Node.js 22 or newer and npm 10 or newer
- PostgreSQL 14 or newer
- Redis 6 or newer (optional; API gracefully falls back to direct database access if disconnected)
- Stellar testnet credentials for payment verification
- Pinata-compatible IPFS credentials for document uploads
git clone https://github.com/StellarState/SS-backend.git
cd SS-backend
npm ci
cp .env.example .env
npm run db:migrate
npm run devThe API listens on http://localhost:3000 by default. Confirm startup with:
curl http://localhost:3000/health
curl http://localhost:3000/health/dbGET /metrics is available when METRICS_ENABLED=true.
Start from .env.example. Startup validates required values
and exits with an actionable error instead of running with partial configuration.
Required for a normal local boot:
| Variable | Purpose |
|---|---|
DATABASE_URL |
PostgreSQL connection URL |
JWT_SECRET |
JWT signing secret; use a unique non-placeholder value |
IPFS_API_URL |
Pinning API endpoint |
IPFS_JWT |
Pinning service credential |
| Variable | Default | Purpose |
|---|---|---|
REDIS_URL |
redis://localhost:6379 |
Redis connection URL (falls back to DB if unreachable) |
CACHE_ENABLED |
true |
Enable or disable caching layer |
CACHE_TTL_INVOICES_LIST |
30 |
TTL in seconds for invoice listing (GET /invoices) |
CACHE_TTL_INVOICE_DETAIL |
60 |
TTL in seconds for invoice details (GET /invoices/:id) |
| Variable | Purpose |
|---|---|
KYC_WEBHOOK_SECRET |
Shared secret for HMAC-SHA256 signature verification on KYC webhooks |
WEBHOOK_SECRET |
Default shared secret for generic provider webhook signature checks |
Security-sensitive operational settings include TRUST_PROXY,
CORS_ALLOWED_ORIGINS, ADMIN_IP_WHITELIST, and the RATE_LIMIT_* values.
Only enable TRUST_PROXY when requests arrive through a trusted proxy; Express
uses it when resolving the client IP for global rate limiting.
Soroban funding is opt-in. Configure SOROBAN_ESCROW_ENABLED,
SOROBAN_ESCROW_CONTRACT_ID, SOROBAN_RPC_URL, and the contract-specific
variables documented in the integration guide.
Never commit live keys or Stellar secret seeds.
| Command | Purpose |
|---|---|
npm run dev |
Run the TypeScript server with reloads |
npm run build |
Compile to dist/ |
npm start |
Run the compiled server |
npm test |
Run the Jest suite |
npm run test:e2e |
Run end-to-end tests |
npm run lint |
Lint application TypeScript |
npm run type-check |
Type-check without emitting files |
npm run verify:openapi |
Detect route/OpenAPI drift |
npm run db:migrate |
Apply pending TypeORM migrations |
npm run db:migrate:revert |
Revert the latest migration |
Run the complete local gate with npm run ci. Database integration tests require
a reachable PostgreSQL instance and an appropriate DATABASE_URL.
All application routes use the /api/v1 prefix.
/api/v1/auth— wallet challenge authentication/api/v1/kyc— KYC submission, status, and webhook ingestion/api/v1/invoices— invoice CRUD, publishing, caching, lifecycle, and document upload/api/v1/marketplace— public invoice discovery/api/v1/investments— investment creation and history/api/v1/settlements— settlement operations/api/v1/notifications— user notifications/api/v1/keys— creator key config, per-transaction buy limit, and curve migration status/api/v1/swaps— atomic swap history and lookup/api/v1/royalties— creator royalty earnings, per-transfer breakdown and claim history/api/v1/dividends— issuer cycle config plus holder claimable balances, claim history and summary/api/v1/subscriptions— public gated-content subscription status (?wallet=&key_id=)/api/v1/onboarding— onboarding tour completion state/api/v1/admin— allowlisted administrative operations
The checked-in OpenAPI contract is docs/openapi.json.
Feature-specific references are available under docs/:
| Doc | Covers |
|---|---|
docs/MARKETPLACE_API.md |
Public invoice discovery API |
docs/INVOICE_LIFECYCLE.md |
Invoice state machine |
docs/INVOICE_DOCUMENT_UPLOAD.md |
Document upload flow |
docs/INVOICE_INVEST_API.md |
Fractional investment flow |
docs/SOROBAN_INTEGRATION_GUIDE.md |
Escrow/funding integration |
docs/DB_WORKFLOW.md |
Migration workflow |
docs/IPFS.md |
IPFS pinning details |
Issuer-facing draft lifecycle endpoints (all under /api/v1):
| Endpoint | Transition | Authorization | Notes |
|---|---|---|---|
POST /invoices/:id/submit |
draft → pending review | issuer (invoice owner) only | Validates required fields before transitioning |
PATCH /invoices/:id/submit |
draft → pending review | issuer (invoice owner) only | Alias for submit |
POST /admin/invoices/:id/approve |
pending review → live | admin only | Triggers live listing on the marketplace immediately |
PATCH /admin/invoices/:id/approve |
pending review → live | admin only | Alias for approve |
POST /admin/invoices/:id/reject |
pending → draft with reason | admin only | Requires a non-empty rejection reason |
DELETE /invoices/:id |
delete | issuer (invoice owner) only | Restricted to draft-state invoices only |
Invalid transition attempts return a structured error carrying the current
status, the requested target status, and the allowed next statuses, so callers
can recover without guessing the lifecycle graph. The full state machine is
documented in docs/INVOICE_LIFECYCLE.md.
src/
├── config/ environment, database, cache, and Stellar configuration
├── controllers/ HTTP request/response adapters
├── middleware/ security, validation, HMAC webhooks, and observability
├── models/ TypeORM entities
├── routes/ API route composition and caching integration
├── services/ business, cache, and external-integration logic
│ └── stellar/ Horizon and Soroban integrations
├── workers/ bounded background reconciliation
├── observability/ hardened structured logging, cycle-safe redaction, and metrics
└── utils/ shared pure helpers
Controllers should remain thin; business rules belong in services, and database
schema changes must be represented by migrations. See
DEVELOPMENT.md for the full workflow.
- Redis Caching Layer: Frequently accessed invoice listing and detail responses are cached in Redis with configurable TTLs (30s and 60s respectively). Cached responses are served within 10ms with
X-Cache: HIT, and mutations (create, update, publish, delete) automatically invalidate cache keys. If Redis is unavailable or disconnected, the service falls back to PostgreSQL without error (X-Cache: MISS). - HMAC-SHA256 Webhook Verification: Inbound webhooks are verified using constant-time HMAC-SHA256 comparisons (
crypto.timingSafeEqual) against configured shared secrets before processing, with raw payload preservation onreq.rawBodyand support for configurable signature header names. - Hardened Observability: Winston structured logging stamps request correlation IDs via
AsyncLocalStorage, redacts sensitive keys, and safely sanitizesBigInt, circular references, andErrorobjects to guarantee non-throwing execution. - Rate Limiting & Boundary Guards: Global throttling defaults to 100 requests per minute with per-wallet rate limits on invoice publishing and investment operations.
- Boundary Defense: Input sanitization, Helmet, CORS allowlists, admin CIDR allowlists, and parameterized TypeORM queries secure the HTTP boundary.
See SECURITY.md for private vulnerability reporting.
.env.example is the canonical list, grouped by domain:
| Group | Variables |
|---|---|
| Server | PORT, NODE_ENV |
| HTTP security | TRUST_PROXY, CORS_ORIGIN, CORS_ALLOWED_ORIGINS, CORS_ALLOW_CREDENTIALS, HTTP_BODY_SIZE_LIMIT, HTTP_SHUTDOWN_TIMEOUT_MS |
| Rate limiting | RATE_LIMIT_ENABLED, RATE_LIMIT_WINDOW_MS, RATE_LIMIT_MAX |
| Database | DATABASE_URL |
| Stellar | STELLAR_NETWORK, STELLAR_HORIZON_URL, STELLAR_USDC_ASSET_CODE, STELLAR_USDC_ASSET_ISSUER, STELLAR_ESCROW_PUBLIC_KEY, STELLAR_VERIFY_ALLOWED_AMOUNT_DELTA, STELLAR_VERIFY_RETRY_ATTEMPTS, STELLAR_VERIFY_RETRY_BASE_DELAY_MS, PLATFORM_SECRET_KEY |
| Smart contracts | ESCROW_CONTRACT_ID, TOKEN_CONTRACT_ID, and the Soroban escrow variables from the integration guide |
| IPFS | IPFS_API_URL, IPFS_JWT, IPFS_MAX_FILE_SIZE_MB, IPFS_ALLOWED_MIME_TYPES, IPFS_UPLOAD_RATE_LIMIT_* |
| Auth | JWT_SECRET, JWT_EXPIRES_IN, AUTH_CHALLENGE_TTL_MS |
| Observability | LOG_LEVEL, METRICS_ENABLED |
| Background reconciliation | STELLAR_RECONCILIATION_* |
SENDGRID_API_KEY, FROM_EMAIL |
|
| Admin | ADMIN_API_KEY, ADMIN_IP_WHITELIST |
Detailed guidance lives in DEVELOPMENT.md; the common
failures:
| Symptom | Likely cause / fix |
|---|---|
npm ci rejects the lockfile |
Use Node 22 + npm 10 and reinstall with the committed lockfile (npm ci), not npm install. |
| Server exits at startup with a configuration error | Startup validation fails on missing/placeholder values — complete .env (see the table above). |
| Database connection or migration failure | Check DATABASE_URL, ensure PostgreSQL 14+ is reachable, then npm run db:migrate. |
Unexpected 429 responses |
Global rate limiting (100 req/min default) — raise RATE_LIMIT_MAX locally or wait out the window. |
| Jest hangs after tests finish | Known open-handle issue — run npm run test:ci which passes --forceExit. |
Full guidance: DEVELOPMENT.md (§ Troubleshooting).
Logs are structured JSON, written by the src/observability/logger.ts
module.
- Verbosity — set
LOG_LEVEL(error,warn,info, ordebug). It defaults toinfo, and tosilentwhenNODE_ENV=testso test output stays clean. Suppressed levels cost nothing: their log calls short-circuit before any metadata processing. - Correlation IDs — every log line emitted while handling an HTTP request is stamped with that request's correlation ID by the request-observability middleware, so service-level logs join to the HTTP access log.
- Redaction — the log pipeline redacts Stellar secret keys, JWTs, Bearer
headers, and values under sensitive keys (
password,secret,token,authorization, …) before anything reaches a transport. - Resilience — a failing transport or formatter emits a fallback error line instead of crashing the request handler; oversized metadata is bounded so runaway callers cannot inflate log lines.
- Health endpoints —
GET /health(liveness),GET /health/db(database), andGET /metrics(Prometheus, only whenMETRICS_ENABLED=true).
Pull requests must target dev. Read CONTRIBUTING.md
before opening a change and use Conventional Commits. For setup and troubleshooting,
see DEVELOPMENT.md.
MIT