SazareHookV2
0xbbe4baf7d38b7465beddffeaf29f96fc50c8a888
Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
13,391 bytes
ABI entries
66
44 read · 4 write
License
none
Contract information
- Address
- 0xbbe4baf7d38b7465beddffeaf29f96fc50c8a888
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.26+commit.8a97fa7a
- Optimization
- Enabled
- Creator
- 0x4e59b44847…26c0B4956C
- Creation tx
- 0xefd818cfc0…a22ac3ffff
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (44)
BAND_BUY_SHARE_BPS() → uint256
BPS() → uint256
CAP() → uint256
DEAD() → address
FLOOR_BUY_SHARE_BPS() → uint256
FLOOR_FEE_SHARE_BPS() → uint256
SWAP_FEE_BPS() → uint256
TICK_SPACING() → int24
WAD() → uint256
afterAddLiquidity(address, tuple, tuple, int256, int256, bytes) → bytes4, int256
afterDonate(address, tuple, uint256, uint256, bytes) → bytes4
afterInitialize(address, tuple, uint160, int24) → bytes4
afterRemoveLiquidity(address, tuple, tuple, int256, int256, bytes) → bytes4, int256
afterSwap(address, tuple, tuple, int256, bytes) → bytes4, int128
bandReserve() → uint256
bandState() → tuple
beforeAddLiquidity(address, tuple, tuple, bytes) → bytes4
beforeDonate(address, tuple, uint256, uint256, bytes) → bytes4
beforeRemoveLiquidity(address, tuple, tuple, bytes) → bytes4
burnedForever() → uint256
curveMath() → address
curveScale() → uint256
feeRecipient() → address
floorPrice() → uint256
floorReserve() → uint256
initialized() → bool
inventory() → uint256
inventoryBacking() → uint256
issuancePrice() → uint256
issuanceScale() → uint256
mintedEver() → uint256
pInit() → uint256
poolManager() → address
quoteBuy(uint256) → uint256, uint256, uint256
quoteBuyExactTokens(uint256) → uint256, uint256, uint256
redeemableSupply() → uint256
saleCap() → uint256
spotPrice() → uint256
tailInventory() → uint256
token() → address
totalDepthContributed() → uint256
totalInventoryContributed() → uint256
totalProtocolFeesPaid() → uint256
weth() → address
Events (4)
DepthAddedInventoryDepthAddedProtocolFeePaidSazareSwap
ABI
[
{
"inputs": [
{
"internalType": "contract IPoolManager",
"name": "poolManager_",
"type": "address"
},
{
"internalType": "contract ERC20",
"name": "weth_",
"type": "address"
},
{
"internalType": "contract SazareV2Math",
"name": "curveMath_",
"type": "address"
},
{
"internalType": "uint256",
"name": "cap_",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "pInit_",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "curveScale_",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "tailInventory_",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "reserveSeed",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "dust",
"type": "uint256"
},
{
"internalType": "address",
"name": "reserveSeeder",
"type": "address"
},
{
"internalType": "address",
"name": "feeRecipient_",
"type": "address"
},
{
"internalType": "string",
"name": "tokenName_",
"type": "string"
},
{
"internalType": "string",
"name": "tokenSymbol_",
"type": "string"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "AlreadyInitialized",
"type": "error"
},
{
"inputs": [],
"name": "AmountTooLarge",
"type": "error"
},
{
"inputs": [],
"name": "CapExceeded",
"type": "error"
},
{
"inputs": [],
"name": "ExactOutput",
"type": "error"
},
{
"inputs": [],
"name": "InsufficientBandReserve",
"type": "error"
},
{
"inputs": [],
"name": "InsufficientInventory",
"type": "error"
},
{
"inputs": [],
"name": "InvalidParameter",
"type": "error"
},
{
"inputs": [],
"name": "InvalidPool",
"type": "error"
},
{
"inputs": [],
"name": "LiquidityForbidden",
"type": "error"
},
{
"inputs": [],
"name": "NotPostCap",
"type": "error"
},
{
"inputs": [],
"name": "OnlyPoolManager",
"type": "error"
},
{
"inputs": [],
"name": "SlippageExceeded",
"type": "error"
},
{
"inputs": [],
"name": "ZeroOutput",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "contributor",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "wethAmount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "scaleAdded",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "newScale",
"type": "uint256"
}
],
"name": "DepthAdded",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "contributor",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tokenAmount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "newInventory",
"type": "uint256"
}
],
"name": "InventoryDepthAdded",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "ProtocolFeePaid",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bool",
"name": "isBuy",
"type": "bool"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amountIn",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amountOut",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "spotPriceAfter",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "floorPriceAfter",
"type": "uint256"
}
],
"name": "SazareSwap",
"type": "event"
},
{
"inputs": [],
"name": "BAND_BUY_SHARE_BPS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "BPS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "CAP",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "DEAD",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "FLOOR_BUY_SHARE_BPS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "FLOOR_FEE_SHARE_BPS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "SWAP_FEE_BPS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "TICK_SPACING",
"outputs": [
{
"internalType": "int24",
"name": "",
"type": "int24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "WAD",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "",
"type": "tuple"
},
{
"components": [
{
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"internalType": "int256",
"name": "liquidityDelta",
"type": "int256"
},
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
}
],
"internalType": "struct IPoolManager.ModifyLiquidityParams",
"name": "",
"type": "tuple"
},
{
"internalType": "BalanceDelta",
"name": "",
"type": "int256"
},
{
"internalType": "BalanceDelta",
"name": "",
"type": "int256"
},
{
"internalType": "bytes",
"name": "",
"type": "bytes"
}
],
"name": "afterAddLiquidity",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
},
{
"internalType": "BalanceDelta",
"name": "",
"type": "int256"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "",
"type": "tuple"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "",
"type": "bytes"
}
],
"name": "afterDonate",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "",
"type": "tuple"
},
{
"internalType": "uint160",
"name": "",
"type": "uint160"
},
{
"internalType": "int24",
"name": "",
"type": "int24"
}
],
"name": "afterInitialize",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "",
"type": "tuple"
},
{
"components": [
{
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"internalType": "int256",
"name": "liquidityDelta",
"type": "int256"
},
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
}
],
"internalType": "struct IPoolManager.ModifyLiquidityParams",
"name": "",
"type": "tuple"
},
{
"internalType": "BalanceDelta",
"name": "",
"type": "int256"
},
{
"internalType": "BalanceDelta",
"name": "",
"type": "int256"
},
{
"internalType": "bytes",
"name": "",
"type": "bytes"
}
],
"name": "afterRemoveLiquidity",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
},
{
"internalType": "BalanceDelta",
"name": "",
"type": "int256"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "",
"type": "tuple"
},
{
"components": [
{
"internalType": "bool",
"name": "zeroForOne",
"type": "bool"
},
{
"internalType": "int256",
"name": "amountSpecified",
"type": "int256"
},
{
"internalType": "uint160",
"name": "sqrtPriceLimitX96",
"type": "uint160"
}
],
"internalType": "struct IPoolManager.SwapParams",
"name": "",
"type": "tuple"
},
{
"internalType": "BalanceDelta",
"name": "",
"type": "int256"
},
{
"internalType": "bytes",
"name": "",
"type": "bytes"
}
],
"name": "afterSwap",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
},
{
"internalType": "int128",
"name": "",
"type": "int128"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [],
"name": "bandReserve",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "bandState",
"outputs": [
{
"components": [
{
"internalType": "uint160",
"name": "sqrtFloorX96",
"type": "uint160"
},
{
"internalType": "uint160",
"name": "sqrtPriceX96",
"type": "uint160"
},
{
"internalType": "uint160",
"name": "sqrtUpperX96",
"type": "uint160"
},
{
"internalType": "uint256",
"name": "liquidity",
"type": "uint256"
}
],
"internalType": "struct Band.State",
"name": "",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "",
"type": "tuple"
},
{
"components": [
{
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"internalType": "int256",
"name": "liquidityDelta",
"type": "int256"
},
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
}
],
"internalType": "struct IPoolManager.ModifyLiquidityParams",
"name": "",
"type": "tuple"
},
{
"internalType": "bytes",
"name": "",
"type": "bytes"
}
],
"name": "beforeAddLiquidity",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "",
"type": "tuple"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "",
"type": "bytes"
}
],
"name": "beforeDonate",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "key",
"type": "tuple"
},
{
"internalType": "uint160",
"name": "",
"type": "uint160"
}
],
"name": "beforeInitialize",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "",
"type": "tuple"
},
{
"components": [
{
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"internalType": "int256",
"name": "liquidityDelta",
"type": "int256"
},
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
}
],
"internalType": "struct IPoolManager.ModifyLiquidityParams",
"name": "",
"type": "tuple"
},
{
"internalType": "bytes",
"name": "",
"type": "bytes"
}
],
"name": "beforeRemoveLiquidity",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "key",
"type": "tuple"
},
{
"components": [
{
"internalType": "bool",
"name": "zeroForOne",
"type": "bool"
},
{
"internalType": "int256",
"name": "amountSpecified",
"type": "int256"
},
{
"internalType": "uint160",
"name": "sqrtPriceLimitX96",
"type": "uint160"
}
],
"internalType": "struct IPoolManager.SwapParams",
"name": "params",
"type": "tuple"
},
{
"internalType": "bytes",
"name": "",
"type": "bytes"
}
],
"name": "beforeSwap",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
},
{
"internalType": "BeforeSwapDelta",
"name": "",
"type": "int256"
},
{
"internalType": "uint24",
"name": "",
"type": "uint24"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "burnedForever",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "curveMath",
"outputs": [
{
"internalType": "contract SazareV2Math",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "curveScale",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "deepenSzr",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "minScaleAdded",
"type": "uint256"
}
],
"name": "deepenWeth",
"outputs": [
{
"internalType": "uint256",
"name": "scaleAdded",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "feeRecipient",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "floorPrice",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "floorReserve",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "initialized",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "inventory",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "inventoryBacking",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "issuancePrice",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "issuanceScale",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "mintedEver",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "pInit",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "poolManager",
"outputs": [
{
"internalType": "contract IPoolManager",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "gross",
"type": "uint256"
}
],
"name": "quoteBuy",
"outputs": [
{
"internalType": "uint256",
"name": "tokensOut",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "spotPriceAfter",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "totalFee",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokensOut",
"type": "uint256"
}
],
"name": "quoteBuyExactTokens",
"outputs": [
{
"internalType": "uint256",
"name": "grossWethIn",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "spotPriceAfter",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "totalFee",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "redeemableSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "saleCap",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "spotPrice",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "tailInventory",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "token",
"outputs": [
{
"internalType": "contract SazareToken",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalDepthContributed",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalInventoryContributed",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalProtocolFeesPaid",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "weth",
"outputs": [
{
"internalType": "contract ERC20",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import {SazareToken} from "./SazareToken.sol";
import {FullMath} from "v4-core/libraries/FullMath.sol";
import {Hooks} from "v4-core/libraries/Hooks.sol";
import {TickMath} from "v4-core/libraries/TickMath.sol";
import {IHooks} from "v4-core/interfaces/IHooks.sol";
import {IPoolManager} from "v4-core/interfaces/IPoolManager.sol";
import {PoolKey} from "v4-core/types/PoolKey.sol";
import {Currency} from "v4-core/types/Currency.sol";
import {BeforeSwapDelta, toBeforeSwapDelta} from "v4-core/types/BeforeSwapDelta.sol";
import {BalanceDelta} from "v4-core/types/BalanceDelta.sol";
import {ERC20} from "solmate/src/tokens/ERC20.sol";
import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol";
import {Band} from "./libraries/Band.sol";
import {SazareV2Math} from "./SazareV2Math.sol";
/// @notice Sazare v2 custom-accounting market with a fixed issuance curve and
/// a permanently deepen-able full-range post-cap market.
/// @dev Fresh minting always uses issuanceScale. Permanent WETH contributions
/// increase secondary depth without cheapening issuance. At cap, one unified
/// virtual range spans from the live redemption floor to MAX_SQRT_PRICE.
contract SazareHookV2 is IHooks {
using Hooks for IHooks;
using SafeTransferLib for ERC20;
uint256 public constant BPS = 10_000;
uint256 public constant WAD = 1e18;
uint256 public constant SWAP_FEE_BPS = 100;
uint256 public constant FLOOR_FEE_SHARE_BPS = 7_000;
/// @notice Baseline share of net buy input permanently assigned to the redemption floor.
/// @dev When the live floor approaches spot, the floor receives more than this
/// minimum so that every token delivered remains fully floor backed.
uint256 public constant FLOOR_BUY_SHARE_BPS = 5_000;
uint256 public constant BAND_BUY_SHARE_BPS = BPS - FLOOR_BUY_SHARE_BPS;
int24 public constant TICK_SPACING = 60;
address public constant DEAD = 0x000000000000000000000000000000000000dEaD;
error OnlyPoolManager();
error InvalidPool();
error AlreadyInitialized();
error ExactOutput();
error LiquidityForbidden();
error InvalidParameter();
error ZeroOutput();
error CapExceeded();
error AmountTooLarge();
error InsufficientInventory();
error InsufficientBandReserve();
error SlippageExceeded();
error NotPostCap();
IPoolManager public immutable poolManager;
ERC20 public immutable weth;
SazareV2Math public immutable curveMath;
SazareToken public immutable token;
uint256 public immutable CAP;
uint256 public immutable pInit;
/// @notice Immutable exponential scale used only for newly minted supply.
uint256 public immutable issuanceScale;
/// @notice Supply sold on the issuance curve before the full-range tail is minted.
uint256 public immutable saleCap;
/// @notice Protocol-owned inventory minted at transition to seed post-cap asks.
uint256 public immutable tailInventory;
address public immutable feeRecipient;
/// @notice Permanent floor backing. Never inferred from token balance.
uint256 public floorReserve;
/// @notice WETH backing the premium trading path and contributed depth.
uint256 public bandReserve;
/// @notice Floor-directed sell fees parked with non-redeemable inventory.
/// @dev Activated into floorReserve pro rata when inventory returns to circulation.
uint256 public inventoryBacking;
/// @notice Tokens held by the hook and available for later buys.
uint256 public inventory;
/// @notice Lifetime minted amount. Burns never reopen cap room.
uint256 public mintedEver;
/// @notice Token-denominated secondary-band scale (18-decimal token units).
/// @dev Starts equal to issuanceScale and only rises through deepenWeth.
uint256 public curveScale;
/// @notice Monotone start price for the next newly minted SZR.
/// @dev Advances only when fresh supply is issued. Secondary depth and inventory
/// trading cannot lower it, so contributions cannot cheapen remaining issuance.
uint256 public issuancePrice;
/// @dev Stored curve spot. The effective spot is max(stored spot, live floor).
uint256 private _curveSpotPrice;
bool public initialized;
uint256 public totalProtocolFeesPaid;
uint256 public totalDepthContributed;
uint256 public totalInventoryContributed;
uint256 private _lastFloorBurn;
uint256 private _lastProtocolFee;
event ProtocolFeePaid(address indexed recipient, uint256 amount);
event DepthAdded(address indexed contributor, uint256 wethAmount, uint256 scaleAdded, uint256 newScale);
event InventoryDepthAdded(address indexed contributor, uint256 tokenAmount, uint256 newInventory);
event SazareSwap(
bool indexed isBuy, uint256 amountIn, uint256 amountOut, uint256 spotPriceAfter, uint256 floorPriceAfter
);
constructor(
IPoolManager poolManager_,
ERC20 weth_,
SazareV2Math curveMath_,
uint256 cap_,
uint256 pInit_,
uint256 curveScale_,
uint256 tailInventory_,
uint256 reserveSeed,
uint256 dust,
address reserveSeeder,
address feeRecipient_,
string memory tokenName_,
string memory tokenSymbol_
) {
if (
address(poolManager_) == address(0) || address(weth_) == address(0) || address(curveMath_) == address(0)
|| address(curveMath_).codehash != keccak256(type(SazareV2Math).runtimeCode) || cap_ == 0 || pInit_ == 0
|| curveScale_ == 0 || reserveSeed == 0 || dust == 0 || dust > cap_ || reserveSeeder == address(0)
|| feeRecipient_ == address(0) || pInit_ > uint256(type(int256).max)
|| curveScale_ > uint256(type(int256).max) || tailInventory_ == 0 || tailInventory_ >= cap_ - dust
) revert InvalidParameter();
if (curveScale_ >= cap_ - dust) revert InvalidParameter();
poolManager = poolManager_;
weth = weth_;
curveMath = curveMath_;
CAP = cap_;
pInit = pInit_;
issuanceScale = curveScale_;
tailInventory = tailInventory_;
saleCap = cap_ - tailInventory_;
curveScale = curveScale_;
issuancePrice = pInit_;
_curveSpotPrice = pInit_;
feeRecipient = feeRecipient_;
token = new SazareToken(address(this), tokenName_, tokenSymbol_);
floorReserve = reserveSeed;
mintedEver = dust;
token.mint(DEAD, dust);
weth_.safeTransferFrom(reserveSeeder, address(this), reserveSeed);
if (pInit_ * dust <= reserveSeed * WAD) revert InvalidParameter();
IHooks(this).validateHookPermissions(_permissions());
}
function beforeInitialize(address, PoolKey calldata key, uint160) external override onlyManager returns (bytes4) {
if (initialized) revert AlreadyInitialized();
if (
key.fee != 0 || key.tickSpacing != TICK_SPACING || address(key.hooks) != address(this)
|| !_isCanonicalPair(key.currency0, key.currency1)
) revert InvalidPool();
initialized = true;
return IHooks.beforeInitialize.selector;
}
function beforeAddLiquidity(address, PoolKey calldata, IPoolManager.ModifyLiquidityParams calldata, bytes calldata)
external
view
override
onlyManager
returns (bytes4)
{
revert LiquidityForbidden();
}
function beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata params, bytes calldata)
external
override
onlyManager
returns (bytes4, BeforeSwapDelta, uint24)
{
if (!initialized || !_isCanonicalKey(key)) revert InvalidPool();
if (params.amountSpecified >= 0) revert ExactOutput();
uint256 amount = uint256(-params.amountSpecified);
if (amount > uint256(uint128(type(int128).max))) revert AmountTooLarge();
Currency input = params.zeroForOne ? key.currency0 : key.currency1;
Currency output = params.zeroForOne ? key.currency1 : key.currency0;
bool isBuy = Currency.unwrap(input) == address(weth);
if (!isBuy && Currency.unwrap(input) != address(token)) revert InvalidPool();
uint256 out = isBuy ? _buy(amount) : _sell(amount);
if (out == 0 || out > uint256(uint128(type(int128).max))) revert ZeroOutput();
poolManager.take(input, address(this), amount);
_settle(output, out);
if (_lastProtocolFee != 0) {
uint256 protocolFee = _lastProtocolFee;
_lastProtocolFee = 0;
_payProtocolFee(protocolFee);
}
if (!isBuy && _lastFloorBurn != 0) {
uint256 burnAmount = _lastFloorBurn;
_lastFloorBurn = 0;
token.burn(address(this), burnAmount);
}
if (mintedEver < CAP) _syncSpotToFloor();
emit SazareSwap(isBuy, amount, out, spotPrice(), floorPrice());
return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(amount)), -int128(uint128(out))), 0);
}
/// @notice Permanently contribute WETH to trading depth.
/// @dev The contributor receives no shares and has no withdrawal claim. Before
/// cap this leaves spot unchanged. Post-cap the unified range is re-derived
/// from its holdings, so adding WETH raises its spot while floor is unchanged.
function deepenWeth(uint256 amount, uint256 minScaleAdded) external returns (uint256 scaleAdded) {
if (amount == 0) revert ZeroOutput();
uint256 price = spotPrice();
scaleAdded = FullMath.mulDiv(amount, WAD, price);
if (scaleAdded == 0) revert ZeroOutput();
if (scaleAdded < minScaleAdded) revert SlippageExceeded();
weth.safeTransferFrom(msg.sender, address(this), amount);
curveScale += scaleAdded;
bandReserve += amount;
totalDepthContributed += amount;
emit DepthAdded(msg.sender, amount, scaleAdded, curveScale);
}
/// @notice Permanently contribute SZR to the post-cap full-range ask side.
/// @dev The contributor receives no shares or withdrawal claim. Because the
/// tokens become non-redeemable inventory while floorReserve is unchanged,
/// the redemption floor rises immediately.
function deepenSzr(uint256 amount) external {
if (mintedEver < CAP) revert NotPostCap();
if (amount == 0 || amount >= redeemableSupply()) revert InvalidParameter();
ERC20(address(token)).safeTransferFrom(msg.sender, address(this), amount);
inventory += amount;
totalInventoryContributed += amount;
emit InventoryDepthAdded(msg.sender, amount, inventory);
}
function redeemableSupply() public view returns (uint256) {
return token.totalSupply() - inventory;
}
function burnedForever() external view returns (uint256) {
return mintedEver - token.totalSupply();
}
function floorPrice() public view returns (uint256) {
return FullMath.mulDiv(floorReserve, WAD, redeemableSupply());
}
function spotPrice() public view returns (uint256) {
if (mintedEver >= CAP) return curveMath.priceFromSqrt(_postCapBandState().sqrtPriceX96);
uint256 floor = floorPrice();
return _curveSpotPrice < floor ? floor : _curveSpotPrice;
}
/// @notice Live post-cap virtual position on [floor, MAX_SQRT_PRICE].
function bandState() external view returns (Band.State memory) {
if (mintedEver < CAP) revert NotPostCap();
return _postCapBandState();
}
function quoteBuy(uint256 gross)
external
view
returns (uint256 tokensOut, uint256 spotPriceAfter, uint256 totalFee)
{
return _previewBuy(gross);
}
/// @notice Smallest estimated gross WETH input that delivers at least `tokensOut`.
/// @dev Quote only; swaps remain exact-input. Primarily lets the final pre-cap
/// buyer size an input without donating an unbounded cap-clamp excess.
function quoteBuyExactTokens(uint256 tokensOut)
external
view
returns (uint256 grossWethIn, uint256 spotPriceAfter, uint256 totalFee)
{
if (mintedEver >= CAP) return _quotePostCapBuyExactTokens(tokensOut);
uint256 room = _mintRoom();
uint256 available = inventory + room;
if (tokensOut == 0 || tokensOut > available) revert InsufficientInventory();
uint256 price = spotPrice();
uint256 initialPrice = price;
uint256 roundingBandInput = curveScale / WAD + 1;
uint256 fromInventory = tokensOut > inventory ? inventory : tokensOut;
uint256 netNeeded;
if (fromInventory != 0) {
uint256 bandNet;
(spotPriceAfter, bandNet) = _walkUpByTokens(price, fromInventory, curveScale);
netNeeded = bandNet;
price = spotPriceAfter;
}
uint256 fromMint = tokensOut - fromInventory;
if (fromMint != 0) {
uint256 mintNet;
uint256 mintStart = _issuanceStartPrice();
(spotPriceAfter, mintNet) = _walkUpByTokens(mintStart, fromMint, issuanceScale);
netNeeded += mintNet;
roundingBandInput = issuanceScale / WAD + 1;
}
if (fromMint == 0) spotPriceAfter = price;
// `walkUpByTokens` rounds its endpoint up while `walkUpByNet` rounds
// its endpoint down. Reserve four price-resolution units so an exact
// token quote always delivers at least its requested amount even at a
// floor-raised, high-price endpoint.
(grossWethIn, totalFee) = curveMath.grossForNet(
_netForBandInput(netNeeded + 4 * roundingBandInput, initialPrice)
);
}
function _buy(uint256 gross) private returns (uint256 out) {
uint256 spotAfter;
uint256 fee;
(out, spotAfter, fee) = _previewBuy(gross);
uint256 floorFee = FullMath.mulDivRoundingUp(fee, FLOOR_FEE_SHARE_BPS, BPS);
uint256 protocolFee = fee - floorFee;
uint256 net = gross - fee;
uint256 srPre = redeemableSupply();
uint256 rectangle = FullMath.mulDivRoundingUp(out, floorReserve, srPre);
uint256 bandInput = _bandBudget(net);
uint256 floorInput = net - bandInput;
if (rectangle > floorInput) revert ZeroOutput();
uint256 inventoryPre = inventory;
uint256 fromInventory = out > inventoryPre ? inventoryPre : out;
uint256 fromMint = out - fromInventory;
uint256 backingActivated;
if (fromInventory != 0) {
backingActivated = fromInventory == inventoryPre
? inventoryBacking
: FullMath.mulDiv(inventoryBacking, fromInventory, inventoryPre);
inventoryBacking -= backingActivated;
inventory = inventoryPre - fromInventory;
}
if (fromMint != 0) {
issuancePrice = spotAfter;
mintedEver += fromMint;
token.mint(address(this), fromMint);
if (mintedEver == saleCap) {
mintedEver = CAP;
inventory += tailInventory;
token.mint(address(this), tailInventory);
}
}
floorReserve += floorInput + floorFee + backingActivated;
bandReserve += bandInput;
_curveSpotPrice = spotAfter;
_lastProtocolFee = protocolFee;
}
function _previewBuy(uint256 gross)
private
view
returns (uint256 tokensOut, uint256 spotPriceAfter, uint256 totalFee)
{
if (gross == 0) revert ZeroOutput();
totalFee = FullMath.mulDivRoundingUp(gross, SWAP_FEE_BPS, BPS);
uint256 net = gross - totalFee;
if (net == 0) revert ZeroOutput();
uint256 bandInput = _bandBudget(net);
uint256 floorInput = net - bandInput;
if (mintedEver >= CAP) return _previewPostCapBuy(bandInput, floorInput, totalFee);
uint256 room = _mintRoom();
uint256 available = inventory + room;
if (available == 0) revert CapExceeded();
uint256 price = spotPrice();
uint256 remainingBandInput = bandInput;
uint256 inventoryPre = inventory;
if (inventoryPre != 0) {
(uint256 inventoryEndSpot, uint256 inventoryNet) = _walkUpByTokens(price, inventoryPre, curveScale);
if (remainingBandInput < inventoryNet) {
(tokensOut, spotPriceAfter) = _walkUpByNet(price, remainingBandInput, curveScale);
if (tokensOut > inventoryPre) tokensOut = inventoryPre;
if (tokensOut == 0) revert ZeroOutput();
return (tokensOut, spotPriceAfter, totalFee);
}
tokensOut = inventoryPre;
price = inventoryEndSpot;
remainingBandInput -= inventoryNet;
}
if (remainingBandInput != 0) {
if (room == 0) {
// `quoteBuyExactTokens` reserves four resolution units. Accept
// that bounded exact-in surplus after a pure inventory quote.
if (remainingBandInput > 4 * (curveScale / WAD + 1)) revert InsufficientInventory();
} else if (FullMath.mulDiv(remainingBandInput, WAD, issuanceScale) == 0) {
// A rounded exact-in quote can leave sub-resolution WETH after
// inventory is exhausted. It is retained as band backing rather
// than making an otherwise valid inventory purchase revert.
} else {
uint256 mintStart = _issuanceStartPrice();
(uint256 mintedOut, uint256 mintEndSpot) = _walkUpByNet(mintStart, remainingBandInput, issuanceScale);
if (mintedOut > room) {
(uint256 exactSpot, uint256 exactNet) = _walkUpByTokens(mintStart, room, issuanceScale);
// Keep the cap clamp consistent with the exact-token quote
// buffer. Without this, an otherwise valid final-cap quote
// can revert solely because of the quote's rounding reserve.
if (remainingBandInput > exactNet + 4 * (issuanceScale / WAD + 1)) {
revert InsufficientInventory();
}
mintedOut = room;
mintEndSpot = exactSpot;
}
tokensOut += mintedOut;
price = mintEndSpot;
}
}
spotPriceAfter = price;
if (tokensOut == 0) revert ZeroOutput();
}
function _walkUpByNet(uint256 price, uint256 net, uint256 scale)
private
view
returns (uint256 tokensOut, uint256 nextPrice)
{
(tokensOut, nextPrice) = curveMath.walkUpByNet(price, net, scale);
if (tokensOut == 0 || nextPrice == price) revert ZeroOutput();
}
function _walkUpByTokens(uint256 price, uint256 tokens, uint256 scale)
private
view
returns (uint256 nextPrice, uint256 netNeeded)
{
return curveMath.walkUpByTokens(price, tokens, scale);
}
/// @dev Fresh issuance is anchored only to its immutable issuance history and
/// the live redemption boundary. A secondary-inventory endpoint is not
/// an input: otherwise a WETH depth contribution could indirectly lower
/// the price at which the mint leg begins after inventory is cleared.
function _issuanceStartPrice() private view returns (uint256 startPrice) {
startPrice = issuancePrice;
uint256 floor = floorPrice();
if (startPrice < floor) startPrice = floor;
}
/// @dev The curve only spends the portion retained by the trading band.
/// The floor bound prevents a fixed allocation from under-backing output
/// when live spot is close to the redemption boundary.
function _bandBudget(uint256 net) private view returns (uint256) {
uint256 byShare = FullMath.mulDiv(net, BAND_BUY_SHARE_BPS, BPS);
uint256 price = spotPrice();
uint256 floor = floorPrice();
uint256 byFloor = FullMath.mulDiv(net, price, price + floor);
return byShare < byFloor ? byShare : byFloor;
}
function _netForBandInput(uint256 bandInput, uint256 price) private view returns (uint256 netNeeded) {
uint256 byShare = FullMath.mulDivRoundingUp(bandInput, BPS, BAND_BUY_SHARE_BPS);
uint256 byFloor = FullMath.mulDivRoundingUp(bandInput, price + floorPrice(), price);
return byShare > byFloor ? byShare : byFloor;
}
function _previewPostCapBuy(uint256 bandInput, uint256 floorInput, uint256 totalFee)
private
view
returns (uint256 tokensOut, uint256 spotPriceAfter, uint256)
{
if (inventory <= 1) revert CapExceeded();
Band.State memory b = _postCapBandState();
if (b.liquidity == 0 || b.sqrtPriceX96 >= TickMath.MAX_SQRT_PRICE) revert ZeroOutput();
uint256 toMax = curveMath.amount1Delta(b.sqrtPriceX96, TickMath.MAX_SQRT_PRICE, b.liquidity, true);
uint256 spent = bandInput < toMax ? bandInput : toMax;
uint160 sqrtNext =
spent == toMax ? TickMath.MAX_SQRT_PRICE : curveMath.nextFromAmount1In(b.sqrtPriceX96, b.liquidity, spent);
if (sqrtNext > TickMath.MAX_SQRT_PRICE) sqrtNext = TickMath.MAX_SQRT_PRICE;
tokensOut = curveMath.amount0Delta(b.sqrtPriceX96, sqrtNext, b.liquidity, false);
uint256 maxTokens = inventory - 1;
if (tokensOut > maxTokens) tokensOut = maxTokens;
tokensOut = curveMath.capTokensByRectangle(tokensOut, floorInput, floorReserve, redeemableSupply());
if (tokensOut == 0) revert ZeroOutput();
uint256 geometricOut = curveMath.amount0Delta(b.sqrtPriceX96, sqrtNext, b.liquidity, false);
if (tokensOut < geometricOut) {
sqrtNext = curveMath.nextFromAmount0Out(b.sqrtPriceX96, b.liquidity, tokensOut);
}
spotPriceAfter = curveMath.priceFromSqrt(sqrtNext);
return (tokensOut, spotPriceAfter, totalFee);
}
function _quotePostCapBuyExactTokens(uint256 tokensOut)
private
view
returns (uint256 grossWethIn, uint256 spotPriceAfter, uint256 totalFee)
{
if (tokensOut == 0 || inventory <= 1 || tokensOut >= inventory) revert InsufficientInventory();
Band.State memory b = _postCapBandState();
if (b.liquidity == 0 || b.sqrtPriceX96 >= TickMath.MAX_SQRT_PRICE) revert ZeroOutput();
uint160 sqrtNext = curveMath.nextFromAmount0Out(b.sqrtPriceX96, b.liquidity, tokensOut);
if (sqrtNext > TickMath.MAX_SQRT_PRICE) sqrtNext = TickMath.MAX_SQRT_PRICE;
uint256 bandInput = curveMath.amount1Delta(b.sqrtPriceX96, sqrtNext, b.liquidity, true);
uint256 netNeeded = _netForBandInput(bandInput, curveMath.priceFromSqrt(b.sqrtPriceX96)) + 2;
(grossWethIn, totalFee) = curveMath.grossForNet(netNeeded);
spotPriceAfter = curveMath.priceFromSqrt(sqrtNext);
}
function _sellPostCap(uint256 amount) private returns (uint256 out) {
if (amount == 0) revert ZeroOutput();
uint256 remaining = amount;
uint256 protocolFeeAccrued;
if (bandReserve != 0) {
Band.State memory b = _postCapBandState();
if (b.liquidity != 0 && b.sqrtPriceX96 > b.sqrtFloorX96) {
uint256 capacity = curveMath.amount0Delta(b.sqrtFloorX96, b.sqrtPriceX96, b.liquidity, false);
uint256 bandTokens = remaining < capacity ? remaining : capacity;
if (bandTokens != 0) {
uint256 grossCurve;
uint160 nextSqrt;
if (bandTokens == capacity) {
grossCurve = bandReserve;
bandReserve = 0;
nextSqrt = b.sqrtFloorX96;
} else {
nextSqrt = curveMath.nextFromAmount0In(b.sqrtPriceX96, b.liquidity, bandTokens);
if (nextSqrt < b.sqrtFloorX96) nextSqrt = b.sqrtFloorX96;
grossCurve = curveMath.amount1Delta(nextSqrt, b.sqrtPriceX96, b.liquidity, false);
if (grossCurve > bandReserve) grossCurve = bandReserve;
bandReserve -= grossCurve;
}
uint256 fee = FullMath.mulDivRoundingUp(grossCurve, SWAP_FEE_BPS, BPS);
uint256 floorFee = FullMath.mulDivRoundingUp(fee, FLOOR_FEE_SHARE_BPS, BPS);
uint256 protocolFee = fee - floorFee;
floorReserve += floorFee;
inventory += bandTokens;
remaining -= bandTokens;
out = grossCurve - fee;
protocolFeeAccrued = protocolFee;
}
}
}
if (remaining != 0) {
uint256 sr = redeemableSupply();
uint256 grossFloor = FullMath.mulDiv(remaining, floorReserve, sr);
if (grossFloor == 0) revert ZeroOutput();
uint256 fee = FullMath.mulDivRoundingUp(grossFloor, SWAP_FEE_BPS, BPS);
uint256 protocolFee = fee - FullMath.mulDivRoundingUp(fee, FLOOR_FEE_SHARE_BPS, BPS);
uint256 payout = grossFloor - fee;
floorReserve -= payout + protocolFee;
protocolFeeAccrued += protocolFee;
_lastFloorBurn = remaining;
out += payout;
uint256 srAfterBurn = sr - remaining;
if (srAfterBurn == 0) revert InvalidParameter();
}
_lastProtocolFee = protocolFeeAccrued;
if (out == 0) revert ZeroOutput();
}
function _postCapBandState() private view returns (Band.State memory) {
return curveMath.bandState(bandReserve, inventory, floorReserve, redeemableSupply());
}
function _mintRoom() private view returns (uint256) {
return mintedEver < saleCap ? saleCap - mintedEver : 0;
}
function _sell(uint256 amount) private returns (uint256 out) {
if (mintedEver >= CAP) return _sellPostCap(amount);
if (amount == 0) revert ZeroOutput();
uint256 remaining = amount;
uint256 protocolFeeAccrued;
uint256 price = spotPrice();
uint256 floor = floorPrice();
if (price > floor && bandReserve != 0) {
uint256 capacity = curveMath.tokensToFloor(price, floor, curveScale);
uint256 bandTokens = remaining < capacity ? remaining : capacity;
if (bandTokens != 0) {
(uint256 nextPrice, uint256 grossCurve) = curveMath.walkDownByTokens(price, bandTokens, curveScale);
if (bandTokens == capacity || nextPrice < floor) nextPrice = floor;
if (nextPrice == floor) grossCurve = FullMath.mulDiv(curveScale, price - nextPrice, WAD);
uint256 srPre = redeemableSupply();
uint256 floorRelease = FullMath.mulDiv(floorReserve, bandTokens, srPre);
if (grossCurve < floorRelease) grossCurve = floorRelease;
uint256 premium = grossCurve - floorRelease;
if (premium > bandReserve) revert InsufficientBandReserve();
uint256 fee = FullMath.mulDivRoundingUp(grossCurve, SWAP_FEE_BPS, BPS);
uint256 floorFee = FullMath.mulDivRoundingUp(fee, FLOOR_FEE_SHARE_BPS, BPS);
uint256 protocolFee = fee - floorFee;
bandReserve -= premium;
floorReserve = floorReserve - floorRelease + 1;
inventoryBacking += floorFee - 1;
inventory += bandTokens;
remaining -= bandTokens;
out += grossCurve - fee;
protocolFeeAccrued += protocolFee;
_curveSpotPrice = nextPrice;
}
}
if (remaining != 0) {
uint256 sr = redeemableSupply();
uint256 grossFloor = FullMath.mulDiv(remaining, floorReserve, sr);
if (grossFloor == 0) revert ZeroOutput();
uint256 fee = FullMath.mulDivRoundingUp(grossFloor, SWAP_FEE_BPS, BPS);
uint256 protocolFee = fee - FullMath.mulDivRoundingUp(fee, FLOOR_FEE_SHARE_BPS, BPS);
uint256 payout = grossFloor - fee;
floorReserve -= payout + protocolFee;
protocolFeeAccrued += protocolFee;
_lastFloorBurn = remaining;
out += payout;
uint256 srAfterBurn = sr - remaining;
if (srAfterBurn == 0) revert InvalidParameter();
_curveSpotPrice = FullMath.mulDiv(floorReserve, WAD, srAfterBurn);
}
_lastProtocolFee = protocolFeeAccrued;
if (out == 0) revert ZeroOutput();
}
function _syncSpotToFloor() private {
uint256 floor = floorPrice();
if (_curveSpotPrice < floor) _curveSpotPrice = floor;
}
function _payProtocolFee(uint256 amount) private {
if (amount == 0) return;
totalProtocolFeesPaid += amount;
weth.safeTransfer(feeRecipient, amount);
emit ProtocolFeePaid(feeRecipient, amount);
}
function _settle(Currency currency, uint256 amount) private {
poolManager.sync(currency);
ERC20(Currency.unwrap(currency)).safeTransfer(address(poolManager), amount);
poolManager.settle();
}
function _isCanonicalPair(Currency currency0, Currency currency1) private view returns (bool) {
address w = address(weth);
address t = address(token);
return (w < t && Currency.unwrap(currency0) == w && Currency.unwrap(currency1) == t)
|| (t < w && Currency.unwrap(currency0) == t && Currency.unwrap(currency1) == w);
}
function _isCanonicalKey(PoolKey calldata key) private view returns (bool) {
return key.fee == 0 && key.tickSpacing == TICK_SPACING && address(key.hooks) == address(this)
&& _isCanonicalPair(key.currency0, key.currency1);
}
function _permissions() private pure returns (Hooks.Permissions memory) {
return Hooks.Permissions({
beforeInitialize: true,
afterInitialize: false,
beforeAddLiquidity: true,
afterAddLiquidity: false,
beforeRemoveLiquidity: false,
afterRemoveLiquidity: false,
beforeSwap: true,
afterSwap: false,
beforeDonate: false,
afterDonate: false,
beforeSwapReturnDelta: true,
afterSwapReturnDelta: false,
afterAddLiquidityReturnDelta: false,
afterRemoveLiquidityReturnDelta: false
});
}
modifier onlyManager() {
_onlyManager();
_;
}
function _onlyManager() private view {
if (msg.sender != address(poolManager)) revert OnlyPoolManager();
}
function afterInitialize(address, PoolKey calldata, uint160, int24) external pure override returns (bytes4) {
revert InvalidPool();
}
function afterAddLiquidity(
address,
PoolKey calldata,
IPoolManager.ModifyLiquidityParams calldata,
BalanceDelta,
BalanceDelta,
bytes calldata
) external pure override returns (bytes4, BalanceDelta) {
revert InvalidPool();
}
function beforeRemoveLiquidity(
address,
PoolKey calldata,
IPoolManager.ModifyLiquidityParams calldata,
bytes calldata
) external pure override returns (bytes4) {
revert InvalidPool();
}
function afterRemoveLiquidity(
address,
PoolKey calldata,
IPoolManager.ModifyLiquidityParams calldata,
BalanceDelta,
BalanceDelta,
bytes calldata
) external pure override returns (bytes4, BalanceDelta) {
revert InvalidPool();
}
function afterSwap(address, PoolKey calldata, IPoolManager.SwapParams calldata, BalanceDelta, bytes calldata)
external
pure
override
returns (bytes4, int128)
{
revert InvalidPool();
}
function beforeDonate(address, PoolKey calldata, uint256, uint256, bytes calldata)
external
pure
override
returns (bytes4)
{
revert InvalidPool();
}
function afterDonate(address, PoolKey calldata, uint256, uint256, bytes calldata)
external
pure
override
returns (bytes4)
{
revert InvalidPool();
}
}
Chain explorer4260msChain node86ms