HoodboundHook

0xf9b4b0f5d116ea0d6de6877d9e2d1f93837020cc

Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
7,957 bytes
ABI entries
50
14 read · 19 write
License
none

Contract information

Address
0xf9b4b0f5d116ea0d6de6877d9e2d1f93837020cc
Chain
Robinhood Chain (4663)
Compiler
v0.8.26+commit.8a97fa7a
Optimization
Enabled
Creation tx
0x8742bf89f2…39e2db32b1

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (14)

BPS_DENOMINATOR()uint256
MAX_TITHE_BPS()uint256
buyTitheBps()uint16
gatesOpen()bool
getHookPermissions()tuple
maxBuyAmount()uint256
onERC721Received(address, address, uint256, bytes)bytes4
owner()address
poolManager()address
positionManager()address
sellTitheBps()uint16
token()address
treasury()address
uncollectedTithes()uint256

Events (6)

DrawbridgeRaisedGatesThrownOpenOwnershipTransferredTitheLoweredTithesCollectedTreasurerAppointed

ABI

[
  {
    "inputs": [
      {
        "internalType": "contract IPoolManager",
        "name": "poolManager_",
        "type": "address"
      },
      {
        "internalType": "contract IPositionManager",
        "name": "positionManager_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "token_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "treasury_",
        "type": "address"
      },
      {
        "internalType": "uint16",
        "name": "buyTitheBps_",
        "type": "uint16"
      },
      {
        "internalType": "uint16",
        "name": "sellTitheBps_",
        "type": "uint16"
      },
      {
        "internalType": "uint256",
        "name": "maxBuyAmount_",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "owner_",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "HookNotImplemented",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "maxBuy",
        "type": "uint256"
      }
    ],
    "name": "MaxBuyExceeded",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotPoolManager",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotTheLord",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "OnlyEthTokenPool",
    "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": "TitheCanOnlyDecrease",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TitheTooHigh",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroAddress",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint128",
        "name": "liquidityRemoved",
        "type": "uint128"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      }
    ],
    "name": "DrawbridgeRaised",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [],
    "name": "GatesThrownOpen",
    "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": "uint16",
        "name": "buyTitheBps",
        "type": "uint16"
      },
      {
        "indexed": false,
        "internalType": "uint16",
        "name": "sellTitheBps",
        "type": "uint16"
      }
    ],
    "name": "TitheLowered",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "treasury",
        "type": "address"
      }
    ],
    "name": "TithesCollected",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "treasury",
        "type": "address"
      }
    ],
    "name": "TreasurerAppointed",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "BPS_DENOMINATOR",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MAX_TITHE_BPS",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "abolishTithes",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "sender",
        "type": "address"
      },
      {
        "components": [
          {
            "internalType": "Currency",
            "name": "currency0",
            "type": "address"
          },
          {
            "internalType": "Currency",
            "name": "currency1",
            "type": "address"
          },
          {
            "internalType": "uint24",
            "name": "fee",
            "type": "uint24"
          },
          {
            "internalType": "int24",
            "name": "tickSpacing",
            "type": "int24"
          },
          {
            "internalType": "contract IHooks",
            "name": "hooks",
            "type": "address"
          }
        ],
        "internalType": "struct PoolKey",
        "name": "key",
        "type": "tuple"
      },
      {
        "components": [
          {
            "internalType": "int24",
            "name": "tickLower",
            "type": "int24"
          },
          {
            "internalType": "int24",
            "name": "tickUpper",
            "type": "int24"
          },
          {
            "internalType": "int256",
            "name": "liquidityDelta",
            "type": "int256"
          },
          {
            "internalType": "bytes32",
            "name": "salt",
            "type": "bytes32"
          }
        ],
        "internalType": "struct ModifyLiquidityParams",
        "name": "params",
        "type": "tuple"
      },
      {
        "internalType": "BalanceDelta",
        "name": "delta0",
        "type": "int256"
      },
      {
        "internalType": "BalanceDelta",
        "name": "delta1",
        "type": "int256"
      },
      {
        "internalType": "bytes",
        "name": "hookData",
        "type": "bytes"
      }
    ],
    "name": "afterAddLiquidity",
    "outputs": [
      {
        "internalType": "bytes4",
        "name": "",
        "type": "bytes4"
      },
      {
        "internalType": "BalanceDelta",
        "name": "",
        "type": "int256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "sender",
        "type": "address"
      },
      {
        "components": [
          {
            "internalType": "Currency",
            "name": "currency0",
            "type": "address"
          },
          {
            "internalType": "Currency",
            "name": "currency1",
            "type": "address"
          },
          {
            "internalType": "uint24",
            "name": "fee",
            "type": "uint24"
          },
          {
            "internalType": "int24",
            "name": "tickSpacing",
            "type": "int24"
          },
          {
            "internalType": "contract IHooks",
            "name": "hooks",
            "type": "address"
          }
        ],
        "internalType": "struct PoolKey",
        "name": "key",
        "type": "tuple"
      },
      {
        "internalType": "uint256",
        "name": "amount0",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "amount1",
        "type": "uint256"
      },
      {
        "internalType": "bytes",
        "name": "hookData",
        "type": "bytes"
      }
    ],
    "name": "afterDonate",
    "outputs": [
      {
        "internalType": "bytes4",
        "name": "",
        "type": "bytes4"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "sender",
        "type": "address"
      },
      {
        "components": [
          {
            "internalType": "Currency",
            "name": "currency0",
            "type": "address"
          },
          {
            "internalType": "Currency",
            "name": "currency1",
            "type": "address"
          },
          {
            "internalType": "uint24",
            "name": "fee",
            "type": "uint24"
          },
          {
            "internalType": "int24",
            "name": "tickSpacing",
            "type": "int24"
          },
          {
            "internalType": "contract IHooks",
            "name": "hooks",
            "type": "address"
          }
        ],
        "internalType": "struct PoolKey",
        "name": "key",
        "type": "tuple"
      },
      {
        "internalType": "uint160",
        "name": "sqrtPriceX96",
        "type": "uint160"
      },
      {
        "internalType": "int24",
        "name": "tick",
        "type": "int24"
      }
    ],
    "name": "afterInitialize",
    "outputs": [
      {
        "internalType": "bytes4",
        "name": "",
        "type": "bytes4"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "sender",
        "type": "address"
      },
      {
        "components": [
          {
            "internalType": "Currency",
            "name": "currency0",
            "type": "address"
          },
          {
            "internalType": "Currency",
            "name": "currency1",
            "type": "address"
          },
          {
            "internalType": "uint24",
            "name": "fee",
            "type": "uint24"
          },
          {
            "internalType": "int24",
            "name": "tickSpacing",
            "type": "int24"
          },
          {
            "internalType": "contract IHooks",
            "name": "hooks",
            "type": "address"
          }
        ],
        "internalType": "struct PoolKey",
        "name": "key",
        "type": "tuple"
      },
      {
        "components": [
          {
            "internalType": "int24",
            "name": "tickLower",
            "type": "int24"
          },
          {
            "internalType": "int24",
            "name": "tickUpper",
            "type": "int24"
          },
          {
            "internalType": "int256",
            "name": "liquidityDelta",
            "type": "int256"
          },
          {
            "internalType": "bytes32",
            "name": "salt",
            "type": "bytes32"
          }
        ],
        "internalType": "struct ModifyLiquidityParams",
        "name": "params",
        "type": "tuple"
      },
      {
        "internalType": "BalanceDelta",
        "name": "delta0",
        "type": "int256"
      },
      {
        "internalType": "BalanceDelta",
        "name": "delta1",
        "type": "int256"
      },
      {
        "internalType": "bytes",
        "name": "hookData",
        "type": "bytes"
      }
    ],
    "name": "afterRemoveLiquidity",
    "outputs": [
      {
        "internalType": "bytes4",
        "name": "",
        "type": "bytes4"
      },
      {
        "internalType": "BalanceDelta",
        "name": "",
        "type": "int256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "sender",
        "type": "address"
      },
      {
        "components": [
          {
            "internalType": "Currency",
            "name": "currency0",
            "type": "address"
          },
          {
            "internalType": "Currency",
            "name": "currency1",
            "type": "address"
          },
          {
            "internalType": "uint24",
            "name": "fee",
            "type": "uint24"
          },
          {
            "internalType": "int24",
            "name": "tickSpacing",
            "type": "int24"
          },
          {
            "internalType": "contract IHooks",
            "name": "hooks",
            "type": "address"
          }
        ],
        "internalType": "struct PoolKey",
        "name": "key",
        "type": "tuple"
      },
      {
        "components": [
          {
            "internalType": "bool",
            "name": "zeroForOne",
            "type": "bool"
          },
          {
            "internalType": "int256",
            "name": "amountSpecified",
            "type": "int256"
          },
          {
            "internalType": "uint160",
            "name": "sqrtPriceLimitX96",
            "type": "uint160"
          }
        ],
        "internalType": "struct SwapParams",
        "name": "params",
        "type": "tuple"
      },
      {
        "internalType": "BalanceDelta",
        "name": "delta",
        "type": "int256"
      },
      {
        "internalType": "bytes",
        "name": "hookData",
        "type": "bytes"
      }
    ],
    "name": "afterSwap",
    "outputs": [
      {
        "internalType": "bytes4",
        "name": "",
        "type": "bytes4"
      },
      {
        "internalType": "int128",
        "name": "",
        "type": "int128"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newTreasury",
        "type": "address"
      }
    ],
    "name": "appointTreasurer",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "sender",
        "type": "address"
      },
      {
        "components": [
          {
            "internalType": "Currency",
            "name": "currency0",
            "type": "address"
          },
          {
            "internalType": "Currency",
            "name": "currency1",
            "type": "address"
          },
          {
            "internalType": "uint24",
            "name": "fee",
            "type": "uint24"
          },
          {
            "internalType": "int24",
            "name": "tickSpacing",
            "type": "int24"
          },
          {
            "internalType": "contract IHooks",
            "name": "hooks",
            "type": "address"
          }
        ],
        "internalType": "struct PoolKey",
        "name": "key",
        "type": "tuple"
      },
      {
        "components": [
          {
            "internalType": "int24",
            "name": "tickLower",
            "type": "int24"
          },
          {
            "internalType": "int24",
            "name": "tickUpper",
            "type": "int24"
          },
          {
            "internalType": "int256",
            "name": "liquidityDelta",
            "type": "int256"
          },
          {
            "internalType": "bytes32",
            "name": "salt",
            "type": "bytes32"
          }
        ],
        "internalType": "struct ModifyLiquidityParams",
        "name": "params",
        "type": "tuple"
      },
      {
        "internalType": "bytes",
        "name": "hookData",
        "type": "bytes"
      }
    ],
    "name": "beforeAddLiquidity",
    "outputs": [
      {
        "internalType": "bytes4",
        "name": "",
        "type": "bytes4"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "sender",
        "type": "address"
      },
      {
        "components": [
          {
            "internalType": "Currency",
            "name": "currency0",
            "type": "address"
          },
          {
            "internalType": "Currency",
            "name": "currency1",
            "type": "address"
          },
          {
            "internalType": "uint24",
            "name": "fee",
            "type": "uint24"
          },
          {
            "internalType": "int24",
            "name": "tickSpacing",
            "type": "int24"
          },
          {
            "internalType": "contract IHooks",
            "name": "hooks",
            "type": "address"
          }
        ],
        "internalType": "struct PoolKey",
        "name": "key",
        "type": "tuple"
      },
      {
        "internalType": "uint256",
        "name": "amount0",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "amount1",
        "type": "uint256"
      },
      {
        "internalType": "bytes",
        "name": "hookData",
        "type": "bytes"
      }
    ],
    "name": "beforeDonate",
    "outputs": [
      {
        "internalType": "bytes4",
        "name": "",
        "type": "bytes4"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "sender",
        "type": "address"
      },
      {
        "components": [
          {
            "internalType": "Currency",
            "name": "currency0",
            "type": "address"
          },
          {
            "internalType": "Currency",
            "name": "currency1",
            "type": "address"
          },
          {
            "internalType": "uint24",
            "name": "fee",
            "type": "uint24"
          },
          {
            "internalType": "int24",
            "name": "tickSpacing",
            "type": "int24"
          },
          {
            "internalType": "contract IHooks",
            "name": "hooks",
            "type": "address"
          }
        ],
        "internalType": "struct PoolKey",
        "name": "key",
        "type": "tuple"
      },
      {
        "internalType": "uint160",
        "name": "sqrtPriceX96",
        "type": "uint160"
      }
    ],
    "name": "beforeInitialize",
    "outputs": [
      {
        "internalType": "bytes4",
        "name": "",
        "type": "bytes4"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "sender",
        "type": "address"
      },
      {
        "components": [
          {
            "internalType": "Currency",
            "name": "currency0",
            "type": "address"
          },
          {
            "internalType": "Currency",
            "name": "currency1",
            "type": "address"
          },
          {
            "internalType": "uint24",
            "name": "fee",
            "type": "uint24"
          },
          {
            "internalType": "int24",
            "name": "tickSpacing",
            "type": "int24"
          },
          {
            "internalType": "contract IHooks",
            "name": "hooks",
            "type": "address"
          }
        ],
        "internalType": "struct PoolKey",
        "name": "key",
        "type": "tuple"
      },
      {
        "components": [
          {
            "internalType": "int24",
            "name": "tickLower",
            "type": "int24"
          },
          {
            "internalType": "int24",
            "name": "tickUpper",
            "type": "int24"
          },
          {
            "internalType": "int256",
            "name": "liquidityDelta",
            "type": "int256"
          },
          {
            "internalType": "bytes32",
            "name": "salt",
            "type": "bytes32"
          }
        ],
        "internalType": "struct ModifyLiquidityParams",
        "name": "params",
        "type": "tuple"
      },
      {
        "internalType": "bytes",
        "name": "hookData",
        "type": "bytes"
      }
    ],
    "name": "beforeRemoveLiquidity",
    "outputs": [
      {
        "internalType": "bytes4",
        "name": "",
        "type": "bytes4"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "sender",
        "type": "address"
      },
      {
        "components": [
          {
            "internalType": "Currency",
            "name": "currency0",
            "type": "address"
          },
          {
            "internalType": "Currency",
            "name": "currency1",
            "type": "address"
          },
          {
            "internalType": "uint24",
            "name": "fee",
            "type": "uint24"
          },
          {
            "internalType": "int24",
            "name": "tickSpacing",
            "type": "int24"
          },
          {
            "internalType": "contract IHooks",
            "name": "hooks",
            "type": "address"
          }
        ],
        "internalType": "struct PoolKey",
        "name": "key",
        "type": "tuple"
      },
      {
        "components": [
          {
            "internalType": "bool",
            "name": "zeroForOne",
            "type": "bool"
          },
          {
            "internalType": "int256",
            "name": "amountSpecified",
            "type": "int256"
          },
          {
            "internalType": "uint160",
            "name": "sqrtPriceLimitX96",
            "type": "uint160"
          }
        ],
        "internalType": "struct SwapParams",
        "name": "params",
        "type": "tuple"
      },
      {
        "internalType": "bytes",
        "name": "hookData",
        "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": "buyTitheBps",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "collectTheTithes",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "gatesOpen",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "getHookPermissions",
    "outputs": [
      {
        "components": [
          {
            "internalType": "bool",
            "name": "beforeInitialize",
            "type": "bool"
          },
          {
            "internalType": "bool",
            "name": "afterInitialize",
            "type": "bool"
          },
          {
            "internalType": "bool",
            "name": "beforeAddLiquidity",
            "type": "bool"
          },
          {
            "internalType": "bool",
            "name": "afterAddLiquidity",
            "type": "bool"
          },
          {
            "internalType": "bool",
            "name": "beforeRemoveLiquidity",
            "type": "bool"
          },
          {
            "internalType": "bool",
            "name": "afterRemoveLiquidity",
            "type": "bool"
          },
          {
            "internalType": "bool",
            "name": "beforeSwap",
            "type": "bool"
          },
          {
            "internalType": "bool",
            "name": "afterSwap",
            "type": "bool"
          },
          {
            "internalType": "bool",
            "name": "beforeDonate",
            "type": "bool"
          },
          {
            "internalType": "bool",
            "name": "afterDonate",
            "type": "bool"
          },
          {
            "internalType": "bool",
            "name": "beforeSwapReturnDelta",
            "type": "bool"
          },
          {
            "internalType": "bool",
            "name": "afterSwapReturnDelta",
            "type": "bool"
          },
          {
            "internalType": "bool",
            "name": "afterAddLiquidityReturnDelta",
            "type": "bool"
          },
          {
            "internalType": "bool",
            "name": "afterRemoveLiquidityReturnDelta",
            "type": "bool"
          }
        ],
        "internalType": "struct Hooks.Permissions",
        "name": "",
        "type": "tuple"
      }
    ],
    "stateMutability": "pure",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint16",
        "name": "newBuyTitheBps",
        "type": "uint16"
      },
      {
        "internalType": "uint16",
        "name": "newSellTitheBps",
        "type": "uint16"
      }
    ],
    "name": "lowerTheTithe",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "maxBuyAmount",
    "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": "poolManager",
    "outputs": [
      {
        "internalType": "contract IPoolManager",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "positionManager",
    "outputs": [
      {
        "internalType": "contract IPositionManager",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      },
      {
        "internalType": "uint128",
        "name": "liquidityToRemove",
        "type": "uint128"
      },
      {
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      }
    ],
    "name": "raiseTheDrawbridge",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "renounceOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "sellTitheBps",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "throwOpenTheGates",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "token",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "transferOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "treasury",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "uncollectedTithes",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes",
        "name": "",
        "type": "bytes"
      }
    ],
    "name": "unlockCallback",
    "outputs": [
      {
        "internalType": "bytes",
        "name": "",
        "type": "bytes"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  }
]

Source code

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

import {BaseHook} from "uniswap-hooks/base/BaseHook.sol";
import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
import {BeforeSwapDelta, BeforeSwapDeltaLibrary, toBeforeSwapDelta} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol";
import {SwapParams} from "@uniswap/v4-core/src/types/PoolOperation.sol";
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol";
import {IPositionManager} from "@uniswap/v4-periphery/src/interfaces/IPositionManager.sol";
import {Actions} from "@uniswap/v4-periphery/src/libraries/Actions.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

/*//////////////////////////////////////////////////////////////////////////
    ██╗  ██╗ ██████╗  ██████╗ ██████╗ ██████╗  ██████╗ ██╗   ██╗███╗   ██╗██████╗
    ██║  ██║██╔═══██╗██╔═══██╗██╔══██╗██╔══██╗██╔═══██╗██║   ██║████╗  ██║██╔══██╗
    ███████║██║   ██║██║   ██║██║  ██║██████╔╝██║   ██║██║   ██║██╔██╗ ██║██║  ██║
    ██╔══██║██║   ██║██║   ██║██║  ██║██╔══██╗██║   ██║██║   ██║██║╚██╗██║██║  ██║
    ██║  ██║╚██████╔╝╚██████╔╝██████╔╝██████╔╝╚██████╔╝╚██████╔╝██║ ╚████║██████╔╝
    ╚═╝  ╚═╝ ╚═════╝  ╚═════╝ ╚═════╝ ╚═════╝  ╚═════╝  ╚═════╝ ╚═╝  ╚═══╝╚═════╝

    A P2E Roguelike on Robinhood Chain

    Website:  https://hoodbound.io/
    X:        https://x.com/PlayHoodbound
//////////////////////////////////////////////////////////////////////////*/

/// @title Hoodbound Castle Gate — Uniswap v4 launch hook
/// @custom:website https://hoodbound.io/
/// @custom:x https://x.com/PlayHoodbound
/// @notice Guards the ETH/HB pool with Hoodbound's launch mechanics:
///         - 10% buy tithe / 40% sell tithe at launch; the lord may only LOWER or
///           abolish them, never raise them
///         - 1% of supply max buy per swap, until the gates are thrown open
///         - tithes are charged at swap time, ALWAYS in native ETH (buys are skimmed
///           on their ETH input, sells on their ETH output); they accrue as ERC-6909
///           claims and anyone may deliver them to the treasury via collectTheTithes()
///         - the castle (this hook) holds the LP position; `raiseTheDrawbridge`
///           lets the lord withdraw liquidity in an emergency
/// @dev Uniswap v4 does not support fee-on-transfer tokens, so the tithe lives here
///      (via afterSwap + AFTER_SWAP_RETURNS_DELTA) instead of in the token contract.
///      The pool must be native-ETH / token, which makes currency0 = ETH, currency1 = token,
///      so zeroForOne == buy.
contract HoodboundHook is BaseHook, Ownable {
    using SafeCast for uint256;

    uint256 public constant BPS_DENOMINATOR = 10_000;
    /// @dev Hard ceiling on either tithe, protects against fat-fingered constructor args.
    uint256 public constant MAX_TITHE_BPS = 5_000;

    /// @notice The Uniswap v4 PositionManager, custodian of the LP position NFT.
    IPositionManager public immutable positionManager;
    /// @notice The token this hook serves; the only pool it can be attached to is ETH/token.
    address public immutable token;
    /// @notice Max tokens a single buy may receive while the gates are closed (1% of supply).
    uint256 public immutable maxBuyAmount;

    uint16 public buyTitheBps;
    uint16 public sellTitheBps;
    bool public gatesOpen;
    address public treasury;

    event TitheLowered(uint16 buyTitheBps, uint16 sellTitheBps);
    event TithesCollected(uint256 amount, address treasury);
    event GatesThrownOpen();
    event TreasurerAppointed(address treasury);
    event DrawbridgeRaised(uint256 tokenId, uint128 liquidityRemoved, address recipient);

    error OnlyEthTokenPool();
    error NotTheLord();
    error MaxBuyExceeded(uint256 amount, uint256 maxBuy);
    error TitheCanOnlyDecrease();
    error TitheTooHigh();
    error ZeroAddress();

    constructor(
        IPoolManager poolManager_,
        IPositionManager positionManager_,
        address token_,
        address treasury_,
        uint16 buyTitheBps_,
        uint16 sellTitheBps_,
        uint256 maxBuyAmount_,
        address owner_
    ) BaseHook(poolManager_) Ownable(owner_) {
        if (address(positionManager_) == address(0) || token_ == address(0) || treasury_ == address(0)) {
            revert ZeroAddress();
        }
        if (buyTitheBps_ > MAX_TITHE_BPS || sellTitheBps_ > MAX_TITHE_BPS) revert TitheTooHigh();
        positionManager = positionManager_;
        token = token_;
        treasury = treasury_;
        buyTitheBps = buyTitheBps_;
        sellTitheBps = sellTitheBps_;
        maxBuyAmount = maxBuyAmount_;
    }

    function getHookPermissions() public pure override returns (Hooks.Permissions memory) {
        return Hooks.Permissions({
            beforeInitialize: true,
            afterInitialize: false,
            beforeAddLiquidity: false,
            afterAddLiquidity: false,
            beforeRemoveLiquidity: false,
            afterRemoveLiquidity: false,
            beforeSwap: true,
            afterSwap: true,
            beforeDonate: false,
            afterDonate: false,
            beforeSwapReturnDelta: true,
            afterSwapReturnDelta: true,
            afterAddLiquidityReturnDelta: false,
            afterRemoveLiquidityReturnDelta: false
        });
    }

    /// @dev Only allow this hook to be attached to the native-ETH / token pool, so the
    ///      buy/sell direction assumptions below always hold. Additionally, only the
    ///      owner may create the pool: the contracts are deployed before launch, and
    ///      without this check anyone could pre-initialize the pool at a hostile price.
    function _beforeInitialize(address, PoolKey calldata key, uint160) internal view override returns (bytes4) {
        if (!key.currency0.isAddressZero() || Currency.unwrap(key.currency1) != token) revert OnlyEthTokenPool();
        if (tx.origin != owner()) revert NotTheLord();
        return this.beforeInitialize.selector;
    }

    /// @dev The tithe is ALWAYS charged in native ETH (currency0). When ETH is the
    ///      swap's "specified" currency (exact-input buys, exact-output sells) it must
    ///      be skimmed here in beforeSwap; the remaining cases are handled in afterSwap.
    function _beforeSwap(address, PoolKey calldata, SwapParams calldata params, bytes calldata)
        internal
        override
        returns (bytes4, BeforeSwapDelta, uint24)
    {
        bool isBuy = params.zeroForOne;
        uint256 titheBps = isBuy ? buyTitheBps : sellTitheBps;
        // ETH (currency0) is specified on exact-input buys and exact-output sells
        bool ethIsSpecified = isBuy == (params.amountSpecified < 0);
        if (titheBps == 0 || !ethIsSpecified) {
            return (this.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0);
        }

        uint256 absSpecified =
            params.amountSpecified < 0 ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);
        uint256 feeAmount = absSpecified * titheBps / BPS_DENOMINATOR;
        if (feeAmount == 0) return (this.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0);

        // buys: skim the ETH input before it hits the pool
        // exact-output sells: gross the swap up so the user still receives the full
        // requested ETH, with the tithe taken on top
        _accrueTithe(feeAmount);
        return (this.beforeSwap.selector, toBeforeSwapDelta(feeAmount.toInt128(), 0), 0);
    }

    function _afterSwap(address, PoolKey calldata, SwapParams calldata params, BalanceDelta delta, bytes calldata)
        internal
        override
        returns (bytes4, int128)
    {
        // currency0 = native ETH, currency1 = token (enforced in _beforeInitialize)
        bool isBuy = params.zeroForOne;

        if (isBuy && !gatesOpen) {
            // delta.amount1() is positive on a buy: tokens credited to the swapper
            uint256 tokensOut = uint256(uint128(delta.amount1()));
            if (tokensOut > maxBuyAmount) revert MaxBuyExceeded(tokensOut, maxBuyAmount);
        }

        uint256 titheBps = isBuy ? buyTitheBps : sellTitheBps;
        if (titheBps == 0) return (this.afterSwap.selector, 0);

        // ETH is the "unspecified" currency on exact-output buys and exact-input sells;
        // the other two cases were already tithed in _beforeSwap.
        bool ethIsUnspecified = (params.amountSpecified < 0) != isBuy;
        if (!ethIsUnspecified) return (this.afterSwap.selector, 0);

        int128 ethAmount = delta.amount0();
        uint256 absEth = ethAmount < 0 ? uint256(uint128(-ethAmount)) : uint256(uint128(ethAmount));
        uint256 feeAmount = absEth * titheBps / BPS_DENOMINATOR;
        if (feeAmount == 0) return (this.afterSwap.selector, 0);

        _accrueTithe(feeAmount);
        return (this.afterSwap.selector, feeAmount.toInt128());
    }

    /// @dev The swapper's ETH only physically reaches the PoolManager when the router
    ///      settles at the end of the transaction, so the tithe cannot be transferred
    ///      out mid-swap. Instead it accrues as an ERC-6909 claim (pure accounting,
    ///      backed by the swap's settlement) and is redeemed via collectTheTithes().
    function _accrueTithe(uint256 feeAmount) internal {
        poolManager.mint(address(this), 0, feeAmount); // id 0 = native ETH
    }

    /// @notice Redeem all accrued tithes as native ETH and deliver them to the treasury.
    /// @dev Permissionless on purpose: the ETH can only ever go to the treasury.
    function collectTheTithes() external returns (uint256 amount) {
        amount = abi.decode(poolManager.unlock(""), (uint256));
    }

    /// @notice ETH tithes accrued and not yet collected.
    function uncollectedTithes() external view returns (uint256) {
        return poolManager.balanceOf(address(this), 0);
    }

    function unlockCallback(bytes calldata) external onlyPoolManager returns (bytes memory) {
        uint256 amount = poolManager.balanceOf(address(this), 0);
        if (amount > 0) {
            poolManager.burn(address(this), 0, amount);
            poolManager.take(Currency.wrap(address(0)), treasury, amount);
            emit TithesCollected(amount, treasury);
        }
        return abi.encode(amount);
    }

    // ------------------------------------------------------------------
    // The lord's decrees (owner controls)
    // ------------------------------------------------------------------

    /// @notice Lower the tithes. Tithes can never be raised again once lowered.
    function lowerTheTithe(uint16 newBuyTitheBps, uint16 newSellTitheBps) public onlyOwner {
        if (newBuyTitheBps > buyTitheBps || newSellTitheBps > sellTitheBps) revert TitheCanOnlyDecrease();
        buyTitheBps = newBuyTitheBps;
        sellTitheBps = newSellTitheBps;
        emit TitheLowered(newBuyTitheBps, newSellTitheBps);
    }

    /// @notice Abolish both tithes permanently (they can never be re-imposed).
    function abolishTithes() external onlyOwner {
        lowerTheTithe(0, 0);
    }

    /// @notice Throw open the gates: remove the max-buy limit permanently.
    function throwOpenTheGates() external onlyOwner {
        gatesOpen = true;
        emit GatesThrownOpen();
    }

    /// @notice Appoint a new treasurer to collect the tithes. Must accept native ETH.
    function appointTreasurer(address newTreasury) external onlyOwner {
        if (newTreasury == address(0)) revert ZeroAddress();
        treasury = newTreasury;
        emit TreasurerAppointed(newTreasury);
    }

    /// @notice Emergency withdrawal of pool liquidity held by this hook.
    /// @dev Decreases the LP position (which this contract owns as an NFT) via the
    ///      PositionManager and sends the resulting ETH + tokens to `recipient`.
    /// @param tokenId The LP position NFT id (logged at deployment).
    /// @param liquidityToRemove Liquidity units to withdraw; pass 0 to withdraw everything.
    /// @param recipient Where the withdrawn ETH and tokens go. Must accept native ETH.
    function raiseTheDrawbridge(uint256 tokenId, uint128 liquidityToRemove, address recipient) external onlyOwner {
        if (recipient == address(0)) revert ZeroAddress();

        uint128 positionLiquidity = positionManager.getPositionLiquidity(tokenId);
        uint128 toRemove = (liquidityToRemove == 0 || liquidityToRemove > positionLiquidity)
            ? positionLiquidity
            : liquidityToRemove;

        bytes memory actions = abi.encodePacked(uint8(Actions.DECREASE_LIQUIDITY), uint8(Actions.TAKE_PAIR));
        bytes[] memory params = new bytes[](2);
        params[0] = abi.encode(tokenId, uint256(toRemove), uint128(0), uint128(0), bytes(""));
        params[1] = abi.encode(Currency.wrap(address(0)), Currency.wrap(token), recipient);
        positionManager.modifyLiquidities(abi.encode(actions, params), block.timestamp);

        emit DrawbridgeRaised(tokenId, toRemove, recipient);
    }

    /// @dev Lets the PositionManager mint the LP position NFT directly to this contract.
    function onERC721Received(address, address, uint256, bytes calldata) external pure returns (bytes4) {
        return this.onERC721Received.selector;
    }
}
Chain explorer3054msChain node82ms