Non-custodial, cross-chain payment infrastructure where payers can send from any supported chain and merchants receive on their preferred chain/asset, with Stellar as the settlement hub.
Useroutr is built around atomic HTLC settlement across chains:
- Inbound: payer locks funds on source chain (EVM/Stellar/Starknet routes).
- Routing: Stellar path payment + Soroban contracts handle conversion and fee deduction.
- Outbound: funds are bridged/disbursed to merchant destination chain.
- Guarantee: either both sides complete or funds are refunded after timeout.
Core backend responsibilities (NestJS modular monolith in apps/api):
- Auth + merchants (JWT/API keys)
- Quotes (30s Redis TTL lock)
- Payments lifecycle + webhook events
- Bridge routing (CCTP, Wormhole, Layerswap)
- Relay service (HTLC event watching + secret propagation)
- Invoices, links, payouts, notifications, analytics
Smart contracts:
- Soroban contracts (
contract/soroban): HTLC, settlement, fee collector, escrow - EVM HTLC (
contract/evm/contracts/HTLCEvm.sol) - Starknet HTLC (
contract/starknet/src/htlc.cairo)
apps/api— NestJS API + Prisma schema/migrationspackages/stellar— shared Stellar clients/helperspackages/types— shared chain/payment typescontract/soroban— Rust Soroban contractscontract/evm— Solidity HTLC + Hardhat configcontract/starknet— Cairo HTLC
- Node.js 20+
- npm 10+
- Docker + Docker Compose
openssl(for secret generation)
Optional for contract work:
- Rust toolchain (
wasm32-unknown-unknown) stellarCLI- Hardhat-compatible wallet keys/testnet RPCs
From repo root:
npm installIf API workspace deps are not installed from root in your environment:
cd apps/api
npm install
cd ../..Create .env from .env.example at repo root:
cp .env.example .envImportant local DB note:
docker-compose.ymlmaps Postgres to host port 5434 (5434:5432)- so set
DATABASE_URLto:
DATABASE_URL="postgresql://useroutr:password@localhost:5434/useroutr"Set at least these for local API startup:
DATABASE_URLREDIS_URL=redis://localhost:6379JWT_SECRET
docker compose up -d
docker compose psFrom apps/api:
npx prisma migrate dev
npx prisma generateFrom apps/api:
npm run start:devAPI runs on http://localhost:3000 by default.
Before deploying to beta or production, ensure all required environment variables are properly configured. This section details all secrets, credentials, and their sources.
.env files are gitignored by default.
./scripts/generate-secrets.sh to generate cryptographically secure random values for JWT and encryption keys.
These variables are required for the API to start:
| Variable | Description | Source | Example |
|---|---|---|---|
DATABASE_URL |
PostgreSQL connection string | Create a database and get the connection URL | postgresql://useroutr:password@localhost:5434/useroutr |
REDIS_URL |
Redis connection string | Redis instance (local or cloud) | redis://localhost:6379 |
NODE_ENV |
Application environment | Set to development, staging, or production |
development |
PORT |
API listen port | Choose any available port | 3000 |
Setup for local development:
docker compose up -d
# Database and Redis are now availableGenerate these using the provided script:
./scripts/generate-secrets.sh > secrets.txt
# Copy values from secrets.txt to .env| Variable | Description | Length | Source |
|---|---|---|---|
JWT_SECRET |
JWT signing key (HS256) | 64+ bytes (base64) | Run ./scripts/generate-secrets.sh |
BANK_SESSION_ENCRYPTION_KEY |
AES-256 key for sensitive data | 32 bytes (base64) | Run ./scripts/generate-secrets.sh |
BANK_WEBHOOK_SECRET |
Verify bank webhook integrity | 32+ bytes (base64) | Run ./scripts/generate-secrets.sh |
JWT_EXPIRY |
JWT token expiration | N/A | Set to 7d or preferred duration |
Example setup:
# Generate secrets
./scripts/generate-secrets.sh > .env.secrets
# Add to .env
cat .env.secrets >> .env| Variable | Description | Source | Format |
|---|---|---|---|
STELLAR_NETWORK |
Stellar environment | Set to testnet or mainnet |
testnet |
STELLAR_HORIZON_URL |
Stellar HTTP API endpoint | Use public endpoints or self-hosted | https://horizon-testnet.stellar.org |
STELLAR_SOROBAN_RPC_URL |
Soroban RPC endpoint | Use public endpoints or self-hosted | https://soroban-testnet.stellar.org |
STELLAR_RELAY_KEYPAIR_SECRET |
Relay account signing key | Generate via stellar key generate |
SXXXXXX... (starts with S) |
STELLAR_RELAY_PUBLIC_KEY |
Relay account public key | Derived from keypair | GXXXXXX... (starts with G) |
Generate Stellar keys:
# Install stellar CLI if needed
brew install stellar-cli
# Generate a new keypair
stellar key generate
# Output will show secret and public key
# IMPORTANT: Fund the account with testnet XLM at https://friendbot.stellar.orgDeploy contracts:
The Soroban contract IDs must be deployed and set in the environment:
| Variable | Description | Source |
|---|---|---|
SOROBAN_HTLC_CONTRACT_ID |
HTLC contract | Deploy contract/soroban and record the ID |
SOROBAN_SETTLEMENT_CONTRACT_ID |
Settlement contract | Deploy contract/soroban and record the ID |
SOROBAN_FEE_COLLECTOR_CONTRACT_ID |
Fee collector contract | Deploy contract/soroban and record the ID |
SOROBAN_ESCROW_CONTRACT_ID |
Escrow contract | Deploy contract/soroban and record the ID |
| Variable | Description | Source | Format |
|---|---|---|---|
EVM_RELAY_PRIVATE_KEY |
EVM relay account private key | Generate via MetaMask or ethers.js | 0xXXXX... (0x-prefixed hex, 64 chars) |
RPC_ETHEREUM |
Ethereum RPC endpoint | Alchemy, Infura, or public endpoint | https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY |
RPC_BASE |
Base RPC endpoint | Alchemy, Infura, or public endpoint | https://base-mainnet.g.alchemy.com/v2/YOUR_KEY |
RPC_BNB |
BNB Chain RPC endpoint | Binance public RPC | https://bsc-dataseed.binance.org |
RPC_POLYGON |
Polygon RPC endpoint | Polygon public RPC | https://polygon-rpc.com |
RPC_ARBITRUM |
Arbitrum RPC endpoint | Arbitrum public RPC | https://arb1.arbitrum.io/rpc |
RPC_AVALANCHE |
Avalanche C-Chain RPC endpoint | Avalanche public RPC | https://api.avax.network/ext/bc/C/rpc |
Deploy HTLC contracts on each chain:
| Variable | Description | Source |
|---|---|---|
HTLC_ADDRESS_ETHEREUM |
HTLC contract on Ethereum | Deploy contract/evm via Hardhat, record address |
HTLC_ADDRESS_BASE |
HTLC contract on Base | Deploy contract/evm via Hardhat, record address |
HTLC_ADDRESS_BNB |
HTLC contract on BNB Chain | Deploy contract/evm via Hardhat, record address |
HTLC_ADDRESS_POLYGON |
HTLC contract on Polygon | Deploy contract/evm via Hardhat, record address |
HTLC_ADDRESS_ARBITRUM |
HTLC contract on Arbitrum | Deploy contract/evm via Hardhat, record address |
HTLC_ADDRESS_AVALANCHE |
HTLC contract on Avalanche | Deploy contract/evm via Hardhat, record address |
Generate an EVM private key:
# Via ethers.js
node -e "const ethers = require('ethers'); const wallet = ethers.Wallet.createRandom(); console.log('Private Key:', wallet.privateKey); console.log('Address:', wallet.address);"
# Or import MetaMask: Export → Private Key (⚠️ keep secure)| Variable | Description | Source | When Required |
|---|---|---|---|
CIRCLE_API_KEY |
Circle CCTP API key | Circle Developer Portal | For stablecoin bridging |
WORMHOLE_ENV |
Wormhole environment | Set to Testnet or Mainnet |
For cross-chain messaging |
LAYERSWAP_API_KEY |
Layerswap API key | Layerswap Dashboard | For Starknet routing |
| Variable | Description | Source | Format |
|---|---|---|---|
STRIPE_SECRET_KEY |
Stripe secret API key | Stripe Dashboard | sk_test_... (test) or sk_live_... (production) |
STRIPE_WEBHOOK_SECRET |
Stripe webhook signing secret | Stripe → Webhooks → Signing secret | whsec_... |
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY |
Stripe publishable key (public) | Stripe Dashboard | pk_test_... (test) or pk_live_... (production) |
Setup:
- Create a Stripe account
- Get API keys from Dashboard → API Keys
- Create a webhook endpoint pointing to
https://api.yourdomain.com/v1/webhooks/stripe - Copy the webhook signing secret
| Variable | Description | Source |
|---|---|---|
RESEND_API_KEY |
Resend email service API key | Resend Dashboard |
EMAIL_FROM |
From address for notifications | Your verified sender address |
| Variable | Description | Source |
|---|---|---|
CLOUDINARY_CLOUD_NAME |
Cloudinary cloud name | Cloudinary Dashboard |
CLOUDINARY_API_KEY |
Cloudinary API key | Cloudinary Settings → API Keys |
CLOUDINARY_API_SECRET |
Cloudinary API secret | Cloudinary Settings → API Keys (keep secure) |
| Variable | Description | Example |
|---|---|---|
API_URL |
Public API URL | https://api.yourdomain.com |
NEXT_PUBLIC_API_URL |
Frontend API endpoint | https://api.yourdomain.com or http://localhost:3000 |
WWW_URL |
Main website URL | https://useroutr.com or http://localhost:3000 |
DASHBOARD_URL |
Dashboard URL | https://dashboard.useroutr.com or http://localhost:3001 |
CHECKOUT_URL |
Checkout widget URL | https://checkout.useroutr.com or http://localhost:3002 |
USEROUTR_FEE_BPS |
Platform fee in basis points | 50 (0.5%) |
| Variable | Description | Source | Environment |
|---|---|---|---|
MONEYGRAM_HOME_DOMAIN |
MoneyGram federation domain | MoneyGram Developer Portal | Testnet: extstellar.moneygram.com / Mainnet: stellar.moneygram.com |
Minimum required for npm run start:dev:
DATABASE_URL=postgresql://useroutr:password@localhost:5434/useroutr
REDIS_URL=redis://localhost:6379
JWT_SECRET=<run: ./scripts/generate-secrets.sh>
NODE_ENV=development
PORT=3000
# STELLAR_RELAY_KEYPAIR_SECRET and EVM_RELAY_PRIVATE_KEY are only required
# when NODE_ENV=production; payment signing features need them at runtime.Add all bridge and payment service integrations:
- All Core Infrastructure variables
- All Authentication variables
- All Stellar variables (with testnet endpoints)
- All EVM variables (with testnet RPCs)
- Circle, Wormhole, Layerswap credentials
- Stripe test keys
- Resend API key
- All URLs pointing to staging domain
- Smart contracts have been audited
- All private keys are stored in a secure key management system (AWS Secrets Manager, HashiCorp Vault, etc.)
- Rate limiting and DDoS protection are configured
- Monitoring and alerting are in place
- Disaster recovery plan is documented
Production variables:
- All Core Infrastructure variables (using production database and Redis)
- All Authentication variables (regenerated and stored securely)
- All Stellar variables (mainnet endpoints)
- All EVM variables (mainnet RPCs, deployed contracts)
- All third-party service keys (production credentials)
- Production URLs and domains
The API automatically validates critical environment variables on startup. If any required variable is missing or contains a placeholder value, the API will fail to start with a clear error message.
Example error:
❌ Environment Configuration Errors:
STARTUP FAILED: STELLAR_RELAY_KEYPAIR_SECRET is a placeholder or missing. Set a valid value.
STARTUP FAILED: JWT_SECRET is a placeholder or missing. Set a valid value.
📋 To generate secrets, run: ./scripts/generate-secrets.sh
Issue: API fails to start with "JWT_SECRET is a placeholder"
Solution:
./scripts/generate-secrets.sh > .env.secrets
# Copy values to .env, ensure no placeholder patterns like "your-secret" remainIssue: "STELLAR_RELAY_KEYPAIR_SECRET not set"
Solution:
stellar key generate
# Fund the account: https://friendbot.stellar.org/?addr=GXXXXXX
# Add secret to .envIssue: "DATABASE_URL connection failed"
Solution:
docker compose up -d # Ensure Postgres is running
docker compose logs postgres # Check for errorsFrom apps/api:
npm run test
npm run test:e2e
npm run buildSoroban contracts:
cd contract/soroban
cargo testEVM contracts:
cd contract/evm
npm install
npx hardhat test- Production deployment requires contract audits, strict key management, and chain-specific bridge credentials.