WickHook

0x3d1bfb1e3634c9d278359143ee7bb0e12cf690c4

Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
23,972 bytes
ABI entries
154
69 read · 27 write
License
none

Contract information

Address
0x3d1bfb1e3634c9d278359143ee7bb0e12cf690c4
Chain
Robinhood Chain (4663)
Compiler
v0.8.26+commit.8a97fa7a
Optimization
Enabled
Creation tx
0x1ee295f401…0d4d59f8fe

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (69)

BOND_FLOOR_WETH()uint128
BURN()address
BURN_BPS()uint16
CHALLENGE_BOND()uint128
CHALLENGE_WINDOW()uint64
DAILY_BPS()uint16
FEE_MAX_WICK()uint256
FEE_MIN_WICK()uint256
FLAT_BPS()uint16
LADDER_RANKS()uint16
LP_FEE()uint24
MAX_TICKS()uint32
MONTHLY_BPS()uint16
ORACLE_MAX_AGE()uint64
POT_BPS()uint16
SCORE_CAP()uint32
STAKING_BPS()uint16
STUCK_GRACE()uint64
SUBMISSION_WINDOW()uint64
TEAM_BPS()uint16
WEEKLY_BPS()uint16
accruing(uint8)uint128, uint128
admin()address
afterAddLiquidity(address, tuple, tuple, int256, int256, bytes)bytes4, int256
afterDonate(address, tuple, uint256, uint256, bytes)bytes4
afterRemoveLiquidity(address, tuple, tuple, int256, int256, bytes)bytes4, int256
beforeAddLiquidity(address, tuple, tuple, bytes)bytes4
beforeDonate(address, tuple, uint256, uint256, bytes)bytes4
beforeInitialize(address, tuple, uint160)bytes4
beforeRemoveLiquidity(address, tuple, tuple, bytes)bytes4
beforeSwap(address, tuple, tuple, bytes)bytes4, int256, uint24
buyAllowed(address)bool
challenged(uint8, uint64, bytes32)bool
challengerOf(uint8, uint64, bytes32)address
claimLeaf(address, uint32, uint8, uint256, uint256, uint256)bytes32
claimed(uint8, uint64, uint8)bool
computeTraceHash(bytes4, uint32[], uint32[])bytes32
configLocked()bool
council()address
creditsOf(address)uint32
currentPeriod(uint8)uint64
engineVersion()uint16
entropySource()address
launchGuardActive()bool
merkleRoot(bytes32[])bytes32
nextRunId()uint256
oracle()address
paidOut(uint8, uint64)uint128, uint128
periodOpenedAt(uint8, uint64)uint64
periodVersion(uint8, uint64)uint16
poolKey()address, address, uint24, int24, address
poolManager()address
poolSet()bool
prize(uint8, uint64)uint128, uint128
prizeOf(uint8, uint64)uint256, uint256
rankBps(uint8)uint16
resolveEvidence(uint8, uint64, bytes32)bytes32
roots(uint8, uint64)bytes32, address, uint128, uint128, uint64, bool, bool, uint32
runFeeQuote()uint256
runIndexOf(address)uint32
runs(uint256)address, uint64, uint64, bytes4, uint32, bytes32, bool
stakingDistributor()address
team()address
tiers(uint256)uint16, uint16
treasury()address
twapObserver()address
weth()address
wick()address
wickIsCurrency0()bool

Events (19)

BundlePurchasedBuyAllowedSetChallengeRefundedChallengedClaimedConfigChangedConfigFrozenEngineVersionChangedPeriodOpenedPotAccruedResolvedRootLeavesRootPostedRootVoidedRunStartedScoreSubmittedSettledTraceRevealedTradingOpened

ABI

[
  {
    "inputs": [
      {
        "internalType": "contract IPoolManager",
        "name": "_pm",
        "type": "address"
      },
      {
        "internalType": "contract IERC20Minimal",
        "name": "_wick",
        "type": "address"
      },
      {
        "internalType": "contract IERC20Minimal",
        "name": "_weth",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "_admin",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "_treasury",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "_team",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "_council",
        "type": "address"
      },
      {
        "internalType": "contract IWickOracle",
        "name": "_oracle",
        "type": "address"
      },
      {
        "internalType": "contract IEntropySource",
        "name": "_entropy",
        "type": "address"
      },
      {
        "internalType": "uint16",
        "name": "_engineVersion",
        "type": "uint16"
      },
      {
        "internalType": "uint128",
        "name": "_bondFloorWeth",
        "type": "uint128"
      },
      {
        "internalType": "uint64",
        "name": "_challengeWindow",
        "type": "uint64"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "AlreadyChallenged",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "AlreadyClaimed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "AlreadySubmitted",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "BadDistributor",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "BadEvidence",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "BadProof",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "BadRank",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "BadRun",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "BadTier",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "BondTooSmall",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ChallengesOpen",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ConfigLocked",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "HookNotImplemented",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NoRoot",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotAdmin",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotAllowlisted",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotChallenged",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotCouncil",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotPoolManager",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotRunOwner",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotSelf",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotStuck",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "OracleStale",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "PeriodRetired",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "PoolAlreadySet",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "PoolNotSet",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "QuoteOutOfClamps",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "RootExists",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "RootNotFinal",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "RootNotOpen",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "RootVoidedAlready",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ScoreMismatch",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ScoreTooHigh",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TradingNotOpen",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "UnknownRun",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "WindowClosed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "WindowStillOpen",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroAddress",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "player",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint8",
        "name": "tier",
        "type": "uint8"
      },
      {
        "indexed": false,
        "internalType": "uint32",
        "name": "runsCredited",
        "type": "uint32"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "paidWick",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "burnedWick",
        "type": "uint256"
      }
    ],
    "name": "BundlePurchased",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "who",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "bool",
        "name": "allowed",
        "type": "bool"
      }
    ],
    "name": "BuyAllowedSet",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint8",
        "name": "board",
        "type": "uint8"
      },
      {
        "indexed": false,
        "internalType": "uint64",
        "name": "periodId",
        "type": "uint64"
      },
      {
        "indexed": false,
        "internalType": "bytes32",
        "name": "leaf",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "challenger",
        "type": "address"
      }
    ],
    "name": "ChallengeRefunded",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint8",
        "name": "board",
        "type": "uint8"
      },
      {
        "indexed": false,
        "internalType": "uint64",
        "name": "periodId",
        "type": "uint64"
      },
      {
        "indexed": false,
        "internalType": "bytes32",
        "name": "leaf",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "challenger",
        "type": "address"
      }
    ],
    "name": "Challenged",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "player",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint8",
        "name": "board",
        "type": "uint8"
      },
      {
        "indexed": false,
        "internalType": "uint64",
        "name": "periodId",
        "type": "uint64"
      },
      {
        "indexed": false,
        "internalType": "uint8",
        "name": "rank",
        "type": "uint8"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "wethAmt",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "wickAmt",
        "type": "uint256"
      }
    ],
    "name": "Claimed",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "what",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "value",
        "type": "address"
      }
    ],
    "name": "ConfigChanged",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [],
    "name": "ConfigFrozen",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint16",
        "name": "value",
        "type": "uint16"
      }
    ],
    "name": "EngineVersionChanged",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint8",
        "name": "board",
        "type": "uint8"
      },
      {
        "indexed": false,
        "internalType": "uint64",
        "name": "periodId",
        "type": "uint64"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "prizeWeth",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "prizeWick",
        "type": "uint256"
      }
    ],
    "name": "PeriodOpened",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint8",
        "name": "board",
        "type": "uint8"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "weth",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "wickAmt",
        "type": "uint256"
      }
    ],
    "name": "PotAccrued",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint8",
        "name": "board",
        "type": "uint8"
      },
      {
        "indexed": false,
        "internalType": "uint64",
        "name": "periodId",
        "type": "uint64"
      },
      {
        "indexed": false,
        "internalType": "bytes32",
        "name": "leaf",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "internalType": "bool",
        "name": "valid",
        "type": "bool"
      },
      {
        "indexed": false,
        "internalType": "bytes32",
        "name": "evidenceHash",
        "type": "bytes32"
      }
    ],
    "name": "Resolved",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint8",
        "name": "board",
        "type": "uint8"
      },
      {
        "indexed": false,
        "internalType": "uint64",
        "name": "periodId",
        "type": "uint64"
      },
      {
        "indexed": false,
        "internalType": "bytes32[]",
        "name": "leaves",
        "type": "bytes32[]"
      }
    ],
    "name": "RootLeaves",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint8",
        "name": "board",
        "type": "uint8"
      },
      {
        "indexed": false,
        "internalType": "uint64",
        "name": "periodId",
        "type": "uint64"
      },
      {
        "indexed": false,
        "internalType": "bytes32",
        "name": "root",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "poster",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "bond",
        "type": "uint256"
      }
    ],
    "name": "RootPosted",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint8",
        "name": "board",
        "type": "uint8"
      },
      {
        "indexed": false,
        "internalType": "uint64",
        "name": "periodId",
        "type": "uint64"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "challenger",
        "type": "address"
      }
    ],
    "name": "RootVoided",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "player",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "runId",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint64",
        "name": "epochId",
        "type": "uint64"
      },
      {
        "indexed": false,
        "internalType": "bytes4",
        "name": "seed",
        "type": "bytes4"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "feePaid",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "bool",
        "name": "creditUsed",
        "type": "bool"
      }
    ],
    "name": "RunStarted",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "runId",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint32",
        "name": "score",
        "type": "uint32"
      },
      {
        "indexed": false,
        "internalType": "bytes32",
        "name": "traceHash",
        "type": "bytes32"
      }
    ],
    "name": "ScoreSubmitted",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint8",
        "name": "board",
        "type": "uint8"
      },
      {
        "indexed": false,
        "internalType": "uint64",
        "name": "periodId",
        "type": "uint64"
      },
      {
        "indexed": false,
        "internalType": "bytes32",
        "name": "root",
        "type": "bytes32"
      }
    ],
    "name": "Settled",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "runId",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint32",
        "name": "flipCount",
        "type": "uint32"
      }
    ],
    "name": "TraceRevealed",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [],
    "name": "TradingOpened",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "BOND_FLOOR_WETH",
    "outputs": [
      {
        "internalType": "uint128",
        "name": "",
        "type": "uint128"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "BURN",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "BURN_BPS",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "CHALLENGE_BOND",
    "outputs": [
      {
        "internalType": "uint128",
        "name": "",
        "type": "uint128"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "CHALLENGE_WINDOW",
    "outputs": [
      {
        "internalType": "uint64",
        "name": "",
        "type": "uint64"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "DAILY_BPS",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "FEE_MAX_WICK",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "FEE_MIN_WICK",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "FLAT_BPS",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "LADDER_RANKS",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "LP_FEE",
    "outputs": [
      {
        "internalType": "uint24",
        "name": "",
        "type": "uint24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MAX_TICKS",
    "outputs": [
      {
        "internalType": "uint32",
        "name": "",
        "type": "uint32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MONTHLY_BPS",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "ORACLE_MAX_AGE",
    "outputs": [
      {
        "internalType": "uint64",
        "name": "",
        "type": "uint64"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "POT_BPS",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "SCORE_CAP",
    "outputs": [
      {
        "internalType": "uint32",
        "name": "",
        "type": "uint32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "STAKING_BPS",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "STUCK_GRACE",
    "outputs": [
      {
        "internalType": "uint64",
        "name": "",
        "type": "uint64"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "SUBMISSION_WINDOW",
    "outputs": [
      {
        "internalType": "uint64",
        "name": "",
        "type": "uint64"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "TEAM_BPS",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "WEEKLY_BPS",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      }
    ],
    "name": "accruing",
    "outputs": [
      {
        "internalType": "uint128",
        "name": "weth",
        "type": "uint128"
      },
      {
        "internalType": "uint128",
        "name": "wick",
        "type": "uint128"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "admin",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      },
      {
        "components": [
          {
            "internalType": "Currency",
            "name": "currency0",
            "type": "address"
          },
          {
            "internalType": "Currency",
            "name": "currency1",
            "type": "address"
          },
          {
            "internalType": "uint24",
            "name": "fee",
            "type": "uint24"
          },
          {
            "internalType": "int24",
            "name": "tickSpacing",
            "type": "int24"
          },
          {
            "internalType": "contract IHooks",
            "name": "hooks",
            "type": "address"
          }
        ],
        "internalType": "struct PoolKey",
        "name": "",
        "type": "tuple"
      },
      {
        "components": [
          {
            "internalType": "int24",
            "name": "tickLower",
            "type": "int24"
          },
          {
            "internalType": "int24",
            "name": "tickUpper",
            "type": "int24"
          },
          {
            "internalType": "int256",
            "name": "liquidityDelta",
            "type": "int256"
          },
          {
            "internalType": "bytes32",
            "name": "salt",
            "type": "bytes32"
          }
        ],
        "internalType": "struct ModifyLiquidityParams",
        "name": "",
        "type": "tuple"
      },
      {
        "internalType": "BalanceDelta",
        "name": "",
        "type": "int256"
      },
      {
        "internalType": "BalanceDelta",
        "name": "",
        "type": "int256"
      },
      {
        "internalType": "bytes",
        "name": "",
        "type": "bytes"
      }
    ],
    "name": "afterAddLiquidity",
    "outputs": [
      {
        "internalType": "bytes4",
        "name": "",
        "type": "bytes4"
      },
      {
        "internalType": "BalanceDelta",
        "name": "",
        "type": "int256"
      }
    ],
    "stateMutability": "pure",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      },
      {
        "components": [
          {
            "internalType": "Currency",
            "name": "currency0",
            "type": "address"
          },
          {
            "internalType": "Currency",
            "name": "currency1",
            "type": "address"
          },
          {
            "internalType": "uint24",
            "name": "fee",
            "type": "uint24"
          },
          {
            "internalType": "int24",
            "name": "tickSpacing",
            "type": "int24"
          },
          {
            "internalType": "contract IHooks",
            "name": "hooks",
            "type": "address"
          }
        ],
        "internalType": "struct PoolKey",
        "name": "",
        "type": "tuple"
      },
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      },
      {
        "internalType": "bytes",
        "name": "",
        "type": "bytes"
      }
    ],
    "name": "afterDonate",
    "outputs": [
      {
        "internalType": "bytes4",
        "name": "",
        "type": "bytes4"
      }
    ],
    "stateMutability": "pure",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      },
      {
        "components": [
          {
            "internalType": "Currency",
            "name": "currency0",
            "type": "address"
          },
          {
            "internalType": "Currency",
            "name": "currency1",
            "type": "address"
          },
          {
            "internalType": "uint24",
            "name": "fee",
            "type": "uint24"
          },
          {
            "internalType": "int24",
            "name": "tickSpacing",
            "type": "int24"
          },
          {
            "internalType": "contract IHooks",
            "name": "hooks",
            "type": "address"
          }
        ],
        "internalType": "struct PoolKey",
        "name": "key",
        "type": "tuple"
      },
      {
        "internalType": "uint160",
        "name": "",
        "type": "uint160"
      },
      {
        "internalType": "int24",
        "name": "",
        "type": "int24"
      }
    ],
    "name": "afterInitialize",
    "outputs": [
      {
        "internalType": "bytes4",
        "name": "",
        "type": "bytes4"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      },
      {
        "components": [
          {
            "internalType": "Currency",
            "name": "currency0",
            "type": "address"
          },
          {
            "internalType": "Currency",
            "name": "currency1",
            "type": "address"
          },
          {
            "internalType": "uint24",
            "name": "fee",
            "type": "uint24"
          },
          {
            "internalType": "int24",
            "name": "tickSpacing",
            "type": "int24"
          },
          {
            "internalType": "contract IHooks",
            "name": "hooks",
            "type": "address"
          }
        ],
        "internalType": "struct PoolKey",
        "name": "",
        "type": "tuple"
      },
      {
        "components": [
          {
            "internalType": "int24",
            "name": "tickLower",
            "type": "int24"
          },
          {
            "internalType": "int24",
            "name": "tickUpper",
            "type": "int24"
          },
          {
            "internalType": "int256",
            "name": "liquidityDelta",
            "type": "int256"
          },
          {
            "internalType": "bytes32",
            "name": "salt",
            "type": "bytes32"
          }
        ],
        "internalType": "struct ModifyLiquidityParams",
        "name": "",
        "type": "tuple"
      },
      {
        "internalType": "BalanceDelta",
        "name": "",
        "type": "int256"
      },
      {
        "internalType": "BalanceDelta",
        "name": "",
        "type": "int256"
      },
      {
        "internalType": "bytes",
        "name": "",
        "type": "bytes"
      }
    ],
    "name": "afterRemoveLiquidity",
    "outputs": [
      {
        "internalType": "bytes4",
        "name": "",
        "type": "bytes4"
      },
      {
        "internalType": "BalanceDelta",
        "name": "",
        "type": "int256"
      }
    ],
    "stateMutability": "pure",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      },
      {
        "components": [
          {
            "internalType": "Currency",
            "name": "currency0",
            "type": "address"
          },
          {
            "internalType": "Currency",
            "name": "currency1",
            "type": "address"
          },
          {
            "internalType": "uint24",
            "name": "fee",
            "type": "uint24"
          },
          {
            "internalType": "int24",
            "name": "tickSpacing",
            "type": "int24"
          },
          {
            "internalType": "contract IHooks",
            "name": "hooks",
            "type": "address"
          }
        ],
        "internalType": "struct PoolKey",
        "name": "key",
        "type": "tuple"
      },
      {
        "components": [
          {
            "internalType": "bool",
            "name": "zeroForOne",
            "type": "bool"
          },
          {
            "internalType": "int256",
            "name": "amountSpecified",
            "type": "int256"
          },
          {
            "internalType": "uint160",
            "name": "sqrtPriceLimitX96",
            "type": "uint160"
          }
        ],
        "internalType": "struct SwapParams",
        "name": "params",
        "type": "tuple"
      },
      {
        "internalType": "BalanceDelta",
        "name": "delta",
        "type": "int256"
      },
      {
        "internalType": "bytes",
        "name": "",
        "type": "bytes"
      }
    ],
    "name": "afterSwap",
    "outputs": [
      {
        "internalType": "bytes4",
        "name": "",
        "type": "bytes4"
      },
      {
        "internalType": "int128",
        "name": "",
        "type": "int128"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      },
      {
        "components": [
          {
            "internalType": "Currency",
            "name": "currency0",
            "type": "address"
          },
          {
            "internalType": "Currency",
            "name": "currency1",
            "type": "address"
          },
          {
            "internalType": "uint24",
            "name": "fee",
            "type": "uint24"
          },
          {
            "internalType": "int24",
            "name": "tickSpacing",
            "type": "int24"
          },
          {
            "internalType": "contract IHooks",
            "name": "hooks",
            "type": "address"
          }
        ],
        "internalType": "struct PoolKey",
        "name": "",
        "type": "tuple"
      },
      {
        "components": [
          {
            "internalType": "int24",
            "name": "tickLower",
            "type": "int24"
          },
          {
            "internalType": "int24",
            "name": "tickUpper",
            "type": "int24"
          },
          {
            "internalType": "int256",
            "name": "liquidityDelta",
            "type": "int256"
          },
          {
            "internalType": "bytes32",
            "name": "salt",
            "type": "bytes32"
          }
        ],
        "internalType": "struct ModifyLiquidityParams",
        "name": "",
        "type": "tuple"
      },
      {
        "internalType": "bytes",
        "name": "",
        "type": "bytes"
      }
    ],
    "name": "beforeAddLiquidity",
    "outputs": [
      {
        "internalType": "bytes4",
        "name": "",
        "type": "bytes4"
      }
    ],
    "stateMutability": "pure",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      },
      {
        "components": [
          {
            "internalType": "Currency",
            "name": "currency0",
            "type": "address"
          },
          {
            "internalType": "Currency",
            "name": "currency1",
            "type": "address"
          },
          {
            "internalType": "uint24",
            "name": "fee",
            "type": "uint24"
          },
          {
            "internalType": "int24",
            "name": "tickSpacing",
            "type": "int24"
          },
          {
            "internalType": "contract IHooks",
            "name": "hooks",
            "type": "address"
          }
        ],
        "internalType": "struct PoolKey",
        "name": "",
        "type": "tuple"
      },
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      },
      {
        "internalType": "bytes",
        "name": "",
        "type": "bytes"
      }
    ],
    "name": "beforeDonate",
    "outputs": [
      {
        "internalType": "bytes4",
        "name": "",
        "type": "bytes4"
      }
    ],
    "stateMutability": "pure",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      },
      {
        "components": [
          {
            "internalType": "Currency",
            "name": "currency0",
            "type": "address"
          },
          {
            "internalType": "Currency",
            "name": "currency1",
            "type": "address"
          },
          {
            "internalType": "uint24",
            "name": "fee",
            "type": "uint24"
          },
          {
            "internalType": "int24",
            "name": "tickSpacing",
            "type": "int24"
          },
          {
            "internalType": "contract IHooks",
            "name": "hooks",
            "type": "address"
          }
        ],
        "internalType": "struct PoolKey",
        "name": "",
        "type": "tuple"
      },
      {
        "internalType": "uint160",
        "name": "",
        "type": "uint160"
      }
    ],
    "name": "beforeInitialize",
    "outputs": [
      {
        "internalType": "bytes4",
        "name": "",
        "type": "bytes4"
      }
    ],
    "stateMutability": "pure",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      },
      {
        "components": [
          {
            "internalType": "Currency",
            "name": "currency0",
            "type": "address"
          },
          {
            "internalType": "Currency",
            "name": "currency1",
            "type": "address"
          },
          {
            "internalType": "uint24",
            "name": "fee",
            "type": "uint24"
          },
          {
            "internalType": "int24",
            "name": "tickSpacing",
            "type": "int24"
          },
          {
            "internalType": "contract IHooks",
            "name": "hooks",
            "type": "address"
          }
        ],
        "internalType": "struct PoolKey",
        "name": "",
        "type": "tuple"
      },
      {
        "components": [
          {
            "internalType": "int24",
            "name": "tickLower",
            "type": "int24"
          },
          {
            "internalType": "int24",
            "name": "tickUpper",
            "type": "int24"
          },
          {
            "internalType": "int256",
            "name": "liquidityDelta",
            "type": "int256"
          },
          {
            "internalType": "bytes32",
            "name": "salt",
            "type": "bytes32"
          }
        ],
        "internalType": "struct ModifyLiquidityParams",
        "name": "",
        "type": "tuple"
      },
      {
        "internalType": "bytes",
        "name": "",
        "type": "bytes"
      }
    ],
    "name": "beforeRemoveLiquidity",
    "outputs": [
      {
        "internalType": "bytes4",
        "name": "",
        "type": "bytes4"
      }
    ],
    "stateMutability": "pure",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "sender",
        "type": "address"
      },
      {
        "components": [
          {
            "internalType": "Currency",
            "name": "currency0",
            "type": "address"
          },
          {
            "internalType": "Currency",
            "name": "currency1",
            "type": "address"
          },
          {
            "internalType": "uint24",
            "name": "fee",
            "type": "uint24"
          },
          {
            "internalType": "int24",
            "name": "tickSpacing",
            "type": "int24"
          },
          {
            "internalType": "contract IHooks",
            "name": "hooks",
            "type": "address"
          }
        ],
        "internalType": "struct PoolKey",
        "name": "",
        "type": "tuple"
      },
      {
        "components": [
          {
            "internalType": "bool",
            "name": "zeroForOne",
            "type": "bool"
          },
          {
            "internalType": "int256",
            "name": "amountSpecified",
            "type": "int256"
          },
          {
            "internalType": "uint160",
            "name": "sqrtPriceLimitX96",
            "type": "uint160"
          }
        ],
        "internalType": "struct SwapParams",
        "name": "params",
        "type": "tuple"
      },
      {
        "internalType": "bytes",
        "name": "",
        "type": "bytes"
      }
    ],
    "name": "beforeSwap",
    "outputs": [
      {
        "internalType": "bytes4",
        "name": "",
        "type": "bytes4"
      },
      {
        "internalType": "BeforeSwapDelta",
        "name": "",
        "type": "int256"
      },
      {
        "internalType": "uint24",
        "name": "",
        "type": "uint24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "buyAllowed",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint8",
        "name": "tier",
        "type": "uint8"
      }
    ],
    "name": "buyBundle",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint8",
        "name": "board",
        "type": "uint8"
      },
      {
        "internalType": "uint64",
        "name": "periodId",
        "type": "uint64"
      },
      {
        "internalType": "bytes32",
        "name": "leaf",
        "type": "bytes32"
      },
      {
        "internalType": "bytes32[]",
        "name": "proof",
        "type": "bytes32[]"
      }
    ],
    "name": "challenge",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      },
      {
        "internalType": "uint64",
        "name": "",
        "type": "uint64"
      },
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "name": "challenged",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      },
      {
        "internalType": "uint64",
        "name": "",
        "type": "uint64"
      },
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "name": "challengerOf",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint8",
        "name": "board",
        "type": "uint8"
      },
      {
        "internalType": "uint64",
        "name": "periodId",
        "type": "uint64"
      },
      {
        "internalType": "uint32",
        "name": "score",
        "type": "uint32"
      },
      {
        "internalType": "uint8",
        "name": "rank",
        "type": "uint8"
      },
      {
        "internalType": "uint256",
        "name": "runId0",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "runId1",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "runId2",
        "type": "uint256"
      },
      {
        "internalType": "bytes32[]",
        "name": "proof",
        "type": "bytes32[]"
      }
    ],
    "name": "claim",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "player",
        "type": "address"
      },
      {
        "internalType": "uint32",
        "name": "score",
        "type": "uint32"
      },
      {
        "internalType": "uint8",
        "name": "rank",
        "type": "uint8"
      },
      {
        "internalType": "uint256",
        "name": "r0",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "r1",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "r2",
        "type": "uint256"
      }
    ],
    "name": "claimLeaf",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "pure",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      },
      {
        "internalType": "uint64",
        "name": "",
        "type": "uint64"
      },
      {
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      }
    ],
    "name": "claimed",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes4",
        "name": "seed",
        "type": "bytes4"
      },
      {
        "internalType": "uint32[]",
        "name": "nonces",
        "type": "uint32[]"
      },
      {
        "internalType": "uint32[]",
        "name": "flipTicks",
        "type": "uint32[]"
      }
    ],
    "name": "computeTraceHash",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "pure",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "configLocked",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "council",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "creditsOf",
    "outputs": [
      {
        "internalType": "uint32",
        "name": "",
        "type": "uint32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      }
    ],
    "name": "currentPeriod",
    "outputs": [
      {
        "internalType": "uint64",
        "name": "",
        "type": "uint64"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "Currency",
        "name": "feeCurrency",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      },
      {
        "internalType": "bool",
        "name": "feeIsWick",
        "type": "bool"
      }
    ],
    "name": "deliverStakeReward",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "engineVersion",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "entropySource",
    "outputs": [
      {
        "internalType": "contract IEntropySource",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint8",
        "name": "board",
        "type": "uint8"
      },
      {
        "internalType": "uint64",
        "name": "periodId",
        "type": "uint64"
      }
    ],
    "name": "finalize",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "launchGuardActive",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "lockConfig",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32[]",
        "name": "leaves",
        "type": "bytes32[]"
      }
    ],
    "name": "merkleRoot",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "pure",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "nextRunId",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "openTrading",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "oracle",
    "outputs": [
      {
        "internalType": "contract IWickOracle",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      },
      {
        "internalType": "uint64",
        "name": "",
        "type": "uint64"
      }
    ],
    "name": "paidOut",
    "outputs": [
      {
        "internalType": "uint128",
        "name": "weth",
        "type": "uint128"
      },
      {
        "internalType": "uint128",
        "name": "wick",
        "type": "uint128"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      },
      {
        "internalType": "uint64",
        "name": "",
        "type": "uint64"
      }
    ],
    "name": "periodOpenedAt",
    "outputs": [
      {
        "internalType": "uint64",
        "name": "",
        "type": "uint64"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      },
      {
        "internalType": "uint64",
        "name": "",
        "type": "uint64"
      }
    ],
    "name": "periodVersion",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "poke",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "poolKey",
    "outputs": [
      {
        "internalType": "Currency",
        "name": "currency0",
        "type": "address"
      },
      {
        "internalType": "Currency",
        "name": "currency1",
        "type": "address"
      },
      {
        "internalType": "uint24",
        "name": "fee",
        "type": "uint24"
      },
      {
        "internalType": "int24",
        "name": "tickSpacing",
        "type": "int24"
      },
      {
        "internalType": "contract IHooks",
        "name": "hooks",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "poolManager",
    "outputs": [
      {
        "internalType": "contract IPoolManager",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "poolSet",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint8",
        "name": "board",
        "type": "uint8"
      },
      {
        "internalType": "uint64",
        "name": "periodId",
        "type": "uint64"
      },
      {
        "internalType": "bytes32[]",
        "name": "leaves",
        "type": "bytes32[]"
      }
    ],
    "name": "postRoot",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      },
      {
        "internalType": "uint64",
        "name": "",
        "type": "uint64"
      }
    ],
    "name": "prize",
    "outputs": [
      {
        "internalType": "uint128",
        "name": "weth",
        "type": "uint128"
      },
      {
        "internalType": "uint128",
        "name": "wick",
        "type": "uint128"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint8",
        "name": "board",
        "type": "uint8"
      },
      {
        "internalType": "uint64",
        "name": "periodId",
        "type": "uint64"
      }
    ],
    "name": "prizeOf",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint8",
        "name": "rank",
        "type": "uint8"
      }
    ],
    "name": "rankBps",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "pure",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint8",
        "name": "board",
        "type": "uint8"
      },
      {
        "internalType": "uint64",
        "name": "periodId",
        "type": "uint64"
      },
      {
        "internalType": "bytes32",
        "name": "leaf",
        "type": "bytes32"
      },
      {
        "internalType": "bool",
        "name": "valid",
        "type": "bool"
      },
      {
        "internalType": "bytes32",
        "name": "evidenceHash",
        "type": "bytes32"
      }
    ],
    "name": "resolve",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      },
      {
        "internalType": "uint64",
        "name": "",
        "type": "uint64"
      },
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "name": "resolveEvidence",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "runId",
        "type": "uint256"
      },
      {
        "internalType": "uint32[]",
        "name": "nonces",
        "type": "uint32[]"
      },
      {
        "internalType": "uint32[]",
        "name": "flipTicks",
        "type": "uint32[]"
      }
    ],
    "name": "revealTrace",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint8",
        "name": "board",
        "type": "uint8"
      },
      {
        "internalType": "uint64",
        "name": "periodId",
        "type": "uint64"
      }
    ],
    "name": "rollStuckPeriod",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      },
      {
        "internalType": "uint64",
        "name": "",
        "type": "uint64"
      }
    ],
    "name": "roots",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "root",
        "type": "bytes32"
      },
      {
        "internalType": "address",
        "name": "poster",
        "type": "address"
      },
      {
        "internalType": "uint128",
        "name": "bondWeth",
        "type": "uint128"
      },
      {
        "internalType": "uint128",
        "name": "bondWick",
        "type": "uint128"
      },
      {
        "internalType": "uint64",
        "name": "postedAt",
        "type": "uint64"
      },
      {
        "internalType": "bool",
        "name": "finalized",
        "type": "bool"
      },
      {
        "internalType": "bool",
        "name": "voided",
        "type": "bool"
      },
      {
        "internalType": "uint32",
        "name": "openChallenges",
        "type": "uint32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "runFeeQuote",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "runIndexOf",
    "outputs": [
      {
        "internalType": "uint32",
        "name": "",
        "type": "uint32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "name": "runs",
    "outputs": [
      {
        "internalType": "address",
        "name": "player",
        "type": "address"
      },
      {
        "internalType": "uint64",
        "name": "startedAt",
        "type": "uint64"
      },
      {
        "internalType": "uint64",
        "name": "epochId",
        "type": "uint64"
      },
      {
        "internalType": "bytes4",
        "name": "seed",
        "type": "bytes4"
      },
      {
        "internalType": "uint32",
        "name": "score",
        "type": "uint32"
      },
      {
        "internalType": "bytes32",
        "name": "traceHash",
        "type": "bytes32"
      },
      {
        "internalType": "bool",
        "name": "submitted",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "who",
        "type": "address"
      },
      {
        "internalType": "bool",
        "name": "allowed",
        "type": "bool"
      }
    ],
    "name": "setBuyAllowed",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "a",
        "type": "address"
      }
    ],
    "name": "setCouncil",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint16",
        "name": "v",
        "type": "uint16"
      }
    ],
    "name": "setEngineVersion",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "contract IEntropySource",
        "name": "e",
        "type": "address"
      }
    ],
    "name": "setEntropySource",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "contract IWickOracle",
        "name": "o",
        "type": "address"
      }
    ],
    "name": "setOracle",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "d",
        "type": "address"
      }
    ],
    "name": "setStakingDistributor",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "a",
        "type": "address"
      }
    ],
    "name": "setTeam",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "a",
        "type": "address"
      }
    ],
    "name": "setTreasury",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "o",
        "type": "address"
      }
    ],
    "name": "setTwapObserver",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "stakingDistributor",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "startRun",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "runId",
        "type": "uint256"
      },
      {
        "internalType": "bytes4",
        "name": "seed",
        "type": "bytes4"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "prevRunId",
        "type": "uint256"
      },
      {
        "internalType": "uint32",
        "name": "score",
        "type": "uint32"
      },
      {
        "internalType": "bytes32",
        "name": "traceHash",
        "type": "bytes32"
      }
    ],
    "name": "submitAndStart",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "runId",
        "type": "uint256"
      },
      {
        "internalType": "bytes4",
        "name": "seed",
        "type": "bytes4"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "runId",
        "type": "uint256"
      },
      {
        "internalType": "uint32",
        "name": "score",
        "type": "uint32"
      },
      {
        "internalType": "bytes32",
        "name": "traceHash",
        "type": "bytes32"
      }
    ],
    "name": "submitScore",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint8",
        "name": "board",
        "type": "uint8"
      },
      {
        "internalType": "uint64",
        "name": "periodId",
        "type": "uint64"
      }
    ],
    "name": "sweepUnclaimed",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "team",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "name": "tiers",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "buyRuns",
        "type": "uint16"
      },
      {
        "internalType": "uint16",
        "name": "freeRuns",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "treasury",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "twapObserver",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "weth",
    "outputs": [
      {
        "internalType": "contract IERC20Minimal",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "wick",
    "outputs": [
      {
        "internalType": "contract IERC20Minimal",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "wickIsCurrency0",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  }
]

Source code

// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;

// ---------------------------------------------------------------------------
// WICK v4 hook — implementation of wick-hook-spec.md (reconstructed 2026-07-21).
//
// DRAFT for independent review. A money-holding hook does not launch without
// an audit (CLAUDE.md, non-negotiable). Spec deviations are marked DRAFT with
// the reasoning inline.
//
// LEGAL-CRITICAL invariants (spec §9), enforced structurally:
//   1. Run-fee and bundle value never reaches pot balances: _splitBurn() is
//      the ONLY sink for entry proceeds and writes only to BURN + treasury.
//   2. The treasury is a plain address here; no function credits pots from it.
//   3. A period's prize is written exactly once, at period open, and no
//      later code path mutates it.
// ---------------------------------------------------------------------------

import {IHooks} from "v4-core/src/interfaces/IHooks.sol";
import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol";
import {PoolKey} from "v4-core/src/types/PoolKey.sol";
import {BalanceDelta} from "v4-core/src/types/BalanceDelta.sol";
import {ModifyLiquidityParams, SwapParams} from "v4-core/src/types/PoolOperation.sol";
import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "v4-core/src/types/BeforeSwapDelta.sol";
import {Currency} from "v4-core/src/types/Currency.sol";
import {IERC20Minimal} from "v4-core/src/interfaces/external/IERC20Minimal.sol";

import {IWickOracle} from "./interfaces/IWickOracle.sol";
import {IEntropySource} from "./interfaces/IEntropySource.sol";

interface IWickTwapObserver {
    function poke() external;
}

interface IStakingNotify {
    function notifyReward(uint256 weth, uint256 wick) external;
}

contract WickHook is IHooks {
    // ----------------------------------------------------------------- types

    enum Board {
        Daily,
        Weekly,
        Monthly
    }

    struct Run {
        address player;
        uint64 startedAt;
        uint64 epochId;
        bytes4 seed;
        uint32 score;
        bytes32 traceHash;
        bool submitted;
    }

    struct Pot {
        uint128 weth;
        uint128 wick;
    }

    struct RootState {
        bytes32 root;
        address poster;
        uint128 bondWeth; // sized to the pot's WETH leg (+ floor)
        uint128 bondWick; // sized to the pot's WICK leg — so a WICK-heavy pot still bonds
        uint64 postedAt;
        bool finalized;
        bool voided;
        uint32 openChallenges;
    }

    struct BundleTier {
        uint16 buyRuns;
        uint16 freeRuns;
    }

    // ------------------------------------------------------------- constants

    address public constant BURN = 0x000000000000000000000000000000000000dEaD;

    uint256 public constant FEE_MIN_WICK = 100e18; //   clamp, adjustable pre-launch
    // Raised pre-launch: at ~$2k FDV, 0.000125 WETH/run ≈ 125k tokens.
    uint256 public constant FEE_MAX_WICK = 200_000e18;
    uint64 public constant SUBMISSION_WINDOW = 30 minutes;
    uint32 public constant MAX_TICKS = 72_000; // engine run cap (10 min @ 120hz)
    uint32 public constant SCORE_CAP = 44_000; // engine v4 true max = 43,480 (survive to 72k-tick cap); +~1.2% margin
    /// Fraud-catch time between a root being posted and claims opening. This
    /// IS the settlement security in v1 (scores verify off-chain only): a
    /// shorter window = faster claims but less time to challenge a fraudulent
    /// root. Sized per deployment — the automated check-sentinel challenges
    /// within the hour, so testnets can run minutes; mainnet value is a
    /// launch decision (spec: conservative 24h vs sentinel-backed shorter).
    uint64 public immutable CHALLENGE_WINDOW;
    uint128 public immutable BOND_FLOOR_WETH; // absolute floor so a tiny pot still bonds (launch param, OPEN)
    uint128 public constant CHALLENGE_BOND = 0.02 ether; // WETH cost to challenge — stops free finalize-DoS (OPEN)
    uint64 public constant STUCK_GRACE = 30 days; // no root within this of open -> prize rollable forward
    uint16 public constant BURN_BPS = 7500; // entry proceeds: 75% burn / 25% team
    uint24 public constant LP_FEE = 0; // pool charges 0%; the hook does all fee capture

    // hook capture of swap OUTPUT, bps. Tax 3% buy / 3% sell + 0.5% hook fee =
    // 3.5% captured each side (LP keeps its 0.5%; all-in 4%). Split: tax 25% ->
    // team, hook swap-fee 50% -> team => 2.5% pot + 1.0% team, symmetric.
    // per-side capture of swap OUTPUT (bps). REVISED 2026-07-21 for staking:
    // 3% tax split 25/75 team/pot (0.75%/2.25%) + 1% swap fee -> 100% staking.
    // pool LP fee is 0, so all 4% all-in is captured here.
    uint16 public constant POT_BPS = 225;
    uint16 public constant TEAM_BPS = 75;
    uint16 public constant STAKING_BPS = 100;

    // board inflow split (spec §6)
    uint16 public constant DAILY_BPS = 7000;
    uint16 public constant WEEKLY_BPS = 2000;
    uint16 public constant MONTHLY_BPS = 1000;

    // payout ladder (spec §7); ranks 1..10 explicit, 11..50 flat 25 bps
    uint16 public constant FLAT_BPS = 25;
    uint16 public constant LADDER_RANKS = 50;

    // oracle staleness bound — spec: OPEN. DRAFT default, fail closed.
    uint64 public constant ORACLE_MAX_AGE = 30 minutes;

    // ------------------------------------------------------------- immutable

    IPoolManager public immutable poolManager;
    IERC20Minimal public immutable wick;
    IERC20Minimal public immutable weth;
    // admin = a TimelockController (delay lives there, not here); every setter
    // below is onlyAdmin, so all config changes are public + time-delayed.
    address public immutable admin;

    // mutable config (timelock-gated). Deliberately NOT immutable so a broken
    // oracle / rotated key / new engine version doesn't force a full redeploy.
    address public treasury; // reserved (pot bootstrap / ops); unfunded by fees
    address public team; // dedicated team-revenue wallet (entry 25% + trading team cuts)
    address public council; // 3-of-5 multisig (composition OPEN)
    IWickOracle public oracle;
    IEntropySource public entropySource;
    address public stakingDistributor; // veWICK yield sink for the 1% staking fee

    /// engine version applied to FUTURE periods; each period pins its own at open
    /// (periodVersion) so a bump never rewrites a settled score (spec §12).
    uint16 public engineVersion;

    // ----------------------------------------------------------------- state

    PoolKey public poolKey;
    bool public poolSet;
    bool public wickIsCurrency0;

    // launch guard: while active, only allowlisted senders may BUY (sells always
    // open). openTrading() flips it off permanently — one-way, never re-lockable.
    bool public launchGuardActive = true;
    mapping(address => bool) public buyAllowed;

    // one-way freeze of mutable config (team/oracle/etc.). Call after openTrading
    // once roles/wiring are final — clears long-lived "admin can redirect" scanner flags.
    bool public configLocked;

    uint256 public nextRunId = 1;
    mapping(uint256 => Run) public runs;
    mapping(address => uint32) public runIndexOf; // per-address run counter
    mapping(address => uint32) public creditsOf; // bundle credits (spec §4)

    // accrual pots per board (current period's accruals -> next period's prize)
    mapping(uint8 => Pot) public accruing;
    // prize snapshots: board => periodId => Pot. Written ONCE at open. LEGAL-CRITICAL.
    mapping(uint8 => mapping(uint64 => Pot)) public prize;
    mapping(uint8 => uint64) public currentPeriod;
    mapping(uint8 => mapping(uint64 => uint16)) public periodVersion; // engine version pinned at open
    mapping(uint8 => mapping(uint64 => uint64)) public periodOpenedAt; // for the stuck-period fallback

    // settlement
    mapping(uint8 => mapping(uint64 => RootState)) public roots;
    mapping(uint8 => mapping(uint64 => mapping(bytes32 => bool))) public challenged;
    mapping(uint8 => mapping(uint64 => mapping(bytes32 => address))) public challengerOf; // who challenged a leaf
    /// Council's evidence commitment for a resolved leaf (audit H-01).
    mapping(uint8 => mapping(uint64 => mapping(bytes32 => bytes32))) public resolveEvidence;
    /// Enumerable open challenges for the current (board, period) root. Cleared
    /// on void so a repost starts clean (audit C-02) and so void can refund
    /// every non-winning challenger without a second bond payout (audit C-01).
    mapping(uint8 => mapping(uint64 => bytes32[])) internal openChallengeLeaves;
    mapping(uint8 => mapping(uint64 => mapping(uint8 => bool))) public claimed; // by rank
    mapping(uint8 => mapping(uint64 => Pot)) public paidOut;

    BundleTier[4] public tiers; // launch parameters (spec §4)

    // ---------------------------------------------------------------- events

    event RunStarted(
        address indexed player, uint256 indexed runId, uint64 epochId, bytes4 seed, uint256 feePaid, bool creditUsed
    );
    event ScoreSubmitted(uint256 indexed runId, uint32 score, bytes32 traceHash);
    event BundlePurchased(address indexed player, uint8 tier, uint32 runsCredited, uint256 paidWick, uint256 burnedWick);
    event PeriodOpened(uint8 board, uint64 periodId, uint256 prizeWeth, uint256 prizeWick);
    event PotAccrued(uint8 board, uint256 weth, uint256 wickAmt);
    event RootPosted(uint8 board, uint64 periodId, bytes32 root, address poster, uint256 bond);
    event RootLeaves(uint8 board, uint64 periodId, bytes32[] leaves);
    event Challenged(uint8 board, uint64 periodId, bytes32 leaf, address challenger);
    event TraceRevealed(uint256 indexed runId, uint32 flipCount);
    /// `evidenceHash` commits the council to an off-chain verifier/watcher report
    /// (IPFS CID digest, report keccak, etc.). Empty hash is rejected — closes the
    /// silent-discretion gap of an unconstrained resolve(bool) (audit H-01).
    event Resolved(uint8 board, uint64 periodId, bytes32 leaf, bool valid, bytes32 evidenceHash);
    event RootVoided(uint8 board, uint64 periodId, address challenger);
    event ChallengeRefunded(uint8 board, uint64 periodId, bytes32 leaf, address challenger);
    event Settled(uint8 board, uint64 periodId, bytes32 root);
    event Claimed(address indexed player, uint8 board, uint64 periodId, uint8 rank, uint256 wethAmt, uint256 wickAmt);

    // ---------------------------------------------------------------- errors

    error NotPoolManager();
    error NotCouncil();
    error PoolAlreadySet();
    error PoolNotSet();
    error OracleStale();
    error QuoteOutOfClamps();
    error UnknownRun();
    error NotRunOwner();
    error AlreadySubmitted();
    error WindowClosed();
    error ScoreTooHigh();
    error BadTier();
    error RootExists();
    error NoRoot();
    error RootNotOpen();
    error RootNotFinal();
    error WindowStillOpen();
    error ChallengesOpen();
    error BadProof();
    error AlreadyClaimed();
    error BadRank();
    error NotChallenged();
    error BondTooSmall();
    error HookNotImplemented();
    error NotAdmin();
    error ZeroAddress();
    error ConfigLocked();
    error TradingNotOpen();
    error NotStuck();
    error NotAllowlisted();
    error AlreadyChallenged();
    error RootVoidedAlready();
    error ScoreMismatch();
    error BadRun();
    error NotSelf();
    error BadEvidence();
    error PeriodRetired();
    error BadDistributor();

    modifier onlyPoolManager() {
        if (msg.sender != address(poolManager)) revert NotPoolManager();
        _;
    }

    modifier onlyCouncil() {
        if (msg.sender != council) revert NotCouncil();
        _;
    }

    modifier onlyAdmin() {
        if (msg.sender != admin) revert NotAdmin();
        _;
    }

    /// Admin setters that must die after lockConfig(). openTrading() is NOT gated
    /// here so the fair-launch sequence can be: deploy → openTrading → (days) → lockConfig.
    modifier onlyAdminConfig() {
        if (msg.sender != admin) revert NotAdmin();
        if (configLocked) revert ConfigLocked();
        _;
    }

    // ----------------------------------------------------------- constructor

    constructor(
        IPoolManager _pm,
        IERC20Minimal _wick,
        IERC20Minimal _weth,
        address _admin,
        address _treasury,
        address _team,
        address _council,
        IWickOracle _oracle,
        IEntropySource _entropy,
        uint16 _engineVersion,
        uint128 _bondFloorWeth,
        uint64 _challengeWindow
    ) {
        if (_challengeWindow == 0) revert BadProof(); // zero window = instant fraud; never allowed
        poolManager = _pm;
        wick = _wick;
        weth = _weth;
        admin = _admin;
        treasury = _treasury;
        team = _team;
        council = _council;
        oracle = _oracle;
        entropySource = _entropy;
        engineVersion = _engineVersion;
        BOND_FLOOR_WETH = _bondFloorWeth;
        CHALLENGE_WINDOW = _challengeWindow;
        tiers[0] = BundleTier(5, 1);
        tiers[1] = BundleTier(10, 2);
        tiers[2] = BundleTier(50, 10);
        tiers[3] = BundleTier(100, 20);
    }

    // -------------------------------------------------- admin (timelock-gated)
    // Setters are onlyAdminConfig (Timelock + not yet lockConfig). After
    // lockConfig(), only openTrading is already spent — no further redirects.
    event ConfigChanged(bytes32 indexed what, address value);
    event EngineVersionChanged(uint16 value);
    event ConfigFrozen();

    /// TWAP observation feed: when set, every swap pokes the observer so the
    /// run-fee TWAP stays live at zero keeper cost. Timelocked like all config.
    address public twapObserver;

    function setTwapObserver(address o) external onlyAdminConfig {
        twapObserver = o;
        emit ConfigChanged("twapObserver", o);
    }

    /// Wire (or clear) the staking fee sink. Non-zero targets must have code
    /// (no EOA footgun — audit P5-03). Zero disables staking cut → team
    /// fallback in afterSwap (S-03). Full notifyReward probe is impossible here
    /// (distributor onlyHook); deploy scripts must point at StakingDistributor.
    function setStakingDistributor(address d) external onlyAdminConfig {
        if (d != address(0) && d.code.length == 0) revert BadDistributor();
        stakingDistributor = d;
        emit ConfigChanged("stakingDistributor", d);
    }

    function setOracle(IWickOracle o) external onlyAdminConfig {
        if (address(o) == address(0)) revert ZeroAddress();
        oracle = o;
        emit ConfigChanged("oracle", address(o));
    }

    function setEntropySource(IEntropySource e) external onlyAdminConfig {
        if (address(e) == address(0)) revert ZeroAddress();
        entropySource = e;
        emit ConfigChanged("entropy", address(e));
    }

    function setTeam(address a) external onlyAdminConfig {
        if (a == address(0)) revert ZeroAddress();
        team = a;
        emit ConfigChanged("team", a);
    }

    function setTreasury(address a) external onlyAdminConfig {
        if (a == address(0)) revert ZeroAddress();
        treasury = a;
        emit ConfigChanged("treasury", a);
    }

    function setCouncil(address a) external onlyAdminConfig {
        if (a == address(0)) revert ZeroAddress();
        council = a;
        emit ConfigChanged("council", a);
    }

    /// Applies to FUTURE periods only (each period pins its version at open), so
    /// a bump never rewrites an already-settled score.
    function setEngineVersion(uint16 v) external onlyAdminConfig {
        engineVersion = v;
        emit EngineVersionChanged(v);
    }

    // -------------------------------------------------------- launch guard
    event BuyAllowedSet(address indexed who, bool allowed);
    event TradingOpened();

    /// Add/remove addresses that may buy during the guarded window. During the
    /// guard, a buy must originate from an allowlisted `sender` (the tester's
    /// address swapping directly, or an allowlisted router).
    function setBuyAllowed(address who, bool allowed) external onlyAdminConfig {
        buyAllowed[who] = allowed;
        emit BuyAllowedSet(who, allowed);
    }

    /// One-way: permanently open the market. Cannot be re-locked.
    /// Not gated by configLocked — fair launch must open before lockConfig.
    function openTrading() external onlyAdmin {
        launchGuardActive = false;
        emit TradingOpened();
    }

    /// One-way: freeze all config setters forever. Requires trading already open
    /// so a premature lock cannot strand the launch guard. Intended days/weeks
    /// after launch once team/oracle/staking wiring is final.
    function lockConfig() external onlyAdmin {
        if (configLocked) revert ConfigLocked();
        if (launchGuardActive) revert TradingNotOpen();
        configLocked = true;
        emit ConfigFrozen();
    }

    // ------------------------------------------------------------ run entry

    /// spec §3. Credits consumed before any charge; quote fail-closed;
    /// 75/25 burn/team; seed from (epoch, entropy, player, runIndex).
    function startRun() external returns (uint256 runId, bytes4 seed) {
        return _startRun();
    }

    function _startRun() internal returns (uint256 runId, bytes4 seed) {
        _rollAll();
        uint64 epochId = _epochNow();
        uint256 feePaid = 0;
        bool creditUsed = false;

        if (creditsOf[msg.sender] > 0) {
            creditsOf[msg.sender] -= 1;
            creditUsed = true;
        } else {
            feePaid = _quoteChecked();
            wick.transferFrom(msg.sender, address(this), feePaid);
            _splitBurn(feePaid);
        }

        uint32 idx = ++runIndexOf[msg.sender];
        seed = bytes4(keccak256(abi.encodePacked(epochId, entropySource.entropy(), msg.sender, idx)));

        runId = nextRunId++;
        runs[runId] = Run({
            player: msg.sender,
            startedAt: uint64(block.timestamp),
            epochId: epochId,
            seed: seed,
            score: 0,
            traceHash: bytes32(0),
            submitted: false
        });
        emit RunStarted(msg.sender, runId, epochId, seed, feePaid, creditUsed);
    }

    /// spec §4. Same 80/20 split as run fees; proceeds NEVER touch pots.
    function buyBundle(uint8 tier) external {
        if (tier > 3) revert BadTier();
        _rollAll();
        BundleTier memory t = tiers[tier];
        uint256 price = _quoteChecked() * t.buyRuns;
        wick.transferFrom(msg.sender, address(this), price);
        uint256 burned = _splitBurn(price);
        uint32 total = uint32(t.buyRuns) + uint32(t.freeRuns);
        creditsOf[msg.sender] += total;
        emit BundlePurchased(msg.sender, tier, total, price, burned);
    }

    /// spec §5. One submission per run, inside the 30-minute window.
    function submitScore(uint256 runId, uint32 score, bytes32 traceHash) external {
        _submit(runId, score, traceHash);
    }

    /// UX batching: submit the previous run's score and start the next run in
    /// ONE transaction (one wallet signature while grinding). Pure composition
    /// of the two audited paths — no new state, no new trust.
    function submitAndStart(uint256 prevRunId, uint32 score, bytes32 traceHash)
        external
        returns (uint256 runId, bytes4 seed)
    {
        _submit(prevRunId, score, traceHash);
        return _startRun();
    }

    function _submit(uint256 runId, uint32 score, bytes32 traceHash) internal {
        Run storage r = runs[runId];
        if (r.player == address(0)) revert UnknownRun();
        if (r.player != msg.sender) revert NotRunOwner();
        if (r.submitted) revert AlreadySubmitted();
        if (block.timestamp > r.startedAt + SUBMISSION_WINDOW) revert WindowClosed();
        if (score > SCORE_CAP) revert ScoreTooHigh();
        r.submitted = true;
        r.score = score;
        r.traceHash = traceHash;
        emit ScoreSubmitted(runId, score, traceHash);
    }

    // ------------------------------------------------------- entry proceeds

    /// The ONLY sink for run-fee / bundle value. Writes to BURN and the team
    /// wallet and nowhere else — this function carrying every entry wei is what
    /// makes invariant 1 (entry never funds a pot) auditable at a glance.
    function _splitBurn(uint256 amount) internal returns (uint256 burned) {
        burned = (amount * BURN_BPS) / 10_000;
        wick.transfer(BURN, burned);
        wick.transfer(team, amount - burned);
    }

    function _quoteChecked() internal view returns (uint256 fee) {
        (uint256 amt, uint256 asOf) = oracle.quoteRunFeeWick();
        if (block.timestamp > asOf + ORACLE_MAX_AGE) revert OracleStale();
        if (amt < FEE_MIN_WICK || amt > FEE_MAX_WICK) revert QuoteOutOfClamps();
        return amt;
    }

    function runFeeQuote() external view returns (uint256) {
        return _quoteChecked();
    }

    // ------------------------------------------------- periods & pot vault

    function _epochNow() internal view returns (uint64) {
        return uint64(block.timestamp / 1 days);
    }

    /// Sunday 00:00 UTC close. Unix day 0 was a Thursday; +4 aligns weeks so
    /// they roll on Sundays.
    function _weekNow() internal view returns (uint64) {
        return uint64((block.timestamp / 1 days + 4) / 7);
    }

    /// Calendar month index (year*12+month), civil-from-days (Howard Hinnant).
    function _monthNow() internal view returns (uint64) {
        uint256 z = block.timestamp / 1 days + 719_468;
        uint256 era = z / 146_097;
        uint256 doe = z - era * 146_097;
        uint256 yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
        uint256 y = yoe + era * 400;
        uint256 doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
        uint256 mp = (5 * doy + 2) / 153;
        uint256 m = mp < 10 ? mp + 3 : mp - 9;
        if (m <= 2) y += 1;
        return uint64(y * 12 + m);
    }

    function _periodNow(uint8 board) internal view returns (uint64) {
        if (board == uint8(Board.Daily)) return _epochNow();
        if (board == uint8(Board.Weekly)) return _weekNow();
        return _monthNow();
    }

    /// Period id that a run's epochId (UTC day) belongs to on `board`.
    function _periodOfDay(uint8 board, uint64 day) internal pure returns (uint64) {
        if (board == uint8(Board.Daily)) return day;
        if (board == uint8(Board.Weekly)) return (day + 4) / 7;
        // same civil-from-days as _monthNow, but from a day index
        uint256 z = uint256(day) + 719_468;
        uint256 era = z / 146_097;
        uint256 doe = z - era * 146_097;
        uint256 yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
        uint256 y = yoe + era * 400;
        uint256 doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
        uint256 mp = (5 * doy + 2) / 153;
        uint256 m = mp < 10 ? mp + 3 : mp - 9;
        if (m <= 2) y += 1;
        return uint64(y * 12 + m);
    }

    /// Sum of up to 3 submitted run scores for msg.sender in `periodId`.
    /// Zeros pad unused slots; duplicates / wrong player / wrong period revert.
    function _runScoreSum(uint8 board, uint64 periodId, uint256 r0, uint256 r1, uint256 r2)
        internal
        view
        returns (uint32 sum)
    {
        uint256[3] memory ids = [r0, r1, r2];
        bool any;
        for (uint256 i = 0; i < 3; i++) {
            uint256 id = ids[i];
            if (id == 0) continue;
            for (uint256 j = 0; j < i; j++) {
                if (ids[j] == id) revert BadRun();
            }
            Run storage r = runs[id];
            if (r.player != msg.sender || !r.submitted) revert BadRun();
            if (_periodOfDay(board, r.epochId) != periodId) revert BadRun();
            sum += r.score;
            any = true;
        }
        if (!any) revert BadRun();
    }

    /// Canonical leaf (audit H-02): player ‖ score ‖ rank ‖ up to 3 runIds.
    function claimLeaf(address player, uint32 score, uint8 rank, uint256 r0, uint256 r1, uint256 r2)
        public
        pure
        returns (bytes32)
    {
        return keccak256(abi.encodePacked(player, score, rank, r0, r1, r2));
    }

    /// Lazy rollover (spec §6). On boundary: snapshot prize = accruals, once.
    /// LEGAL-CRITICAL: prize[board][p] is never written anywhere else.
    function _roll(uint8 board) internal {
        uint64 p = _periodNow(board);
        uint64 cur = currentPeriod[board];
        if (p > cur) {
            Pot memory a = accruing[board];
            prize[board][p] = a; // the ONE write
            periodVersion[board][p] = engineVersion; // pin the engine version for this period
            periodOpenedAt[board][p] = uint64(block.timestamp);
            delete accruing[board];
            currentPeriod[board] = p;
            emit PeriodOpened(board, p, a.weth, a.wick);
        }
    }

    function _rollAll() internal {
        _roll(uint8(Board.Daily));
        _roll(uint8(Board.Weekly));
        _roll(uint8(Board.Monthly));
    }

    /// spec §6: public poke with no other effect.
    function poke() external {
        _rollAll();
    }

    function _accrue(bool isWick, uint256 amount) internal {
        uint128 d = uint128((amount * DAILY_BPS) / 10_000);
        uint128 w = uint128((amount * WEEKLY_BPS) / 10_000);
        uint128 m = uint128(amount) - d - w;
        if (isWick) {
            accruing[uint8(Board.Daily)].wick += d;
            accruing[uint8(Board.Weekly)].wick += w;
            accruing[uint8(Board.Monthly)].wick += m;
        } else {
            accruing[uint8(Board.Daily)].weth += d;
            accruing[uint8(Board.Weekly)].weth += w;
            accruing[uint8(Board.Monthly)].weth += m;
        }
    }

    function prizeOf(uint8 board, uint64 periodId) external view returns (uint256, uint256) {
        Pot memory p = prize[board][periodId];
        return (p.weth, p.wick);
    }

    // ------------------------------------------------------------ settlement

    /// spec §8. DRAFT lifecycle: one root per (board, period); an invalid leaf
    /// voids the whole root (bond -> challenger) and reopens posting, instead
    /// of on-chain rank-shifting. Correct final ranks come from a corrected
    /// repost; simpler and audit-friendlier than mutating rank math on-chain.
    /// Bond covers BOTH pot legs (WETH sized to max(potWeth, floor); WICK sized
    /// to potWick) so a WICK-heavy pot can't be claimed with a ~0 bond. Since
    /// bond ~= the whole pot, posting a fraudulent root risks the pot's value.
    /// The poster supplies the FULL leaf list; the contract derives the root
    /// itself and emits the leaves. This closes the withholding attack: with
    /// only a bare root on-chain, a fraudulent poster could keep the leaves
    /// secret, making every leaf unchallengeable, ride out the window, then
    /// finalize and claim against self-made proofs. Publishing the leaves in
    /// calldata (≤50, trivial on an L2) makes every entry challengeable by
    /// anyone from the moment the root exists.
    function postRoot(uint8 board, uint64 periodId, bytes32[] calldata leaves) external {
        _rollAll();
        if (periodId >= currentPeriod[board]) revert RootNotOpen(); // period must be closed
        if (leaves.length == 0 || leaves.length > LADDER_RANKS) revert BadProof();
        bytes32 root = merkleRoot(leaves);
        RootState storage s = roots[board][periodId];
        if (s.root != bytes32(0) && !s.voided) revert RootExists();
        // Defensive: a prior void should have cleared these; wipe again so a
        // repost never inherits sticky challenge flags/bonds (audit C-02).
        _clearOpenChallenges(board, periodId);
        Pot memory pz = prize[board][periodId];
        Pot memory paid = paidOut[board][periodId];
        // rollStuckPeriod marks paidOut == prize and moves funds to accruing.
        // A later postRoot+finalize would leave claims unpayable (uint128 overflow
        // on paidOut). Retire the period instead (audit P5-02).
        if (
            !s.finalized && (pz.weth > 0 || pz.wick > 0) && paid.weth == pz.weth && paid.wick == pz.wick
        ) revert PeriodRetired();
        uint128 bondWeth = pz.weth < BOND_FLOOR_WETH ? BOND_FLOOR_WETH : pz.weth;
        uint128 bondWick = pz.wick;
        weth.transferFrom(msg.sender, address(this), bondWeth);
        if (bondWick > 0) wick.transferFrom(msg.sender, address(this), bondWick);
        roots[board][periodId] = RootState({
            root: root, poster: msg.sender, bondWeth: bondWeth, bondWick: bondWick,
            postedAt: uint64(block.timestamp), finalized: false, voided: false, openChallenges: 0
        });
        emit RootPosted(board, periodId, root, msg.sender, bondWeth);
        emit RootLeaves(board, periodId, leaves);
    }

    /// Sorted-pair keccak fold, odd node promotes — identical to _verify and
    /// to the off-chain builder (wick-rootposter.js). Public so anyone can
    /// recompute a posted root from its emitted leaves.
    function merkleRoot(bytes32[] calldata leaves) public pure returns (bytes32) {
        uint256 n = leaves.length;
        bytes32[] memory level = new bytes32[](n);
        for (uint256 i = 0; i < n; i++) level[i] = leaves[i];
        while (n > 1) {
            uint256 m = 0;
            for (uint256 i = 0; i < n; i += 2) {
                if (i + 1 == n) { level[m++] = level[i]; continue; } // odd promotes
                (bytes32 a, bytes32 b) = level[i] < level[i + 1]
                    ? (level[i], level[i + 1]) : (level[i + 1], level[i]);
                level[m++] = keccak256(abi.encodePacked(a, b));
            }
            n = m;
        }
        return level[0];
    }

    /// Challenging costs a WETH bond (returned if the challenge is upheld, slashed
    /// to the poster if it was baseless) — this is what stops a free finalize-DoS.
    function challenge(uint8 board, uint64 periodId, bytes32 leaf, bytes32[] calldata proof) external {
        RootState storage s = roots[board][periodId];
        if (s.root == bytes32(0) || s.voided) revert NoRoot();
        if (s.finalized) revert RootNotOpen();
        if (block.timestamp > s.postedAt + CHALLENGE_WINDOW) revert WindowClosed();
        if (challenged[board][periodId][leaf]) revert AlreadyChallenged();
        if (!_verify(proof, s.root, leaf)) revert BadProof();
        weth.transferFrom(msg.sender, address(this), CHALLENGE_BOND);
        challenged[board][periodId][leaf] = true;
        challengerOf[board][periodId][leaf] = msg.sender;
        openChallengeLeaves[board][periodId].push(leaf);
        s.openChallenges += 1;
        emit Challenged(board, periodId, leaf, msg.sender);
    }

    /// FROZEN consensus-critical trace format (engine v5, in-run entropy):
    ///   traceBytes = uint32 seed (BE)
    ///              ‖ uint32 nonceCount (BE) ‖ nonceCount × uint32 beacon (BE)
    ///              ‖ uint32 flipCount  (BE) ‖ flipCount  × uint32 tick   (BE)
    ///   traceHash  = keccak256(traceBytes)
    /// The nonces are the live beacon words the run absorbed at each checkpoint;
    /// committing to them here stops a challenged player from swapping in
    /// favourable nonces after the fact. Whether the nonces are the *authentic*
    /// on-chain beacons for the run's block window is the off-chain verifier /
    /// watcher check applied at resolve() (v1 optimistic model; v2 = zk replay).
    /// Identical to the client's canonical packer and verify.js; proven by the
    /// golden-vector differential test. Do NOT change without a version bump.
    function computeTraceHash(bytes4 seed, uint32[] calldata nonces, uint32[] calldata flipTicks)
        public pure returns (bytes32)
    {
        bytes memory packed = abi.encodePacked(uint32(seed), uint32(nonces.length));
        for (uint256 i = 0; i < nonces.length; i++) {
            packed = abi.encodePacked(packed, nonces[i]);
        }
        packed = abi.encodePacked(packed, uint32(flipTicks.length));
        for (uint256 i = 0; i < flipTicks.length; i++) {
            packed = abi.encodePacked(packed, flipTicks[i]);
        }
        return keccak256(packed);
    }

    function revealTrace(uint256 runId, uint32[] calldata nonces, uint32[] calldata flipTicks) external {
        Run storage r = runs[runId];
        if (r.player == address(0) || !r.submitted) revert UnknownRun();
        // well-formedness (spec §5): strictly ascending, 1-based, within the run cap
        for (uint256 i = 0; i < flipTicks.length; i++) {
            if (flipTicks[i] == 0 || flipTicks[i] > MAX_TICKS) revert BadProof();
            if (i > 0 && flipTicks[i] <= flipTicks[i - 1]) revert BadProof();
        }
        if (computeTraceHash(r.seed, nonces, flipTicks) != r.traceHash) revert BadProof();
        emit TraceRevealed(runId, uint32(flipTicks.length));
    }

    /// Council applies the deterministic verifier's output — no council-chosen
    /// recipient: bond flows are fixed by the RECORDED challenger.
    /// `valid` = challenged score is legitimate (baseless challenge);
    /// `!valid` = fraudulent (root voided).
    ///
    /// Trust note (audit H-01): v1 still trusts the council's boolean. The
    /// required non-zero `evidenceHash` does not verify the report on-chain —
    /// it forces an auditable commitment so a silent flip is visible on-chain.
    /// v2 (zk replay) retires this trust assumption.
    ///
    /// On void (audit C-01/C-02): poster bond is paid EXACTLY once (fields zeroed),
    /// every other open challenge bond is refunded to its challenger, and the
    /// challenge bookkeeping is wiped so a repost can be challenged cleanly.
    function resolve(uint8 board, uint64 periodId, bytes32 leaf, bool valid, bytes32 evidenceHash)
        external
        onlyCouncil
    {
        if (evidenceHash == bytes32(0)) revert BadEvidence();
        RootState storage s = roots[board][periodId];
        if (s.root == bytes32(0)) revert NoRoot();
        if (s.voided) revert RootVoidedAlready();
        if (s.finalized) revert RootNotFinal();
        if (!challenged[board][periodId][leaf]) revert NotChallenged();
        address chal = challengerOf[board][periodId][leaf];
        challenged[board][periodId][leaf] = false;
        s.openChallenges -= 1;
        resolveEvidence[board][periodId][leaf] = evidenceHash;
        emit Resolved(board, periodId, leaf, valid, evidenceHash);
        if (valid) {
            // baseless challenge: challenger forfeits their bond to the poster
            challengerOf[board][periodId][leaf] = address(0);
            _removeOpenChallengeLeaf(board, periodId, leaf); // audit P6-02: no queue bloat
            weth.transfer(s.poster, CHALLENGE_BOND);
        } else {
            // fraud proven: void once; pay poster bond + this challenger's bond
            // exactly once; refund every other still-open challenge bond.
            s.voided = true;
            uint256 payWeth = uint256(s.bondWeth) + CHALLENGE_BOND;
            uint256 payWick = uint256(s.bondWick);
            s.bondWeth = 0;
            s.bondWick = 0;
            weth.transfer(chal, payWeth);
            if (payWick > 0) wick.transfer(chal, payWick);
            _refundOtherChallenges(board, periodId, leaf);
            delete openChallengeLeaves[board][periodId];
            s.openChallenges = 0;
            emit RootVoided(board, periodId, chal);
        }
    }

    /// Drop one leaf from the open-challenge queue (swap-and-pop). Used when a
    /// challenge is resolved baseless so the array cannot grow without bound
    /// across re-challenge cycles (audit P6-02).
    function _removeOpenChallengeLeaf(uint8 board, uint64 periodId, bytes32 leaf) internal {
        bytes32[] storage q = openChallengeLeaves[board][periodId];
        uint256 n = q.length;
        for (uint256 i = 0; i < n; i++) {
            if (q[i] == leaf) {
                q[i] = q[n - 1];
                q.pop();
                return;
            }
        }
    }

    /// Refund CHALLENGE_BOND to every open challenger except `winnerLeaf`
    /// (already paid above). Clears their challenged flags.
    function _refundOtherChallenges(uint8 board, uint64 periodId, bytes32 winnerLeaf) internal {
        bytes32[] storage q = openChallengeLeaves[board][periodId];
        uint256 n = q.length;
        for (uint256 i = 0; i < n; i++) {
            bytes32 L = q[i];
            if (L == winnerLeaf) continue;
            if (!challenged[board][periodId][L]) continue; // already resolved baseless
            address c = challengerOf[board][periodId][L];
            challenged[board][periodId][L] = false;
            challengerOf[board][periodId][L] = address(0);
            if (c != address(0)) {
                weth.transfer(c, CHALLENGE_BOND);
                emit ChallengeRefunded(board, periodId, L, c);
            }
        }
    }

    /// Wipe challenge flags/queue for a (board, period). Used on void cleanup
    /// and defensively on postRoot before a repost.
    function _clearOpenChallenges(uint8 board, uint64 periodId) internal {
        bytes32[] storage q = openChallengeLeaves[board][periodId];
        uint256 n = q.length;
        for (uint256 i = 0; i < n; i++) {
            bytes32 L = q[i];
            challenged[board][periodId][L] = false;
            challengerOf[board][periodId][L] = address(0);
        }
        delete openChallengeLeaves[board][periodId];
    }

    function finalize(uint8 board, uint64 periodId) external {
        RootState storage s = roots[board][periodId];
        if (s.root == bytes32(0) || s.voided) revert NoRoot();
        if (block.timestamp <= s.postedAt + CHALLENGE_WINDOW) revert WindowStillOpen();
        if (s.openChallenges != 0) revert ChallengesOpen();
        if (s.finalized) revert RootNotFinal();
        s.finalized = true;
        uint256 refundWeth = s.bondWeth;
        uint256 refundWick = s.bondWick;
        s.bondWeth = 0; // audit P5-04: don't leave stale bond fields after refund
        s.bondWick = 0;
        if (refundWeth > 0) weth.transfer(s.poster, refundWeth);
        if (refundWick > 0) wick.transfer(s.poster, refundWick);
        emit Settled(board, periodId, s.root);
    }

    /// Permissionless stuck-period fallback: if a closed period never got a valid
    /// root within STUCK_GRACE of opening, anyone can roll its prize into the
    /// current accrual pot so funds can't be locked forever in an admin-less
    /// contract. Invariant-safe (pot -> pot; no entry proceeds involved).
    function rollStuckPeriod(uint8 board, uint64 periodId) external {
        _rollAll();
        if (periodId >= currentPeriod[board]) revert RootNotOpen(); // must be a closed period
        RootState storage s = roots[board][periodId];
        if (s.finalized) revert RootNotFinal();
        if (s.root != bytes32(0) && !s.voided) revert RootExists(); // a live root exists; not stuck
        uint64 openedAt = periodOpenedAt[board][periodId];
        if (openedAt == 0 || block.timestamp < uint256(openedAt) + STUCK_GRACE) revert NotStuck();
        Pot memory pz = prize[board][periodId];
        Pot memory paid = paidOut[board][periodId];
        paidOut[board][periodId] = pz; // mark fully disbursed so it can't roll twice
        accruing[board].weth += pz.weth - paid.weth;
        accruing[board].wick += pz.wick - paid.wick;
    }

    // ---------------------------------------------------------------- claims

    /// spec §7 + audit H-02. Leaf = (player, score, rank, r0, r1, r2).
    /// `score` MUST equal the sum of the named runs' on-chain submitted scores;
    /// invented scores without matching runs cannot claim. Pass 0 for unused
    /// run slots (1–3 runs). In-kind payout of both legs.
    function claim(
        uint8 board,
        uint64 periodId,
        uint32 score,
        uint8 rank,
        uint256 runId0,
        uint256 runId1,
        uint256 runId2,
        bytes32[] calldata proof
    ) external {
        RootState storage s = roots[board][periodId];
        if (!s.finalized) revert RootNotFinal();
        if (rank == 0 || rank > LADDER_RANKS) revert BadRank();
        if (claimed[board][periodId][rank]) revert AlreadyClaimed();
        if (_runScoreSum(board, periodId, runId0, runId1, runId2) != score) revert ScoreMismatch();
        if (!_verify(proof, s.root, claimLeaf(msg.sender, score, rank, runId0, runId1, runId2))) {
            revert BadProof();
        }
        claimed[board][periodId][rank] = true;
        _payoutClaim(board, periodId, rank);
    }

    function _payoutClaim(uint8 board, uint64 periodId, uint8 rank) internal {
        uint16 bps = rankBps(rank);
        Pot memory p = prize[board][periodId];
        uint256 outWeth = (uint256(p.weth) * bps) / 10_000;
        uint256 outWick = (uint256(p.wick) * bps) / 10_000;
        paidOut[board][periodId].weth += uint128(outWeth);
        paidOut[board][periodId].wick += uint128(outWick);
        if (outWeth > 0) weth.transfer(msg.sender, outWeth);
        if (outWick > 0) wick.transfer(msg.sender, outWick);
        emit Claimed(msg.sender, board, periodId, rank, outWeth, outWick);
    }

    /// spec §7: unclaimed rolls to the next period. DRAFT: sweepable into the
    /// current accrual pot 30 days after finalization.
    function sweepUnclaimed(uint8 board, uint64 periodId) external {
        RootState storage s = roots[board][periodId];
        if (!s.finalized) revert RootNotFinal();
        if (block.timestamp < uint256(s.postedAt) + CHALLENGE_WINDOW + 30 days) revert WindowStillOpen();
        Pot memory p = prize[board][periodId];
        Pot memory paid = paidOut[board][periodId];
        uint128 remWeth = p.weth - paid.weth;
        uint128 remWick = p.wick - paid.wick;
        paidOut[board][periodId] = p; // mark fully disbursed
        accruing[board].weth += remWeth;
        accruing[board].wick += remWick;
    }

    function rankBps(uint8 rank) public pure returns (uint16) {
        if (rank == 1) return 5000;
        if (rank == 2) return 2000;
        if (rank == 3) return 1000;
        if (rank == 4) return 240;
        if (rank == 5) return 200;
        if (rank == 6) return 160;
        if (rank == 7) return 130;
        if (rank == 8) return 100;
        if (rank == 9) return 90;
        if (rank == 10) return 80;
        return FLAT_BPS; // 11..50
    }

    function _verify(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
        bytes32 h = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            bytes32 p = proof[i];
            h = h < p ? keccak256(abi.encodePacked(h, p)) : keccak256(abi.encodePacked(p, h));
        }
        return h == root;
    }

    // ------------------------------------------------------------- v4 hooks

    /// afterInitialize: bind the pool, set the 0.5% dynamic LP fee.
    function afterInitialize(address, PoolKey calldata key, uint160, int24) external onlyPoolManager returns (bytes4) {
        if (poolSet) revert PoolAlreadySet();
        poolKey = key;
        poolSet = true;
        wickIsCurrency0 = Currency.unwrap(key.currency0) == address(wick);
        poolManager.updateDynamicLPFee(key, LP_FEE);
        return IHooks.afterInitialize.selector;
    }

    /// afterSwap: capture fee from the swap's UNSPECIFIED currency (v4 rule —
    /// same pattern as FeeTakingHook). Exact-in → fee on output; exact-out → fee
    /// on input. Taking the positive-delta (output) leg while returning an
    /// unspecified delta breaks exact-out settlement (audit O-03).
    function afterSwap(address, PoolKey calldata key, SwapParams calldata params, BalanceDelta delta, bytes calldata)
        external
        onlyPoolManager
        returns (bytes4, int128)
    {
        if (!poolSet) revert PoolNotSet();
        _rollAll();

        // keep the run-fee TWAP series live: one observation per swap. A
        // reverting observer must never brick trading, hence the try/catch.
        if (twapObserver != address(0)) {
            try IWickTwapObserver(twapObserver).poke() {} catch {}
        }

        // unspecified token: the opposite of the specified side
        bool specifiedIsC0 = (params.amountSpecified < 0) == params.zeroForOne;
        Currency feeCurrency = specifiedIsC0 ? key.currency1 : key.currency0;
        int128 swapAmount = specifiedIsC0 ? delta.amount1() : delta.amount0();
        if (swapAmount < 0) swapAmount = -swapAmount;
        if (swapAmount == 0) return (IHooks.afterSwap.selector, 0);

        bool feeIsWick = (Currency.unwrap(feeCurrency) == address(wick));
        uint256 amt = uint256(uint128(swapAmount));
        uint256 potCut = (amt * POT_BPS) / 10_000;
        uint256 teamCut = (amt * TEAM_BPS) / 10_000;
        uint256 stakeCut = (amt * STAKING_BPS) / 10_000;
        uint256 total = potCut + teamCut + stakeCut;
        if (total == 0) return (IHooks.afterSwap.selector, 0);

        poolManager.take(feeCurrency, address(this), total);
        IERC20Minimal feeTok = IERC20Minimal(Currency.unwrap(feeCurrency));

        _accrue(feeIsWick, potCut);
        if (teamCut > 0) feeTok.transfer(team, teamCut);

        // 1% staking cut -> distributor (dual-leg). Falls back to team if the
        // distributor isn't wired or notifyReward reverts (audit S-03 — never
        // brick swaps). Trade proceeds only; invariant 1 OK.
        if (stakeCut > 0) {
            if (stakingDistributor != address(0)) {
                try this.deliverStakeReward(feeCurrency, stakeCut, feeIsWick) {}
                catch {
                    feeTok.transfer(team, stakeCut);
                }
            } else {
                feeTok.transfer(team, stakeCut);
            }
        }
        emit PotAccrued(uint8(Board.Daily), feeIsWick ? 0 : potCut, feeIsWick ? potCut : 0);

        // hook is owed `total` of the unspecified currency
        return (IHooks.afterSwap.selector, int128(uint128(total)));
    }

    /// External so afterSwap can try/catch: transfer+notify are atomic; on
    /// failure the catch path sends the cut to team instead.
    function deliverStakeReward(Currency feeCurrency, uint256 amount, bool feeIsWick) external {
        if (msg.sender != address(this)) revert NotSelf();
        IERC20Minimal feeTok = IERC20Minimal(Currency.unwrap(feeCurrency));
        feeTok.transfer(stakingDistributor, amount);
        IStakingNotify(stakingDistributor).notifyReward(feeIsWick ? 0 : amount, feeIsWick ? amount : 0);
    }

    // unused hook callbacks — permissions exclude them; revert defensively
    function beforeInitialize(address, PoolKey calldata, uint160) external pure returns (bytes4) {
        revert HookNotImplemented();
    }

    function beforeAddLiquidity(address, PoolKey calldata, ModifyLiquidityParams calldata, bytes calldata)
        external
        pure
        returns (bytes4)
    {
        revert HookNotImplemented();
    }

    function afterAddLiquidity(
        address,
        PoolKey calldata,
        ModifyLiquidityParams calldata,
        BalanceDelta,
        BalanceDelta,
        bytes calldata
    ) external pure returns (bytes4, BalanceDelta) {
        revert HookNotImplemented();
    }

    function beforeRemoveLiquidity(address, PoolKey calldata, ModifyLiquidityParams calldata, bytes calldata)
        external
        pure
        returns (bytes4)
    {
        revert HookNotImplemented();
    }

    function afterRemoveLiquidity(
        address,
        PoolKey calldata,
        ModifyLiquidityParams calldata,
        BalanceDelta,
        BalanceDelta,
        bytes calldata
    ) external pure returns (bytes4, BalanceDelta) {
        revert HookNotImplemented();
    }

    /// Launch guard: while active, a buy (trader receives WICK) must come from an
    /// allowlisted sender. Sells are always open. No fee override, zero delta.
    /// NOTE: requires the beforeSwap permission bit in the mined hook address.
    function beforeSwap(address sender, PoolKey calldata, SwapParams calldata params, bytes calldata)
        external
        view
        onlyPoolManager
        returns (bytes4, BeforeSwapDelta, uint24)
    {
        if (launchGuardActive) {
            bool isBuy = params.zeroForOne != wickIsCurrency0; // trader receives WICK
            if (isBuy && !buyAllowed[sender]) revert NotAllowlisted();
        }
        return (IHooks.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0);
    }

    function beforeDonate(address, PoolKey calldata, uint256, uint256, bytes calldata) external pure returns (bytes4) {
        revert HookNotImplemented();
    }

    function afterDonate(address, PoolKey calldata, uint256, uint256, bytes calldata) external pure returns (bytes4) {
        revert HookNotImplemented();
    }
}
Chain explorer3436msChain node80ms