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
- Creator
- 0x6d606cc634…2C7B835Bb9
- 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