Steer Protocol
AdministerRunbooks

Deployment Runbook

Deploy protocol hooks, markets, LI.FI executors, and the quote-server service with a verifiable handoff trail.

Scope

Use this runbook when deploying or replacing:

  • the protocol HybridCollateralSettlementHook as part of a market stack.
  • a registered-issuer or native-stablecoin market.
  • the LI.FI LifiHookExecutor.
  • the quote-server Railway/Docker service that signs quotes, fetches M1 material, runs oracle updates, and optionally operates the LI.FI loop.

Do not use this page for routine policy changes after deployment. Use Risk Policy Change or the narrower admin runbook for that action.

Authority Required

Deployment private key, RPC access, repository write access for generated manifests, and custody of any service secrets required by the quote server.

Before Any Broadcast

From the repository root:

make install
make test-deploy
forge test
make size
make working-tree-secret-check

For a docs/interface release that exposes the new market:

pnpm --filter docs types:check
make interface-secret-check
git diff --check

Record the chain id, config path, intended manifest path, deployer address, and rollback owner before running a broadcast command.

Deployment Surfaces

SurfaceSource of truthNotes
Protocol hookmarket deployment scriptThe hook address is mined for Uniswap v4 permission bits. Do not deploy it with an arbitrary script.
Registered-issuer marketscript/production/DeployRegisteredIssuerStack.s.solDeploys oracle, risk config, manager, issuer source/controller/adapter, hook, router, and manifest.
Native-stablecoin marketscript/production/DeploySampleStablecoinStack.s.solDeploys native/sample stablecoin path, manager, controller, hook, router, and optional registry/liquidation pieces.
LI.FI hook executorscript/production/DeployLifiHookExecutor.s.solDeploys the executor contract used as the v1 LI.FI solver identity.
Quote serverquote-server/Dockerfile and root railway.jsonRuns the private signing/M1/oracle/LI.FI service.
Public gatewaygateway/Dockerfile and gateway/railway.jsonServes interface/docs and proxies /quote/* to the private quote-server service.

Protocol Hook Deployment

The protocol hook is deployed by the market stack scripts. The scripts mine the hook salt, deploy HybridCollateralSettlementHook, verify the deployed address matches expectedHook, and write hook, expectedHook, and hookSalt into the manifest.

Registered-issuer dry run:

export DEPLOY_CONFIG=deployments/config/sepolia-m1-staging-active.json
make deploy-dry-run DEPLOY_CONFIG="$DEPLOY_CONFIG"

Registered-issuer broadcast:

DEPLOY_CONFIG="$DEPLOY_CONFIG" \
forge script script/production/DeployRegisteredIssuerStack.s.sol \
  --tc DeployRegisteredIssuerStackScript \
  --rpc-url "$SEPOLIA_RPC_URL" \
  --broadcast \
  --private-key "$SEPOLIA_DEPLOYER_PRIVATE_KEY"

Native-stablecoin dry run:

export DEPLOY_CONFIG=deployments/config/sepolia-native-stablecoin-registry-v3.json
forge script script/production/DeploySampleStablecoinStack.s.sol \
  --tc DeploySampleStablecoinStackScript \
  --rpc-url "$SEPOLIA_RPC_URL" \
  --sender "$SEPOLIA_DEPLOYER_ADDRESS"

Native-stablecoin broadcast:

make deploy-native-stablecoin DEPLOY_CONFIG="$DEPLOY_CONFIG"

Hook gotchas:

  • admin in the config must match the broadcasting deployer.
  • chainId in the config must match the RPC chain.
  • Mainnet configs must not use fixed-oracle rehearsal mode.
  • The hook address is not negotiable after mining. If hook and expectedHook diverge, stop instead of patching the manifest by hand.
  • The deployed router must remain allowlisted on the hook by codehash and address.

Registered Issuer Stack

Use this path for M1/registered-issuer collateral markets.

export DEPLOY_CONFIG=deployments/config/sepolia-m1-staging-active.json
make deploy-dry-run DEPLOY_CONFIG="$DEPLOY_CONFIG"
DEPLOY_CONFIG="$DEPLOY_CONFIG" \
forge script script/production/DeployRegisteredIssuerStack.s.sol \
  --tc DeployRegisteredIssuerStackScript \
  --rpc-url "$SEPOLIA_RPC_URL" \
  --broadcast \
  --private-key "$SEPOLIA_DEPLOYER_PRIVATE_KEY"

After broadcast:

export MANIFEST=deployments/manifests/11155111-registered-issuer-m1-staging-active.json
make env-check MANIFEST="$MANIFEST"
make registered-issuer-payload MANIFEST="$MANIFEST" AMOUNT=1000000
make live-qa-check MANIFEST="$MANIFEST"

Add initial liquidity only after the manifest and payload checks pass:

make add-registered-issuer-liquidity \
  MANIFEST="$MANIFEST" \
  DEPLOY_CONFIG="$DEPLOY_CONFIG" \
  LP_AMOUNT0=1000000 \
  LP_AMOUNT1=1000000

Native Stablecoin Stack

export DEPLOY_CONFIG=deployments/config/sepolia-native-stablecoin-registry-v3.json
make deploy-native-stablecoin DEPLOY_CONFIG="$DEPLOY_CONFIG"

After broadcast:

export MANIFEST=deployments/manifests/11155111-native-stablecoin-registry-v3.json
make env-check MANIFEST="$MANIFEST"
make seed-native-liquidity MANIFEST="$MANIFEST" DEPLOY_CONFIG="$DEPLOY_CONFIG"

If the market needs liquidation support:

make deploy-liquidation-controller MANIFEST="$MANIFEST"

Native gotchas:

  • Production multi-market native stablecoin deployments should use StableDebtRegistry; isolated direct-manager minter authority is testnet-only unless explicitly approved.
  • Backfilled liquidation support mutates the manifest. Review the manifest diff before handing the market to operators or bots.
  • If a deployment was interrupted, prefer a checked resume config and verified on-chain reads over trusting stale local broadcast output.

LI.FI Hook Executor Deployment

Deploy the executor before running live LI.FI fill mode:

export DEPLOY_CONFIG=deployments/config/11155111-lifi-hook-executor.json

DEPLOY_CONFIG="$DEPLOY_CONFIG" \
forge script script/production/DeployLifiHookExecutor.s.sol \
  --tc DeployLifiHookExecutorScript \
  --rpc-url "$SEPOLIA_RPC_URL" \
  --broadcast \
  --private-key "$SEPOLIA_DEPLOYER_PRIVATE_KEY"

Then verify the executor manifest and LI.FI readiness:

pnpm --filter @steerprotocol/collateral-rail-quote-server lifi:readiness -- --strict

Executor gotchas:

  • The manifest solverId is derived from the executor address.
  • DEFAULT_ADMIN_ROLE, OPERATOR_ROLE, SOLVER_SIGNER_ROLE, input settler, router, and output settler are verified by the deploy script.
  • Stop order opening before replacing an executor that live orders may reference.

Quote Server Deployment

Build and test the image locally from the repository root:

pnpm install --frozen-lockfile --filter @steerprotocol/collateral-rail-sdk
pnpm --filter @steerprotocol/collateral-rail-sdk build
pnpm install --frozen-lockfile --filter @steerprotocol/collateral-rail-quote-server
pnpm --filter @steerprotocol/collateral-rail-quote-server build
pnpm --filter @steerprotocol/collateral-rail-quote-server test

docker build -f quote-server/Dockerfile -t collateral-rail-quote-server .
docker run --rm -p 8080:8080 --env-file quote-server.env collateral-rail-quote-server

In another shell:

make quote-health QUOTE_SERVER_URL=http://127.0.0.1:8080

The root railway.json deploys the quote server with quote-server/Dockerfile and health check path /healthz.

Minimum Railway environment:

VariableNotes
CHAIN_IDtarget chain id
COLLATERAL_MANAGERactive market manager
SETTLEMENT_CONTROLLERrequired for registered-issuer paths
SPONSOR_SIGNER_PRIVATE_KEY or QUOTE_SIGNER_PRIVATE_KEYquote signer; must match on-chain config
QUOTE_RPC_URL, RPC_URL, or SEPOLIA_RPC_URLrequired for risk-aware reads and fixed oracle updater
M1_JWTrequired for M1-backed deposit/redeem endpoints
M1_IS_TESTNETtrue for testnet M1, false for production M1

Optional Railway environment for fixed oracle updates:

ORACLE_UPDATER_PRIVATE_KEY=0x...
QUOTE_FIXED_ORACLE_UPDATES_FILE=interface/public/manifests/catalog.json
QUOTE_FIXED_ORACLE_UPDATE_INTERVAL_SECONDS=21600
QUOTE_FIXED_ORACLE_PRICE_API_BASE=https://api.coinbase.com

Optional Railway environment for LI.FI operation:

LI_FI_INTENTS_API_KEY=...
LI_FI_ORDER_SERVER_BASE=https://order-dev.li.fi
LI_FI_INVENTORY_ROUTES_JSON=[...]
LI_FI_TRUST_COMPONENTS_JSON=[...]
LI_FI_REPLAY_PGLITE_DATA_DIR=/data/lifi-replay
LI_FI_SERVE_VERIFY_IDENTITY=true
LI_FI_SERVE_FILL_ORDERS=false
LI_FI_SERVE_SUBMIT_FINALISE=false

After Railway deploy, run readiness from an environment that matches the service. If LI.FI operation is enabled, run one bounded loop before unattended mode:

pnpm --filter @steerprotocol/collateral-rail-quote-server lifi:readiness -- --strict
pnpm --filter @steerprotocol/collateral-rail-quote-server lifi:serve -- --live --once

Quote-server gotchas:

  • /healthz only proves the HTTP process responds. It does not prove quote issuance, fixed oracle updates, M1 material, LI.FI quote publishing, fills, or finalise safety.
  • Server secrets must not appear in VITE_* browser variables.
  • QUOTE_FIXED_ORACLE_UPDATES and QUOTE_FIXED_ORACLE_UPDATES_FILE are mutually exclusive.
  • Fixed oracle updates require both RPC and ORACLE_UPDATER_PRIVATE_KEY.
  • Blank fixedOraclePriceE18 values in manifests should be treated as unset; if a service crashes while parsing catalog data, inspect manifest/catalog fields before rotating keys.
  • For M1 registered-issuer quotes, publish preview inventory only; fetch final executable M1 permit material just in time for build/fill.

Public Gateway And Catalog

If the deployment changes the public interface or market list, regenerate the interface catalog before building the gateway:

INTERFACE_DEPLOYMENT_MANIFESTS=deployments/manifests/11155111-registered-issuer-m1-staging-active.json,deployments/manifests/11155111-native-stablecoin-registry-v3.json \
INTERFACE_DEFAULT_DEPLOYMENT_MANIFEST=deployments/manifests/11155111-registered-issuer-m1-staging-active.json \
node gateway/scripts/write-interface-env.mjs

make interface-check

The gateway Railway service uses gateway/railway.json, serves the interface and docs, and proxies /quote/* plus /healthz to the private quote-server service. Set this only if the private Railway DNS name changes:

QUOTE_SERVER_UPSTREAM=http://<quote-service>.railway.internal:8080

Gateway gotchas:

  • The public catalog is generated at build time from configured manifest paths. If the live site shows stale markets, check gateway/scripts/write-interface-env.mjs, interface/public/manifests/catalog.json, and the copied deployment manifests.
  • PUBLIC_SEPOLIA_RPC_URL and PUBLIC_WALLETCONNECT_PROJECT_ID are public browser values. Do not use quote signer, deployer, M1, or LI.FI secrets here.

Verification

Deployment is not done until:

  • Manifest exists under deployments/manifests/ and has verified: true.
  • hook, expectedHook, and hookSalt are present and consistent.
  • make env-check MANIFEST=... passes.
  • A small quote or payload path succeeds against the new market.
  • Quote-server /healthz responds and any enabled M1, oracle updater, or LI.FI path has its own readiness evidence.
  • Interface catalog changes, if any, point at the same manifest facts.

Handoff Notes

Record:

  • deployer, chain id, config path, manifest path, and commit hash.
  • broadcast transaction hashes and generated manifest diff.
  • hook address, expected hook address, hook salt, router, manager, controller, oracle, risk config, quote signer, and executor if applicable.
  • quote-server service URL, Railway service name, and configured health path.
  • readiness command outputs.
  • any follow-up admin steps such as signer rotation, liquidation controller backfill, catalog update, or gateway redeploy.

Rollback

  • Stop quote issuance and LI.FI fill submission first.
  • Revert manifest/catalog changes if clients are reading the wrong deployment.
  • Restore the previous quote-server environment or image if the service is bad.
  • Disable signed quote mode or enter emergency pause if deployed on-chain state is unsafe.
  • Resume with canary quotes or lifi:serve -- --live --once, not unattended traffic.

Source Files

  • script/production/DeployRegisteredIssuerStack.s.sol
  • script/production/DeploySampleStablecoinStack.s.sol
  • script/production/DeployLifiHookExecutor.s.sol
  • quote-server/Dockerfile
  • railway.json
  • gateway/Dockerfile
  • gateway/railway.json
  • gateway/scripts/write-interface-env.mjs
  • deployments/config/
  • deployments/manifests/

On this page