# Uniswap V3 LP NFT Wrapper

{% hint style="info" %}
**Uniswap V3 Wrapper** **Contract Addresses**

Ethereum Mainnet:&#x20;

[<mark style="color:orange;">0x2Ef97d5f5b55561f391EbFb3C8c277fFd7e34635</mark>](https://etherscan.io/address/0x2Ef97d5f5b55561f391EbFb3C8c277fFd7e34635)

Rinkeb&#x79;**:**&#x20;

[<mark style="color:green;">0xA38bcF5647F13e383669838B5BBdfd050dd330a7</mark>](https://rinkeby.etherscan.io/address/0xA38bcF5647F13e383669838B5BBdfd050dd330a7)

Goerli:&#x20;

[<mark style="color:yellow;">0xfdE78EEC3e9511778B970fBafa2FCE3eBa1BF5e9</mark>](https://goerli.etherscan.io/address/0xfdE78EEC3e9511778B970fBafa2FCE3eBa1BF5e9)
{% endhint %}

### Structure of NaftaWrapper

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:

```jsx
/// @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:

```jsx
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:

```jsx
/// @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.
