# 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 ```bash 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: ```bash # 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: ```bash # 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: ```bash # 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 ```bash # 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 ```bash # 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: ```bash 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: ```bash # 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: ```bash # 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: ```bash # 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** | |