ERC1155: storing and retrieving IPFS hash using 'uri' method

I’m using the OpenZeppelin implementation of ERC1155 and want to explore the possibility of storing my metadata JSON file on IPFS. Unfortunately, the metadata will not be known ahead of time and needs to be added dynamically as part of the minting process.

I understand that it’s far more common to use a centralised metadata server (e.g. “https ://api.myserver.com/metadata/{id}.json”) but I’m keen to make my contract as decentralised as possible and IPFS seems to be the way to go for this.

This post by wighawag describes how it’s possible to override uri to return the IPFS hash for any given token but I’m struggling to implement this.

Here’s what I’ve got so far but I’m not sure that my approach is the best one, especially using the mint method’s data property in this way.

Any thoughts would be very much appreciated. Thanks!

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

import "@openzeppelin/contracts@4.2.0/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts@4.2.0/access/AccessControl.sol";
import "@openzeppelin/contracts@4.2.0/token/ERC1155/extensions/ERC1155Burnable.sol";

contract TestHashContract is ERC1155, AccessControl, ERC1155Burnable {
    bytes32 public constant URI_SETTER_ROLE = keccak256("URI_SETTER_ROLE");
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    
    // Mapping from token ID to IPFS hash
    mapping(uint256 => bytes) private _hashes;
    
    string private _uri;
    
    function _setURI(string memory newuri) internal virtual override {
        _uri = newuri;
    }
    
    function uri(uint256 id) public view virtual override returns (string memory) {
        //return _uri;
        return string(abi.encodePacked(_uri, _hashes[id]));
    }

    constructor() ERC1155("") {
        _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _setupRole(URI_SETTER_ROLE, msg.sender);
        _setupRole(MINTER_ROLE, msg.sender);
    }

    function setURI(string memory newuri) public {
        require(hasRole(URI_SETTER_ROLE, msg.sender));
        _setURI(newuri);
    }

    function mint(address account, uint256 id, uint256 amount, bytes memory data)
        public
    {
        require(hasRole(MINTER_ROLE, msg.sender));
        _hashes[id] = data;
        _mint(account, id, amount, data);
    }

    function mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data)
        public
    {
        require(hasRole(MINTER_ROLE, msg.sender));
        _mintBatch(to, ids, amounts, data);
    }

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

Did you ever work this out? I’m having the same problem now!

I did eventually get it working. Here's my code:

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

import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
//import "@openzeppelin/contracts@4.2.0/access/AccessControl.sol";
import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Burnable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract DeMu1155 is ERC1155, ERC1155Burnable, Ownable {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIdCounter;
    
    bytes32 public constant URI_SETTER_ROLE = keccak256("URI_SETTER_ROLE");
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    bytes32 public constant FEE_SETTER_ROLE = keccak256("FEE_SETTER_ROLE");
    
    uint256 private _fee;
    
    // Mapping from token ID to IPFS hash
    mapping(uint256 => bytes) private _hashes;
    
    // Mapping from token ID to artists
    mapping(uint256 => mapping(address => bool)) private _artists;
    
    string private _uri;
    
    function _setURI(string memory newuri) internal virtual override {
        _uri = newuri;
    }
    
    function getFee() public view returns (uint256) {
        return _fee;
    }
    
    function setFee(uint256 newFee) public onlyOwner {
        //require(hasRole(FEE_SETTER_ROLE, msg.sender));
        _fee = newFee;
    }
    
    function uri(uint256 id) public view virtual override returns (string memory) {
        //return _uri;
        return string(abi.encodePacked(_uri, _hashes[id]));
    }
    
    // Returns the artists associated with a given token
    // function getArtists(uint256 tokenID) public view returns (address[] memory) {
    //     address[] memory ret = new address[artists[tokenID].length];
    //     for (uint i = 0; i < artists[tokenID].length; i++) {
    //         ret[i] = artists[tokenID][i];
    //     }
    //     return ret;
    // }
    
    // Checks if an NFT is created by an artists
    function checkArtist(uint256 tokenID, address artist) public view returns (bool) {
        return (_artists[tokenID][artist] == true);
    }

    constructor() ERC1155("") {
        // _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
        // _setupRole(URI_SETTER_ROLE, msg.sender);
        // _setupRole(MINTER_ROLE, msg.sender);
        // _setupRole(FEE_SETTER_ROLE, msg.sender);
    }

    function setURI(string memory newuri) public onlyOwner {
        //require(hasRole(URI_SETTER_ROLE, msg.sender));
        _setURI(newuri);
    }

    function mint(address account, uint256 amount, address[] memory artists, bytes memory data)
        public onlyOwner
    {
        //require(hasRole(MINTER_ROLE, msg.sender));
        require(artists.length < 5, "Too many artists");
        _hashes[_tokenIdCounter.current()] = data;
        for (uint256 i = 0; i < artists.length; i++) {
            _artists[_tokenIdCounter.current()][artists[i]] = true;
        }
        //_artists[_tokenIdCounter.current()] = artists;
        _mint(account, _tokenIdCounter.current(), amount, data);
        _tokenIdCounter.increment();
    }

    // function mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, address[] memory artists, bytes memory data)
    //     public
    // {
    //     require(hasRole(MINTER_ROLE, msg.sender));
    //     require(artists.length < 5, "Too many artists");
    //     _mintBatch(to, ids, amounts, data);
    // }

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

1 Like