Reputation: 43
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
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