Skip to content
SynapsVaultPublic

About

No description, website, or topics provided.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

⬡ SynapsVault — Backend

Express API server for the SynapsVault knowledge vault marketplace

CI Node.js TypeScript Stellar MIT


Overview

SynapsVault-backend is the Express.js API powering the SynapsVault marketplace. It handles:

  • Publisher registration — API key issuance, wallet linking
  • Resource publishing — file upload to Supabase, link registration
  • x402 payment verification — Stellar USDC micropayment validation
  • On-chain registration — Soroban vault-registry contract calls
  • Catalog serving — searched, filtered, paginated resource listings
  • Admin panel — protected stats, force-delist, payment audit
  • Webhook delivery — HMAC-signed event fanout to publisher endpoints

Architecture

                    ┌─────────────────────────────────────────┐
                    │           SynapsVault Backend             │
                    │                                         │
  Frontend ──────►  │  Express app                            │
  (React/Vite)      │  ├── /resources    CRUD + catalog       │
                    │  ├── /verify-content  AI originality    │
                    │  ├── /publishers   profile + keys       │
                    │  ├── /registry     on-chain status      │
                    │  ├── /payments     receipts + history   │
                    │  ├── /admin        protected stats      │
                    │  ├── /health       k8s probes           │
                    │  └── /docs         OpenAPI + Swagger    │
                    │                                         │
                    │  Middleware stack                        │
                    │  ├── CORS + Security headers            │
                    │  ├── Rate limiting (IP + wallet)        │
                    │  ├── API key auth                       │
                    │  ├── Request signature auth             │
                    │  ├── Audit log (all mutations)          │
                    │  ├── Circuit breaker (Stellar RPC)      │
                    │  └── Request tracing (OpenTelemetry)    │
                    └──────────────┬──────────────────────────┘
                                   │
               ┌───────────────────┼───────────────────┐
               ▼                   ▼                   ▼
        Supabase DB         Stellar Horizon      Soroban Contracts
        (PostgreSQL)        (x402 + USDC)       (vault-registry
        resources           payment verify       access-lease
        payments            tx submission        subscription)
        publishers

Quick start

Requires Node.js 24 (npm 11) and PostgreSQL 16.

git clone https://github.com/SynapsVault/backend SynapsVault-backend
cd SynapsVault-backend
npm ci

cp .env.example .env
# Fill in the required values (marked "required" in .env.example). The server
# validates config on boot and exits listing anything missing.

npm run db:migrate   # apply drizzle migrations
npm run dev          # dev server with hot reload (default port 4021)

npm run typecheck
npm test
npm run build && npm start

Docker

# Single container (listens on 3000; run migrations first)
docker build -t synapsvault-backend .
docker run --env-file .env -p 3000:3000 synapsvault-backend

# Full stack (API + Postgres), using values from .env
docker compose up

API reference

The OpenAPI 3 spec is served at /openapi.json, with Swagger UI at /docs.

Method Route Auth Description
GET /health, /health/ready — Liveness / readiness (DB + Soroban RPC)
GET /metrics Bearer METRICS_TOKEN Prometheus metrics
GET /resources — Catalog: search, price/type/status filters, sort, pagination
POST /resources API key Publish a file (multipart) or link resource
GET /resources/:id/meta — Public preview
GET /resources/:id x402 Pay and access the resource
DELETE /resources/:id API key Delist own resource
GET /resources/:id/register/prepare API key Unsigned on-chain register tx
POST /resources/:id/register API key Submit signed register tx
POST /resources/:id/price[/prepare] API key Update on-chain price
POST /resources/:id/ownership[/prepare] API key Transfer on-chain ownership
POST /verify-content x402 AI originality check
GET /agent/status — Verification agent stats
POST /publishers — Register; returns the API key once
GET /publishers/me[/resources|/analytics] API key Profile, resources, earnings
GET PATCH /publishers/me/rate-limit API key View / override own rate limit
GET PATCH /publishers/me/webhooks API key Webhook URL, secret, events
POST /publishers/me/webhooks/test API key Send a signed test event
GET /publishers/leaderboard — Creator leaderboard
GET /payments/:id/receipt — Payment receipt
GET /buyers/:address/payments — Buyer purchase history
GET /registry/status — On-chain registry stats
GET /admin/stats, /admin/audit Admin key Platform metrics, payment audit
POST /admin/delist/:id Admin key Force delist

Errors

Every error response has the same shape:

{ "error": "Resource not found", "code": "NOT_FOUND", "requestId": "…" }

code is one of VALIDATION_ERROR, UNAUTHORIZED, FORBIDDEN, NOT_FOUND, CONFLICT, PAYLOAD_TOO_LARGE, RATE_LIMITED, INTERNAL_ERROR, UPSTREAM_ERROR, SERVICE_UNAVAILABLE or GATEWAY_TIMEOUT. Validation errors add field-level details. The requestId matches the x-request-id header.

Webhooks

Publishers configure one endpoint with PATCH /publishers/me/webhooks (webhookUrl, webhookSecret, webhookEvents, webhookEnabled). Events: resource.purchased, payment.received, resource.listed, resource.delisted. Deliveries are POST { event, timestamp, data }. When a secret is set, X-SynapsVault-Signature holds the hex HMAC-SHA256 of ${timestamp}.${body}, where timestamp is the ISO-8601 value in X-SynapsVault-Timestamp and body is the raw JSON body (#45). Each delivery also carries a unique X-SynapsVault-Id header for deduplication.

Verification example (Node.js):

const crypto = require("crypto");

function verifyWebhook(req, secret) {
  const timestamp = req.headers["x-synapsvault-timestamp"];
  const signature = req.headers["x-synapsvault-signature"];

  // Reject deliveries older than 5 minutes to prevent replays.
  const age = Date.now() - new Date(timestamp).getTime();
  if (age > 5 * 60 * 1000) throw new Error("Webhook too old");

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${req.rawBody}`)   // rawBody = Buffer / string before JSON.parse
    .digest("hex");

  if (!crypto.timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(signature, "hex"))) {
    throw new Error("Invalid webhook signature");
  }
}

Failures are retried with exponential backoff (WEBHOOK_MAX_ATTEMPTS). Private and loopback URLs are rejected.

Request signatures (#42, #43, #44)

Publisher mutation endpoints (POST /resources, DELETE /resources/:id, etc.) support — and in production require — HMAC-SHA256 request signatures.

How it works

Every publisher receives a per-publisher signing secret (separate from the API key) on registration. The signing secret is the HMAC key, not the API key itself. This means an attacker who intercepts the API key cannot forge signed requests.

Signing a request (client side)

const crypto = require("crypto");

function signRequest({ signingSecret, method, path, timestamp, bodyHash, idempotencyKey }) {
  const parts = [method.toUpperCase(), path, timestamp, bodyHash];
  if (idempotencyKey) parts.push(idempotencyKey);
  const canonical = parts.join("\n");
  return crypto.createHmac("sha256", signingSecret).update(canonical, "utf8").digest("hex");
}

const timestamp = Math.floor(Date.now() / 1000).toString();
const bodyHash = crypto.createHash("sha256").update(rawBody, "utf8").digest("hex");

const signature = signRequest({
  signingSecret: "<your signing secret>",
  method: "POST",
  path: "/resources/res-1/register",
  timestamp,
  bodyHash,
});

// Add to request headers:
// x-api-key: <your API key>
// X-Timestamp: <timestamp>
// X-Signature: <signature>

Rotating the signing secret

POST /publishers/me/signing-secret/rotate
x-api-key: <your API key>

Returns { signingSecret, rotatedAt }. The new secret is shown once — store it securely. The old secret is immediately invalid.

Environment variables

Variable Default Description
REQUIRE_REQUEST_SIGNATURE true (prod) / false (dev) Enforce HMAC signatures on publisher mutations (#44)
SIGNING_SECRET_KEY — AES-256 encryption key for signing secrets at rest. Required when REQUIRE_REQUEST_SIGNATURE=true (#42)
SIGNATURE_MAX_SKEW_MS 300000 Max clock skew (ms) for X-Timestamp

Production behaviour (#44): REQUIRE_REQUEST_SIGNATURE defaults to true in production. If it is explicitly set to false, the server logs a security warning at boot. SIGNING_SECRET_KEY must be provided when signatures are required.

Rate limiting

All rate limits use a sliding-window algorithm. Responses include RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset headers. Blocked requests return 429 with { "error": "Too many requests", "code": "RATE_LIMITED", "retryAfterSeconds": N } and a Retry-After header.

Limiter Scope Default Env vars
Registration IP 10 per hour RATE_LIMIT_REGISTER_IP_MAX, RATE_LIMIT_REGISTER_IP_WINDOW_MS
Verify-content IP 10 / min RATE_LIMIT_VERIFY_IP_MAX, RATE_LIMIT_VERIFY_IP_WINDOW_MS
Verify-content Wallet 5 / hour RATE_LIMIT_VERIFY_WALLET_MAX, RATE_LIMIT_VERIFY_WALLET_WINDOW_MS
Publish resource IP 20 / min RATE_LIMIT_PUBLISH_IP_MAX, RATE_LIMIT_PUBLISH_IP_WINDOW_MS
Publish resource Wallet 10 / hour RATE_LIMIT_PUBLISH_WALLET_MAX, RATE_LIMIT_PUBLISH_WALLET_WINDOW_MS
Publisher API routes Per publisher 60 RPM PUBLISHER_RATE_LIMIT_RPM_DEFAULT, PUBLISHER_RATE_LIMIT_MAX_RPM

Registration rate limit (POST /publishers): at most RATE_LIMIT_REGISTER_IP_MAX (default 10) registrations per IP within RATE_LIMIT_REGISTER_IP_WINDOW_MS (default 1 hour). This prevents bulk account and API-key creation from a single IP address.

Publishers can inspect and override their own per-publisher limit via PATCH /publishers/me/rate-limit (up to PUBLISHER_RATE_LIMIT_MAX_RPM).

Environment variables

See .env.example for the full list and descriptions. When deploying to Railway, set TRUST_PROXY=1 so Express rate limits use the client IP forwarded by Railway. Proxy trust is disabled outside production.

CI

Every push and PR to main/dev runs ci.yml:

  1. Typecheck & Test: npm ci, npm run typecheck, npm test, npm run build
  2. Docker build & smoke test: builds the image, applies migrations to a Postgres service, boots the container and checks key endpoints.

catalog-seed.yml runs weekly (or on demand) to seed a testnet catalog and verify it end to end.

Repo siblings

Repo Description
SynapsVault-frontend React UI
SynapsVault-contracts Soroban contracts

License

MIT © 2025 Busiii-adetiba

Database TLS

Outside localhost the Postgres server certificate is verified (rejectUnauthorized: true). If your database uses a private CA, set DATABASE_CA_CERT to the PEM-encoded CA bundle (literal \n sequences are accepted for single-line env vars).

About

No description, website, or topics provided.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages