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)

        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)

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


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.

1 Answer 1


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.

  • tokenURI mutability is the reason why i went looking for an alternate extension, so if it's possible to make it immutable, that would solve my problem. you say, "The best way to handle this is to set metadata only when minting and do not build in any way to change that." do you have a sense of how specifically this would be done? one thing that i found in reddit.com/r/NFT/comments/n0a5v7/… — specifically point #2 ("Change tokenID property's datatype from uint256 to bytes32 to store hash values of metadata"). is there a simpler method?
    – user90019
    Dec 29, 2021 at 18:36
  • You simple just don't have anywhere inside your contract that changes the metadata after it is set. This means it is immutable. Dec 30, 2021 at 3:22
  • it seems the only two functions determining an erc721 token are _safeMint(address to, uint256 tokenId, bytes _data) and _setTokenURI(uint256 tokenId, string _tokenURI). of those 2 functions' 5 params, only tokenId (or the combination of tokenId and the contract itself) could make a given token unique. but this tokenId has absolutely no strong connection (hash) either to the input _data OR (if you are in fact using metadata at all—i am not) the input _tokenURI. this seems crazy: all uniqueness of the token comes down to Counters.Counter private _tokenIds; and _tokenIds.increment();?
    – user90019
    Dec 31, 2021 at 5:06

Your Answer

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