Oiler Network
Published in

Oiler Network

Nafta Wrappers — Uniswap V3 LP NFT Example

Nafta is a general-purpose NFT Lending platform where you can lend and borrow NFTs. Some NFTs require special treatment to work properly and safely — let’s explore what a NaftaWrapper is and why we need it.

NFT Flash Loan behaves the same way as any ERC20 Flash Loan would: funds that are borrowed have to be repaid in the same transaction, with the only difference that instead of funds, we borrow NFTs. Read Nafta's article for more details.

In this article we are going to explore an example of NaftaWrapper based on Uniswap V3 Liquidity Providers NFTs.

What is Uniswap LP NFT and why rent it?

nafta.market

Uniswap V3 liquidity provider positions are represented as ERC-721 tokens ( NFTs ), which contain unique boundary data, pair, and fee tiers based on the pool and selected parameters. Compared to Uniswap V2 LP tokens, Uniswap V3 stores the amount of fees accrued in an NFT — so every liquidity provider is able to know how much fees they have earned and can extract these fees when needed.

If you have a position and have a projection of future returns — you can exchange this future money for today’s money by renting it — you get the rent payment right now, and the borrower will get the possibility to extract all fees for the whole rental period, basically performing the swap from floating stream of payments to a fixed upfront rental payment.

Why ‘wrap’ Uniswap LP NFTs?

Anyone holding the NFT can call collect() function and extract the fees. But also, anyone holding the NFT can call a function to cash out the liquidity locked inside the position and run away with it. And we don’t want that, we want to expose only one function to the borrower, and it’s collect().

This can be achieved with the Nafta Wrapper — a simple NFT-Wrapper, that allows only collect() to be called on Uniswap V3 NFT, and nothing else. Effectively making it safe for LPs to rent their NFTs.

We believe that this Wrapper is a great template for other projects wanting to integrate Nafta, as any other NFT can be wrapped in a similar manner.

imgflip.com

Structure of Nafta Wrapper

First of all, NaftaWrapper is an ERC721 NFT itself, and also it should comply with IFlashNFTReceiver interface so that Nafta contract can use it for Flashloans.

A Wrapper should be able to wrap and unwrap the NFTs that are fed to it, and give back WrapperNFTs:

/// @notice Wraps Uniswap V3 NFT
/// @param tokenId The ID of the uniswap nft (minted wrappedNFT will have the same ID)
function wrap(uint256 tokenId) external {
nftOwners[tokenId] = msg.sender;
_safeMint(msg.sender, tokenId);
IERC721(uniV3Address).safeTransferFrom(msg.sender, address(this), tokenId);
}
/// @notice Unwraps Uniswap V3 NFT
/// @param tokenId The ID of the uniswap nft (minted wrappedNFT has the same ID)
function unwrap(uint256 tokenId) external {
require(nftOwners[tokenId] == msg.sender, "Only owner can unwrap NFT");
require(ownerOf(tokenId) == msg.sender, "You must hold wrapped NFT to unwrap");
_burn(tokenId);
IERC721(uniV3Address).safeTransferFrom(address(this), msg.sender, tokenId);
}

Notice we keep track of who wrapped the NFT with nftOwners - cause otherwise we wouldn’t know who can unwrap it (the owner() wouldn’t work here - cause when you flashLoan - you become an owner).

Then we have our payload function, that allows some useful action on the wrapped NFT, in our case — the extraction of fees:

function extractUniswapFees(uint256 tokenId, address recipient) external {
require(ownerOf(tokenId) == msg.sender, "Only holder of wrapper can extract fees");
INonfungiblePositionManager nonfungiblePositionManager = INonfungiblePositionManager(uniV3Address);
// get required information about the UNI-V3 NFT position
(, , address token0, address token1, , , , , , , , ) = nonfungiblePositionManager.positions(tokenId);
INonfungiblePositionManager.CollectParams memory params = INonfungiblePositionManager.CollectParams({
tokenId: tokenId,
recipient: recipient,
amount0Max: type(uint128).max,
amount1Max: type(uint128).max
});
// collect the fee's from the NFT
(uint256 amount0, uint256 amount1) = nonfungiblePositionManager.collect(params);
emit FeesCollected(token0, amount0, token1, amount1);
}

And finally, we have a standard IFlashNFTReceiver.executeOperation() function, that will be called by Nafta on any FlashLoan act:

/// @notice Handles Nafta flashloan to Extract UniswapV3 fees
/// @dev This function is called by Nafta contract.
/// @dev Nafta gives you the NFT and expects it back, so we need to approve it.
/// @dev Also it expects feeInWeth fee paid - so should also be approved.
/// @param nftAddress The address of NFT contract
/// @param nftId The address of NFT contract
/// @param msgSender address of the account calling the contract
/// @param data optional calldata passed into the function optional
/// @return returns a boolean true on success
function executeOperation(
address nftAddress,
uint256 nftId,
uint256 feeInWeth,
address msgSender,
bytes calldata data
) external override returns (bool) {
emit ExecuteCalled(nftAddress, nftId, feeInWeth, msgSender, data);
require(nftAddress == address(this), "Only Wrapped UNIV3 NFTs are supported"); // do the uniswap fee extraction thing
this.extractUniswapFees(nftId, msgSender);
// Approve NFT back to Nafta to return it
this.approve(msg.sender, nftId);
return true;
}

And that’s it!

As a bonus and as a UX convenience feature, we also have combined wrapAndAddToNafta() and unwrapAndRemoveFromNafta() functions that save our users the count of transactions they have to make by automatically adding the wrapped NFT to Nafta Pool (or removing it and unwrapping). This is not a requirement, but for sure a nice addition.

UniswapV3 Wrapper on Github

You can check the source code of UniswapV3Wrapper on our Nafta github:

https://github.com/OilerNetwork/nafta/blob/8481e076ad6910ccfc98bf8cea6ea600c9263b21/contracts/example/UniV3Wrapper.sol

Feel free to use it as an example for building your own wrappers.

UniswapV3 Wrapper Contracts

The UniswapV3Wrapper contract was deployed by us under the following address, and anyone can use it now!

UniswapV3Wrapper Mainnet:
0x2Ef97d5f5b55561f391EbFb3C8c277fFd7e34635
UniswapV3Wrapper Rinkeby:
0xA38bcF5647F13e383669838B5BBdfd050dd330a7
UniswapV3Wrapper Görli:
0xfdE78EEC3e9511778B970fBafa2FCE3eBa1BF5e9

What’s next?

If you have any idea of another use case that can benefit from Nafta — let us know and we will try to integrate it. As more and more wrappers will be developed — Nafta will continue to grow.

BOUNTY!

If you want to help us with developing a wrapper for your NFT project, or a project you like — just do it & be rewarded with OIL tokens! → Follow the rules on our Discord #nafta-wrappers channel.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store