2

Would it be possible to create a ERC721 Token for say a game item, but I want the Token to basically have ETH inside the token, so that a user could destroy the token to reclai

3 Answers 3

1

Yes you can. Provided you keep the interface compliant with the ERC721 standard you are free to implement any kind of logic you like, including additional functions. ERC721 defines what it must have, not what it must not have.

In terms of "how" you can adapt this or similar approach to your use-case.

Contracts can hold Eth. By extension, and ERC721 can hold a balance of ETH if you manage the deposit and withdrawal functions. Additionally, it would fall on you to track balances because the ETH held by the contract at the protocol level would be a lump sum. It's like a bank machine that holds all of the money, and governs how much money each user has access to.

Have a look at the storage structure from Open Zeppelin's implementation.

https://github.com/OpenZeppelin/openzeppelin-solidity/blob/v1.6.0/contracts/token/ERC721/ERC721Token.sol

// Mapping from token ID to owner
mapping (uint256 => address) private tokenOwner;

// Mapping from token ID to approved address
mapping (uint256 => address) private tokenApprovals;

// Mapping from owner to list of owned token IDs
  mapping (address => uint256[]) private ownedTokens;

// Mapping from token ID to index of the owner tokens list
mapping(uint256 => uint256) private ownedTokensIndex;

Three of those mappings are about token ID => something important.

If you want to put Eth inside of tokens, then you will need a place to record how much Eth is in each one:

// Mapping from token ID to Eth balance
mapping(uint256 => uint256) private tokenEthBalance;

For aesthetics, you might decide this is too many mappings for token ID and you might refactor:

struct TokenStruct {
  address tokenOwner;
  address tokenApprovals;
  uint256 ownedTokensIndex;
  uint256 tokenEthBalance;
}

mapping(uint256 => TokenStruct) private tokens;

Doing so would mean a lot of refactoring of a standard contract, but ERC721 is silent on the internal mechanics. Anything is acceptable as long as the interface functions exist and perform according to the spec.

You would have to create functions to deposit and withdraw Eth from a token and track the Eth balances as you go. You would have to apply the usual access control rules and check for sufficient funds. I imagine what you would want is for the contents (Eth) of a token to travel with the tokens (the default) and rule that only the token holder can extract Eth from a token. It all depends on the use-case you're trying to address.

Hope it helps.

2
  • Thank you for this solid reply!
    – Cer Hewer
    Commented Apr 25, 2019 at 0:20
  • Thanks for upvoting and accepting my answer ;-) Commented Apr 25, 2019 at 2:05
1

Thank you for the question. This example starts with the reference implementation and adds the feature that Ether can be deposited to a token at mint time and retrieved at burn time.

Tested using the v2 implementation of the ERC-721 reference implementation.

⚠️ WARNING: This example does not have any permissions checking. Anybody can create tokens and anybody can destroy anybody's tokens to take the money inside. You will need to add permissions checking (call me for help). I did not want to make an assumption about what you wanted and get it wrong.

There are strong use cases for this technique including:

  • Staking
  • Value transfer
  • Anti-spam

The example follows:

pragma solidity ^0.5.6;

import "https://github.com/0xcert/ethereum-erc721/src/contracts/tokens/nf-token-metadata.sol";
import "https://github.com/0xcert/ethereum-erc721/src/contracts/ownership/ownable.sol";

/**
 * @dev This is an example contract implementation of NFToken with metadata extension.
 */
contract MyGameItems is
  NFTokenMetadata,
  Ownable
{
  /// Map tokens to stored ETH value
  mapping (uint256 => uint256) private tokenValue;

  /**
   * @dev Contract constructor. Sets metadata extension `name` and `symbol`. 
   */
  constructor()
    public
  {
    nftName = "My Game Items";
    nftSymbol = "MGI";
  }

  /**
   * @dev Mints a new NFT.
   * @param _to The address that will own the minted NFT.
   * @param _tokenId of the NFT to be minted by the msg.sender.
   * @param _uri String representing RFC 3986 URI.
   */
  function mint(
    address _to,
    uint256 _tokenId,
    string calldata _uri
  )
    external
    payable
    onlyOwner
  {
    // ⚠️ WARNING THIS HAS NO PERMISSIONS CHECKING
    super._mint(_to, _tokenId);
    super._setTokenUri(_tokenId, _uri);
    tokenValue[_tokenId] = msg.value;
  }

  /**
   * @dev Removes a NFT from owner.
   * @param _tokenId Which NFT we want to remove.
   */
  function burn(
    uint256 _tokenId
  )
    external
    onlyOwner
  {
    // ⚠️ WARNING THIS HAS NO PERMISSIONS CHECKING
    super._burn(_tokenId);
    msg.sender.transfer(tokenValue[_tokenId]);
  }  
}
1
  • Hint: if using Remix IDE to test this, first set the compiler to 0.5.6 on the top right. Use the "+commit" version. Commented Apr 26, 2019 at 14:17
0

The ERC721 standard has no requirements with regard to how or when your contract mints or burns tokens. This means that you're free to devise whatever mint or burn functionality suits your project.

So yes, this would be allowed by the standard.

2
  • Well, I guess the question really is how do I get the token to hold eth in it.
    – Cer Hewer
    Commented Apr 24, 2019 at 23:29
  • It really depends on the greater structure of your project, but I would suggest using a mapping of tokenId to ether held in each token, then when a token is destroyed, transfer that amount to the ownerOf for that token. Commented Apr 24, 2019 at 23:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.