How to store IPFS hash on Ethereum blockchain using smart contracts?

Viewed 1973

I'm working on a react + IPFS DAPP and making a simple Pet shop app. for now I have just create smart contract on remix and trying it's functions to work properly but I'm facing a problem.

transact to PetShop.generatePet errored: Error encoding arguments: Error: invalid arrayify value (argument="value", value="QmWmyoMoctfbAaiEs2G46gpeUmhqFRDW6KWo64y5r581Vz", code=INVALID_ARGUMENT, version=bytes/5.0.5)

whenever I'm calling generatePet function and passing IPFS hash it returns above mentioned error.

contract petshop

pragma solidity ^0.6.6;

import "https://github.com/MuhammadSajid404/ERC721-Token/blob/master/ERC721.sol";

contract PetShop is ERC721 {
    
      uint256 public tokenId;
      uint256 public prevOwnerTokenID;
      
      mapping(uint256 => uint256) public priceMapping;
      mapping(uint256 => bytes32) tokenIdToOffchainContentHash;
      
      event PetGenerated(address, uint256, uint256, bytes32);
      event BuyPet(uint256, address, address);
      event SuccessfulEtherWithdrawal(uint256, address, bool);
      
       constructor() public
        ERC721("ShanBuilders", "SBRS")
        {}
     
       function generatePet(uint256 _petPrice, bytes32 contentHash) public returns(bool) {
         
         require(msg.sender != address(0), "Please! Check back! Registeration should not be from zero address");
         require(msg.sender == ownerA, "Only contract owner can generate more pets");
         
         tokenId++;
         require(tokenId <= 16, "More than 16 pets is not allowed");
         
         priceMapping[tokenId] = _petPrice;
         tokenIdToOffchainContentHash[tokenId] = contentHash;
         _mint(ownerA, tokenId);
         emit PetGenerated(ownerA, tokenId, _petPrice, contentHash);
         return true;
       }
       
       function checkPrice(uint256 _tokenId) public view returns(uint256) {
           return priceMapping[_tokenId];
       }
       
       function checkHashForAToken(uint256 _tokenId) public view returns(bytes32) {
           return tokenIdToOffchainContentHash[_tokenId];
       }
       
       function buyPet(uint256 _tokenId) public payable returns(bool, string memory) {
           
           prevOwnerTokenID = _tokenId;
           address buyer = msg.sender;
           address _owner = ownerOf(prevOwnerTokenID);
           
           require(buyer != address(0), "Should not be zero address");
           require(_exists(prevOwnerTokenID), "Invalid property Id, not registered");
           require(msg.value == checkPrice(prevOwnerTokenID), "Please Send The Required Value");
           
           withDraw(msg.value);
            _transfer(_owner, buyer, prevOwnerTokenID);
           emit BuyPet(_tokenId, _owner, buyer);
           return (true, "Succesful");
       }
       
       function withDraw(uint256 _amount) public returns(bool) {
       
        address _owner = ownerOf(prevOwnerTokenID);
        require(_amount > 0, "Amount must be valid");
        
        payable(_owner).transfer(_amount);
        emit SuccessfulEtherWithdrawal(_amount, _owner, true);
        return true;
    }
     
}

Note: I would prefer using bytes over string because they consume less gas than strings.. how to solve this problem, I have seen a lot of answers on stackoverflow but actually i'm not getting understand any of them.

2 Answers

Using sha3-224 would make the IPFS CID short enough to store it in bytes32. Example:

  1. Add a file to IPFS and use sha3-224:

    $ ipfs add --cid-version=1 --hash=sha3-224 <file>
    bafybohbdfanrni6fre2bmjeivvwqhhpwq3wkqbxsd25az26qgsda
    
  2. Convert CID to base16:

    $ ipfs cid format -b base16 bafybohbdfanrni6fre2bmjeivvwqhhpwq3wkqbxsd25az26qgsda
    f0170171c23281b16a3c58934162488ad6d039df686eca806f21eba0cebd03486
    
  3. Remove the multibase prefix. It's the first character of the CID (in version 1) and it's f for base16 encodings. See multibase table on GitHub.

    0170171c23281b16a3c58934162488ad6d039df686eca806f21eba0cebd03486

  4. In Solidity

    bytes32 ipfs_cid = 0x0170171c23281b16a3c58934162488ad6d039df686eca806f21eba0cebd03486
    

Alternativly, if you have a CIDv0 (Qm...), you can convert it to CIDv1 and split into a 32-Bit long prefix (excluding multibase prefix) and a 256-Bit long hash and store them seperately.

$ ipfs cid base32 QmccqhJg5wm5kNjAP4k4HrYxoqaXUGNuotDUqfvYBx8jrR # Convert CIDv0 to CIDv1
bafybeiguffkqavstb6lvfkay5ebplabvc73smdwqiwwxouv4zaupv3vbei
$ ipfs cid format -b base16 bafybeiguffkqavstb6lvfkay5ebplabvc73smdwqiwwxouv4zaupv3vbei # Convert from base32 (default) to base16 
f01701220d429550056530f9752a818e902f5803517f7260ed045ad7752bcc828faeea122
bytes4 ipfs_cid_prefix = 0x01701220
bytes32 ipfs_cid_hash = 0xd429550056530f9752a818e902f5803517f7260ed045ad7752bcc828faeea122

Note: When encoding the CID from the contract in your application, don't forget to prepend the multibase prefix back according to the table on GitHub.

Related