ERC721 mint with multiple URIs

Hi, I was thinking of a case where each uri is set upon mint.
The mint function takes in an amount to mint and a corresponding number of uri in an array.
Then as the tokenID is increased, the current tokenID is is set to the uri in the array.
User mints 2 items, token id of first item is 1 and second item is 2
The uri given as [uri1, uri2] is then assigned, 1-uri1 and 2 to uri2
When the tokenURI is called, with tokenID argument, instead of concatenating a base uri with tokenid, it returns the uri from the mapping perhaps.
(Id=> uri) token
TokenURI(1) returns
token[1] =uri1

I wonder if it’s possible with the current protocols in place for ERC721

Thank you for your opinions

This should be possible and relatively simple to put together. You should give this a try and write the mint function that you need, then share it here.

Take a look at Contracts Wizard and choose Mintable + URI Storage. You should change safeMint so it takes an array of URIs instead of a single one, and then inside the function add a for loop.

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

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

contract MyToken is ERC721, ERC721URIStorage, Ownable {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIdCounter;
    uint256 _tokenId = 1;

    constructor() ERC721("MyToken", "MTK") {}

    function safeMint(address to, string[] memory uri) public onlyOwner {
         for(uint8 i = 0; i<uri.length; i++){
        _tokenId = _tokenIdCounter.current();      
        _tokenIdCounter.increment();
        _safeMint(to, _tokenId);
        _setTokenURI(_tokenId, uri[i]);
        }
    }

    // The following functions are overrides required by Solidity.

    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);
    }
}

Thank you so much!
this works pretty well, although I tried to initialize the token id as one, so the counter starts from one but it doesn't work that way. Is there a way i can make it start counting from 1?

Include _tokenIdCounter.increment() in the constructor.

1 Like

It works! I'll give you credit in the smart contract comment haha. Thank you so much

1 Like

I have written something similar but it does not work, does anyone has any insights?

I have added a bulkMint function which takes a string array and in the function I will iterate the string array and call safeMint multiple times.
I found that if I pass a string array with only 1 element, it works. However, if there are more than 1 strings, it throws error.
Succeeded tx: https://mumbai.polygonscan.com/tx/0xf25edc6187f47f88493063c004487f35261360f8e0015d480573b15bff800056
Failed tx: https://mumbai.polygonscan.com/tx/0xeac51304c5a553dd3569663192fdca433e8f4febedf30a84d617c570cdc580ed

function bulkMint(address to, string[] memory uris)
        public
        onlyRole(MINTER_ROLE)
    {
        for (uint8 i = 0; i < uris.length; i++) {
            safeMint(to, uris[i]);
        }
    }

    function safeMint(address to, string memory uri)
        public
        onlyRole(MINTER_ROLE)
    {
        uint256 tokenId = _tokenIdCounter.current();
        _tokenIdCounter.increment();
        _safeMint(to, tokenId);
        _setTokenURI(tokenId, uri);
    }

my contract:

That error looks really strange! The code looks good to me. Have you tested it locally with Hardhat?