Struggling with assigning token name during mint (New Dev)

Hey everyone, (or whoever should happen to find this thread)

I'm Rhyot, an electronic music artist and I suppose now 'new solidity developer'. I'm essentially writing a smart contract to mint a collection of music NFTs to deploy a set collection amount of .mp4 ERC721 tokens to the blockchain, each NFT with a unique name. Currently, I'm able to get most of what I need to happen to happen, though I'm lost as to actually naming the tokens with the mint function. In my case, I am minting each token from a singular .mp4 file and a singular metadata.json file. I've gotten the metadata baseURI to properly concatenate with the tokenIdString, and I roughly understand how to concat the tokenBaseName with the tokenIdString, but I'm not sure how to actually call a function that will name a minted NFT based on that string.

I've been learning with chatGPT (which frankly is a confused mess but understands most of the syntax and language that I do not), and building from OpenZeppelin Contract Wizard templates in Remix IDE. This is now only day 5 of my dev experience with solidity, so generally, I'm not upset with where I'm at so far, just needing to tap in for some help from a more experienced community.

Once I can get safeMint to do all the things I need it to do, then I'll build out an updated safeMintBatch to basically loop the function. I've currently disabled safeMintBatch within the contract, but you'll see it commented out and still residing therein.

I've included my contract code below. Thanks in advance for advice!

:1234: Contract Code


// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import "@openzeppelin/contracts@4.8.0/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts@4.8.0/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts@4.8.0/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts@4.8.0/access/Ownable.sol";
import "@openzeppelin/contracts@4.8.0/utils/Counters.sol";
//import "@openzeppelin/contracts@4.8.0/proxy/utils/Initializable.sol";
//import "@openzeppelin/contracts/utils/Strings.sol";

/// @custom:security-contact rhyot@rhyotmusic.com
contract RhyotGenesis is 
//    Initializable, 
    ERC721, 
    ERC721Enumerable, 
    ERC721URIStorage, 
    Ownable
{
    using Counters for Counters.Counter;
    using Strings for uint256;

    Counters.Counter private _tokenIdCounter;

    // Set the MaxSupply of NFTs that can be minted (Note: default _maxBatchSize will be equal to this value setting)
    uint256 MaxSupply = 100;
    // Set the base name for the NFTs to which the token ID will be concatenated during the mint call
    string public constant tokenBaseName = "Rhyot - Genesis #";

    uint256 private _maxBatchSize = MaxSupply;
    bool private _safeMintBatch;
    uint256 private _totalSupply;
    
    /// Constructor
    constructor() ERC721("RhyotGenesis", "RHYGEN") {
        _tokenIdCounter.increment();
    }

    // Set the base URI for the collection to the metadata.json file in IPFS
    function _baseURI() internal pure override returns (string memory) {
        return "ipfs://Qmc9C6PywPykjLpPqEtbnAwMgtFVKYxEo3mS6baE3X4oFY/";
    }
    
    // Return the current value of MaxSupply
    function getMaxSupply() public view returns (uint256) {
        return MaxSupply;
    }
    
    // Return the current value of maxBatchSize
    function getMaxBatchSize() public view returns (uint256) {
        return _maxBatchSize;
    }
    
    // Return the current values of both MaxSupply and maxBatchSize in a single call
    function getMaxValues() public view returns (uint256, uint256) {
        return (_maxBatchSize, MaxSupply);
    }

    // Set Maximum Values for Batch Size and Supply
    function setMaxValues(uint256 maxBatchSize, uint256 _MaxSupply) public onlyOwner
    {
        // Ensure that maxBatchSize is not negative
            require(maxBatchSize >= 0, "Batch Size cannot be negative");
        // Ensure that maxBatchSize is not greater than MaxSupply
            require(maxBatchSize <= _MaxSupply, "Batch Size cannot be greater than Max Supply");
        // Check if the values are being set to their current values
            require(_maxBatchSize != maxBatchSize || MaxSupply != _MaxSupply, "Values are already set to their current values");
        // Update the variables
            _maxBatchSize = maxBatchSize;
            MaxSupply = _MaxSupply;
    }

    // Returns the Token ID of the next NFT that will be minted
    function getNextTokenId() public view returns (uint256) {
        return _tokenIdCounter.current();
    }

    // Mint a single NFT
    function safeMint(address to) public onlyOwner {
        // disable safeMintBatch
       _safeMintBatch = false;
        // Check if the max supply has been reached
        require(_totalSupply < MaxSupply, "Max Supply reached");
        // Increment the total supply and the token ID counter
        _totalSupply++;
        uint256 tokenId = _tokenIdCounter.current();
        _tokenIdCounter.increment();
        // Convert the token ID to a string
        string memory tokenIdString = tokenId.toString();
        // Concatenate the `tokenBaseName` and `tokenIdString` using the `abi.encodePacked` function to create tokenFinalName
        bytes memory tokenFinalName = abi.encode(tokenBaseName, tokenIdString);
        // Use the `abi.decode` function to decode the concatenated string from `tokenFinalName`to `printFinalName`
        string memory printFinalName = abi.decode(tokenFinalName, (string));
        // Mint the NFT and set the token URI
        _safeMint(to, tokenId);
        _setTokenURI(tokenId, tokenIdString);
        super._setName(printFinalName);
        // re-enable safeMintBatch
        _safeMintBatch = true;
    }

/* THIS SECTION CURRENTLY DISABLED FOR REPAIRS TO SAFEMINT

    // Mint a batch of NFTs 
    function safeMintBatch(address to, uint256 batchSize) public onlyOwner {
            // Check if the batch size is greater than the maximum supply
            require(batchSize <= MaxSupply, "Batch size cannot be greater than the maximum supply");
            // Check if the batch size is greater than the maximum batch size
            require(batchSize <= _maxBatchSize, "Batch size cannot be greater than the maximum batch size");
            // Check if the total number of NFTs plus the batch size is greater than the maximum supply
            require(_tokenIdCounter.current() + batchSize <= MaxSupply, "Minting this batch would exceed the maximum supply");
            // Otherwise, loop through the batch size, incrementing the token ID each time
            for (uint i = 0; i < batchSize; i++) {
                // Increment the total supply
                _totalSupply++;
                // Get the current token ID and increment the counter
                uint256 tokenId = _tokenIdCounter.current();
                _tokenIdCounter.increment();
                // Convert the token ID to a string
                string memory tokenIdString = tokenId.toString();
                // Set the base URI for the collection
                string memory baseURI = _baseURI();
                // Concatenate the `tokenName`, `baseURI`, and `tokenIdString` using the `abi.encodePacked` function
                bytes memory data = abi.encodePacked(tokenName, baseURI, tokenIdString);
                // Use the `abi.decode` function to decode the concatenated string from `data`
                string memory fullUri = abi.decode(data, (string));
                // Mint the NFT and set the token URI
                _safeMint(to, tokenId);
                _setTokenURI(tokenId, fullUri);
            }
*/            

    // The following functions are overrides required by Solidity
    function _beforeTokenTransfer(address from, address to, uint256 tokenId, uint256 batchSize)
        internal
        override(ERC721, ERC721Enumerable)
    {
        super._beforeTokenTransfer(from, to, tokenId, batchSize);
    }

    function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) {
        super._burn(tokenId);
    }

    function tokenURI(uint256 tokenId)
        public
        view
        override(ERC721, ERC721URIStorage)
        returns (string memory)
    {
        return super.tokenURI(tokenId);
    }

    function supportsInterface(bytes4 interfaceId)
        public
        view
        override(ERC721, ERC721Enumerable)
        returns (bool)
    {
        return super.supportsInterface(interfaceId);
    }
}

:computer: Environment

Using Remix IDE with 0.8.17 compiler.

Hey @rhyot, welcome!

If I understand correctly, most of the problems you're having right now are related to concatenating strings.

Every string in solidity is a list of bytes, so you can parse bytes into string and vice-versa relatively easily:

 function toBytes(string memory a) public pure returns(bytes memory) {
      return bytes(a);
  }

  function toString(bytes memory a) public pure returns(string memory) {
      return string(a);
  }

And also, every type in Solidity can be parsed to bytes by just encoding it (with abi.encodePacked), so it all comes down to this example:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

contract Example {
    function concat(string memory a, string memory b)
        public
        pure
        returns (string memory)
    {
        bytes memory strings = abi.encodePacked(a, b);
        return string(strings);
    }
}

By the way, I'd suggest looking at the ABI specification docs to understand how abi.encodePacked works under the hood.
Hope this helps :slight_smile:

Thanks so much @ernestognw! I really appreciate you popping by to help.

So I was running into a weird issue where abi.encodePacked was actually causing the safeMint to revert, but by dropping 'Packed' for abi.encode the transactions would work without a problem.

The concatenations are all working in my code as well, so the immediate question is how to write the code to set the concatenated strings as token names specifically, so that they don't just churn out as tokenIds as default.

Many thanks!

No problem, happy to help.

So I was running into a weird issue where abi.encodePacked was actually causing the safeMint to revert, but by dropping 'Packed' for abi.encode the transactions would work without a problem.

:warning: Beware there are differences between encode and encodePacked that may result in a unexpected result, please check this post and the ABI spec docs just to make sure you're doing what you want, otherwise it may be dangerous to blindly rely on them. I'd assume you'll be fine practicing.

The concatenations are all working in my code as well, so the immediate question is how to write the code to set the concatenated strings as token names specifically, so that they don't just churn out as tokenIds as default.

I don't think that's possible. Your NFT contract is not a single NFT, it's just a contract that creates tokens (like a collection), so, setting the name will set the name of the whole collection, not a single NFT.

This may work if your contract was a collection with just 1 token, but since you're adding a batchMint, I think you're misunderstanding the properties of the contract.

Also, I see your metadata is uploaded in the baseUri, which should be a directory with the metadata of each tokenId.

I'd suggest taking a look to our docs, and have a read to the EIP721 specification to get a better sense of what's the contract designed for.

Best

Interesting, so it seems that there isn't actually an ERC721 function to name the NFTs, and that there isn't a way to say, mint 100 of the same NFT using a singular metadata.json and a singular MP4, and have the contract itself name and mint them separately?

I wanted to kind of make an open edition collection direct on the blockchain as opposed to lazy minting, and that if a token was minted then the contract would assign the minted token its own unique name (not just be the token id), and attach the metadata (description, attributes, etc) of the collection. Seeing the NFT world out there, this seems far from impossible to do, eh?

Hmm so then how does open edition minting tackle this, or it is not possible to do outside of lazy mint methods offered by various platforms?

Contract goals:
• Set base URI (the singular metadata.json file for the music video NFT)
• Set base name, in this case "Rhyot - Genesis #"
• Count and mint in succession from token #1 through to the max supply
• Concat the token to the base name, and mint with each new created name, eg. token #1 would be 'Rhyot - Genesis #1'
• Batch mint simply repeats a loop of safe mint through batch size

It seems could really only achieve this by running a Python script to generate a batch of metadata.json files, one each for the max supply of NFTs in a collection, generating names for each of the id's within them, set base uri to the ipfs metadata folder that contains them all, and then gut the naming stuff from the contract, right?

Woo, okay got it all working! Thank you @ernestognw. Basic understanding of the process achieved. I was able to get everything to work the way I needed by writing a metadata python script that added padded 0's to the name (Rhyot - Genesis #013) but didn't append that to the .json file names, didn't mess with it in the contract, so while tokenid's and uri's are just the standard token id's, the names at least have the stylization I wanted. The script worked to generate based on a singular base metadata file, and a singular image and animation url.

So! From never having coded solidity to custom deployed contract in eight days. I'm happy with that.

mint 100 of the same NFT using a singular metadata.json and a singular MP4, and have the contract itself name and mint them separately?

There is a way of having the same metadata for all the tokens of the same kind, but ERC721 is not suitable for that, take a look to ERC1155

the contract would assign the minted token its own unique name (not just be the token id), and attach the metadata (description, attributes, etc) of the collection

There's no standard way to give a name to a token, only IDs. If you want to set a name in the contract, that'd be a custom implementation you should do on your own. The metadata is usually used for giving a name.

Set base URI (the singular metadata.json file for the music video NFT)

Note that ERC721 uses baseUri as a prefix to the token id to show the metadata. You can always override tokenUri but that looks weird to me. ERC721 is not suited for sharing the metadata across tokens, that'd be the case for an ERC1155.

• Set base name, in this case "Rhyot - Genesis #"
• Concat the token to the base name, and mint with each new created name, eg. token #1 would be 'Rhyot - Genesis #1'

If you're trying to get this shown on OpenSea, this is done via the metadata, no need for a name at the contract level. See OpenSea Metadata Standards.

Count and mint in succession from token #1 through to the max supply

The mint function is not part of the ERC721 standard, you can do it with a custom implementation.

Batch mint simply repeats a loop of safe mint through batch size

For ERC721, check ERC721Consecutive, and for ERC1155, it natively supports batch operations.

It seems could really only achieve this by running a Python script to generate a batch of metadata.json files, one each for the max supply of NFTs in a collection, generating names for each of the id's within them, set base uri to the ipfs metadata folder that contains them all, and then gut the naming stuff from the contract, right?

Yes. Create a folder with the tokenId prefixes, eg (ipfs://<hash>/1, ipfs://<hash>//2), etc.
The base ERC721 implementation already concatenates the tokenId to the base URI, producing this result

Thank you @ernestognw

Glad you got it working :partying_face:

Edit:
Better than using ChatGPT for learning, I'd suggest you using our Wizard!

Flattening and verifying, the new boss fight.

Being as I deployed this on Remix, I found that I could simply flatten using the available flattener plugin by navigating to the contract in the explorer, right click > flatten, then go down to the flattener tab, and viola - right? WELL. Interestingly, I'm getting a 500 'INTERNAL SERVER ERROR' when running the plugin. For kicks I tried flattening a template OZ contract, just one of the default starters. Same result.

Any ideas?

Not sure why but my npm in visual studio code seems super jacked up and broken also. I did try using truffle-flattener and couldn't get anything to install without vulnerabilities, i.e., nothing seemed to work locally, hence, doubling down on trying to get remix to do the job.

Update: Moved over to my PC, ran the VS Code setup for truffle, got a flattened contract to output, and now running into the ominous " But we were unable to locate a matching bytecode (err_code_2)"

I have a bad feeling that something about deploying via Remix but having to flatten via Truffle has created some issues and I may not be able to get this to verify. If the bytecode is different and I otherwise don't have a way to go pull the source code of the deployed contract, I might be screwed, eh?

This could be the case. Unfortunately deploying via Remix is not the best way to guarantee verifiability.

If you haven't seen it check out our guide where we include this and other recommendations:

Yeah things definitely got a little challenging there, and unfortunately it took a redeploy to fix, but thankfully I've got everything up and live, verified, minted, voila. Just trying to get rarible to hide the collection now. I didn't consider the value of a burn or self destruct function, but in future iterations I may experiment with that just to avoid blockchain clutter remaining in the event something does go wrong on the official deploy.

Thanks so much for the tips and feedback here. Cheers.

There are different opinions about this. Personally I don't think it's your responsibility to avoid blockchain clutter. It's a problem that needs to be solved by the protocol.

I appreciate that @frangio. The deeper I dive the more aware I become of all the digital space junk flying around across web3 already, between abandoned projects, general trash, art assets, video files, blah blah, just eternally existing across ipfs and the chains themselves lol