James Bachini

NFT Token Vault Solidity

NFT Token Vault

This was inspired by the CryptoKitties WG0 token vault which accepts gen zero CryptoKitty deposits and mints 1 ERC20 token for each. Users can then buy tokens on exchange and claim NFT’s from the vault.

The code is open source and available at: https://github.com/jamesbachini/NFTvault

How The NFTvault Contract Works

When you deposit your NFTs into the vault, the contract mints an equal amount of ERC20 tokens, one for each NFT. These tokens are like a claim ticket you get when you check your coat at a fancy restaurant. Except because ERC20 tokens are fungible, you might end up with someone else’s coat. As long as you hold onto these tokens, you have the right to reclaim any NFT from the vault.

The newly minted ERC20 tokens can be used to setup Uniswap pools, lending and other DeFi products that are built around the ERC20 standard. It also enables fractural ownership because you can deposit one NFT to the vault and then send half a token to a friend.

The vault is elegantly simple, you need to approve spend on the NFTs then call the depositNFTs function with the IDs of the NFTs you want to deposit. The contract checks to make sure you actually own those NFTs before transfering them into the vault. In exchange, you get some shiny newly minted ERC20 tokens in your wallet.

When you’re ready to get your NFTs back, you just call reclaimNFTs with the token IDs you want. The contract will burn the equivalent number of ERC20 tokens from your balance and return your NFTs to you.

NFT Token Vault Code

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";

contract NFTVault is ERC20, IERC721Receiver {
    IERC721 public nftContract;

    event NFTDeposited(address indexed depositor, uint256[] tokenIds);
    event NFTReclaimed(address indexed claimant, uint256[] tokenIds);

    constructor(address _nftContract) ERC20("NFT Vault Token", "NVT") {
        nftContract = IERC721(_nftContract);
    }

    function depositNFTs(uint256[] calldata tokenIds) external {
        uint256 length = tokenIds.length;
        require(length > 0, "No NFTs provided");
        for (uint256 i = 0; i < length; i++) {
            uint256 tokenId = tokenIds[i];
            require(nftContract.ownerOf(tokenId) == msg.sender, "You don't own this NFT");
            nftContract.safeTransferFrom(msg.sender, address(this), tokenId);
        }
        _mint(msg.sender, length * 10**decimals());
        emit NFTDeposited(msg.sender, tokenIds);
    }

    function reclaimNFTs(uint256[] calldata tokenIds) external {
        uint256 tokensToBurn = tokenIds.length * 10**decimals();
        require(balanceOf(msg.sender) >= tokensToBurn, "Insufficient tokens");
        _burn(msg.sender, tokensToBurn);
        for (uint256 i = 0; i < tokenIds.length; i++) {
            uint256 tokenId = tokenIds[i];
            require(nftContract.ownerOf(tokenId) == address(this), "NFT not in vault");
            nftContract.safeTransferFrom(address(this), msg.sender, tokenId);
        }
        emit NFTReclaimed(msg.sender, tokenIds);
    }

    function onERC721Received(
        address, address, uint256, bytes calldata
    ) external pure override returns (bytes4) {
        return IERC721Receiver.onERC721Received.selector;
    }
}

Code Walkthrough

The NFTVault contract leverages the ERC20 token standard to represent fractional ownership or access rights to deposited NFTs. The contract itself inherits from the ERC20 contract and implements the IERC721Receiver interface, which allows it to safely receive and hold NFTs.

Upon deployment, the constructor requires the address of an ERC721 contract, which it then binds to the nftContract state variable. This is the NFT contract from which NFTs can be deposited into the vault. The contract also initializes an ERC20 token, which is minted whenever NFTs are deposited.

NFT Deposit Mechanism

The depositNFTs function enables users to deposit their NFTs by passing an array of token IDs to this function.

Before an NFT can be deposited, the contract checks that the caller is indeed the owner of each NFT using nftContract.ownerOf(tokenId) == msg.sender. This ensures that only NFTs owned by the caller can be deposited. Once validated, the contract calls safeTransferFrom to move each NFT from the caller to the vault, effectively locking them in the contract.

As compensation, the caller receives NVT tokens equivalent to the number of NFTs deposited, with each NFT representing 10**decimals() NVT tokens. This relationship between NFTs and the NVT token forms the basis for potential fractional ownership or other financial derivatives based on the NFTs.

NFT Reclaim Mechanism

The reclaimNFTs function allows users to withdraw their NFTs from the vault by burning an equivalent amount of NVT tokens. The function checks that the user holds enough tokens to redeem the specified NFTs.

Each NFT redemption burns 10**decimals() NVT tokens, ensuring that the token supply reflects the NFTs held in the vault. The function also verifies that the NFT is still in the vault before transferring it back to the user, safeguarding against discrepancies that could arise if the NFT was removed or transferred by other means.

To maintain compliance with the ERC721 standard, the contract implements the onERC721Received function from the IERC721Receiver interface. This function allows the contract to receive ERC721 tokens safely, ensuring it can interact with other contracts that expect a recipient to implement this interface.


Get The Blockchain Sector Newsletter, binge the YouTube channel and connect with me on Twitter

The Blockchain Sector newsletter goes out a few times a month when there is breaking news or interesting developments to discuss. All the content I produce is free, if you’d like to help please share this content on social media.

Thank you.

James Bachini

Disclaimer: Not a financial advisor, not financial advice. The content I create is to document my journey and for educational and entertainment purposes only. It is not under any circumstances investment advice. I am not an investment or trading professional and am learning myself while still making plenty of mistakes along the way. Any code published is experimental and not production ready to be used for financial transactions. Do your own research and do not play with funds you do not want to lose.