# 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.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.oiler.network/oiler-network/products/nafta/uniswap-v3-lp-nft-wrapper.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
