A minimal, production-ready microservice for Universal Money Address (UMA) domain and user registry. Supports Lightning Network payments and multi-chain settlements (Polygon, Ethereum, Arbitrum, Optimism, Base, Solana, Plasma).
- Quick Start
- Architecture
- API Documentation
- Integration Guide
- Configuration
- Deployment
- Testing
- Troubleshooting
- Node.js 16+
- MongoDB 4.4+
- Spark Wallet (optional, for Lightning payments)
# Clone the repository
git clone <repository-url>
cd uma-microservice
# Install dependencies
npm install
# Copy environment configuration
cp env.example .env
# Edit .env with your configuration
nano .env
# Initialize database
npm run db:init
# Start the service
npm start
# Use the CLI to add users manually
npm run cli -- --domain example.com --username alice --spark-key your-spark-public-key --evm-address 0x1234...# Check service health
curl http://localhost:3000/health
# Get API information
curl http://localhost:3000/
# View Swagger documentation
open http://localhost:3000/documentationUse the built-in CLI to add users manually:
# Add a user with required fields
npm run cli -- --domain example.com --username alice --spark-key abc123def456... --evm-address 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb
# Add a user with display name
npm run cli -- --domain example.com --username bob --spark-key def456ghi789... --evm-address 0x123456789abcdef --display-name "Bob Smith"
# Get help
npm run cli -- --helpThe CLI will:
- Create the domain if it doesn't exist
- Validate all input parameters
- Create the user with Spark public key and EVM address
- Display the created user's UMA address
This microservice provides UMA (Universal Money Address) functionality as a standalone service that can be integrated into larger applications. It handles:
- Domain Management - Register and manage domains for UMA addresses
- User Registry - Create and manage users within domains
- UMA Protocol - Handle LNURL requests for payment lookups and execution
- Multi-chain Payments - Support for Lightning, Polygon, Ethereum, Arbitrum, Optimism, Base, Solana, and Plasma settlement layers
├── src/
│ ├── server.js # Fastify server with Swagger docs
│ ├── routes/admin.js # Domain and user management APIs
│ ├── services/ # Business logic
│ │ ├── domains.js # Domain operations
│ │ ├── users.js # User operations
│ │ ├── uma.js # UMA protocol handling
│ │ └── payments.js # Payment request management
│ └── db/ # Database layer
│ ├── database.js # MongoDB connection & utilities
│ └── init.js # Database initialization
Interactive API documentation is available at /documentation when the service is running:
http://localhost:3000/documentation
# Lookup user and get payment options
GET /.well-known/lnurlp/{username}
# Request payment with specific amount
GET /.well-known/lnurlp/{username}?amount=1000&nonce=optional&settlementLayer=polygon# Domain management
POST /api/admin/domains # Create domain
GET /api/admin/domain/{domainId} # Get domain details
DELETE /api/admin/domain/{domainId} # Delete domain
PATCH /api/admin/domain/{domainId}/currency/{code} # Update currency settings
# User management
GET /api/admin/users/{domainId} # List domain users
POST /api/admin/users/{domainId} # Create user
DELETE /api/admin/users/{domainId}/{username} # Delete user
# Service endpoints
GET /health # Health check
GET / # API informationUpdate currency settings (activation status, limits) for a domain:
# Deactivate a currency
curl -X PATCH http://localhost:3000/api/admin/domain/{domainId}/currency/USDT_POLYGON \
-H "Authorization: Bearer {API_KEY}" \
-H "Content-Type: application/json" \
-d '{"active": false}'
# Update limits
curl -X PATCH http://localhost:3000/api/admin/domain/{domainId}/currency/BTC \
-H "Authorization: Bearer {API_KEY}" \
-H "Content-Type: application/json" \
-d '{"minSendable": 5000, "maxSendable": 50000000}'
# Update both
curl -X PATCH http://localhost:3000/api/admin/domain/{domainId}/currency/USD \
-H "Authorization: Bearer {API_KEY}" \
-H "Content-Type: application/json" \
-d '{"active": true, "minSendable": 100, "maxSendable": 10000}'This microservice is designed to be called from your main backend application. Here's the recommended integration pattern:
// In your main backend application
class PaymentService {
constructor() {
this.umaServiceUrl = process.env.UMA_SERVICE_URL;
}
// Handle UMA address resolution
async resolveUmaAddress(umaAddress) {
// Parse umaAddress (e.g., "alice@example.com")
const [username, domain] = umaAddress.split('@');
// Call UMA microservice for lookup
const lookupUrl = `${this.umaServiceUrl}/.well-known/lnurlp/${username}`;
const lookupResponse = await fetch(lookupUrl);
if (!lookupResponse.ok) {
throw new Error('UMA address not found');
}
const lnurlData = await lookupResponse.json();
return lnurlData;
}
// Create payment request
async createPayment(umaAddress, amountMsats, options = {}) {
const [username, domain] = umaAddress.split('@');
const params = new URLSearchParams({
amount: amountMsats.toString(),
currency: options.currency || 'USD',
settlementLayer: options.settlementLayer || 'polygon'
});
if (options.nonce) {
params.append('nonce', options.nonce);
}
const payUrl = `${this.umaServiceUrl}/.well-known/lnurlp/${username}?${params}`;
const payResponse = await fetch(payUrl);
if (!payResponse.ok) {
const error = await payResponse.json();
throw new Error(error.reason || 'Payment request failed');
}
return await payResponse.json();
}
// Domain and user management
async setupUmaDomain(domain, ownerEmail) {
const response = await fetch(`${this.umaServiceUrl}/api/admin/domains`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
domain,
ownerEmail,
isDefault: false
})
});
if (!response.ok) {
throw new Error('Failed to create domain');
}
return await response.json();
}
async createUmaUser(domainId, username, options = {}) {
const response = await fetch(`${this.umaServiceUrl}/api/admin/users/${domainId}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username,
displayName: options.displayName,
addresses: options.addresses || {},
sparkPublicKey: options.sparkPublicKey
})
});
if (!response.ok) {
throw new Error('Failed to create user');
}
return await response.json();
}
}// 1. Setup domain (one-time)
const domainResult = await paymentService.setupUmaDomain('example.com', 'admin@example.com');
const domainId = domainResult.domain.id;
// 2. Create users
await paymentService.createUmaUser(domainId, 'alice', {
displayName: 'Alice Smith',
addresses: {
polygon: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
lightning: 'lnbc1000n1pj9x3z0pp5...'
},
sparkPublicKey: 'optional-spark-key-for-lightning'
});
// 3. Handle payments
const lnurlData = await paymentService.resolveUmaAddress('alice@example.com');
// Returns LNURL data with payment options
const paymentRequest = await paymentService.createPayment('alice@example.com', 1000000, {
settlementLayer: 'polygon',
currency: 'USD'
});
// Returns payment details with blockchain address or Lightning invoiceThe microservice returns structured error responses:
// Successful responses
{ success: true, data: {...} }
// Error responses
{
error: 'Bad Request',
message: 'Username is required'
}
// UMA protocol errors
{
status: 'ERROR',
reason: 'User not found'
}Important: This microservice does NOT handle authentication or authorization. It assumes your backend has already:
- Authenticated the user making requests
- Authorized the user to perform the requested operations
- Validated input data
Your backend should:
// Example middleware in your main app
async function umaProxyMiddleware(req, res) {
// 1. Authenticate user
const user = await authenticateUser(req);
// 2. Authorize action
if (req.method === 'POST' && req.path.includes('/users/')) {
await authorizeDomainAccess(user, req.params.domainId);
}
// 3. Proxy to UMA microservice
const umaResponse = await proxyToUmaService(req);
res.send(umaResponse);
}# Server Configuration
PORT=3000
BASE_URL=http://localhost:3000
# Database Configuration
MONGODB_URI=mongodb://localhost:27017/uma-service
DB_NAME=uma-service
# Spark Wallet Configuration (Optional)
SPARK_SEED="your-spark-seed-here"- Multi-tenant mode: Create separate domains for different businesses
- Self-hosted mode: Use
isDefault: truefor single-domain deployments - Domain verification: Not required - domains are created instantly
For Lightning Network payments, configure Spark:
# Install Spark
# Configure SPARK_SEED in .env
# Users can optionally provide sparkPublicKey for Lightning invoices- Database: Use MongoDB replica set for production
- Monitoring: Implement health checks and metrics
- Scaling: Service is stateless, can be horizontally scaled
- Security: Run behind reverse proxy with TLS
- Backup: Regular database backups
# Health endpoint
curl https://your-domain.com/health
# Expected response
{
"status": "ok",
"timestamp": "2024-01-01T12:00:00.000Z"
}# Run all tests
npm test
# Run with coverage
npm run test:coverage
# Run specific test file
node tests/database.test.jsThe service includes comprehensive test coverage:
- Unit tests for all services and utilities
- Integration tests for end-to-end UMA flows
- Error handling and edge case testing
- Database operations testing
# Check MongoDB status
mongosh --eval "db.adminCommand('ismaster')"
# Reset database
npm run db:init# Verify Spark configuration
curl http://localhost:3000/health
# Check Spark logs
# Ensure SPARK_SEED is correctly set# Test domain lookup
curl "http://localhost:3000/.well-known/lnurlp/testuser"
# Check domain exists
curl "http://localhost:3000/api/admin/domains"Enable debug logging:
DEBUG=* npm startCheck application logs for errors:
# Server logs are output to console
# Check for MongoDB connection errors
# Verify environment variables are loaded