General

This document is better viewed on the docs page.

Ready-to-use hooks built on top of the base and fee abstract contracts

Hooks

LiquidityPenaltyHook

import "uniswap-hooks/src/general/LiquidityPenaltyHook.sol";

This hook implements a mechanism penalize liquidity provision based on time of adding and removal of liquidty. The main purpose is to prevent JIT (Just in Time) attacks on liquidity pools. Specifically, it checks if a liquidity position was added to the pool within a certain block number range (at least 1 block) and if so, it donates some of the fees to the pool (up to 100% of the fees). This way, the hook effectively taxes JIT attackers by donating their expected profits back to the pool. The hook calculates the fee donation based on the block number when the liquidity was added and the block number offset.

At constructor, the hook requires a block number offset. This offset is the number of blocks at which the hook will donate the fees to the pool. The minimum value is 1.

The hook donates the fees to the current in range liquidity providers (at the time of liquidity removal). If the block number offset is much later than the actual block number when the liquidity was added, the liquidity providers who benefited from the fees will be the ones in range at the time of liquidity removal, not the ones in range at the time of liquidity addition.
This is experimental software and is provided on an "as is" and "as available" basis. We do not give any warranties and will not be liable for any losses incurred through any use of this code base.

Available since v0.1.1

constructor(contract IPoolManager _poolManager, uint256 _blockNumberOffset) public

Set the PoolManager address and the block number offset.

_afterAddLiquidity(address sender, struct PoolKey key, struct IPoolManager.ModifyLiquidityParams params, BalanceDelta, BalanceDelta, bytes) → bytes4, BalanceDelta internal

Hooks into the afterAddLiquidity hook to record the block number when the liquidity was added to track JIT liquidity positions.

_afterRemoveLiquidity(address sender, struct PoolKey key, struct IPoolManager.ModifyLiquidityParams params, BalanceDelta, BalanceDelta feeDelta, bytes) → bytes4, BalanceDelta internal

Hooks into the afterRemoveLiquidity hook to donate accumulated fees for a JIT liquidity position created.

_calculateLiquidityPenalty(BalanceDelta feeDelta, PoolId poolId, bytes32 positionKey) → BalanceDelta liquidityPenalty internal

Calculates the fee donation when a liquidity position is removed before the block number offset.

getHookPermissions() → struct Hooks.Permissions permissions public

MIN_BLOCK_NUMBER_OFFSET() → uint256 public

lastAddedLiquidity() → mapping(PoolId => mapping(bytes32 => uint256)) public

blockNumberOffset() → uint256 public

BlockNumberOffsetTooLow() error

Hook was attempted to be deployed with a block number offset that is too low.

AntiSandwichHook

import "uniswap-hooks/src/general/AntiSandwichHook.sol";

Sandwich-resistant hook, based on this implementation.

This hook implements the sandwich-resistant AMM design introduced here. Specifically, this hook guarantees that no swaps get filled at a price better than the price at the beginning of the slot window (i.e. one block).

Within a slot window, swaps impact the pool asymmetrically for buys and sells. When a buy order is executed, the offer on the pool increases in accordance with the xy=k curve. However, the bid price remains constant, instead increasing the amount of liquidity on the bid. Subsequent sells eat into this liquidity, while decreasing the offer price according to xy=k.

Swaps in the other direction do not get the positive price difference compared to the initial price before the first swap in the block.
This is experimental software and is provided on an "as is" and "as available" basis. We do not give any warranties and will not be liable for any losses incurred through any use of this code base.

Available since v1.1.0

constructor(contract IPoolManager _poolManager) public

_beforeSwap(address sender, struct PoolKey key, struct IPoolManager.SwapParams params, bytes hookData) → bytes4, BeforeSwapDelta, uint24 internal

Handles the before swap hook, setting up checkpoints at the beginning of blocks and calculating target outputs for subsequent swaps.

For the first swap in a block: - Saves the current pool state as a checkpoint

For subsequent swaps in the same block: - Calculates a target output based on the beginning-of-block state - Sets the inherited _targetOutput and _applyTargetOutput variables to enforce price limits

This implementation skips calling super._beforeSwap in the first swap of the block. Consider execution side effects might be missed if there is more than one definition for this function.

_afterSwap(address sender, struct PoolKey key, struct IPoolManager.SwapParams params, BalanceDelta delta, bytes hookData) → bytes4, int128 internal

Handles the after swap hook, initializing the full pool state checkpoint for the first swap in a block and updating the target output if needed.

For the first swap in a block: - Saves a detailed checkpoint of the pool state including liquidity and tick information - This checkpoint will be used for subsequent swaps to calculate fair execution prices

For all swaps: - Caps the target output to the actual swap amount to prevent excessive fee collection

_getTargetOutput(address, struct PoolKey key, struct IPoolManager.SwapParams params, bytes) → uint256 targetOutput, bool applyTargetOutput internal

Calculates the fair output amount based on the pool state at the beginning of the block. This prevents sandwich attacks by ensuring trades can’t get better prices than what was available at the start of the block.

The anti-sandwich mechanism works by: * For currency0 to currency1 swaps (zeroForOne = true): The pool behaves normally with xy=k curve * For currency1 to currency0 swaps (zeroForOne = false): The price is fixed at the beginning-of-block price, which prevents attackers from manipulating the price within a block

_afterSwapHandler(struct PoolKey key, struct IPoolManager.SwapParams params, BalanceDelta, uint256, uint256 feeAmount) internal

Handles the excess tokens collected during the swap due to the anti-sandwich mechanism. When a swap executes at a worse price than what’s currently available in the pool (due to enforcing the beginning-of-block price), the excess tokens are donated back to the pool to benefit all liquidity providers.

getHookPermissions() → struct Hooks.Permissions permissions public

Set the hook permissions, specifically beforeSwap, afterSwap, and afterSwapReturnDelta.

LimitOrderHook

import "uniswap-hooks/src/general/LimitOrderHook.sol";

This hook implements a mechanism to place limit orders on a liquidity pool. Specifically, it allows users to place limit orders at a specific tick, which will be filled if the price of the pool crosses the tick.

The hook implements the placing of orders by adding liquidity to the pool in a tick range out of range of the current price. Note that, given the way v4 pools work, if one adds liquidity out of range, the liquidity added will be in a single currency, instead of both, as in an in-range addition.

Users can cancel their limit orders at any time until it is filled and liquidity is removed from the pool. Users can also withdraw their liquidity after the limit order is filled.

This is experimental software and is provided on an "as is" and "as available" basis. We do not give any warranties and will not be liable for any losses incurred through any use of this code base.

Available since v1.1.0

Internal Variables

constructor(contract IPoolManager _poolManager) public

Set the PoolManager address.

_afterInitialize(address, struct PoolKey key, uint160, int24 tick) → bytes4 internal

Hooks into the afterInitialize hook to set the last tick lower for the pool.

_afterSwap(address, struct PoolKey key, struct IPoolManager.SwapParams params, BalanceDelta, bytes) → bytes4, int128 internal

Hooks into the afterSwap hook to get the ticks crossed by the swap and fill the orders that are crossed, filling them.

placeOrder(struct PoolKey key, int24 tick, bool zeroForOne, uint128 liquidity) external

Places a limit order by adding liquidity out of range at a specific tick. The order will be filled when the pool price crosses the specified tick. Takes a PoolKey key, target tick, direction zeroForOne indicating whether to buy currency0 or currency1, and amount of liquidity to place. The interaction with the poolManager is done via the unlock function, which will trigger the unlockCallback function.

cancelOrder(struct PoolKey key, int24 tickLower, bool zeroForOne, address to) external

Cancels a limit order by removing liquidity from the pool. Takes a PoolKey key, tickLower of the order, direction zeroForOne indicating whether it was buying currency0 or currency1, and recipient address to for the removed liquidity. Note that partial cancellation is not supported - the entire liquidity added by the msg.sender will be removed. Note also that cancelling an order will cancel the order placed by the msg.sender, not orders placed by other users in the same tick range. The interaction with the poolManager is done via the unlock function, which will trigger the unlockCallback function.

withdraw(OrderIdLibrary.OrderId orderId, address to) → uint256 amount0, uint256 amount1 external

Withdraws liquidity from a filled order, sending it to address to. Takes an OrderId orderId of the filled order to withdraw from. Returns the withdrawn amounts as (amount0, amount1). Can only be called after the order is filled - use cancelOrder to remove liquidity from unfilled orders. The interaction with the poolManager is done via the unlock function, which will trigger the unlockCallback function.

unlockCallback(bytes rawData) → bytes returnData external

Handles callbacks from the PoolManager for order operations. Takes encoded rawData containing the callback type and operation-specific data. Returns encoded data containing fees accrued for cancel operations, or empty bytes otherwise. Only callable by the PoolManager.

_handlePlaceCallback(struct LimitOrderHook.CallbackDataPlace placeData) internal

Internal handler for place order callbacks. Takes placeData containing the order details and adds the specified liquidity to the pool out of range. Reverts if the order would be placed in range or on the wrong side of the range.

_handleCancelCallback(struct LimitOrderHook.CallbackDataCancel cancelData) → uint256 amount0Fee, uint256 amount1Fee internal

Internal handler for cancel order callbacks. Takes cancelData containing the cancellation details and removes liquidity from the pool. Returns accrued fees (amount0Fee, amount1Fee) which are allocated to remaining limit order placers, or to the cancelling user if they’re removing all liquidity.

_handleWithdrawCallback(struct LimitOrderHook.CallbackDataWithdraw withdrawData) internal

Internal handler for withdraw callbacks. Takes withdrawData containing withdrawal amounts and recipient, burns the specified currency amounts from the hook, and transfers them to the recipient address.

_fillOrder(struct PoolKey key, int24 tickLower, bool zeroForOne) internal

Internal handler for filling limit orders when price crosses a tick. Takes a PoolKey key, target tickLower, and direction zeroForOne. Removes liquidity from filled orders, mints the received currencies to the hook, and updates order state to track filled amounts.

_getCrossedTicks(PoolId poolId, int24 tickSpacing) → int24 tickLower, int24 lower, int24 upper internal

Internal helper that calculates the range of ticks crossed during a price change. Takes a PoolId poolId and tickSpacing, returns the current tickLower and the range of ticks crossed (lower, upper) that need to be checked for limit orders.

getTickLowerLast(PoolId poolId) → int24 public

Returns the last recorded lower tick for a given pool. Takes a PoolId poolId and returns the stored tickLowerLast value.

getOrderId(struct PoolKey key, int24 tickLower, bool zeroForOne) → OrderIdLibrary.OrderId public

Retrieves the order id for a given pool position. Takes a PoolKey key, target tickLower, and direction zeroForOne indicating whether it’s buying currency0 or currency1. Returns the {OrderId} associated with this position, or the default order id if no order exists.

getOrderLiquidity(OrderIdLibrary.OrderId orderId, address owner) → uint256 external

Get the liquidity of an order for a given order id and owner. Takes an {OrderId} orderId and owner address and returns the amount of liquidity the owner has contributed to the order.

getHookPermissions() → struct Hooks.Permissions permissions public

Get the hook permissions for this contract. Returns a Hooks.Permissions struct configured to enable afterInitialize and afterSwap hooks while disabling all other hooks.

orderIdNext() → OrderIdLibrary.OrderId public

The next order id to be used.

tickLowerLasts() → mapping(PoolId => int24) public

The last tick lower for each pool.

orders() → mapping(bytes32 => OrderIdLibrary.OrderId) public

Tracks each order id for a given identifier, defined by keccak256 of the key, tick lower, and zero for one.

orderInfos() → mapping(OrderIdLibrary.OrderId => struct LimitOrderHook.OrderInfo) public

Tracks the order info for each order id.

Place(address indexed owner, OrderIdLibrary.OrderId indexed orderId, struct PoolKey key, int24 tickLower, bool zeroForOne, uint128 liquidity) event

Event emitted when a limit order is placed.

Fill(OrderIdLibrary.OrderId indexed orderId, struct PoolKey key, int24 tickLower, bool zeroForOne) event

Event emitted when a limit order is filled.

Cancel(address indexed owner, OrderIdLibrary.OrderId indexed orderId, struct PoolKey key, int24 tickLower, bool zeroForOne, uint128 liquidity) event

Event emitted when a limit order is canceled.

Withdraw(address indexed owner, OrderIdLibrary.OrderId indexed orderId, uint128 liquidity) event

Event emitted when a limit order is withdrawn.

ZeroLiquidity() error

Zero liquidity was attempted to be added or removed.

InRange() error

Limit order was placed in range.

CrossedRange() error

Limit order placed on the wrong side of the range.

AlreadyInitialized() error

Hook was already initialized.

Filled() error

Limit order was already filled.

NotFilled() error

Limit order is not filled.

bytes ZERO_BYTES internal constant

The zero bytes.