Lopjf
Lopjf

Reputation: 43

Vault that receives NFTs and send tokens in exchange, vice-versa

I'm trying to build a contract that creates a parity 1:1 in between a token (e.g. PunkToken) and an NFT collection (e.g. CryptoPunk).

The idea is to deploy both the token and the NFT collection contracts. Then deploy a vault that stores all the created NFTs. Users can then exchange in both directions PunkToken for CryptoPunk vice-versa at a fixed 1:1 parity.

'Require' asides, my idea was to do something like this:

    // create two functions for doing the swap both ways

    function tokenToNft() public payable {
        _transfer(address(this), msg.sender, 1);   // _transfer function from ERC721
    }

    function nftToToken() public payable {
        _transfer(address(this), msg.sender, 1);   // _transfer function from ERC20
    }

To do so, I tried importing both ERC20 and ERC721 but quickly ran into clashes due to the identical functions names. I then tried to change the names of the ERC20 functions. Not working since the token became unrecognized by the blockchain (I believe).

Upvotes: 2

Views: 321

Answers (1)

Petr Hejda
Petr Hejda

Reputation: 43581

You can make use of the ERC721 function onERC721Received() that the collection is supposed to invoke on your contract as the recipient when a safe transfer to your contract occurs.

ERC20 does not implement any hook, so you'll need to use the approve() + transferFrom() approach to pull the ERC20 tokens from their wallet.

Your contract will act as an intermediary, so it needs to hold these specific tokens.

pragma solidity ^0.8.16;
 
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
 
contract MyVault is IERC721Receiver {
    IERC20 immutable public erc20Token;
    IERC721 immutable public erc721Collection;

    uint256 constant public AMOUNT_OF_ERC20_PER_ERC721 = 1 * 1e18; // 1 token, 18 decimals

    constructor(IERC20 _erc20Token, IERC721 _erc721Collection) {
        erc20Token = _erc20Token;
        erc721Collection = _erc721Collection;
    }

    // ERC721 to ERC20
    function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes calldata _data) external returns(bytes4) {
        // TODO validate that you're receiving from a whitelisted collection
        // TODO store which token ID you have received from whom, so that you can return it later

        // Transfer 1 ERC20 token in exchange for the NFT
        bool success = erc20Token.transfer(_operator, AMOUNT_OF_ERC20_PER_ERC721);
        require(success);

        // required by the ERC721 standard
        return this.onERC721Received.selector;
    }

    function erc20toErc721() external {
        // Pull 1 ERC20 token from the user's address to your contract.
        // The user needs to `approve()` your contract to spend their tokens first
        // directly on the ERC20 address, without your contract as the intermediary,
        // otherwise this `transferFrom()` fails
        bool success = erc20Token.transferFrom(msg.sender, address(this), AMOUNT_OF_ERC20_PER_ERC721);
        require(success);

        // Hardcoded for simplification. TODO retrieve which token ID they are eligigle to claim
        uint256 tokenId = 1;

        // send the NFT from your Vault contract to the user
        erc721Collection.safeTransferFrom(address(this), msg.sender, tokenId);
    }
}

Upvotes: 2

Related Questions