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.