Coordinator Setup
Deploy and run the Veil coordinator service.
Overview
The coordinator watches for due schedules, stores recipient payloads and proofs off-chain, and drives the execution flow through MagicBlock ER.
Flow:
- Accept schedule registration payloads over
POST /api/schedules - Validate registration against the on-chain vault and schedule
- Poll Solana for schedules due for execution
- Delegate schedule state to ER
- Execute
claim_paymentattempts - Commit final state back to Solana
- Record execution runs and attempts in PostgreSQL
Prerequisites
- Node.js 18+
- PostgreSQL database
- Redis database (recommended for distributed rate limiting)
- ER authority keypair
- Solana RPC endpoint
Installation
cd coordinator
npm installDatabase Setup
- Create PostgreSQL database
createdb veil_coordinator- Configure
DATABASE_URLin.env
DATABASE_URL=postgresql://user:password@localhost:5432/veil_coordinator- Run migrations
npm run db:migrate- Optional: inspect data with Drizzle Studio
npm run db:studioConfiguration
Copy .env.example to .env and configure the following variables.
Solana Configuration
SOLANA_RPC_URL=https://api.devnet.solana.com
CLAIM_EXECUTION_LAYER=solanaClaim execution layer:
solana- execute payout claims on the base Solana RPCer- execute payout claims on the ER RPC
Keep this on solana unless your payout flow is explicitly prepared for ER-side token-account writes.
ER Configuration
ER_RPC_URL=https://devnet-as.magicblock.app/
ER_VALIDATOR=MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57
ER_AUTHORITY_KEYPAIR_PATH=./er-authority-keypair.jsonYou can also load the ER authority from environment variables instead of a file:
ER_AUTHORITY_KEYPAIR_JSON=[1,2,3,...]or
ER_AUTHORITY_KEYPAIR_B64=base64-encoded-jsonServer Configuration
PORT=3001
POLL_INTERVAL_MS=60000
API_JSON_LIMIT=1mb
MAX_EXECUTION_ATTEMPTS=5
RETRY_BASE_DELAY_MS=30000
RETRY_MAX_DELAY_MS=7200000
MAX_RUNNABLE_EXECUTIONS_PER_POLL=10Metrics Configuration
METRICS_ENABLED=true
METRICS_PUBLIC=false
METRICS_AUTH_TOKEN=replace-with-a-random-secretMetrics access:
METRICS_ENABLED=falsedisables/api/metricsMETRICS_PUBLIC=falseprotects/api/metricsbehind a bearer tokenMETRICS_AUTH_TOKENis required when metrics are private
Rate Limiting Configuration
RATE_LIMIT_ENABLED=true
RATE_LIMIT_DRY_RUN=false
RATE_LIMIT_REDIS_URL=redis://default:password@redis-host:6379
RATE_LIMIT_REDIS_KEY_PREFIX=veil:rate-limit
RATE_LIMIT_REGISTER_CAPACITY=5
RATE_LIMIT_REGISTER_REFILL_RATE_PER_SECOND=0.0016666667
RATE_LIMIT_REGISTER_CONCURRENCY=3
RATE_LIMIT_SCHEDULE_READ_CAPACITY=60
RATE_LIMIT_SCHEDULE_READ_REFILL_RATE_PER_SECOND=1
RATE_LIMIT_EXECUTION_HISTORY_CAPACITY=30
RATE_LIMIT_EXECUTION_HISTORY_REFILL_RATE_PER_SECOND=1How the limiter behaves:
POST /api/schedulesis wallet-aware whenvaultEmployeris present, otherwise IP-based- schedule registration also has a concurrency cap
- schedule reads and execution-history reads use separate token-bucket policies
- Redis is the primary backend
- if Redis is unavailable, registration falls back to in-memory limiting and read routes fail open
Safe rollout:
- Deploy with
RATE_LIMIT_DRY_RUN=true - Verify logs and normal dashboard traffic
- Flip
RATE_LIMIT_DRY_RUN=false - Redeploy to enforce limits
Complete Example .env
# Solana
SOLANA_RPC_URL=https://api.devnet.solana.com
CLAIM_EXECUTION_LAYER=solana
# ER
ER_RPC_URL=https://devnet-as.magicblock.app/
ER_VALIDATOR=MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57
ER_AUTHORITY_KEYPAIR_PATH=./er-authority-keypair.json
# Server
PORT=3001
POLL_INTERVAL_MS=60000
API_JSON_LIMIT=1mb
MAX_EXECUTION_ATTEMPTS=5
RETRY_BASE_DELAY_MS=30000
RETRY_MAX_DELAY_MS=7200000
MAX_RUNNABLE_EXECUTIONS_PER_POLL=10
# Database
DATABASE_URL=postgresql://user:password@localhost:5432/veil_coordinator
DB_POOL_MAX=10
# Metrics
METRICS_ENABLED=true
METRICS_PUBLIC=false
METRICS_AUTH_TOKEN=replace-with-a-random-secret
# Rate Limiting
RATE_LIMIT_ENABLED=true
RATE_LIMIT_DRY_RUN=false
RATE_LIMIT_REDIS_URL=redis://default:password@redis-host:6379
RATE_LIMIT_REDIS_KEY_PREFIX=veil:rate-limit
RATE_LIMIT_REGISTER_CAPACITY=5
RATE_LIMIT_REGISTER_REFILL_RATE_PER_SECOND=0.0016666667
RATE_LIMIT_REGISTER_CONCURRENCY=3
RATE_LIMIT_SCHEDULE_READ_CAPACITY=60
RATE_LIMIT_SCHEDULE_READ_REFILL_RATE_PER_SECOND=1
RATE_LIMIT_EXECUTION_HISTORY_CAPACITY=30
RATE_LIMIT_EXECUTION_HISTORY_REFILL_RATE_PER_SECOND=1ER Validators
Available devnet validators:
- US:
MUS3hc9TCw4cGC12vHNoYcCGzJG1txjgQLZWVoeNHNd - EU:
MEUGGrYPxKk17hCr7wpT6s8dtNokZj5U2L57vjYMS8e - Asia:
MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57 - TEE:
FnE6VJT5QNZdedZPnCoLsARgBwoE6DeJNjBs2H1gySXA
Running
Development
npm run devProduction
npm run build
npm run startProduction Considerations
- use environment-specific RPC endpoints
- secure the ER authority keypair and never commit it
- protect
/api/metricsunless you explicitly want it public - use Redis for distributed rate limiting across coordinator replicas
- roll out rate limiting in dry-run mode before enforcing it
- monitor both RPC usage and limiter backend health