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
| Variable | rhaios-mcp | anvil-forks | mock-bundler | rhaios-frontend |
|---|
DATABASE_URL | ${{rhaios-db.DATABASE_URL}} | — | — | ${{rhaios-db.DATABASE_URL}} |
RHAIOS_ENVIRONMENT | staging | — | — | staging |
MCP_TRANSPORT | http | — | — | — |
MCP_PORT | 8080 | — | — | — |
ETH_RPC_URL | (empty) | https://eth-mainnet.g.alchemy.com/v2/KEY | http://anvil-forks:8081/rpc/chain/1 | — |
BASE_RPC_URL | (empty) | https://base-mainnet.g.alchemy.com/v2/KEY | http://anvil-forks:8081/rpc/chain/8453 | — |
ANVIL_FORKS_URL | http://anvil-forks:8081 | — | — | — |
MOCK_BUNDLER_URL | http://mock-bundler:8082 | — | — | — |
ETH_BUNDLER_URL | http://mock-bundler:8082/rpc/1 | — | — | — |
BASE_BUNDLER_URL | http://mock-bundler:8082/rpc/8453 | — | — | — |
PIMLICO_API_KEY | (empty) | — | — | — |
LOG_LEVEL | debug | debug | debug | — |
NODE_ENV | production | production | production | production |
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
| Variable | rhaios-mcp | rhaios-frontend |
|---|
DATABASE_URL | ${{rhaios-db.DATABASE_URL}} | ${{rhaios-db.DATABASE_URL}} |
RHAIOS_ENVIRONMENT | prod | prod |
MCP_TRANSPORT | http | — |
MCP_PORT | 8080 | — |
ETH_RPC_URL | https://eth-mainnet.g.alchemy.com/v2/KEY | — |
BASE_RPC_URL | https://base-mainnet.g.alchemy.com/v2/KEY | — |
PIMLICO_API_KEY | pk_live_... | — |
LOG_LEVEL | info | — |
NODE_ENV | production | production |
RPC Resolution (3-Tier)
resolveRpcUrl() in packages/mcp-tools/src/chain-runtime.ts resolves the RPC endpoint for each chain:
- 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.
- Tier 2 — Anvil forks (
ANVIL_FORKS_URL): Derives ${ANVIL_FORKS_URL}/rpc/chain/${chainId}. Used in staging.
- 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:
- Explicit env var (
ETH_BUNDLER_URL, BASE_BUNDLER_URL): Per-chain override.
- Mock bundler (
MOCK_BUNDLER_URL): Derives ${MOCK_BUNDLER_URL}/rpc/${chainId}. Used in staging.
- 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:
- MCP health:
curl https://your-mcp-url/health returns 200
- Frontend: Landing page loads without errors
- RPC routing (staging): Boot logs show RPC URLs containing
anvil-forks, not publicnode.com or mainnet.base.org
- Bundler routing (staging): Bundler URLs contain
mock-bundler, not pimlico.io
- E2E flow:
yield_prepare → yield_setup_relay → yield_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.