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
- Creator
- 0x4e59b44847…26c0B4956C
- 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