ChibiLiquidityVault

0x485a9dea97538ec24e61de511bd69e9e8eea2a4d

Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
18,281 bytes
ABI entries
125
46 read · 26 write
License
none

Contract information

Address
0x485a9dea97538ec24e61de511bd69e9e8eea2a4d
Chain
Robinhood Chain (4663)
Compiler
v0.8.26+commit.8a97fa7a
Optimization
Enabled
Creation tx
0x34b052420f…306c244051

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (46)

CLAIM_INTENT_TYPEHASH()bytes32
RAID_EPOCH_SECONDS()uint256
RAID_EXPOSURE_SECONDS()uint256
SHEEP_PROTECTED_BPS()uint16
TEAM_LP_FEE_BPS()uint16
WOLF_CAPTURE_BPS()uint16
WOLF_TAX_BPS()uint16
accFeesPerSheepUru()uint256
accFeesPerSheepWeth()uint256
accSubsidyPerSheep()uint256
accWolfPerWolfUru()uint256
accWolfPerWolfWeth()uint256
bookedUru()uint256
bookedWeth()uint256
burnRedeem(uint256, address)
chibi()address
chibiMinter()address
claimNonce(address)uint256
eip712Domain()bytes1, string, string, uint256, address, bytes32, uint256[]
gameController()address
isActive(uint256)bool
lpTokenId()uint256
onERC721Received(address, address, uint256, bytes)bytes4
owner()address
paused()bool
pendingOwner()address
pendingSheepYield(uint256)uint256, uint256
pendingWolfYield(uint256)uint256, uint256
positionManager()address
previewRaidPool()uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256
previewSheepYield(uint256)uint256, uint256, uint256, uint256
previewWolfSpoils(uint256)uint256, uint256, uint256, uint256
raidAtRiskUru()uint256
raidAtRiskWeth()uint256
sheepDebtFeesUru(uint256)uint256
sheepDebtFeesWeth(uint256)uint256
sheepDebtSubsidy(uint256)uint256
teamFeeRecipient()address
totalActiveSheep()uint256
totalActiveWolves()uint256
uru()address
uruIsToken0()bool
weth()address
wolfDebtUru(uint256)uint256
wolfDebtWeth(uint256)uint256
yieldReserve()address

Events (21)

AuthorizationChangedBurnRedeemedClaimedClaimedWolfEIP712DomainChangedEmergencyWithdrawnLpEmergencyTransferredLpTokenInitializedOwnershipTransferStartedOwnershipTransferredPausedRaidPoolSettledRaidedSheepRegisteredSubsidyAddedTeamFeeRecipientChangedTeamLpFeesPaidUnpausedUruProceedsAddedWolfPoolFundedFromMintWolfRegistered

ABI

[
  {
    "inputs": [
      {
        "internalType": "contract IERC20",
        "name": "_uru",
        "type": "address"
      },
      {
        "internalType": "contract IERC20",
        "name": "_weth",
        "type": "address"
      },
      {
        "internalType": "contract IChibiCore",
        "name": "_chibi",
        "type": "address"
      },
      {
        "internalType": "contract IUniswapV4PositionManager",
        "name": "_positionManager",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "_teamFeeRecipient",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "_initialOwner",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "AlreadyRegistered",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ClaimIntentExpired",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ClaimIntentWrongNonce",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ClaimIntentWrongTokenIds",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ECDSAInvalidSignature",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "length",
        "type": "uint256"
      }
    ],
    "name": "ECDSAInvalidSignatureLength",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "s",
        "type": "bytes32"
      }
    ],
    "name": "ECDSAInvalidSignatureS",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "EmptyBatch",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "EnforcedPause",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ExpectedPause",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidClaimIntentSignature",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidShortString",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "LpTokenAlreadyInitialized",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "LpTokenNotInitialized",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NoActiveSheep",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NoActiveWolves",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotActive",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotAuthorized",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotOwner",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotSheep",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotWolf",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      }
    ],
    "name": "OwnableInvalidOwner",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "OwnableUnauthorizedAccount",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ProceedsNotReceived",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "RaidEpochCollision",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ReentrancyGuardReentrantCall",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint8",
        "name": "bits",
        "type": "uint8"
      },
      {
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "SafeCastOverflowedUintDowncast",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "SafeERC20FailedOperation",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "string",
        "name": "str",
        "type": "string"
      }
    ],
    "name": "StringTooLong",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroAddress",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroAmount",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "string",
        "name": "role",
        "type": "string"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newAddress",
        "type": "address"
      }
    ],
    "name": "AuthorizationChanged",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "chibiId",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "uruOut",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "wethOut",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "uruTax",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "wethTax",
        "type": "uint256"
      }
    ],
    "name": "BurnRedeemed",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "chibiId",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "uruOut",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "wethOut",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "wolfTaxUru",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "wolfTaxWeth",
        "type": "uint256"
      }
    ],
    "name": "Claimed",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "chibiId",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "uruOut",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "wethOut",
        "type": "uint256"
      }
    ],
    "name": "ClaimedWolf",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [],
    "name": "EIP712DomainChanged",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "EmergencyWithdrawn",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "lpTokenId",
        "type": "uint256"
      }
    ],
    "name": "LpEmergencyTransferred",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "lpTokenId",
        "type": "uint256"
      }
    ],
    "name": "LpTokenInitialized",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "previousOwner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "OwnershipTransferStarted",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "previousOwner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "OwnershipTransferred",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "Paused",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "uruSettled",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "wethSettled",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "sheepUru",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "sheepWeth",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "wolfUru",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "wolfWeth",
        "type": "uint256"
      }
    ],
    "name": "RaidPoolSettled",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "victimId",
        "type": "uint256"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "raider",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "uruStolen",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "wethStolen",
        "type": "uint256"
      }
    ],
    "name": "Raided",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "chibiId",
        "type": "uint256"
      }
    ],
    "name": "SheepRegistered",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "SubsidyAdded",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      }
    ],
    "name": "TeamFeeRecipientChanged",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "uruAmount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "wethAmount",
        "type": "uint256"
      }
    ],
    "name": "TeamLpFeesPaid",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "Unpaused",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "UruProceedsAdded",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "chibiId",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "wethAmount",
        "type": "uint256"
      }
    ],
    "name": "WolfPoolFundedFromMint",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "chibiId",
        "type": "uint256"
      }
    ],
    "name": "WolfRegistered",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "CLAIM_INTENT_TYPEHASH",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "RAID_EPOCH_SECONDS",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "RAID_EXPOSURE_SECONDS",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "SHEEP_PROTECTED_BPS",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "TEAM_LP_FEE_BPS",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "WOLF_CAPTURE_BPS",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "WOLF_TAX_BPS",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "accFeesPerSheepUru",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "accFeesPerSheepWeth",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "accSubsidyPerSheep",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "accWolfPerWolfUru",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "accWolfPerWolfWeth",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "acceptOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256[]",
        "name": "chibiIds",
        "type": "uint256[]"
      }
    ],
    "name": "airdropRegister",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "bookedUru",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "bookedWeth",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "burnRedeem",
    "outputs": [],
    "stateMutability": "pure",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "chibi",
    "outputs": [
      {
        "internalType": "contract IChibiCore",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "chibiMinter",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "chibiId",
        "type": "uint256"
      }
    ],
    "name": "claim",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256[]",
        "name": "chibiIds",
        "type": "uint256[]"
      }
    ],
    "name": "claimMany",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "components": [
          {
            "internalType": "address",
            "name": "user",
            "type": "address"
          },
          {
            "internalType": "uint256",
            "name": "nonce",
            "type": "uint256"
          },
          {
            "internalType": "uint64",
            "name": "deadline",
            "type": "uint64"
          },
          {
            "internalType": "bytes32",
            "name": "tokenIdsHash",
            "type": "bytes32"
          }
        ],
        "internalType": "struct IChibiLiquidityVault.ClaimIntent",
        "name": "intent",
        "type": "tuple"
      },
      {
        "internalType": "bytes",
        "name": "signature",
        "type": "bytes"
      },
      {
        "internalType": "uint256[]",
        "name": "chibiIds",
        "type": "uint256[]"
      }
    ],
    "name": "claimManyFor",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "user",
        "type": "address"
      }
    ],
    "name": "claimNonce",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "nonce",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "chibiId",
        "type": "uint256"
      }
    ],
    "name": "claimWolf",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "creditUruProceeds",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "chibiId",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "wethAmount",
        "type": "uint256"
      }
    ],
    "name": "creditWolfPoolFromMint",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "eip712Domain",
    "outputs": [
      {
        "internalType": "bytes1",
        "name": "fields",
        "type": "bytes1"
      },
      {
        "internalType": "string",
        "name": "name",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "version",
        "type": "string"
      },
      {
        "internalType": "uint256",
        "name": "chainId",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "verifyingContract",
        "type": "address"
      },
      {
        "internalType": "bytes32",
        "name": "salt",
        "type": "bytes32"
      },
      {
        "internalType": "uint256[]",
        "name": "extensions",
        "type": "uint256[]"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      }
    ],
    "name": "emergencyTransferLp",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "emergencyWithdraw",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "gameController",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "harvestFees",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      }
    ],
    "name": "initializeLpToken",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "chibiId",
        "type": "uint256"
      }
    ],
    "name": "isActive",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "lpTokenId",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      },
      {
        "internalType": "bytes",
        "name": "",
        "type": "bytes"
      }
    ],
    "name": "onERC721Received",
    "outputs": [
      {
        "internalType": "bytes4",
        "name": "",
        "type": "bytes4"
      }
    ],
    "stateMutability": "pure",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "owner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pause",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "paused",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pendingOwner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "chibiId",
        "type": "uint256"
      }
    ],
    "name": "pendingSheepYield",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "uruOut",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "wethOut",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "chibiId",
        "type": "uint256"
      }
    ],
    "name": "pendingWolfYield",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "uruOut",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "wethOut",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "positionManager",
    "outputs": [
      {
        "internalType": "contract IUniswapV4PositionManager",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "previewRaidPool",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "atRiskUru",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "atRiskWeth",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "matureUru",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "matureWeth",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "projectedSheepUru",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "projectedSheepWeth",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "projectedWolfUru",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "projectedWolfWeth",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "chibiId",
        "type": "uint256"
      }
    ],
    "name": "previewSheepYield",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "claimableUru",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "claimableWeth",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "atRiskUru",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "atRiskWeth",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "chibiId",
        "type": "uint256"
      }
    ],
    "name": "previewWolfSpoils",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "withdrawableUru",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "withdrawableWeth",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "projectedUru",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "projectedWeth",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "pushSubsidy",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "raidAtRiskUru",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "raidAtRiskWeth",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "victimChibiId",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "raider",
        "type": "address"
      }
    ],
    "name": "raidClaim",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "chibiId",
        "type": "uint256"
      }
    ],
    "name": "registerSheep",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "chibiId",
        "type": "uint256"
      }
    ],
    "name": "registerWolf",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "renounceOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newMinter",
        "type": "address"
      }
    ],
    "name": "setChibiMinter",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newController",
        "type": "address"
      }
    ],
    "name": "setGameController",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newRecipient",
        "type": "address"
      }
    ],
    "name": "setTeamFeeRecipient",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newReserve",
        "type": "address"
      }
    ],
    "name": "setYieldReserve",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "settleRaidPool",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "settledUru",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "settledWeth",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "sheepUru",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "sheepWeth",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "wolfUru",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "wolfWeth",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "chibiId",
        "type": "uint256"
      }
    ],
    "name": "sheepDebtFeesUru",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "chibiId",
        "type": "uint256"
      }
    ],
    "name": "sheepDebtFeesWeth",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "chibiId",
        "type": "uint256"
      }
    ],
    "name": "sheepDebtSubsidy",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "syncSubsidy",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "teamFeeRecipient",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "totalActiveSheep",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "totalActiveWolves",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "transferOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "unpause",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "uru",
    "outputs": [
      {
        "internalType": "contract IERC20",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "uruIsToken0",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "weth",
    "outputs": [
      {
        "internalType": "contract IERC20",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "chibiId",
        "type": "uint256"
      }
    ],
    "name": "wolfDebtUru",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "chibiId",
        "type": "uint256"
      }
    ],
    "name": "wolfDebtWeth",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "yieldReserve",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  }
]

Source code

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

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import {Ownable, Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";

import {GameTypes} from "./libraries/GameTypes.sol";
import {IChibiCore} from "./interfaces/IChibiCore.sol";
import {IChibiLiquidityVault} from "./interfaces/IChibiLiquidityVault.sol";
import {IYieldReserve} from "./interfaces/IYieldReserve.sol";
import {IUniswapV4PositionManager, V4Actions} from "./interfaces/uniswap/IUniswapV4PositionManager.sol";

/// @dev Tiny hook interface so the vault can notify GameController on register
///      without taking an explicit dep on the full controller surface.
interface IGameControllerHook {
    function onChibiRegistered(uint256 chibiId) external;
}

/// @title ChibiLiquidityVault
/// @notice Yield + lazy aggregate raid accounting for the chibi collection. The vault holds
///         the URU/WETH Uniswap V4 LP NFT (a `PositionManager` position) and
///         pulls accumulated swap fees on every harvest by calling
///         `IUniswapV4PositionManager.modifyLiquidities()` with a
///         `DECREASE_LIQUIDITY(0) + TAKE_PAIR` action pair — V4's idiomatic
///         "collect fees only" pattern.
///         `harvestFees()` first invokes the collect path, immediately sends
///         the fixed team share of newly collected LP fees, then snapshots the
///         balance delta vs. `bookedUru/bookedWeth` and credits the
///         unattributed delta into the at-risk raid pool. Direct ERC20 transfers
///         (subsidy, URU proceeds, wolf credit) stay explicit so the vault does
///         not misclassify already-booked balances.
///
///         Each active chibi has 1 share. Yield distribution divides by
///         `totalActiveSheep` / `totalActiveWolves` (Synthetix-style accumulator).
///         Burn-redeem is permanently disabled in v1 — chibi exit = secondary
///         market.
///
///         The LP NFT is bound post-deploy via `initializeLpToken(tokenId)`
///         once the project-created Uniswap V4 pool mints/transfers its
///         position to the vault. Pre-init harvest calls fall through and only
///         run the balance-delta path, so the vault works in subsidy-only mode
///         until the LP is wired in.
///
///         Raid exposure constants are fixed for v1 — change requires redeploy.
///
///         Reviewer invariants:
///         - `bookedUru/bookedWeth` track balances already assigned to claims,
///           wolf spoils, subsidy, mint proceeds, or team fees.
///         - Unbooked URU/WETH balance deltas are the only values that
///           `harvestFees()` routes into the at-risk raid pool.
///         - Before the first sheep registers, unbooked balances are snapped
///           into `booked*` so the first minter cannot capture pre-launch dust.
///         - Matured raid epochs split 70% to protected sheep yield and 30% to
///           wolf spoils; unmatured epochs remain in `raidAtRisk*`.
///         - If wolf spoils accrue before the first wolf, `_wolfCarry*` stores
///           them until the first wolf registers.
///         - Collected V4 LP fees pay the team share first, then the remaining
///           unbooked delta follows the same at-risk raid-pool path.
contract ChibiLiquidityVault is EIP712, Ownable2Step, Pausable, ReentrancyGuard, IERC721Receiver, IChibiLiquidityVault {
    using SafeERC20 for IERC20;

    // ─── Constants ─────────────────────────────────────────────────────────
    uint256 internal constant ACC_PRECISION = 1e36;

    uint16 public constant WOLF_TAX_BPS = 0; // Protected sheep claims are no longer taxed.
    uint256 public constant RAID_EPOCH_SECONDS = 6 hours;
    uint256 public constant RAID_EXPOSURE_SECONDS = 36 hours;
    uint16 public constant WOLF_CAPTURE_BPS = 3000; // 30% of matured at-risk yield to wolves.
    uint16 public constant SHEEP_PROTECTED_BPS = 7000; // 70% of matured at-risk yield to sheep.
    uint16 public constant TEAM_LP_FEE_BPS = 2500; // 25% of collected LP fees funds development
    bytes32 public constant CLAIM_INTENT_TYPEHASH =
        keccak256("ClaimIntent(address user,uint256 nonce,uint64 deadline,bytes32 tokenIdsHash)");

    uint16 internal constant BPS_DENOM = 10_000;
    uint8 internal constant RAID_EPOCH_BUCKETS = 8;

    // ─── Wired deps (immutable post-construction) ──────────────────────────
    IERC20 public immutable uru;
    IERC20 public immutable weth;
    IChibiCore public immutable chibi;
    IUniswapV4PositionManager public immutable positionManager;

    /// @dev True iff URU is token0 in the Uniswap V4 pool. Pool keys are
    ///      currency-ordered by ascending address, so this is resolvable at
    ///      construction time. Drives the `TAKE_PAIR` ordering on collect.
    bool public immutable uruIsToken0;

    // ─── LP position (bound post-deploy via initializeLpToken) ─────────────
    /// @notice The V4 PositionManager tokenId for the vault's LP position. `0`
    ///         means no position is bound and the collect path is a no-op.
    uint256 public lpTokenId;

    // ─── Authorized callers ────────────────────────────────────────────────
    address public chibiMinter;
    address public yieldReserve;
    address public gameController;
    address public teamFeeRecipient;

    // ─── Active counts (option D — 1 share per active chibi) ───────────────
    uint256 public totalActiveSheep;
    uint256 public totalActiveWolves;
    mapping(uint256 chibiId => bool) public isActive;
    mapping(address user => uint256 nonce) public claimNonce;

    // ─── Sheep accumulators (protected URU/WETH; subsidy retained for ABI) ─
    uint256 public accFeesPerSheepUru;
    uint256 public accFeesPerSheepWeth;
    uint256 public accSubsidyPerSheep = 0;
    mapping(uint256 chibiId => uint256) public sheepDebtFeesUru;
    mapping(uint256 chibiId => uint256) public sheepDebtFeesWeth;
    mapping(uint256 chibiId => uint256) public sheepDebtSubsidy;

    // ─── Wolf accumulators (2 — fees URU, fees WETH) ───────────────────────
    uint256 public accWolfPerWolfUru;
    uint256 public accWolfPerWolfWeth;
    mapping(uint256 chibiId => uint256) public wolfDebtUru;
    mapping(uint256 chibiId => uint256) public wolfDebtWeth;

    // ─── Carry buckets (pre-first-wolf credits) ────────────────────────────
    uint256 internal _wolfCarryUru;
    uint256 internal _wolfCarryWeth;

    // ─── Aggregate at-risk raid pool ───────────────────────────────────────
    struct RaidEpochBucket {
        bool active;
        uint64 epochId;
        uint64 maturesAt;
        uint256 uru;
        uint256 weth;
    }

    RaidEpochBucket[RAID_EPOCH_BUCKETS] internal _raidEpochs;
    uint256 public raidAtRiskUru;
    uint256 public raidAtRiskWeth;

    // ─── Booked balances (for harvestFees delta math) ──────────────────────
    /// @dev Invariant: `bookedUru == uru.balanceOf(this)` after every state-mutating
    ///      operation. Pushes (subsidy, wolf credit) increment booked.
    ///      Payouts (claim, collect) decrement booked. Inflows that arrive WITHOUT a
    ///      push call (Uniswap V4 PositionManager `collect()` returns via
    ///      `_collectLpFees()`, swap-fee donations from a future V4 hook, ad-hoc
    ///      treasury transfers) leave balance > booked, and `harvestFees()`
    ///      routes the delta into the at-risk raid pool.
    uint256 public bookedUru;
    uint256 public bookedWeth;

    // ─── Modifiers ─────────────────────────────────────────────────────────
    modifier onlyMinter() {
        if (msg.sender != chibiMinter) revert NotAuthorized();
        _;
    }

    modifier onlyGameController() {
        if (msg.sender != gameController) revert NotAuthorized();
        _;
    }

    // ─── Constructor ───────────────────────────────────────────────────────
    constructor(
        IERC20 _uru,
        IERC20 _weth,
        IChibiCore _chibi,
        IUniswapV4PositionManager _positionManager,
        address _teamFeeRecipient,
        address _initialOwner
    ) EIP712("UrufuClaims", "1") Ownable(_initialOwner) {
        if (
            address(_uru) == address(0) || address(_weth) == address(0) || address(_chibi) == address(0)
                || address(_positionManager) == address(0) || _teamFeeRecipient == address(0)
                || _initialOwner == address(0)
        ) revert ZeroAddress();
        uru = _uru;
        weth = _weth;
        chibi = _chibi;
        positionManager = _positionManager;
        teamFeeRecipient = _teamFeeRecipient;
        uruIsToken0 = address(_uru) < address(_weth);
    }

    // ─── ERC721 receiver (accept the LP NFT on transfer-in) ────────────────
    function onERC721Received(address, address, uint256, bytes calldata) external pure override returns (bytes4) {
        return IERC721Receiver.onERC721Received.selector;
    }

    // ─── Public harvest entry ──────────────────────────────────────────────

    /// @notice Snapshot balance delta vs booked, route new yield into the at-risk pool.
    ///         Public — anyone may call. Idempotent (delta of 0 is a no-op).
    function harvestFees() external whenNotPaused {
        _harvestFees();
        _settleRaidPoolOnly();
    }

    // ─── Mint hooks (only chibiMinter) ─────────────────────────────────────
    function registerSheep(uint256 chibiId) external override onlyMinter whenNotPaused {
        if (isActive[chibiId]) revert AlreadyRegistered();
        if (chibi.roleOf(chibiId) != GameTypes.Role.Sheep) revert NotSheep();

        _harvestFees();

        // First-sheep windfall closure (M2 from llm-review.md): if no sheep
        // existed before this call, exclude any pre-mint vault balance from the
        // accumulator by snapping `booked*` to current balance. Pre-mint dust
        // becomes locked in the vault until owner sweeps via emergencyWithdraw.
        // Without this, a sniper waiting for fees to accumulate could mint
        // first and capture 100% of pre-mint creator-fee inflows.
        if (totalActiveSheep == 0) {
            uint256 currentUru = uru.balanceOf(address(this));
            uint256 currentWeth = weth.balanceOf(address(this));
            if (currentUru > bookedUru) bookedUru = currentUru;
            if (currentWeth > bookedWeth) bookedWeth = currentWeth;
        }

        isActive[chibiId] = true;
        unchecked {
            totalActiveSheep += 1;
        }
        sheepDebtFeesUru[chibiId] = accFeesPerSheepUru;
        sheepDebtFeesWeth[chibiId] = accFeesPerSheepWeth;
        sheepDebtSubsidy[chibiId] = accSubsidyPerSheep;
        emit SheepRegistered(chibiId);

        _notifyGameController(chibiId);
    }

    function registerWolf(uint256 chibiId) external override onlyMinter whenNotPaused {
        if (isActive[chibiId]) revert AlreadyRegistered();
        if (chibi.roleOf(chibiId) != GameTypes.Role.Wolf) revert NotWolf();

        _settleRaidPoolOnly();

        isActive[chibiId] = true;
        uint256 newCount = totalActiveWolves + 1;
        totalActiveWolves = newCount;
        wolfDebtUru[chibiId] = accWolfPerWolfUru;
        wolfDebtWeth[chibiId] = accWolfPerWolfWeth;

        if (_wolfCarryUru > 0) {
            accWolfPerWolfUru += (_wolfCarryUru * ACC_PRECISION) / newCount;
            _wolfCarryUru = 0;
        }
        if (_wolfCarryWeth > 0) {
            accWolfPerWolfWeth += (_wolfCarryWeth * ACC_PRECISION) / newCount;
            _wolfCarryWeth = 0;
        }
        emit WolfRegistered(chibiId);

        _notifyGameController(chibiId);
    }

    /// @notice Batched airdrop registration for the RH cutover. Owner-only,
    ///         one-shot per id (skips already-active). Amortizes
    ///         `_harvestFees` + `_settleRaidPoolOnly` across the whole batch
    ///         (each runs once at the start), then loops per-chibi doing the
    ///         same state mutations `registerSheep`/`registerWolf` would.
    /// @dev    Preserves the first-sheep windfall closure (snap booked* on
    ///         the first sheep registered when totalActiveSheep was 0) and
    ///         the first-wolf carry-credit path (drain `_wolfCarry*` to the
    ///         accumulator when totalActiveWolves goes 0 → 1). Chibi role
    ///         is read from `chibi.roleOf(id)`, so airdropMint MUST run
    ///         before airdropRegister for any given id.
    function airdropRegister(uint256[] calldata chibiIds) external onlyOwner whenNotPaused {
        uint256 len = chibiIds.length;
        if (len == 0) revert EmptyBatch();

        // Amortized once-per-batch pre-work. Both are no-ops when their
        // preconditions aren't met (harvest skips if no sheep active).
        _harvestFees();
        _settleRaidPoolOnly();

        for (uint256 i = 0; i < len; i++) {
            uint256 chibiId = chibiIds[i];
            if (isActive[chibiId]) continue; // idempotent skip for retries

            GameTypes.Role role = chibi.roleOf(chibiId);
            if (role == GameTypes.Role.Sheep) {
                // First-sheep windfall closure — mirrors registerSheep. Pre-airdrop
                // vault balances (bridged URU, seed-phase WETH) get locked out of
                // the accumulator so the first airdroppee doesn't sweep them.
                if (totalActiveSheep == 0) {
                    uint256 currentUru = uru.balanceOf(address(this));
                    uint256 currentWeth = weth.balanceOf(address(this));
                    if (currentUru > bookedUru) bookedUru = currentUru;
                    if (currentWeth > bookedWeth) bookedWeth = currentWeth;
                }

                isActive[chibiId] = true;
                unchecked {
                    totalActiveSheep += 1;
                }
                sheepDebtFeesUru[chibiId] = accFeesPerSheepUru;
                sheepDebtFeesWeth[chibiId] = accFeesPerSheepWeth;
                sheepDebtSubsidy[chibiId] = accSubsidyPerSheep;
                emit SheepRegistered(chibiId);
                _notifyGameController(chibiId);
            } else {
                isActive[chibiId] = true;
                uint256 newCount = totalActiveWolves + 1;
                totalActiveWolves = newCount;
                wolfDebtUru[chibiId] = accWolfPerWolfUru;
                wolfDebtWeth[chibiId] = accWolfPerWolfWeth;

                // First-wolf carry credit — mirrors registerWolf.
                if (_wolfCarryUru > 0) {
                    accWolfPerWolfUru += (_wolfCarryUru * ACC_PRECISION) / newCount;
                    _wolfCarryUru = 0;
                }
                if (_wolfCarryWeth > 0) {
                    accWolfPerWolfWeth += (_wolfCarryWeth * ACC_PRECISION) / newCount;
                    _wolfCarryWeth = 0;
                }
                emit WolfRegistered(chibiId);
                _notifyGameController(chibiId);
            }
        }
    }

    /// @notice Optional credit on wolf mint — called by ChibiMinter if a paid
    ///         public phase routes the wolf-mint payment to the wolf accumulator.
    ///         For v1 (free whitelist mints) this is a no-op path: minter calls with
    ///         amount=0 are rejected, so the function only runs when there's real
    ///         WETH to credit. Caller must have approved the vault for `wethAmount`.
    function creditWolfPoolFromMint(uint256 chibiId, uint256 wethAmount) external override onlyMinter {
        if (wethAmount == 0) revert ZeroAmount();
        weth.safeTransferFrom(msg.sender, address(this), wethAmount);
        bookedWeth += wethAmount;
        _creditWolfAccumulator(0, wethAmount);
        emit WolfPoolFundedFromMint(chibiId, wethAmount);
    }

    // ─── User actions: claim / claimWolf / claimMany ───────────────────────
    function claim(uint256 chibiId) external override nonReentrant whenNotPaused {
        if (chibi.ownerOf(chibiId) != msg.sender) revert NotOwner();
        if (chibi.roleOf(chibiId) != GameTypes.Role.Sheep) revert NotSheep();
        if (!isActive[chibiId]) revert NotActive();

        _syncSubsidy();
        _harvestFees();
        _settleRaidPoolOnly();
        (uint256 uruOut, uint256 wethOut) = _settleSheepClaim(chibiId);
        _payYield(msg.sender, uruOut, wethOut);
    }

    function claimWolf(uint256 chibiId) external override nonReentrant whenNotPaused {
        if (chibi.ownerOf(chibiId) != msg.sender) revert NotOwner();
        if (chibi.roleOf(chibiId) != GameTypes.Role.Wolf) revert NotWolf();
        if (!isActive[chibiId]) revert NotActive();

        _syncSubsidy();
        _harvestFees();
        _settleRaidPoolOnly();
        (uint256 uruOut, uint256 wethOut) = _settleWolf(chibiId);
        _payYield(msg.sender, uruOut, wethOut);
        emit ClaimedWolf(chibiId, uruOut, wethOut);
    }

    /// @notice Claim all provided chibis in one transaction. Sheep are settled
    ///         first, then wolves, so wolves owned by the caller can immediately
    ///         receive their share of sheep claim tax from the same batch.
    function claimMany(uint256[] calldata chibiIds) external override nonReentrant whenNotPaused {
        _claimManyFor(msg.sender, chibiIds);
    }

    /// @notice Relayed batch claim. The relayer pays gas, but ownership checks
    ///         and reward payout are bound to `intent.user`.
    function claimManyFor(ClaimIntent calldata intent, bytes calldata signature, uint256[] calldata chibiIds)
        external
        override
        nonReentrant
        whenNotPaused
    {
        if (intent.user == address(0)) revert ZeroAddress();
        if (block.timestamp > intent.deadline) revert ClaimIntentExpired();
        if (intent.nonce != claimNonce[intent.user]) revert ClaimIntentWrongNonce();
        if (intent.tokenIdsHash != _claimTokenIdsHash(chibiIds)) revert ClaimIntentWrongTokenIds();

        bytes32 digest = _hashTypedDataV4(
            keccak256(
                abi.encode(CLAIM_INTENT_TYPEHASH, intent.user, intent.nonce, intent.deadline, intent.tokenIdsHash)
            )
        );
        if (ECDSA.recover(digest, signature) != intent.user) revert InvalidClaimIntentSignature();

        unchecked {
            claimNonce[intent.user] = intent.nonce + 1;
        }
        _claimManyFor(intent.user, chibiIds);
    }

    function _claimManyFor(address claimant, uint256[] calldata chibiIds) internal {
        uint256 len = chibiIds.length;
        if (len == 0) revert EmptyBatch();

        _syncSubsidy();
        _harvestFees();
        _settleRaidPoolOnly();

        uint256 totalUruOut = 0;
        uint256 totalWethOut = 0;

        for (uint256 i = 0; i < len;) {
            uint256 chibiId = chibiIds[i];
            if (chibi.ownerOf(chibiId) != claimant) revert NotOwner();
            if (!isActive[chibiId]) revert NotActive();
            if (chibi.roleOf(chibiId) == GameTypes.Role.Sheep) {
                (uint256 uruOut, uint256 wethOut) = _settleSheepClaim(chibiId);
                totalUruOut += uruOut;
                totalWethOut += wethOut;
            }
            unchecked {
                i += 1;
            }
        }

        for (uint256 i = 0; i < len;) {
            uint256 chibiId = chibiIds[i];
            if (chibi.roleOf(chibiId) == GameTypes.Role.Wolf) {
                (uint256 uruOut, uint256 wethOut) = _settleWolf(chibiId);
                totalUruOut += uruOut;
                totalWethOut += wethOut;
                emit ClaimedWolf(chibiId, uruOut, wethOut);
            }
            unchecked {
                i += 1;
            }
        }

        _payYield(claimant, totalUruOut, totalWethOut);
    }

    function settleRaidPool()
        external
        override
        whenNotPaused
        returns (
            uint256 settledUru,
            uint256 settledWeth,
            uint256 sheepUru,
            uint256 sheepWeth,
            uint256 wolfUru,
            uint256 wolfWeth
        )
    {
        _syncSubsidy();
        _harvestFees();
        return _settleRaidPoolOnly();
    }

    // ─── GameController hooks ─────────────────────────────────────────────
    function raidClaim(uint256 victimChibiId, address raider)
        external
        override
        nonReentrant
        onlyGameController
        whenNotPaused
    {
        victimChibiId;
        raider;
        revert NotAuthorized();
    }

    /// @notice Burn-redeem is **NOT supported in v1** under the creator-fee model.
    ///         Chibis have no on-chain redeem floor; owners can only claim accrued
    ///         yield and sell on secondary. Reverts unconditionally so a future
    ///         GameController accidentally calling it can't break accounting.
    function burnRedeem(uint256, address) external pure override {
        revert NotAuthorized();
    }

    // ─── Yield pillar push hooks ───────────────────────────────────────────
    function pushSubsidy(uint256 amount) external override {
        if (msg.sender != yieldReserve) revert NotAuthorized();
        if (amount == 0) revert ZeroAmount();
        if (totalActiveSheep == 0) revert NoActiveSheep();
        uru.safeTransferFrom(msg.sender, address(this), amount);
        bookedUru += amount;
        _addAtRiskYield(amount, 0);
        emit SubsidyAdded(amount);
    }

    /// @notice Book URU mint-curve proceeds already transferred into the vault
    ///         by ChibiMinter. When sheep are active, the proceeds are credited
    ///         into the at-risk raid pool before maturing into protected yield.
    /// @dev    If no sheep are active, the proceeds are still booked (so the
    ///         balance-delta paths cannot later misattribute them) but NOT
    ///         distributed. This is intentional and mirrors the first-sheep
    ///         windfall closure in `registerSheep` (M2): auto-crediting a
    ///         pre-sheep balance would hand the first sheep to register a 100%
    ///         windfall. Booked-but-undistributed proceeds are recoverable by
    ///         the owner via `emergencyWithdraw` (which decrements `bookedUru`).
    ///         In practice the URU-curve phase runs last, so sheep exist by then.
    function creditUruProceeds(uint256 amount) external override onlyMinter {
        if (amount == 0) revert ZeroAmount();
        uint256 currentUru = uru.balanceOf(address(this));
        // The minter transfers URU in before calling this; guard that the
        // balance actually grew by `amount` so booking can't outrun real funds.
        if (currentUru < bookedUru + amount) revert ProceedsNotReceived();

        bookedUru += amount;
        if (totalActiveSheep > 0) {
            _addAtRiskYield(amount, 0);
        }
        emit UruProceedsAdded(amount);
    }

    function syncSubsidy() external override whenNotPaused returns (uint256 amount) {
        amount = _syncSubsidy();
    }

    // ─── Views ─────────────────────────────────────────────────────────────
    function pendingSheepYield(uint256 chibiId) external view override returns (uint256 uruOut, uint256 wethOut) {
        if (!isActive[chibiId]) return (0, 0);
        (uruOut, wethOut) = _previewSheepClaimable(chibiId);
    }

    function pendingWolfYield(uint256 chibiId) external view override returns (uint256 uruOut, uint256 wethOut) {
        if (!isActive[chibiId]) return (0, 0);
        (,, uruOut, wethOut) = _previewWolfSpoils(chibiId);
    }

    function previewSheepYield(uint256 chibiId)
        external
        view
        override
        returns (uint256 claimableUru, uint256 claimableWeth, uint256 atRiskUru, uint256 atRiskWeth)
    {
        if (!isActive[chibiId]) return (0, 0, 0, 0);
        (claimableUru, claimableWeth) = _previewSheepClaimable(chibiId);
        uint256 activeSheep = totalActiveSheep;
        if (activeSheep == 0) return (claimableUru, claimableWeth, 0, 0);
        (uint256 totalAtRiskUru, uint256 totalAtRiskWeth, uint256 matureUru, uint256 matureWeth,,,,) =
            _previewRaidPool();
        atRiskUru = (totalAtRiskUru - matureUru) / activeSheep;
        atRiskWeth = (totalAtRiskWeth - matureWeth) / activeSheep;
    }

    function previewWolfSpoils(uint256 chibiId)
        external
        view
        override
        returns (uint256 withdrawableUru, uint256 withdrawableWeth, uint256 projectedUru, uint256 projectedWeth)
    {
        if (!isActive[chibiId]) return (0, 0, 0, 0);
        return _previewWolfSpoils(chibiId);
    }

    function previewRaidPool()
        external
        view
        override
        returns (
            uint256 atRiskUru,
            uint256 atRiskWeth,
            uint256 matureUru,
            uint256 matureWeth,
            uint256 projectedSheepUru,
            uint256 projectedSheepWeth,
            uint256 projectedWolfUru,
            uint256 projectedWolfWeth
        )
    {
        return _previewRaidPool();
    }

    // ─── Internals ─────────────────────────────────────────────────────────
    function _notifyGameController(uint256 chibiId) internal {
        address controller = gameController;
        if (controller != address(0) && controller.code.length > 0) {
            IGameControllerHook(controller).onChibiRegistered(chibiId);
        }
    }

    function _claimTokenIdsHash(uint256[] calldata chibiIds) internal pure returns (bytes32) {
        return keccak256(abi.encode(chibiIds));
    }

    function _addAtRiskYield(uint256 uruAmount, uint256 wethAmount) internal {
        if (uruAmount == 0 && wethAmount == 0) return;
        _settleRaidPoolOnly();

        uint64 epochId = SafeCast.toUint64(block.timestamp / RAID_EPOCH_SECONDS);
        // Deterministic ring-buffer indexing by epoch, not entropy.
        // slither-disable-next-line weak-prng
        uint8 bucketIdx = SafeCast.toUint8(epochId % RAID_EPOCH_BUCKETS);
        RaidEpochBucket storage bucket = _raidEpochs[bucketIdx];
        if (bucket.active && bucket.epochId != epochId) revert RaidEpochCollision();

        if (!bucket.active) {
            bucket.active = true;
            bucket.epochId = epochId;
        }
        uint64 maturesAt = SafeCast.toUint64(block.timestamp + RAID_EXPOSURE_SECONDS);
        if (maturesAt > bucket.maturesAt) bucket.maturesAt = maturesAt;

        if (uruAmount > 0) {
            bucket.uru += uruAmount;
            raidAtRiskUru += uruAmount;
        }
        if (wethAmount > 0) {
            bucket.weth += wethAmount;
            raidAtRiskWeth += wethAmount;
        }
    }

    function _settleRaidPoolOnly()
        internal
        returns (
            uint256 settledUru,
            uint256 settledWeth,
            uint256 sheepUru,
            uint256 sheepWeth,
            uint256 wolfUru,
            uint256 wolfWeth
        )
    {
        for (uint256 i = 0; i < RAID_EPOCH_BUCKETS;) {
            RaidEpochBucket storage bucket = _raidEpochs[i];
            if (bucket.active && bucket.maturesAt <= block.timestamp) {
                settledUru += bucket.uru;
                settledWeth += bucket.weth;
                if (bucket.uru > 0) raidAtRiskUru -= bucket.uru;
                if (bucket.weth > 0) raidAtRiskWeth -= bucket.weth;
                delete _raidEpochs[i];
            }
            unchecked {
                i += 1;
            }
        }

        if (settledUru == 0 && settledWeth == 0) return (0, 0, 0, 0, 0, 0);

        (sheepUru, sheepWeth, wolfUru, wolfWeth) = _splitRaidSettlement(settledUru, settledWeth);

        uint256 activeSheep = totalActiveSheep;
        if (activeSheep > 0) {
            if (sheepUru > 0) accFeesPerSheepUru += (sheepUru * ACC_PRECISION) / activeSheep;
            if (sheepWeth > 0) accFeesPerSheepWeth += (sheepWeth * ACC_PRECISION) / activeSheep;
        }
        if (wolfUru > 0 || wolfWeth > 0) _creditWolfAccumulator(wolfUru, wolfWeth);

        emit RaidPoolSettled(settledUru, settledWeth, sheepUru, sheepWeth, wolfUru, wolfWeth);
    }

    function _splitRaidSettlement(uint256 uruAmount, uint256 wethAmount)
        internal
        view
        returns (uint256 sheepUru, uint256 sheepWeth, uint256 wolfUru, uint256 wolfWeth)
    {
        if (totalActiveWolves == 0) return (uruAmount, wethAmount, 0, 0);
        wolfUru = (uruAmount * WOLF_CAPTURE_BPS) / BPS_DENOM;
        wolfWeth = (wethAmount * WOLF_CAPTURE_BPS) / BPS_DENOM;
        sheepUru = uruAmount - wolfUru;
        sheepWeth = wethAmount - wolfWeth;
    }

    function _previewSheepClaimable(uint256 chibiId) internal view returns (uint256 uruOut, uint256 wethOut) {
        uruOut = (accFeesPerSheepUru - sheepDebtFeesUru[chibiId]) / ACC_PRECISION
            + (accSubsidyPerSheep - sheepDebtSubsidy[chibiId]) / ACC_PRECISION;
        wethOut = (accFeesPerSheepWeth - sheepDebtFeesWeth[chibiId]) / ACC_PRECISION;

        uint256 activeSheep = totalActiveSheep;
        if (activeSheep == 0) return (uruOut, wethOut);
        (,,,, uint256 projectedSheepUru, uint256 projectedSheepWeth,,) = _previewRaidPool();
        uruOut += projectedSheepUru / activeSheep;
        wethOut += projectedSheepWeth / activeSheep;
    }

    function _previewWolfSpoils(uint256 chibiId)
        internal
        view
        returns (uint256 withdrawableUru, uint256 withdrawableWeth, uint256 projectedUru, uint256 projectedWeth)
    {
        withdrawableUru = (accWolfPerWolfUru - wolfDebtUru[chibiId]) / ACC_PRECISION;
        withdrawableWeth = (accWolfPerWolfWeth - wolfDebtWeth[chibiId]) / ACC_PRECISION;
        projectedUru = withdrawableUru;
        projectedWeth = withdrawableWeth;

        uint256 activeWolves = totalActiveWolves;
        if (activeWolves == 0) return (withdrawableUru, withdrawableWeth, projectedUru, projectedWeth);
        (,,,,,, uint256 projectedWolfUru, uint256 projectedWolfWeth) = _previewRaidPool();
        projectedUru += projectedWolfUru / activeWolves;
        projectedWeth += projectedWolfWeth / activeWolves;
    }

    function _previewRaidPool()
        internal
        view
        returns (
            uint256 atRiskUru,
            uint256 atRiskWeth,
            uint256 matureUru,
            uint256 matureWeth,
            uint256 projectedSheepUru,
            uint256 projectedSheepWeth,
            uint256 projectedWolfUru,
            uint256 projectedWolfWeth
        )
    {
        atRiskUru = raidAtRiskUru;
        atRiskWeth = raidAtRiskWeth;

        uint256 currentUru = uru.balanceOf(address(this));
        uint256 currentWeth = weth.balanceOf(address(this));
        if (currentUru > bookedUru) atRiskUru += currentUru - bookedUru;
        if (currentWeth > bookedWeth) atRiskWeth += currentWeth - bookedWeth;
        atRiskUru += _pendingSubsidyAmount();

        for (uint256 i = 0; i < RAID_EPOCH_BUCKETS;) {
            RaidEpochBucket storage bucket = _raidEpochs[i];
            if (bucket.active && bucket.maturesAt <= block.timestamp) {
                matureUru += bucket.uru;
                matureWeth += bucket.weth;
            }
            unchecked {
                i += 1;
            }
        }

        (projectedSheepUru, projectedSheepWeth, projectedWolfUru, projectedWolfWeth) =
            _splitRaidSettlement(matureUru, matureWeth);
    }

    function _harvestFees() internal {
        if (totalActiveSheep == 0) return; // nothing to attribute to; leave balance, retry later
        _collectLpFees();
        uint256 currentUru = uru.balanceOf(address(this));
        uint256 currentWeth = weth.balanceOf(address(this));
        if (currentUru > bookedUru) {
            uint256 newUru = currentUru - bookedUru;
            _addAtRiskYield(newUru, 0);
            bookedUru = currentUru;
        }
        if (currentWeth > bookedWeth) {
            uint256 newWeth = currentWeth - bookedWeth;
            _addAtRiskYield(0, newWeth);
            bookedWeth = currentWeth;
        }
    }

    /// @dev Pull accumulated swap fees from the Uniswap V4 LP NFT, if bound.
    ///      No-op when `lpTokenId == 0` (pre-init) — keeps the vault working
    ///      in subsidy-only mode until the LP is wired in. The collected
    ///      tokens land in this contract's balance and the caller picks them
    ///      up via the standard balance-delta math in `_harvestFees`.
    ///
    ///      V4 routes all position ops through `modifyLiquidities`. To collect
    ///      accrued fees only we encode `DECREASE_LIQUIDITY(0) + TAKE_PAIR`:
    ///      decreasing by 0 liquidity triggers a fee-only settlement, and
    ///      `TAKE_PAIR` moves both currencies from the PoolManager delta to
    ///      the recipient (this vault).
    ///
    ///      The action ids + param shapes are pinned in `V4Actions` /
    ///      documented on `IUniswapV4PositionManager`. Re-verify upstream action
    ///      ids when updating the V4 periphery integration.
    function _collectLpFees() internal {
        uint256 tokenId = lpTokenId;
        if (tokenId == 0) return;

        uint256 beforeUru = uru.balanceOf(address(this));
        uint256 beforeWeth = weth.balanceOf(address(this));

        bytes memory actions = abi.encodePacked(V4Actions.DECREASE_LIQUIDITY, V4Actions.TAKE_PAIR);

        bytes[] memory params = new bytes[](2);
        // DECREASE_LIQUIDITY: (tokenId, liquidity, amount0Min, amount1Min, hookData).
        // liquidity = 0 means "collect accrued fees without removing any liquidity".
        params[0] = abi.encode(tokenId, uint256(0), uint128(0), uint128(0), bytes(""));
        // TAKE_PAIR: (currency0, currency1, recipient). Currencies follow the
        // pool's canonical ordering (lower address first), matched by uruIsToken0.
        address currency0 = uruIsToken0 ? address(uru) : address(weth);
        address currency1 = uruIsToken0 ? address(weth) : address(uru);
        params[1] = abi.encode(currency0, currency1, address(this));

        positionManager.modifyLiquidities(abi.encode(actions, params), block.timestamp);

        uint256 collectedUru = uru.balanceOf(address(this)) - beforeUru;
        uint256 collectedWeth = weth.balanceOf(address(this)) - beforeWeth;
        _payTeamLpFeeShare(collectedUru, collectedWeth);
    }

    function _payTeamLpFeeShare(uint256 collectedUru, uint256 collectedWeth) internal {
        uint256 teamUru = (collectedUru * TEAM_LP_FEE_BPS) / BPS_DENOM;
        uint256 teamWeth = (collectedWeth * TEAM_LP_FEE_BPS) / BPS_DENOM;
        if (teamUru == 0 && teamWeth == 0) return;

        address recipient = teamFeeRecipient;
        if (teamUru > 0) uru.safeTransfer(recipient, teamUru);
        if (teamWeth > 0) weth.safeTransfer(recipient, teamWeth);
        emit TeamLpFeesPaid(recipient, teamUru, teamWeth);
    }

    function _syncSubsidy() internal returns (uint256 amount) {
        uint256 activeSheep = totalActiveSheep;
        address reserve = yieldReserve;
        if (activeSheep == 0 || reserve == address(0) || reserve.code.length == 0) return 0;

        uint256 beforeBalance = uru.balanceOf(address(this));
        try IYieldReserve(reserve).releaseVestedToVault() returns (uint256) {
            uint256 afterBalance = uru.balanceOf(address(this));
            if (afterBalance <= beforeBalance) return 0;
            amount = afterBalance - beforeBalance;
            bookedUru += amount;
            _addAtRiskYield(amount, 0);
            emit SubsidyAdded(amount);
        } catch (bytes memory reason) {
            if (_isNoopSubsidySync(reason)) return 0;
            _revertBytes(reason);
        }
    }

    function _pendingSubsidyAmount() internal view returns (uint256) {
        address reserve = yieldReserve;
        if (reserve == address(0) || reserve.code.length == 0) return 0;
        try IYieldReserve(reserve).vestedAt(uint64(block.timestamp)) returns (uint256 vested) {
            uint256 released = IYieldReserve(reserve).totalReleased();
            return vested > released ? vested - released : 0;
        } catch {
            return 0;
        }
    }

    function _isNoopSubsidySync(bytes memory reason) internal pure returns (bool) {
        if (reason.length < 4) return false;
        bytes4 selector;
        assembly {
            selector := mload(add(reason, 32))
        }
        return selector == IYieldReserve.NotFunded.selector || selector == IYieldReserve.NothingToDrip.selector;
    }

    function _revertBytes(bytes memory reason) internal pure {
        assembly {
            revert(add(reason, 32), mload(reason))
        }
    }

    function _settleSheep(uint256 chibiId) internal returns (uint256 uruOut, uint256 wethOut) {
        uint256 a1 = accFeesPerSheepUru;
        uint256 a2 = accFeesPerSheepWeth;
        uint256 a3 = accSubsidyPerSheep;
        uruOut = (a1 - sheepDebtFeesUru[chibiId]) / ACC_PRECISION + (a3 - sheepDebtSubsidy[chibiId]) / ACC_PRECISION;
        wethOut = (a2 - sheepDebtFeesWeth[chibiId]) / ACC_PRECISION;
        sheepDebtFeesUru[chibiId] = a1;
        sheepDebtFeesWeth[chibiId] = a2;
        sheepDebtSubsidy[chibiId] = a3;
    }

    function _settleSheepClaim(uint256 chibiId) internal returns (uint256 uruOut, uint256 wethOut) {
        (uruOut, wethOut) = _settleSheep(chibiId);

        uint256 uruToWolves = (uruOut * WOLF_TAX_BPS) / BPS_DENOM;
        uint256 wethToWolves = (wethOut * WOLF_TAX_BPS) / BPS_DENOM;
        _creditWolfAccumulator(uruToWolves, wethToWolves);
        uruOut -= uruToWolves;
        wethOut -= wethToWolves;

        emit Claimed(chibiId, uruOut, wethOut, uruToWolves, wethToWolves);
    }

    function _settleWolf(uint256 chibiId) internal returns (uint256 uruOut, uint256 wethOut) {
        uint256 a1 = accWolfPerWolfUru;
        uint256 a2 = accWolfPerWolfWeth;
        uruOut = (a1 - wolfDebtUru[chibiId]) / ACC_PRECISION;
        wethOut = (a2 - wolfDebtWeth[chibiId]) / ACC_PRECISION;
        wolfDebtUru[chibiId] = a1;
        wolfDebtWeth[chibiId] = a2;
    }

    function _creditWolfAccumulator(uint256 uruAmt, uint256 wethAmt) internal {
        uint256 active = totalActiveWolves;
        if (active == 0) {
            if (uruAmt > 0) _wolfCarryUru += uruAmt;
            if (wethAmt > 0) _wolfCarryWeth += wethAmt;
            return;
        }
        if (uruAmt > 0) accWolfPerWolfUru += (uruAmt * ACC_PRECISION) / active;
        if (wethAmt > 0) accWolfPerWolfWeth += (wethAmt * ACC_PRECISION) / active;
    }

    function _payYield(address to, uint256 uruAmt, uint256 wethAmt) internal {
        if (uruAmt > 0) {
            uru.safeTransfer(to, uruAmt);
            bookedUru -= uruAmt;
        }
        if (wethAmt > 0) {
            weth.safeTransfer(to, wethAmt);
            bookedWeth -= wethAmt;
        }
    }

    // ─── Owner ops ─────────────────────────────────────────────────────────
    function setChibiMinter(address newMinter) external onlyOwner {
        if (newMinter == address(0)) revert ZeroAddress();
        chibiMinter = newMinter;
        emit AuthorizationChanged("chibiMinter", newMinter);
    }

    function setYieldReserve(address newReserve) external onlyOwner {
        if (newReserve == address(0)) revert ZeroAddress();
        yieldReserve = newReserve;
        emit AuthorizationChanged("yieldReserve", newReserve);
    }

    function setGameController(address newController) external onlyOwner {
        if (newController == address(0)) revert ZeroAddress();
        gameController = newController;
        emit AuthorizationChanged("gameController", newController);
    }

    function setTeamFeeRecipient(address newRecipient) external onlyOwner {
        if (newRecipient == address(0)) revert ZeroAddress();
        teamFeeRecipient = newRecipient;
        emit TeamFeeRecipientChanged(newRecipient);
    }

    /// @notice One-shot binding of the Uniswap V4 LP NFT held by this vault.
    ///         Caller must already have transferred the NFT to the vault
    ///         (e.g. via `safeTransferFrom`); the vault verifies ownership
    ///         before recording the tokenId. Reverts if already initialized.
    /// @dev    The NFT custody is enforced at call time: if a later transfer
    ///         moves the NFT away, `_collectLpFees` will revert on the next
    ///         harvest — pause + `emergencyTransferLp` is the recovery path.
    function initializeLpToken(uint256 tokenId) external onlyOwner {
        if (lpTokenId != 0) revert LpTokenAlreadyInitialized();
        if (tokenId == 0) revert ZeroAmount();
        if (positionManager.ownerOf(tokenId) != address(this)) revert LpTokenNotInitialized();
        lpTokenId = tokenId;
        emit LpTokenInitialized(tokenId);
    }

    function pause() external onlyOwner {
        _pause();
    }

    function unpause() external onlyOwner {
        _unpause();
    }

    /// @notice Escape hatch: withdraw arbitrary ERC20. Pause-gated.
    /// @dev When withdrawing uru/weth, decrement the matching `booked*` so the
    ///      `harvestFees` invariant (`booked* <= balance`) holds and future
    ///      inflows resume crediting (M3 from llm-review.md).
    function emergencyWithdraw(address token, address to, uint256 amount) external onlyOwner whenPaused {
        if (to == address(0)) revert ZeroAddress();
        if (token == address(uru)) {
            bookedUru = bookedUru > amount ? bookedUru - amount : 0;
        } else if (token == address(weth)) {
            bookedWeth = bookedWeth > amount ? bookedWeth - amount : 0;
        }
        IERC20(token).safeTransfer(to, amount);
        emit EmergencyWithdrawn(to, token, amount);
    }

    /// @notice Pause-gated rescue: send the LP NFT out of the vault to a
    ///         recovery address. Clears `lpTokenId` so subsequent harvests
    ///         fall through to subsidy-only mode until a new LP is bound.
    function emergencyTransferLp(address to) external onlyOwner whenPaused {
        if (to == address(0)) revert ZeroAddress();
        uint256 tokenId = lpTokenId;
        if (tokenId == 0) revert LpTokenNotInitialized();
        lpTokenId = 0;
        positionManager.safeTransferFrom(address(this), to, tokenId);
        emit LpEmergencyTransferred(to, tokenId);
    }
}
Chain explorer5017msChain node93ms