harb/docs/DEPLOYMENT_RUNBOOK.md
openhands 85350caf52 feat: OptimizerV3 with direct 2D staking-to-LP parameter mapping
Core protocol changes for launch readiness:

- OptimizerV3: binary bear/bull mapping from (staking%, avgTax) — avoids
  exploitable AW 30-90 kill zone. Bear: AS=30%, AW=100, CI=0, DD=0.3e18.
  Bull: AS=100%, AW=20, CI=0, DD=1e18. UUPS upgradeable with __gap[48].
- Directional VWAP: only records prices on ETH inflow (buys), preventing
  sell-side dilution of price memory
- Floor formula: unified max(scarcity, mirror, clamp) — VWAP mirror uses
  distance from adjusted VWAP as floor distance, no branching
- PriceOracle (M-1 fix): correct fallback TWAP divisor (60000s, not 300s)
- Access control (M-2 fix): deployer-only guard on one-time setters
- Recenter rate limit (M-3 fix): 60-second cooldown for open recenters
- Safe fallback params: recenter() optimizer-failure defaults changed from
  exploitable CI=50%/AW=50 to safe bear-mode CI=0/AW=100
- Recentered event for monitoring and indexing
- VERSION bump to 2, kraiken-lib COMPATIBLE_CONTRACT_VERSIONS updated

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 18:21:18 +00:00

9.7 KiB

KRAIKEN Mainnet Deployment Runbook

Target chain: Base (L2) Contract version: V2 (OptimizerV3 w/ directional VWAP)


1. Pre-Deployment Checklist

  • All tests pass: cd onchain && forge test
  • Gas snapshot baseline: forge snapshot
  • Security review complete (see analysis/SECURITY_REVIEW.md)
  • Storage layout verified for UUPS upgrade (see analysis/STORAGE_LAYOUT.md)
  • Floor ratchet mitigation status confirmed (branch fix/floor-ratchet)
  • Multisig wallet ready for feeDestination (Gnosis Safe on Base)
  • Deployer wallet funded with sufficient ETH for gas (~0.05 ETH)
  • LiquidityManager funding wallet ready (initial ETH seed for pool positions)
  • .secret seed phrase file present in onchain/ (deployer account)
  • Base RPC endpoint configured and tested
  • Etherscan/Basescan API key ready for contract verification
  • kraiken-lib version updated: COMPATIBLE_CONTRACT_VERSIONS includes 2

2. Contract Deployment Order

All contracts are deployed in a single broadcast transaction via DeployBaseMainnet.sol:

1. Kraiken token (ERC20 + ERC20Permit)
2. Stake contract (Kraiken address, feeDestination)
3. Kraiken.setStakingPool(Stake)
4. Uniswap V3 Pool (create or use existing, FEE=10000)
5. Pool initialization (1 cent starting price)
6. OptimizerV3 implementation + ERC1967Proxy
7. LiquidityManager (factory, WETH, Kraiken, OptimizerProxy)
8. LiquidityManager.setFeeDestination(multisig)
9. Kraiken.setLiquidityManager(LiquidityManager)

Deploy Command

cd onchain

# Verify configuration first
cat script/DeployBaseMainnet.sol  # Check feeDest, weth, v3Factory

# Dry run (no broadcast)
forge script script/DeployBaseMainnet.sol \
  --rpc-url $BASE_RPC \
  --sender $(cast wallet address --mnemonic "$(cat .secret)")

# Live deployment
forge script script/DeployBaseMainnet.sol \
  --rpc-url $BASE_RPC \
  --broadcast \
  --verify \
  --etherscan-api-key $BASESCAN_API_KEY \
  --slow

Critical: The --slow flag submits transactions one at a time, waiting for confirmation. This prevents nonce issues on Base.

Record Deployment Addresses

After deployment, save all addresses from console output:

# Update deployments file
cat >> deployments-mainnet.json << 'EOF'
{
  "chain": "base",
  "chainId": 8453,
  "kraiken": "0x...",
  "stake": "0x...",
  "pool": "0x...",
  "liquidityManager": "0x...",
  "optimizerProxy": "0x...",
  "optimizerImpl": "0x...",
  "feeDestination": "0x...",
  "deployer": "0x...",
  "deployedAt": "2026-XX-XX",
  "txHash": "0x..."
}
EOF

3. Post-Deployment Setup

3.1 Fund LiquidityManager

The LM needs ETH to create initial positions:

# Send ETH to LiquidityManager (unwrapped — it will wrap to WETH internally)
cast send $LIQUIDITY_MANAGER --value 10ether \
  --rpc-url $BASE_RPC \
  --mnemonic "$(cat .secret)"

3.2 Set Recenter Access

Restrict recenter() to the txnBot address:

# Must be called by feeDestination (multisig)
cast send $LIQUIDITY_MANAGER "setRecenterAccess(address)" $TXNBOT_ADDRESS \
  --rpc-url $BASE_RPC \
  --mnemonic "$(cat .secret)"  # or via multisig

3.3 Trigger First Recenter

# Wait for pool to accumulate some TWAP history (~5 minutes of trades)
# Then trigger first recenter (must be called by recenterAccess)
cast send $LIQUIDITY_MANAGER "recenter()" \
  --rpc-url $BASE_RPC \
  --from $TXNBOT_ADDRESS

3.4 Configure txnBot

Update services/txnBot/ configuration for Base mainnet:

  • Set LIQUIDITY_MANAGER address
  • Set KRAIKEN address
  • Set RPC to Base mainnet
  • Deploy txnBot service

3.5 Configure Ponder Indexer

# Update kraiken-lib/src/version.ts
export const COMPATIBLE_CONTRACT_VERSIONS = [2];

# Update Ponder config for Base mainnet addresses
# Set PONDER_NETWORK=BASE in environment

3.6 Update Frontend

  • Update contract addresses in web-app configuration
  • Update kraiken-lib ABIs: cd onchain && forge build then rebuild kraiken-lib
  • Deploy frontend to production

4. Optimizer Upgrade Procedure

If upgrading an existing Optimizer proxy to OptimizerV3:

cd onchain

# Set proxy address
export OPTIMIZER_PROXY=0x...

# Dry run
forge script script/UpgradeOptimizer.sol \
  --rpc-url $BASE_RPC

# Execute upgrade
forge script script/UpgradeOptimizer.sol \
  --rpc-url $BASE_RPC \
  --broadcast \
  --verify \
  --etherscan-api-key $BASESCAN_API_KEY

# Verify post-upgrade
cast call $OPTIMIZER_PROXY "getLiquidityParams()" --rpc-url $BASE_RPC

Expected output: Bear-mode defaults (CI=0, AS=0.3e18, AW=100, DD=0.3e18) since staking will be <91%.


5. Verification Steps

Run these checks after deployment to confirm everything is wired correctly:

# 1. Kraiken token
cast call $KRAIKEN "VERSION()" --rpc-url $BASE_RPC          # Should return 2
cast call $KRAIKEN "peripheryContracts()" --rpc-url $BASE_RPC  # LM + Stake addresses

# 2. LiquidityManager
cast call $LM "feeDestination()" --rpc-url $BASE_RPC       # Should be multisig
cast call $LM "recenterAccess()" --rpc-url $BASE_RPC       # Should be txnBot
cast call $LM "positions(0)" --rpc-url $BASE_RPC           # Floor position (after recenter)
cast call $LM "positions(1)" --rpc-url $BASE_RPC           # Anchor position
cast call $LM "positions(2)" --rpc-url $BASE_RPC           # Discovery position

# 3. OptimizerV3 (through proxy)
cast call $OPTIMIZER "getLiquidityParams()" --rpc-url $BASE_RPC

# 4. Pool state
cast call $POOL "slot0()" --rpc-url $BASE_RPC              # Current tick, price
cast call $POOL "liquidity()" --rpc-url $BASE_RPC          # Total liquidity

# 5. Stake contract
cast call $STAKE "nextPositionId()" --rpc-url $BASE_RPC    # Should be 0 initially

# 6. ETH balance
cast balance $LM --rpc-url $BASE_RPC                       # Should show funded amount

6. Emergency Procedures

6.1 Pause Recentering

WARNING: revokeRecenterAccess() does NOT pause recentering. It makes recenter() permissionless (anyone can call it with 60-second cooldown + TWAP check). In an attack scenario, this would make things worse.

To truly lock out recenters, set recenterAccess to a burn address that no one controls:

# Called by feeDestination (multisig) — sets access to a dead address
cast send $LM "setRecenterAccess(address)" 0x000000000000000000000000000000000000dEaD \
  --rpc-url $BASE_RPC

This leaves existing positions in place but prevents any new recenters. LP positions continue earning fees. To resume, call setRecenterAccess() with the txnBot address again.

6.2 Upgrade Optimizer to Safe Defaults

Deploy a minimal "safe" optimizer that always returns bear parameters:

# Deploy SafeOptimizer with hardcoded bear params
# Upgrade proxy to SafeOptimizer
OPTIMIZER_PROXY=$OPTIMIZER forge script script/UpgradeOptimizer.sol \
  --rpc-url $BASE_RPC --broadcast

6.3 Emergency Parameter Override

If the optimizer needs temporary override, deploy a new implementation with hardcoded safe parameters:

  • CI=0, AS=30% (0.3e18), AW=100, DD=0.3e18 (bear defaults)
  • These were verified safe across all 1050 parameter sweep combinations

6.4 Rollback Plan

There is no rollback for deployed contracts. Mitigation options:

  • Upgrade optimizer proxy to revert to V1/V2 logic
  • Revoke recenter access to freeze positions
  • The LiquidityManager itself is NOT upgradeable (by design — immutable control)
  • In worst case: deploy entirely new contract set, migrate liquidity

6.5 Known Attack Response: Floor Ratchet

If floor ratchet extraction is detected (rapid recenters + floor tick creeping toward current price):

  1. Immediately set recenter access to burn address (0xdEaD) — do NOT use revokeRecenterAccess() as it makes recenter permissionless
  2. Assess floor position state via positions(0)
  3. Deploy patched LiquidityManager if fix is ready
  4. Current mitigation: bear-mode parameters (AW=100) create 7000-tick floor distance, making ratchet extraction significantly harder

7. Monitoring Setup

On-Chain Monitoring

Track these metrics via Ponder or direct RPC polling:

Metric How Alert Threshold
Floor tick distance positions(0).tickLower - currentTick < 2000 ticks
Recenter frequency Count recenter() calls per hour > 10/hour
LM ETH balance address(LM).balance + WETH.balanceOf(LM) < 1 ETH (most ETH is in pool positions)
VWAP drift getVWAP() vs current price > 50% divergence
Optimizer mode getLiquidityParams() return values Unexpected bull in low-staking
Fee revenue WETH transfers to feeDestination Sudden drop to 0

Off-Chain Monitoring

  • txnBot health: GET /api/txn/status — should return healthy
  • Ponder indexing: GET /api/graphql — query stats entity
  • Frontend version check: useVersionCheck() composable validates contract VERSION

Alerting Triggers

  1. Critical: Floor position liquidity = 0 (no floor protection)
  2. Critical: recenter() reverts for > 1 hour
  3. High: > 20 recenters in 1 hour (potential manipulation)
  4. Medium: VWAP compression triggered (high cumulative volume)
  5. Low: Optimizer returns bull mode (verify staking metrics justify it)

8. Deployment Timeline

Step Duration Dependency
Deploy contracts ~2 min Funded deployer wallet
Verify on Basescan ~5 min Deployment complete
Fund LiquidityManager ~1 min Deployment complete
Set recenter access ~1 min feeDestination set (multisig)
Wait for TWAP history ~5-10 min Pool initialized
First recenter ~1 min TWAP history + recenter access
Deploy txnBot ~5 min Addresses configured
Deploy Ponder ~10 min Addresses + kraiken-lib updated
Deploy frontend ~5 min Ponder running
Total ~30-40 min