#!/usr/bin/env bash # recover-bootstrap.sh — Diagnose and recover from a failed VWAP bootstrap. # # If the second recenter() reverts mid-sequence (e.g. "amplitude not reached"), # the LiquidityManager is left with positions deployed but cumulativeVolume == 0. # This script diagnoses the state and retries the bootstrap. # # See docs/mainnet-bootstrap.md "Recovery from failed mid-sequence bootstrap" # for the full manual procedure. # # Usage: # scripts/recover-bootstrap.sh --rpc-url --private-key --lm [OPTIONS] # # Options: # --rpc-url RPC endpoint (required) # --private-key Deployer private key (required) # --lm
LiquidityManager address (required) # --kraiken
Kraiken token address (optional, for seed buy retry) # --seed-eth Extra seed buy amount, e.g. "0.01ether" (default: skip seed buy) # --fee-dest
Set feeDestination to this address if not already correct # --dry-run Diagnose only, do not send transactions # --help Show this help message set -euo pipefail # ── Defaults ────────────────────────────────────────────────────────── RPC_URL="" PRIVATE_KEY="" LM_ADDRESS="" KRAIKEN="" SEED_ETH="" FEE_DEST="" DRY_RUN=false WETH="0x4200000000000000000000000000000000000006" SWAP_ROUTER_MAINNET="0x2626664c2603336E57B271c5C0b26F421741e481" SWAP_ROUTER_SEPOLIA="0x94cC0AaC535CCDB3C01d6787D6413C739ae12bc4" MAX_UINT="0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" # ── Argument parsing ────────────────────────────────────────────────── usage() { sed -n '/^# Usage:/,/^set -euo/p' "$0" | head -n -1 | sed 's/^# \?//' exit 0 } while [[ $# -gt 0 ]]; do case "$1" in --rpc-url) RPC_URL="$2"; shift 2 ;; --private-key) PRIVATE_KEY="$2"; shift 2 ;; --lm) LM_ADDRESS="$2"; shift 2 ;; --kraiken) KRAIKEN="$2"; shift 2 ;; --seed-eth) SEED_ETH="$2"; shift 2 ;; --fee-dest) FEE_DEST="$2"; shift 2 ;; --dry-run) DRY_RUN=true; shift ;; --help|-h) usage ;; *) echo "Unknown option: $1"; usage ;; esac done if [[ -z "$RPC_URL" || -z "$PRIVATE_KEY" || -z "$LM_ADDRESS" ]]; then echo "Error: --rpc-url, --private-key, and --lm are required." echo "Run with --help for usage." exit 1 fi # ── Helpers ─────────────────────────────────────────────────────────── info() { echo "[recover] $*"; } warn() { echo "[recover] WARNING: $*"; } error() { echo "[recover] ERROR: $*" >&2; } detect_swap_router() { local chain_id chain_id="$(cast chain-id --rpc-url "$RPC_URL" 2>/dev/null || echo "")" if [[ "$chain_id" == "8453" ]]; then echo "$SWAP_ROUTER_MAINNET" else echo "$SWAP_ROUTER_SEPOLIA" fi } # ── Step 1: Diagnose ───────────────────────────────────────────────── info "Diagnosing LiquidityManager at $LM_ADDRESS ..." CUMVOL="$(cast call --rpc-url "$RPC_URL" "$LM_ADDRESS" "cumulativeVolume()(uint256)" 2>/dev/null || echo "ERROR")" if [[ "$CUMVOL" == "ERROR" ]]; then error "Cannot read cumulativeVolume — is LM_ADDRESS correct?" exit 1 fi FEE_DESTINATION="$(cast call --rpc-url "$RPC_URL" "$LM_ADDRESS" "feeDestination()(address)" 2>/dev/null || echo "ERROR")" FEE_LOCKED="$(cast call --rpc-url "$RPC_URL" "$LM_ADDRESS" "feeDestinationLocked()(bool)" 2>/dev/null || echo "ERROR")" LAST_RECENTER="$(cast call --rpc-url "$RPC_URL" "$LM_ADDRESS" "lastRecenterTime()(uint256)" 2>/dev/null || echo "0")" info " cumulativeVolume: $CUMVOL" info " feeDestination: $FEE_DESTINATION" info " feeDestinationLocked: $FEE_LOCKED" info " lastRecenterTime: $LAST_RECENTER" # ── Check: already bootstrapped? ───────────────────────────────────── if [[ "$CUMVOL" != "0" ]]; then info "VWAP bootstrap already complete (cumulativeVolume=$CUMVOL). Nothing to recover." exit 0 fi info "cumulativeVolume is 0 — bootstrap incomplete, recovery needed." # ── Check: feeDestination ──────────────────────────────────────────── if [[ "$FEE_DESTINATION" == "0x0000000000000000000000000000000000000000" ]]; then warn "feeDestination is not set (zero address)." if [[ -n "$FEE_DEST" ]]; then info "Will set feeDestination to $FEE_DEST" else warn "Pass --fee-dest
to set it during recovery." fi fi if [[ -n "$FEE_DEST" && "$FEE_DESTINATION" != "$FEE_DEST" ]]; then if [[ "$FEE_LOCKED" == "true" ]]; then error "feeDestination is locked at $FEE_DESTINATION — cannot change to $FEE_DEST." error "Manual intervention required." exit 1 fi if [[ "$DRY_RUN" == "true" ]]; then info "[dry-run] Would set feeDestination to $FEE_DEST" else info "Setting feeDestination to $FEE_DEST ..." cast send --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY" \ "$LM_ADDRESS" "setFeeDestination(address)" "$FEE_DEST" info "feeDestination updated." fi fi # ── Check: cooldown ────────────────────────────────────────────────── NOW="$(cast block latest --rpc-url "$RPC_URL" --field timestamp 2>/dev/null || echo "0")" COOLDOWN_END=$(( LAST_RECENTER + 60 )) if [[ "$NOW" -lt "$COOLDOWN_END" ]]; then REMAINING=$(( COOLDOWN_END - NOW )) warn "Recenter cooldown has not elapsed. ${REMAINING}s remaining." warn "Wait and re-run this script." if [[ "$DRY_RUN" == "true" ]]; then exit 0 fi info "Waiting ${REMAINING}s for cooldown ..." sleep "$REMAINING" fi # ── Optional: extra seed buy ───────────────────────────────────────── if [[ -n "$SEED_ETH" && -n "$KRAIKEN" ]]; then SWAP_ROUTER="$(detect_swap_router)" DEPLOYER_ADDR="$(cast wallet address --private-key "$PRIVATE_KEY")" if [[ "$DRY_RUN" == "true" ]]; then info "[dry-run] Would execute seed buy: $SEED_ETH WETH -> KRAIKEN" else info "Executing seed buy ($SEED_ETH WETH -> KRAIKEN) ..." SEED_WEI="$(cast --to-unit "$SEED_ETH" wei)" cast send --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY" \ "$WETH" "deposit()" --value "$SEED_ETH" cast send --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY" \ "$WETH" "approve(address,uint256)" "$SWAP_ROUTER" "$MAX_UINT" # Determine swap direction from token ordering WETH_LOWER=$(echo "$WETH" | tr '[:upper:]' '[:lower:]' | sed 's/^0x//') KRAIKEN_LOWER=$(echo "$KRAIKEN" | tr '[:upper:]' '[:lower:]' | sed 's/^0x//') if [[ "$WETH_LOWER" < "$KRAIKEN_LOWER" ]]; then SQRT_LIMIT=4295128740 else SQRT_LIMIT=1461446703485210103287273052203988822378723970341 fi cast send --legacy --gas-limit 300000 --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY" \ "$SWAP_ROUTER" "exactInputSingle((address,address,uint24,address,uint256,uint256,uint160))" \ "($WETH,$KRAIKEN,10000,$DEPLOYER_ADDR,$SEED_WEI,0,$SQRT_LIMIT)" info "Seed buy complete." # Wait for cooldown after potential recenter trigger info "Waiting 65s for recenter cooldown ..." sleep 65 fi fi # ── Retry second recenter ──────────────────────────────────────────── if [[ "$DRY_RUN" == "true" ]]; then info "[dry-run] Would call recenter() on $LM_ADDRESS" info "[dry-run] Done. Re-run without --dry-run to execute." exit 0 fi info "Calling recenter() to complete VWAP bootstrap ..." if ! cast send --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY" \ "$LM_ADDRESS" "recenter()" 2>&1; then error "recenter() reverted. Check the revert reason above." error "Common causes:" error " - 'amplitude not reached' -> need larger seed buy (use --seed-eth with --kraiken)" error " - 'price deviated from oracle' -> wait for TWAP history" error " - 'recenter cooldown' -> wait 60s and retry" exit 1 fi # ── Verify ──────────────────────────────────────────────────────────── CUMVOL="$(cast call --rpc-url "$RPC_URL" "$LM_ADDRESS" "cumulativeVolume()(uint256)" 2>/dev/null || echo "0")" if [[ "$CUMVOL" == "0" || -z "$CUMVOL" ]]; then error "Recovery failed — cumulativeVolume is still 0." error "The anchor position may have no fees. Try a larger seed buy:" error " $0 --rpc-url $RPC_URL --private-key --lm $LM_ADDRESS --kraiken --seed-eth 0.01ether" exit 1 fi info "Recovery successful! cumulativeVolume=$CUMVOL" info "VWAP bootstrap is complete."