Cover photo

Building Uniswap V4 Hooks

Understanding Uniswap V4 and Hooks


Uniswap V4 introduces a revolutionary concept in the world of decentralized exchanges: hooks. These customizable smart contracts allow developers to extend and modify the behavior of Uniswap pools in ways never before possible. But before we dive into hooks, let's understand some key components of Uniswap V4's architecture.

Pool Keys and Infinite Pools

In Uniswap V4, each pool is identified by a unique Pool Key. This key contains more than just the addresses of the two tokens in the pair. It includes:

  • The addresses of both tokens (referred to as Currency in V4)

  • The fee tier

  • The tick spacing

  • The address of the hooks contract

This structure allows for an infinite number of pools per token pair, each with its own specific configuration. As a developer, you'll need to carefully consider which pool to use for your swaps.

The Currency Type

Uniswap V4 introduces a new custom Solidity type called Currency. While it's essentially an address type, it can represent both ERC20 tokens and native ETH. When dealing with native ETH, you'll use the zero address.

Swap Parameters

When initiating a swap, you'll need to provide SwapParams, which include:

  • A boolean indicating the swap direction

  • The amount to swap

  • The price limit for the swap, represented as a square root with high precision

The Lock Mechanism

One of the most innovative aspects of Uniswap V4 is its lock mechanism. When you want to interact with a pool, you call the lock function on the PoolManager. This initiates a process where:

  • Uniswap allows your contract to temporarily have negative balances.

  • Your contract's lockAcquired function is called, where you can execute trades and other interactions.

  • After your code finishes, Uniswap checks that all balances are settled.

This approach minimizes token transfers, as they only need to happen once at the end of the process.

Settling Balances

After the swap, you'll need to settle any outstanding balances. If the pool owes you tokens, you can claim them. If you owe the pool, you'll need to pay them back. The process differs slightly for ETH and ERC20 tokens:

  • For ETH, you can send the amount directly with the settle call.

  • For ERC20 tokens, you first transfer the tokens to the pool manager, then call settle separately.

Understanding these core concepts is crucial as we move forward into building our custom hooks. In the next section, we'll explore how hooks integrate with this system and how we can leverage them to create powerful new features on top of Uniswap V4.


Implementing Uniswap V4 Hooks

Now that we understand the core concepts of Uniswap V4, let's dive into the exciting world of hooks. Hooks are the heart of Uniswap V4's extensibility, allowing developers to customize pool behavior in powerful ways.

What Are Hooks?

Hooks are smart contracts that can intercept and modify the behavior of Uniswap V4 pools at specific points in their lifecycle. They allow you to execute custom logic before or after key operations like swaps, liquidity provision, or pool initialization.

The BaseHook Contract

When implementing a hook, you'll typically start by inheriting from the `BaseHook` contract provided by Uniswap. This contract includes several key functions that you can override:

contract MyCustomHook is BaseHook {

    constructor(IPoolManager _poolManager) BaseHook(_poolManager) {}

    function getHooksCalls() public pure override returns (Hooks.Calls memory) {

        return Hooks.Calls({

            beforeInitialize: false,

            afterInitialize: true,

            beforeModifyPosition: false,

            afterModifyPosition: false,

            beforeSwap: false,

            afterSwap: true,

            beforeDonate: false,

            afterDonate: false

        });

    }

    function afterInitialize(address, PoolKey calldata, uint160, int24) external override returns (bytes4) {

        // Custom logic after pool initialization

        return BaseHook.afterInitialize.selector;

    }

    function afterSwap(address, PoolKey calldata, IPoolManager.SwapParams calldata, BalanceDelta) 

        external 

        override 

        returns (bytes4) 

    {

        // Custom logic after a swap

        return BaseHook.afterSwap.selector;

    }

}

Here, we've set up a hook that will execute custom logic after pool initialization and after each swap.

Registering Hook Callbacks

The `getHooksCalls()` function is crucial. It tells Uniswap which operations your hook wants to intercept. In the example above, we're only interested in `afterInitialize` and `afterSwap` events.

https://obrienmakenzi.substack.com/p/building-uniswap-v4-hooks?utm_source=substack&utm_medium=email&utm_content=share&action=share&token=eyJ1c2VyX2lkIjoyMTg5MTA2MTcsInBvc3RfaWQiOjE0NzU1NDQxMSwiaWF0IjoxNzIzMzEyMjA3LCJleHAiOjE3MjU5MDQyMDcsImlzcyI6InB1Yi0yODU5MDg5Iiwic3ViIjoicG9zdC1yZWFjdGlvbiJ9.346eTaw1U-7OsSuxeVAl3ulC3dKGIswB4KFiYQHiB-A


Let's look at a simple example of implementing logic in the `afterSwap` hook:



function afterSwap(

    address,

    PoolKey calldata key,

    IPoolManager.SwapParams calldata params,

    BalanceDelta

) external override poolManagerOnly returns (bytes4) {

    // Get the current tick after the swap

    (, int24 currentTick,,,,) = poolManager.getSlot0(key.toId());

    // Check if we've crossed a significant price threshold

    if (currentTick > SOME_THRESHOLD) {

        // Perform some action, e.g., emit an event

        emit SignificantPriceMove(key.toId(), currentTick);

    }

    return BaseHook.afterSwap.selector;

}

This hook checks if the price (represented by the tick) has crossed a certain threshold after a swap, and emits an event if it has.

Security Considerations

When implementing hooks, it's crucial to consider security implications:

Use the `poolManagerOnly` modifier (provided by `BaseHook`) to ensure only the PoolManager can call your hook functions.

Be mindful of gas costs. Your hook will be executed on every relevant operation, so keep the logic efficient.

Avoid making external calls in your hooks unless absolutely necessary, as this can introduce security vulnerabilities.

Testing Your Hooks

Testing hooks thoroughly is crucial. Uniswap provides a testing framework that allows you to simulate various pool operations and check if your hooks behave as expected.



function testAfterSwapHook() public {

    // Set up a test pool

    PoolKey memory poolKey = PoolKey({

        currency0: Currency.wrap(address(token0)),

        currency1: Currency.wrap(address(token1)),

        fee: 3000,

        tickSpacing: 60,

        hooks: IHooks(address(hookContract))

    });

    // Simulate a swap

    vm.prank(address(poolManager));

    hookContract.afterSwap(

        address(this),

        poolKey,

        IPoolManager.SwapParams({

            zeroForOne: true,

            amountSpecified: 1000,

            sqrtPriceLimitX96: 0

        }),

        BalanceDelta(0, 0)

    );

    // Assert expected behavior

    // ...

}

By implementing hooks, you can create powerful extensions to Uniswap V4, such as dynamic fees, price oracles, or complex trading strategies. The possibilities are vast, limited only by your creativity and the constraints of secure smart contract development.


Understanding Hook Interfaces and Fee Hooks

As we delve deeper into Uniswap V4 hook development, it's crucial to understand the different types of hooks available and how to implement them effectively. In this section, we'll explore the three main hook interfaces and dive into the specifics of fee hooks.

Three Main Hook Interfaces

Uniswap V4 provides three different hook interfaces, each serving a specific purpose:

  1. Before Hooks: These hooks are called before a specific action occurs in the pool. They allow you to implement custom logic or checks before the action is executed.

  2. After Hooks: These hooks are called after a specific action has occurred in the pool. They enable you to perform actions based on the results of the pool operation.

  3. Fee Hooks: These are special hooks that allow you to dynamically set fees for swaps and withdrawals.


Setting Up Fee Hooks

Fee hooks in Uniswap V4 operate slightly differently from other hooks. When initializing a pool in the PoolManager, you need to specify whether you want fee hooks to be executed. This is done by setting specific flags in the fee parameter of the PoolKey struct.

Here's an example of how to initialize a pool with fee hooks:

uint24 myFees = Fees.DYNAMIC_FEE_FLAG + Fees.HOOK_SWAP_FEE_FLAG + 

Fees.HOOK_WITHDRAW_FEE_FLAG; 
poolManager.initialize(IPoolManager.PoolKey({ 
    currency0: Currency.wrap(address(token1)), 
    currency1: Currency.wrap(address(token2)), 
    fee: myFees, 
    tickSpacing: 1, 
    hooks: IHooks(deployedHooks) 
}), sqrtPriceX96);

In this example, myFees is set to include all fee hook flags:

  • DYNAMIC_FEE_FLAG: Enables dynamic fees for the pool

  • HOOK_SWAP_FEE_FLAG: Allows the hook to set fees for swaps

  • HOOK_WITHDRAW_FEE_FLAG: Allows the hook to set fees for withdrawals

By combining these flags, we're telling Uniswap that we want all fee hooks to be executed for this pool.

Implementing Fee Hooks

To implement fee hooks, you need to override the relevant functions in your hook contract:


contract MyFeeHook is BaseHook { 
    function getHooksCalls() public pure override returns (Hooks.Calls memory){ 
        return Hooks.Calls({
            beforeInitialize: false,
            afterInitialize: false, 
            beforeModifyPosition: false, 
            afterModifyPosition: false,
            beforeSwap: true, // We want to set fees before swaps 
            afterSwap: false, 
            beforeDonate: false, 
            afterDonate: false 
        }); 
   } 
   function beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams                  calldata) 
      external 
      override 
      returns (uint24 newFee) 
   { 
  // Implement your custom fee logic here 
  // For example, you could base the fee on current market conditions 
      return calculateDynamicFee(key); 
   } 
   
   function calculateDynamicFee(PoolKey calldata key) internal view returns (uint24){ 
 // Implement your fee calculation logic
 // This could involve checking price oracles, time of day, or any other factors 
   } 
}

We've set up a hook that will execute before each swap to set a dynamic fee. The calculateDynamicFee function would contain your custom logic for determining the appropriate fee based on whatever factors you deem relevant.

By leveraging these different hook interfaces and understanding how to set up fee hooks, you can create highly customized and dynamic behavior for your Uniswap V4 pools. This flexibility allows for innovative DeFi products and trading strategies that were not possible in previous versions of Uniswap.


Now that we've explored the concepts, architecture, and implementation details of Uniswap V4 hooks, you might be eager to start building your own. To help you get started quickly and efficiently, I highly recommend using a scaffolding tool that sets up the basic structure and development environment for Uniswap V4 hook projects.

Getting Started with the Scaffold

I like to start with my builds using the Uniswap V4 Hook Scaffold by 0xbc1p:

https://github.com/0xbc1p/uniswap-v4-scaffold-hook.git

Next Steps

With the scaffold as your starting point, you're well-equipped to start experimenting with Uniswap V4 hooks.

Start by implementing a simple hook,

Try implementing a dynamic fee hook that adjusts fees based on certain conditions. This will give you hands-on experience with one of the most powerful features of Uniswap V4.

Experiment with integrating external, you could create a hook that adjusts behavior based on price feeds from an oracle.

As you develop more complex hooks, pay attention to gas optimization. Efficient hooks are crucial for keeping transaction costs low for users.

Remember, while developing hooks, always prioritize security. Thoroughly test your hooks and consider having them audited before deploying to mainnet.

Conclusion

Uniswap V4 hooks open up a world of possibilities for customizing and extending decentralized exchange functionality. By starting with the provided scaffold and building upon the concepts we've discussed in this blog post, you're well on your way to creating innovative DeFi solutions.

As you embark on your Uniswap V4 hook development journey, don't hesitate to engage with the community. The DeFi space thrives on collaboration, and your unique perspective could lead to the next big innovation in decentralized trading.

Happy coding!

Subscribe