The engine behind Stargate: invoices in Postgres, payments confirmed from the Stellar ledger, webhooks that always arrive.
Stargate Protocol — The Stripe for Stellar. Founded and built by dreamgene · Founder & CEO
| 🌍 Live app | stargate-frontend-psi.vercel.app — the product this API powers |
| 📖 Docs | stargate-frontend-psi.vercel.app/docs · in-repo guides under docs/ |
| 🖥️ Frontend | FX-GENE/stargate-frontend — dashboard, checkout, widget |
| ⛓️ Contracts | FX-GENE/stargate-contracts — Soroban invoice / treasury / compliance |
On-chain payments don't tell you why money arrived. A raw Stellar payment has no invoice number, no merchant, no idempotency, no callback. Turning "a payment landed on the ledger" into "invoice #1042 is paid, the merchant was notified exactly once, and settlement is queued" requires an always-on reconciliation loop, durable delivery, and compliance gates — that's this repository.
- Invoice engine — REST API for merchants: invoice CRUD with per-tier fee calculation and an atomic Postgres sequence (
claim_invoice_seq()) that eliminates numbering races. Each invoice gets a unique muxed Stellar address so inbound payments self-identify. - Payer-aware XDR building —
GET /payments/:id/prepare-tx?payer=G...loads the payer account from Horizon and returns unsigned XDR; the payer signs in their own wallet. The API never holds a customer-fund key. - On-chain compliance oracle — before any XDR is built, the payer address is checked against the Soroban
ComplianceContract(is_allowed()), with results cached 1h in Redis. Blocked addresses never get a transaction to sign. - Rust reconciler sidecar — streams Horizon payments over SSE, deduplicates by paging token, filters USDC by issuer, matches invoices by muxed ID (
stellar-strkey), verifies amounts, and marks invoices paid inside a single DB transaction — then publishes to Redis for real-time UI updates. - Webhooks that arrive — Redis Streams consumer group (
XREADGROUP) with at-least-once delivery, exponential backoff with jitter, HMAC-SHA256 signatures (X-Stargate-Signature), and SSRF hardening: private/loopback IPs are rejected with re-resolution on every redirect hop. - Automated settlement — batches merchant balances and disburses through the
TreasuryContract(propose_settlement+ multi-sig approval, KMS-backed signing in production). - Real-time status —
GET /payments/:id/streamrelays reconciler confirmations to checkout pages over Server-Sent Events; heartbeats every 15s. - Observability — OpenTelemetry tracing with
X-Trace-IDon every response; one trace ID correlates API, reconciler, and webhook worker. Prometheus metrics, structured JSON logs.
┌────────────────────────────────────────────┐
Merchant ──REST────▶│ NestJS API │
Checkout ──SSE─────▶│ auth · invoices · payments · webhooks │──▶ PostgreSQL 15 (RLS)
│ compliance cache · settlement · health │──▶ Redis 7 (cache, streams, pub/sub)
└───────────────┬────────────────────────────┘
│ Soroban RPC (is_allowed / propose_settlement)
▼
stargate-contracts (compliance · treasury)
Stellar Horizon ──SSE payments──▶ Rust reconciler ──▶ Postgres (mark paid, ledger entries)
└────────────▶ Redis publish → API SSE → checkout
Webhook worker ◀── Redis Streams (XREADGROUP STARGATE-WEBHOOKS) ──▶ merchant endpoints (HMAC)
| Repo | Role |
|---|---|
| stargate-frontend | Next.js dashboard, hosted checkout, embeddable widget, public docs |
| stargate-backend (this) | NestJS API, PostgreSQL, Redis Streams webhooks, Rust reconciler |
| stargate-contracts | Soroban contracts: invoice escrow, treasury multi-sig settlement, compliance oracle |
How they communicate: the frontend calls this API (NEXT_PUBLIC_API_URL); this API calls Soroban RPC using contract IDs from env (COMPLIANCE_CONTRACT_ID, TREASURY_CONTRACT_ID, INVOICE_CONTRACT_ID) and validates against the committed ABI snapshots in stargate-contracts/abis/; the reconciler talks to Horizon and Postgres directly.
| Layer | Technology |
|---|---|
| API | NestJS 10 · Node.js 20 · TypeScript 5.5 |
| Database | PostgreSQL 15 · Row-Level Security |
| Cache / Streams | Redis 7 · ioredis · XREADGROUP consumer groups |
| Reconciler | Rust 2021 · tokio · sqlx · stellar-strkey |
| Smart contracts | Soroban RPC (ComplianceContract + TreasuryContract) |
| Auth | JWT + httpOnly cookie BFF · sk_live_ API keys with IP allowlists |
| Observability | OpenTelemetry · Prometheus · structured JSON logs |
src/ NestJS application (modules per domain)
reconciler/ Rust sidecar — Horizon SSE → Postgres reconciliation
db/ SQL migrations
packages/ shared internal packages
scripts/ ops scripts (pre-launch audit, …)
test/ e2e suites
docs/ runbooks & guides (see below)
Dockerfile.api / Dockerfile.reconciler / docker-compose.yml
Key documents in docs/: MERCHANT_QUICKSTART.md · INVOICE_PAYMENT_FLOW.md (sequence diagrams) · RECONCILER.md · LAUNCH_RUNBOOK.md · RECOVERY.md · E2E_TESTING.md · VERSIONING.md · openapi.yaml
Requirements: Node.js 20, Rust toolchain, Docker.
docker compose up -d postgres redis
cp .env.example .env
npm ci
npm run db:migrate
npm run start:dev # API → http://localhost:3001Reconciler (separate process):
cd reconciler
cargo runPoint the frontend at it with NEXT_PUBLIC_API_URL=http://localhost:3001.
npm run typecheck
npm test
npm run test:e2e
npm run build
npm run generate:openapi
cargo test --manifest-path reconciler/Cargo.toml# Create an invoice (authenticated merchant)
curl -X POST http://localhost:3001/invoices \
-H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
-d '{ "amount_usdc": "10.00", "memo": "Order #1001", "expires_in_seconds": 900 }'
# → { "id": "inv_…", "muxed_address": "M…", "payment_url": "…/pay/inv_…", … }
# Public invoice lookup (what the checkout page uses)
curl http://localhost:3001/invoices/public/inv_abc123
# Payer-specific unsigned XDR
curl "http://localhost:3001/payments/inv_abc123/prepare-tx?payer=G..."
# Live status stream (SSE)
curl -N http://localhost:3001/payments/inv_abc123/streamVerify a webhook before trusting it:
const digest = crypto.createHmac('sha256', webhookSecret).update(rawBody).digest('hex');
if (`sha256=${digest}` !== req.headers['x-stargate-signature']) throw new Error('bad signature');Events: invoice.paid · invoice.expired · invoice.cancelled · settlement.completed · merchant.kyc.approved · merchant.kyc.rejected. Delivery is at-least-once — make handlers idempotent on invoice.id.
| Endpoint | Purpose |
|---|---|
GET /health |
Postgres + Redis liveness |
GET /health/deep |
Full dependency check including Soroban RPC |
GET /health/rpc |
Stellar Horizon + Soroban reachability |
Real, end to end (locally):
- Invoice → unsigned XDR → wallet signature → Horizon → reconciler match →
invoice.paidwebhook is a working loop against Stellar testnet when contracts are deployed and env is configured. - Muxed-address matching, atomic invoice sequencing, HMAC webhooks with backoff, SSRF/private-IP blocking, compliance-before-XDR, and OTel tracing are implemented with unit and e2e tests.
Simplified, or honestly unfinished:
- Not publicly hosted yet — there is no public API URL; the deployed frontend runs sandbox/mock flows until this service gets production hosting (current milestone; see
docs/LAUNCH_RUNBOOK.md). - Contract integration is env-gated — compliance/treasury calls require deployed contract IDs (
stargate-contractstestnet scripts); without them the API runs with those features degraded. - Settlement signing uses KMS in the production design; local dev uses plain keys from env.
TODO.mdtracks the open product item: auto-cancel of unpaid invoices after a merchant-configured TTL.
Copy .env.example into your secret manager — never commit real secrets. Notable variables: DATABASE_URL, REDIS_URL, JWT_SECRET, STELLAR_NETWORK, USDC_ISSUER, COMPLIANCE_CONTRACT_ID, TREASURY_CONTRACT_ID, SETTLEMENT_SIGNER_KEYS, OTEL_EXPORTER_OTLP_ENDPOINT.
MIT