How to show the id of the nft in an ERC721 contract after the name when sending custom metadata function tokenURI

Hello Community, I need help with the placement of the id token in the metadata.
everything works fine for me but I can't get it if I upload

{"name": "nameNFT v44 #{tokenId}" ,"description":"PRIVATE TOKEN TO ACCESS .", "image":"ipfs://bafybeiav5............/interrogation .png"}

put the id of the token in front of the name

  /** URI HANDLING **/


event TokenURIChanged(uint256 indexed tokenId, string tokenURI);

string private customBaseURI;

mapping(uint256 => string) private tokenURIMap;

function setBaseURI(string memory customBaseURI_) external onlyOwner {
    customBaseURI = customBaseURI_;
}

function setTokenURI(uint256 tokenId, string memory tokenURI_) external onlyOwner {
    tokenURIMap[tokenId] = tokenURI_;
    emit TokenURIChanged(tokenId, tokenURI_);
}

function _baseURI() internal view virtual override returns (string memory) {
    return customBaseURI;
}



function tokenURI(uint256 tokenId) public view override
    returns (string memory)
{
    string memory tokenURI_ = tokenURIMap[tokenId];

    if (bytes(tokenURI_).length > 0) {
      return tokenURI_;     
    }

    return _baseURI();
}

if there is no tokenURI it shows me baseURI() with the id
#token number (id)

but if tokenURI is uploaded it shows me literal

nameNFT v44 #{tokenId}

and I can't find the solution, can someone help me?

1 Like

what exactly is your question?

It is rather unclear.

1 Like

hello thank you very much for answering

Let me explain, there are collections that have the name of the nft + # the id number

nftname +#2654

In my case I can't configure it to show the id data

all the nfts in my collection use the same .json and I can't concatenate the nftid or tokenId

in this function I upload a .json with these values

{"name": "PRIVANFT v44 #{tokenId}" ,"description":"PRIVATE TOKEN TO ACCESS .", "image":"ipfs://bafy........kxitzxe/interrogation.png"}

but it does not include the tokenId after the name instead what it does is show me the literal #{tokenId}

I understand that if I put between brackets {} it will put the value of the variable in the metadata

Or maybe I'm wrong in that sense, I've already read so much that I'm lost and I'm sure it's nonsense :slight_smile:

in this function:

function tokenURI(uint256 tokenId) public view override
    returns (string memory)
{
    string memory tokenURI_ = tokenURIMap[tokenId];

    if (bytes(tokenURI_).length > 0) {
      return tokenURI_;     
    }

    return _baseURI();
}

if it returns
return _baseURI();
shows the id number of the nft
#2765 for example without more data

so baseURI does but my
setTokenURI does not include it after the name or before
I want it to include it and not have to write 100 id.json

I believe the smart contract has the uri function as an API for a front-end to then go fetch off-chain metadata. The common standard of using {id} is only in the contract itself. The off-chain metadata obviously resides on a second layer. Here your json will want to be complete unless, of course you're building a front-end that also used {id} or {tokenId} as the entry point for a counter of sorts.

1 Like

thanks, actually I want opensea to show me the Name followed by the {id} of each nft without having to write each nft.
Only with the baseURI or tokenURI

i want to call the id value from the .json
I don't know if that is possible?

now show me
NAME #{TOKENID} ... DESCRIPTION

I want you to show me
NAME #345 .... DESCRIPTION

Currently I can change the baseURI, tokenURI I can even change a token range or all the ones already created but I can't put the id of each token behind the name

  /** URI HANDLING **/


event TokenURIChanged(uint256 indexed tokenId, string tokenURI);

string private customBaseURI;

mapping(uint256 => string) private tokenURIMap;

function setBaseURI(string memory customBaseURI_) external onlyOwner {
    customBaseURI = customBaseURI_;
}

function setTokenURI(uint256 tokenId, string memory tokenURI_) external onlyOwner {
    tokenURIMap[tokenId] = tokenURI_;
    emit TokenURIChanged(tokenId, tokenURI_);
}

function _baseURI() internal view virtual override returns (string memory) {
    return customBaseURI;
}


function tokenURI(uint256 tokenId) public view override returns (string memory) {   
    string memory tokenURI_ = tokenURIMap[tokenId];

    if (bytes(tokenURI_).length > 0) {
      return tokenURI_;     
    }
      return _baseURI();     
}


function setAllTokenURI(string memory tokenURI_) external onlyOwner {
    require(gasleft() > gasLimit, "Not enough gas");

    for (uint256 i = 1; i <= totalSupply(); i++) {
        if (tokenExist[i] && !tokenBurned[i]) {
            tokenURIMap[i] = tokenURI_;
            emit TokenURIChanged(i, tokenURI_);
        }
    }
}

function setRangeTokenURI(uint256 fromId, uint256 toId, string memory tokenURI_) external onlyOwner {
    require(fromId > 0 && fromId <= toId && toId <= totalSupply(), "Invalid token ID range");
    require(gasleft() > gasLimit, "Not enough gas");

    for (uint256 i = fromId; i <= toId; i++) {
        if(tokenExist[i] && !tokenBurned[i]){
            tokenURIMap[i] = tokenURI_;
            emit TokenURIChanged(i, tokenURI_);
        }
    }
}

and I only need that to take them to production

If I understand correctly: you're hoping to retrieve some kind of data from the metadata to use in your contract. Not only would that be circular logic it is also not possible as Ethereum does not have access to the rest of the world-wide-web.

1 Like

You can directly return the json for the metadata instead of an ipfs url with the meta data.
It would look something like, ofc you would have to some additional checks:

function tokenURI(uint256 id) public view override returns (string memory) {
  return
	string(
		abi.encodePacked(
			'data:application/json;base64,',
			Base64.encode(
				bytes(
					abi.encodePacked(
						'{"name":"', 'PRIVANFT ', _tokenId.toString(),'",
						'"description":"PRIVATE TOKEN TO ACCESS",',
						'"image":"ipfs://......."}'
					)
				)
			)
		)
	);
}
1 Like

Here is my tokenURI from a 721A contract which works with OpenSea but I'm guessing your contract is working as intended and you simply need to format your metadata correctly.

    function tokenURI(uint256 _tokenId) public view virtual override(ERC721A, IERC721A) returns (string memory) {
        if (!_exists(_tokenId)) revert URIQueryForNonexistentToken();
        return bytes(_baseURI(_tokenId)).length != 0 ? string(abi.encodePacked(_baseURI(_tokenId), "/", _toString(_tokenId))) : '';
    }

Hello, thank you very much for your answer, but something is escaping me or I don't understand the logic of the URIs well.

in your code I get this error

ParserError: Expected string end-quote.
--> probe_2_flat.sol:2111:53:
|
2111 | '{"name":"', 'PRIVANFT ', _tokenId.toString(),'",
| ^^^

I suppose I have to include the following libraries but it still gives me an error


/*
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/interfaces/IERC2981.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/utils/Address.sol";

*/
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/utils/Base64.sol";

thanks i am using ERC721
the mint function is

uint256 public nextTokenId;

function mintWithRestrictions(uint256 count) private nonReentrant{
    require(count <= MAX_MULTIMINT, "Mint at most 10 at a time");
    require(allowedMintCount(msg.sender) >= count, "Minting limit exceeded");
    require(msg.value >= PRICE * count, "Insufficient payment");
    timestamp = block.timestamp;

    ERC721 accessToken = ERC721(accessTokenAddress);
    require(accessToken.balanceOf(msg.sender) > 0, "Access token not owned");

    updateMintCount(msg.sender, count);
    for (uint256 i = 0; i < count; i++) {
        if (tokenExist[nextTokenId] || tokenBurned[nextTokenId]) {
            nextTokenId++;
        }
        _mint(msg.sender, nextTokenId);
        tokenTimestampMap[nextTokenId] = timestamp;
        tokenExist[nextTokenId] = true;
        nextTokenId++;
    }
}

so i am using the nextTokenId variable to record the id of the token inside the the same as its timestamp and if it exists so that it never regenerates it once destroyed

I had an error in the string concatenation :slight_smile: this one should work

return
	string(
		abi.encodePacked(
			'data:application/json;base64,',
			Base64.encode(
				bytes(
					abi.encodePacked(
						'{"name":"PRIVANFT ', Strings.toString(id),'",',
						'"description":"PRIVATE TOKEN TO ACCESS",',
						'"image":"ipfs://......."}'
					)
				)
			)
		)
	);
1 Like

hello thank you very much it seems that it already works but of course those data are in the response to the tokenURI

In order to modify the name, description and images in the following functions, you would have to create a variable for each one. I thought it could only be loaded by an ipfs file

function setBaseURI(string memory customBaseURI_) external onlyOwner {
    customBaseURI = customBaseURI_;
}

function setTokenURI(uint256 tokenId, string memory tokenURI_) external onlyOwner {
    tokenURIMap[tokenId] = tokenURI_;
    emit TokenURIChanged(tokenId, tokenURI_);
}

function setAllTokenURI(string memory tokenURI_) external onlyOwner {
    require(gasleft() > gasLimit, "Not enough gas");

    for (uint256 i = 1; i <= totalSupply(); i++) {
        if (tokenExist[i] && !tokenBurned[i]) {
            tokenURIMap[i] = tokenURI_;
            emit TokenURIChanged(i, tokenURI_);
        }
    }
}

function setRangeTokenURI(uint256 fromId, uint256 toId, string memory tokenURI_) external onlyOwner {
    require(fromId > 0 && fromId <= toId && toId <= totalSupply(), "Invalid token ID range");
    require(gasleft() > gasLimit, "Not enough gas");

    for (uint256 i = fromId; i <= toId; i++) {
        if(tokenExist[i] && !tokenBurned[i]){
            tokenURIMap[i] = tokenURI_;
            emit TokenURIChanged(i, tokenURI_);
        }
    }
}

So could I have something like this?


string private NFTname;
string private NFTdescrip;
string private NFTimage;

function setTokenURI(uint256 tokenId, string memory NFTname_, string memory NFTdescrip_, string memory NFTimage_) external onlyOwner {
    NFTname = NFTname_;
    NFTdescrip = NFTdescrip_;
    NFTimage = NFTimage_;
    //string memory NFTuri;

    tokenURIMap[tokenId] = string(
        abi.encodePacked(
            'data:application/json;base64,',
            Base64.encode(
                bytes(
                    abi.encodePacked(
                        '{"name":', NFTname_,' ' , Strings.toString(tokenId),'",',
                        '"description":', NFTdescrip_,',',
                        '"image":', NFTimage_,'}'
                    )
                )
            )
        )
    );

    //tokenURIMap[tokenId] = NFTuri;
    emit TokenURIChanged(tokenId, NFTname_);
}



function _baseURI() internal view virtual override returns (string memory) {
    return customBaseURI;
}


function tokenURI(uint256 tokenId) public view override returns (string memory) {   
    string memory tokenURI_ = tokenURIMap[tokenId];

    if (bytes(tokenURI_).length > 0) {
      return tokenURI_;     
    }
      return _baseURI();     
}

and also need to change the others ....

_baseURI()
setAllTokenURI
setRangeTokenURI

I'm looking at 721A and it looks very interesting, thanks although I think I'll have to rewrite the code if I use 721A

To be clear: I didn't suggest that. Just that the code does the same thing and works.... Here is a different approach. I've removed all the .json extensions from this test nft-gallery. Feel free to fork it and mess around with the metadata "at will" without having to mess around with ipfs.

1 Like

don't worry, your suggestion is good and that's why i have been studying 721A.
On the other hand, my idea is to go well beyond 1000 equal nfts since they are tokens for a utility to access private content.
That's why I was looking for a way that with a single .json would give me the possibility to include the tokenId behind the name and not have to put 1000 .json files when in reality they will all be the same. although I keep the possibility of changing independent URIs in case I want to put the next 100 with a Christmas touch for example.

In any case, thank you very much, it is always good to know other ways and opinions, among them, that it had not occurred to me to use github as storage :slight_smile:
good idea :slight_smile: