Backend infrastructure for Mux Protocol — powering invisible wallets, payment orchestration, and smart contract interaction on Stellar (Soroban).
Mux Backend abstracts blockchain complexity behind a secure, Web2-friendly API, enabling users to interact with crypto without managing keys, gas, or wallets directly.
Mux Backend is the trusted coordination layer between:
- Web2 authentication providers (Clerk / Better Auth)
- Stellar accounts and Soroban smart contracts
- Frontend clients and SDKs
It handles wallet creation, transaction orchestration, fee sponsorship, and on-chain/off-chain state reconciliation.
- Invisible wallet creation and management
- Secure custody and encryption of Stellar keypairs
- Transaction relaying and fee sponsorship
- Soroban smart contract invocation
- Spending limit and policy enforcement
- Indexing and caching on-chain data
- Serving APIs to frontend applications
- Health monitoring and readiness checks
Liveness probe endpoint for Kubernetes and container orchestration platforms.
Purpose: Indicates whether the application process is alive and responsive. This is a lightweight check that does NOT verify external dependencies like databases.
Readiness probe endpoint for Kubernetes and container orchestration platforms.
Purpose: Indicates whether the application is ready to serve traffic by verifying database connectivity.
Response (200 OK):
{
"status": "ready",
"timestamp": "2026-05-30T12:00:00.000Z",
"database": {
"connected": true,
"responseTime": 15
}
}Response (503 Service Unavailable): Returned when the database is not accessible.
Use Cases:
- Kubernetes readiness probes
- Load balancer health checks
- Container orchestration platforms
- CI/CD deployment verification
Authentication: Public endpoint (no API key required)
Main authentication endpoint for user onboarding and wallet creation.
Purpose: Handles both first-time and returning users. Creates user and wallet if needed, returns existing data if already exists. All operations are idempotent.
Authentication: Public endpoint (no API key required) - This must be public as it's used for initial authentication before an API key is available.
Request Body:
{
"authId": "auth-provider-user-id",
"email": "user@example.com",
"displayName": "User Name",
"authProvider": "CLERK",
"network": "TESTNET"
}Response (200 OK):
{
"user": {
"id": "uuid",
"authId": "auth-provider-user-id",
"email": "user@example.com",
"displayName": "User Name",
"status": "ACTIVE",
"authProvider": "CLERK",
"createdAt": "2026-05-30T12:00:00.000Z",
"updatedAt": "2026-05-30T12:00:00.000Z"
},
"wallet": {
"id": "uuid",
"userId": "uuid",
"publicKey": "GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"network": "TESTNET",
"status": "ACTIVE",
"createdAt": "2026-05-30T12:00:00.000Z",
"updatedAt": "2026-05-30T12:00:00.000Z"
},
"isNewUser": false,
"isNewWallet": false
}Use Cases:
- Initial user authentication and onboarding
- Automatic wallet creation for new users
- Idempotent user/wallet retrieval for returning users
- Integration with Web2 auth providers (Clerk, Better Auth, etc.)
- Automatic Stellar account creation on user signup
- No seed phrases or wallet prompts
- Keys encrypted and stored securely server-side
- Backend-signed and sponsored transactions
- Internal user-to-user transfers
- Support for batching and relaying
- User identity mapped to blockchain accounts
- Programmable spending limits
- Recovery and key rotation flows
- Soroban contract interaction
- Wallet registry and policy enforcement contracts
- Future support for smart wallet accounts
- Track balances and transactions
- Human-readable transaction history
- Cached reads for fast UX
This project uses PostgreSQL via Prisma ORM. You must set the DATABASE_URL environment variable before running migrations or starting the server.
Copy .env.example to .env (or create .env) and set:
DATABASE_URL="postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=public"Examples:
| Environment | Connection string |
|---|---|
| Local dev | postgresql://postgres:postgres@localhost:5432/mux_dev |
| Docker Compose | postgresql://postgres:postgres@db:5432/mux_dev |
| Supabase | postgresql://postgres:[password]@db.[project].supabase.co:5432/postgres |
| Railway / Render | Use the connection string provided by the platform |
# Apply all pending migrations (development)
pnpm prisma:migrate
# Apply migrations in production / CI (non-interactive)
pnpm prisma:migrate:prod
# Seed the database with demo users and wallets (dev only)
pnpm prisma:seedThe
DATABASE_URLvariable is read at runtime and during migration. Never commit credentials to version control — use environment secrets in CI.
- Private keys are never exposed to clients
- Keys are encrypted at rest
- All blockchain transactions are signed server-side
- Fees are sponsored by the platform
- Auth provider is the source of truth for identity
⚠️ This MVP uses a custodial model. Progressive decentralization is planned.
Mux Backend supports two authentication mechanisms:
API keys are used for server-to-server communication and administrative tasks. Each key is securely hashed before storage.
Key Characteristics:
- Format:
mux_live_<random32chars>ormux_test_<random32chars> - Transmitted via
Authorization: Bearer <key>header - Returned only once at creation time
- Hashed with SHA-256 before storage in database
- Can be rotated, revoked, or expire at a configured time
User authentication is orchestrated via the auth service and integrates with Web2 identity providers (Clerk, Better Auth, etc.).
Authentication Flow Steps:
-
Credential Submission
- Client sends
authId, email, displayName, and authProvider toPOST /auth/authenticate - The authId is typically a provider's unique identifier (e.g., Clerk user ID)
- Client sends
-
User Validation
- AuthOrchestrator calls IdempotentUserService to find or create the user
- User record is created with
status: 'ACTIVE'by default
-
Status Check (Inactive User Rejection)
- Before proceeding, the system checks the user's
statusfield - If status is not
ACTIVE(e.g.,INACTIVE,SUSPENDED,DELETED), authentication is rejected with403 Forbiddenand message "Account is inactive" - Missing status field defaults to
ACTIVEfor backward compatibility
- Before proceeding, the system checks the user's
-
Wallet Provisioning
- If user is active, AuthOrchestrator ensures the user has a wallet on the requested network
- Wallet is created automatically on first authentication (idempotent)
-
Response
- Returns authenticated user object with ID, status, authProvider
- Returns wallet object with public key and network
- Includes
isNewUserandisNewWalletflags for client-side logic
Authentication Response Example:
{
"user": {
"id": "user-123",
"authId": "clerk-id-xyz",
"email": "user@example.com",
"displayName": "Jane Doe",
"status": "ACTIVE",
"authProvider": "CLERK"
},
"wallet": {
"id": "wallet-456",
"publicKey": "GABC123...",
"network": "TESTNET",
"status": "ACTIVE"
},
"isNewUser": true,
"isNewWallet": true
}Request Flow for API Key Protected Endpoints:
- Client sends request with
Authorization: Bearer mux_live_...header - ApiKeyGuard intercepts request and extracts the key
- Guard delegates to ApiKeyService for validation:
- Hash the provided key with SHA-256
- Lookup in database by key hash
- Check if key is ACTIVE
- Check if key has not expired
- Update last_used_at timestamp (async)
- If valid, attach ApiKeyContext to request (apiKey, project, developer info)
- If invalid/expired, return
401 Unauthorized
Key Expiry Behavior:
- Expired keys are marked with status
EXPIREDon first validation attempt - Subsequent requests with expired keys fail with "API key has expired"
- Rate limits are enforced per API key
- User status is checked during authentication; inactive users cannot authenticate
- Once authenticated, API key usage is tracked independently of user status
- Sensitive endpoints (payments, transactions) apply stricter rate limits
Key authentication-related environment variables (when applicable):
AUTH_PROVIDER— Identity provider (e.g., CLERK, BETTER_AUTH)JWT_SECRET— (Future) JWT signing secretAPI_KEY_EXPIRY_DAYS— (Future) Default API key expiry duration in daysRATE_LIMIT_RPM— Requests per minute limit (per API key)
- Crypto is infrastructure, not UX
- Auth-first, wallet-second
- Correctness > flexibility
- Explicit over magical abstractions
- Upgrade paths over rewrites
- Smart contract wallet accounts (Soroban)
- Session keys and spending limits
- Wallet recovery flows
- Fiat on/off-ramps via Stellar anchors
- Optional self-custody export for advanced users
MIT
Contributions are welcome. Please open an issue before submitting large changes.
Request Logging Middleware
A lightweight request logging middleware has been added to the application to record incoming HTTP requests and response durations. It:
- Sets an
x-request-idheader (honors incomingx-request-idif present). - Logs method, URL, client IP and request id when requests start and when they finish.
- Is robust to stale/invalid request objects and will not crash the application.
The middleware is registered in src/main.ts and runs for all incoming requests.
POST /wallets- create walletGET /wallets- list walletsGET /wallets/:id- get walletPATCH /wallets/:id- update wallet statusDELETE /wallets/:id- remove wallet
Protected endpoint:
GET /wallets/protected- requires a valid API key. Supply API key inAuthorizationheader asApiKey <key>orBearer <key>.- When a valid key is provided, the route returns a JSON object with
message,developer, andprojectfields.
Authentication and error behavior
- API keys are validated by
ApiKeyGuardandApiKeyService. - Missing or invalid API keys return
401 Unauthorized. - Upstream validation errors (DB connectivity, etc.) surface as
401if they originate fromApiKeyServicethrowingUnauthorizedException; other unexpected errors may surface as 5xx.
Testing
- Unit tests are under
src/**/*spec.ts. - E2E tests are under
test/and use Jest + Supertest.