Skip to main content

Deployment

Rhaios deploys as a set of services on Railway. This guide covers the service topology, per-service environment variable matrix, and the critical distinction between staging (Anvil-forked) and production (live mainnet) deployments.

Service Topology

Staging (Anvil-Forked Mainnet)

Staging uses mainnet chain IDs (Ethereum 1, Base 8453) but routes all traffic through self-hosted Anvil forks and a mock bundler. No real transactions reach mainnet.
┌─────────────────────────────────────────────────┐
│  Railway Project: rhaios-staging                │
│                                                 │
│  rhaios-db         (Postgres)                   │
│                                                 │
│  anvil-forks       (HTTP :8081)                 │
│    ├── Forks from Alchemy (source RPCs)         │
│    └── Serves /rpc/chain/{chainId}              │
│                                                 │
│  mock-bundler      (HTTP :8082)                 │
│    ├── Talks to Anvil fork RPCs                 │
│    └── Serves /rpc/{chainId}                    │
│                                                 │
│  rhaios-mcp        (HTTP :8080)                 │
│    ├── RPC reads → Anvil forks (tier 2)         │
│    ├── Bundler → mock-bundler                   │
│    └── DB → rhaios-db                           │
│                                                 │
│  rhaios-frontend   (Next.js :3000)              │
│    └── DB → rhaios-db                           │
└─────────────────────────────────────────────────┘

Production (Live Mainnet)

Production uses direct RPC providers (Alchemy/Infura) and Pimlico for bundler services. All transactions are real.
┌─────────────────────────────────────────────────┐
│  Railway Project: rhaios-prod                   │
│                                                 │
│  rhaios-db         (Postgres)                   │
│                                                 │
│  rhaios-mcp        (HTTP :8080)                 │
│    ├── RPC reads → Alchemy (tier 1)             │
│    ├── Bundler → Pimlico                        │
│    └── DB → rhaios-db                           │
│                                                 │
│  rhaios-frontend   (Next.js :3000)              │
│    └── DB → rhaios-db                           │
└─────────────────────────────────────────────────┘

Environment Variable Matrix

Per-service env vars are critical. Railway project-level vars are shared across all services. If ETH_RPC_URL is set at the project level for anvil-forks, it also applies to rhaios-mcp — overriding Anvil routing. Always use per-service variables.

Staging Environment

Variablerhaios-mcpanvil-forksmock-bundlerrhaios-frontend
DATABASE_URL${{rhaios-db.DATABASE_URL}}${{rhaios-db.DATABASE_URL}}
RHAIOS_ENVIRONMENTstagingstaging
MCP_TRANSPORThttp
MCP_PORT8080
ETH_RPC_URL(empty)https://eth-mainnet.g.alchemy.com/v2/KEYhttp://anvil-forks:8081/rpc/chain/1
BASE_RPC_URL(empty)https://base-mainnet.g.alchemy.com/v2/KEYhttp://anvil-forks:8081/rpc/chain/8453
ANVIL_FORKS_URLhttp://anvil-forks:8081
MOCK_BUNDLER_URLhttp://mock-bundler:8082
ETH_BUNDLER_URLhttp://mock-bundler:8082/rpc/1
BASE_BUNDLER_URLhttp://mock-bundler:8082/rpc/8453
PIMLICO_API_KEY(empty)
LOG_LEVELdebugdebugdebug
NODE_ENVproductionproductionproductionproduction
Why is ETH_RPC_URL empty for rhaios-mcp? The 3-tier RPC resolution in chain-runtime.ts checks: (1) explicit env var → (2) ANVIL_FORKS_URL → (3) public fallback. Leaving tier 1 empty forces all RPC calls through tier 2 (Anvil forks).

Production Environment

Variablerhaios-mcprhaios-frontend
DATABASE_URL${{rhaios-db.DATABASE_URL}}${{rhaios-db.DATABASE_URL}}
RHAIOS_ENVIRONMENTprodprod
MCP_TRANSPORThttp
MCP_PORT8080
ETH_RPC_URLhttps://eth-mainnet.g.alchemy.com/v2/KEY
BASE_RPC_URLhttps://base-mainnet.g.alchemy.com/v2/KEY
PIMLICO_API_KEYpk_live_...
LOG_LEVELinfo
NODE_ENVproductionproduction

RPC Resolution (3-Tier)

resolveRpcUrl() in packages/mcp-tools/src/chain-runtime.ts resolves the RPC endpoint for each chain:
  1. Tier 1 — Explicit env var (ETH_RPC_URL, BASE_RPC_URL): Always wins if set and non-empty. Used in production with direct Alchemy/Infura URLs.
  2. Tier 2 — Anvil forks (ANVIL_FORKS_URL): Derives ${ANVIL_FORKS_URL}/rpc/chain/${chainId}. Used in staging.
  3. Tier 3 — Public fallback: Hardcoded public RPCs (publicnode.com, mainnet.base.org). Logs a warning. Should never be used in deployed environments.

Bundler Resolution

resolveBundlerUrl() resolves the ERC-4337 bundler endpoint:
  1. Explicit env var (ETH_BUNDLER_URL, BASE_BUNDLER_URL): Per-chain override.
  2. Mock bundler (MOCK_BUNDLER_URL): Derives ${MOCK_BUNDLER_URL}/rpc/${chainId}. Used in staging.
  3. Pimlico (PIMLICO_API_KEY): Derives https://api.pimlico.io/v2/${chainId}/rpc?apikey=KEY. Used in production.
If both PIMLICO_API_KEY and ANVIL_FORKS_URL are set, RPC reads go through Anvil but bundler submissions go to Pimlico mainnet. This creates a read/write split that breaks E2E flows. Set MOCK_BUNDLER_URL or PIMLICO_API_KEY — not both.

Verification Checklist

After deploying, verify:
  1. MCP health: curl https://your-mcp-url/health returns 200
  2. Frontend: Landing page loads without errors
  3. RPC routing (staging): Boot logs show RPC URLs containing anvil-forks, not publicnode.com or mainnet.base.org
  4. Bundler routing (staging): Bundler URLs contain mock-bundler, not pimlico.io
  5. E2E flow: yield_prepareyield_setup_relayyield_execute completes without mainnet RPC calls

Config Files

Rhaios uses a unified config file (rhaios-config.yaml) that combines network definitions, oracle configuration, and v2-core contract addresses. The config loader falls back to legacy format (environments.yaml + SuperLedgerSalts.yaml + deployments.minimal.yaml) if the unified file is not found. Set RHAIOS_CONFIG_FILE to override the config file path. Otherwise, the loader searches from the working directory upward.