Enabling NFTFi with Uniswap V4 Hooks
June 18th, 2024

As the NFT space has risen in value as an asset class in the crypto world, there have been more protocols and products built to attempt to make these assets more liquid, through borrow/lending, providing fractional ownership, etc. Bringing the rise of something called "NFTFi". We've seen several of these apps be successful across different chains, from Tensor & Magic Eden on Solana, Blur on Ethereum, Sudoswap etc. From borrowing and lending, but most interesting, NFT AMMs.

What are the advantages of an NFT AMM & How does an NFT AMM work?

The advantages of NFT AMMs are quite clear, you're able to create instant liquidity on the NFT collection instead of traders waiting on someone to bid for their NFT. This is helpful for traders who need instant liquidity on their NFTs. There are many different designs of NFT AMMs, but the most compelling one is by the team at Tensor on Solana (tensor.trade). What they incentivize is for traders to create "market-making orders" on an entire NFT collection. This market-making order is two-sided, one being a "buy side," where the trader deposits a certain amount of SOL to purchase a certain amount of NFTs, and on the other side being the "sell side," where they can list some of their NFTs in this collection for purchase. The trader makes money on the spread here as the purchase and selling side moves the price along the bonding curve, literally allowing the trader to buy low and sell high.

Tensor's NFT AMM utilizes the concept of bonding curves to determine the pricing of NFTs within the market-making orders. When a trader creates a market-making order on Tensor, they specify a starting price and a "change by" or delta value, which determines how much the price changes after each trade. This delta value can be set as a fixed amount in SOL or as a percentage of the current price. As traders buy or sell NFTs from the market-making order, the price moves along the bonding curve, adjusting according to the delta value. For example, if the starting price is 10 SOL and the delta is set to 1 SOL, each buy will increase the price by 1 SOL, and each sell will decrease the price by 1 SOL. This bonding curve mechanism ensures a dynamic and fair price discovery process within the Tensor NFT AMM, allowing traders to automate their "buy low, sell high" strategy while providing liquidity to the NFT market.

Let’s Implement A Tensor-Like NFT AMM As A Uniswap V4 Hook

The core innovation of Uniswap V4 is hooks, functions that we can trigger at different stages in a pool’s lifecycle/activity. Hooks allow us to tailor the Uniswap AMM to our specific use cases, the design space is unlimited. By leveraging Uniswap V4 hooks, we can build NFTFi protocols that provide liquidity, price discovery, and unique financial mechanisms for NFT collections.

Implementing Our NFTAMMHook

A. Market Making Functionality

The NFTAMMHook contract allows market makers to create orders by specifying the desired price range and quantity of NFTs they wish to trade. The marketMake function is the core of the market making functionality, enabling market makers to create orders and deposit NFTs into the AMM.

When a market maker creates an order, they specify the following parameters:

  • _nftAddress: The address of the NFT collection.

  • startingBuyTick: The starting tick for buying NFTs.

  • startingSellTick: The starting tick for selling NFTs.

  • tokenIds: The token IDs of the NFTs being sold.

  • delta: The percentage change for each order on the bonding curve.

  • fee: The swap fee for buying or selling into the order.

  • maxNumOfNFTs: The maximum number of NFTs the order is willing to purchase.

The market maker also sends Ether (msg.value) along with the function call to cover the cost of buying NFTs.

Inside the marketMake function, the hook performs the following steps:

  1. It validates the input parameters to ensure the NFT address matches the collection, the sell tick is greater than the buy tick, and there is enough Ether to cover the maximum number of NFTs to buy.

  2. It creates a new MMOrder struct to store the order details, including the starting buy and sell ticks, the current tick, the Ether balance, the maximum number of NFTs, the delta, the fee, and the NFT address.

  3. It calculates the square root price ratio (sqrtPriceX96) for each NFT being sold using the createSqrtPriceForSingleToken function, which takes into account the starting sell tick and the delta.

  4. It transfers the NFTs from the market maker to the hook contract using the safeTransferFrom function of the ERC721 contract.

  5. It determines the wrapped token share for the deposited NFTs using the determineWrappedTokenShare function, which calculates the total value of the NFTs in terms of the wrapped token based on the bonding curve.

The determineWrappedTokenShare function calculates the wrapped token share by:

  1. Converting the square root price ratio to a price ratio.

  2. Iterating over the token IDs and calculating the current price for each NFT based on the bonding curve.

  3. Summing up the total value of the NFTs in terms of the wrapped token.

  4. Converting the total value to Ether and updating the market maker's balance in the makerBalances mapping.

The bonding curve is defined by the starting sell tick and the delta, which determines how the price changes for each order. The createSqrtPriceForSingleToken function calculates the square root price ratio for a single NFT based on the starting tick, delta, and the index of the NFT in the array of token IDs.

B. Wrapped Token and Balance Tracking

The NFTAMMHook contract itself represents the wrapped NFT token and tracks the balances of the market makers' orders. The hook contract inherits from the ERC20 contract, allowing it to mint and manage the wrapped tokens.

When a market maker creates an order and deposits NFTs, the hook mints wrapped tokens equivalent to the total value of the deposited NFTs. The makerBalances mapping keeps track of each market maker's balance in terms of the wrapped token.

C. Buying NFTs (Swap Functionality)

When a trader wants to buy an NFT using Ether, they interact with the frontend, which in turn calls the createBuyBidOrder function on the hook contract. This function creates a buy bid order, specifying the market maker's order ID, the desired NFT ID, and the amount of Ether the trader is willing to pay.

The createBuyBidOrder function performs the following steps:

  1. It validates that the deposit amount is greater than zero and that the deposit amount is equal to or greater than the current price of the NFT based on the current tick.

  2. It creates a BidOrder struct with the relevant information, including the market maker's address, the immediate flag (set to true), the Ether value, the NFT ID, the current tick, and the order ID.

  3. It stores the bid order in the bidsToBuyers mapping, associating the bid ID with the trader's address.

  4. It returns the encoded bid order data.

The frontend then calls the swap function on the swapRouter contract, passing the pool key, swap parameters, test settings, and the encoded bid order data.

Inside the swap function, the afterSwap hook is called, which handles the logic to transfer the NFT from the hook to the trader. The afterSwap function performs the following steps:

  1. It decodes the bid order data to retrieve the relevant information.

  2. If the swap is a "zero for one" swap (Ether for NFT), it transfers the NFT from the hook contract to the trader using the safeTransferFrom function of the ERC721 contract.

  3. It sends the Ether payment to the market maker using the call function with the bidOrder.ethValue.

  4. It updates the market maker's order by calculating the new starting sell tick based on the current square root price ratio and the delta.

After the swap is complete, the trader receives the NFT instead of the wrapped tokens.

D. Selling NFTs

The selling functionality allows traders to sell their NFTs to the market maker's order. The process is similar to buying NFTs, but in reverse.

The trader calls the createSellOrder function on the hook contract, specifying the market maker's order ID, the NFT ID they want to sell, and the market maker's address.

The createSellOrder function performs the following steps:

  1. It retrieves the market maker's order using the order ID.

  2. It transfers the NFT from the trader to the hook contract using the safeTransferFrom function of the ERC721 contract.

  3. It creates a BidOrder struct with the relevant information, including the market maker's address, the immediate flag (set to true), the current price of the NFT based on the current tick, the NFT ID, the current tick, and the order ID.

  4. It stores the bid order in the bidsToBuyers mapping, associating the bid ID with the trader's address.

  5. It returns the encoded bid order data.

The frontend then calls the swap function on the swapRouter contract, passing the pool key, swap parameters, test settings, and the encoded bid order data.

Inside the swap function, the afterSwap hook is called, which handles the logic to transfer the NFT from the hook to the market maker and send the Ether payment to the trader. The afterSwap function performs the following steps:

  1. It decodes the bid order data to retrieve the relevant information.

  2. If the swap is a "one for zero" swap (NFT for Ether), it transfers the NFT from the hook contract to the market maker using the safeTransferFrom function of the ERC721 contract.

  3. It updates the market maker's order by calculating the new starting buy tick based on the current square root price ratio and the delta.

  4. It subtracts the Ether value from the market maker's Ether balance in the order.

After the swap is complete, the market maker receives the NFT, and the trader receives the Ether payment.

Our NFTAMMHook shows how Uniswap V4's hook system can work well with the new world of NFTFi, helping to create active markets for NFTs and opening up new chances for market makers and traders. As the NFT market keeps growing, developers have the chance to use Uniswap V4 hooks as a way of creating new NFTFi systems amongst the myriad of other design spaces.

You can see the code for the NFTAMMHook below!

Subscribe to Adam
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.
More from Adam

Skeleton

Skeleton

Skeleton