Solidity ERC-721 Extension – Using Immutable Plaintext String Instead of tokenURI

erc-721solidity

trying to implement this tutorial https://ethereum.org/en/developers/tutorials/how-to-write-and-deploy-an-nft/ but without any metadata/tokenURI and with plain text data encoded on-chain instead, as described in the "Note" at the bottom of https://docs.openzeppelin.com/contracts/3.x/erc721:

you’ll notice that the item’s information is included in the metadata,
but that information isn’t on-chain! So a game developer could change
the underlying metadata, changing the rules of the game! If you’d
like to put all item information on-chain, you can extend ERC721 to do
so (though it will be rather costly).
You could also leverage IPFS
to store the tokenURI information, but these techniques are out of the
scope of this overview guide. [my emphasis]

my naive method was to make the following changes to their example MyNFT.sol file:

//Contract based on [https://docs.openzeppelin.com/contracts/3.x/erc721](https://docs.openzeppelin.com/contracts/3.x/erc721)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
// import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; [1]

contract MyNFT is ERC721, Ownable { // [2]
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;

    constructor() public ERC721("MyNFT", "NFT") {}

    function mintNFT(address recipient)
        public onlyOwner
        returns (uint256)
    {
        _tokenIds.increment();

        uint256 newItemId = _tokenIds.current();
        _mint(recipient, newItemId);
        // _setTokenURI(newItemId, tokenURI); [1]

        return newItemId;
    }
}

[1] i commented this out

[2] the tutorial's version of this was contract MyNFT is ERC721URIStorage, Ownable

i then tried some cheap trix in the transaction data (namely the msg param below)

async function mintIt(msg) {
    const nonce = await web3.eth.getTransactionCount(PUBLIC_KEY, 'latest'); //get latest nonce

    //the transaction
    const tx = {
        'from': PUBLIC_KEY,
        'to': contractAddress,
        'nonce': nonce,
        'gas': 500000,
        'data': msg + nftContract.methods.mintNFT(PUBLIC_KEY).encodeABI()
    };

    const signPromise = web3.eth.accounts.signTransaction(tx, PRIVATE_KEY)

    signPromise
        .then((signedTx) => {
            web3.eth.sendSignedTransaction(
                signedTx.rawTransaction,
                function (err, hash) {
                if (!err) {
                    console.log(
                    "The hash of your transaction is: ",
                    hash,
                    "\nCheck Alchemy's Mempool to view the status of your transaction!"
                    )
                } else {
                    console.log(
                    "Something went wrong when submitting your transaction:",
                    err
                    )
                }
            }
        )
        })
        .catch((err) => {
            console.log(" Promise failed:", err)
        })
}

mintNFT("hello")

which didn't work.

i did succeed in deploying when i removed the msg param from mintIt(), but of course in that case the data (i.e., the word "hello") wasn't actually present in the contract anywhere.

tl;dr asking for the best or simplest extension with which i could mint an erc721 token containing a word, on-chain (i.e., not mutable like an tokenURI). if i am misunderstanding that last bit — i.e., if the tokenURI is immutable — i'd still feel like using it to hold just plain text would be like using a backhoe to dig a grave or some such.

Best Answer

ERC-721 already allows to return text inside your tokenURI by using a data scheme URI. There is no need to "extend" it, this is merely an implementation detail.

This is a well-known technique and several projects are using it, costs be damned. One that I've seen is https://onchainmonkey.com which gets the text and the entire image data on-chain.

Your next problem is immutability. The best way to handle this is to set metadata only when minting and do not build in any way to change that. Other approaches may be acceptable too.