RaribleClone Duplicate Value, Unauthorized, RPC error

Hy everyone,

I am encountering the following problems, following the rarible clone tutorial.

When creating a new user, the console spits out:

DuplicateValue
When following the link in the error message:


I can add an Avatar though, and it seems to work. But I would like to understand why this error pops up.

Next is, that when trying to mint, I get this error:
NFTMintRPCRevert

Code:

main.js
Moralis.initialize("VyXM3keXqHlBnaH5FUv8rT6Y9DDMCBBreVc2kyJK");
Moralis.serverURL = 'https://m0e24kwy0shp.moralis.io:2053/server';
const TOKEN_CONTRACT_ADDRESS = "0x53d966c9aC5F55419b99EE8719C1C076157AbE6C";




init = async () => {
 hideElement(userInfo);
 hideElement(createItemForm);
 window.web3 = await Moralis.Web3.enable();
 window.tokenContract = new web3.eth.Contract(tokenContractAbi, TOKEN_CONTRACT_ADDRESS);
 initUser();
}

initUser = async () => {
    if(await Moralis.User.current()){
        hideElement(userConnectButton);
        showElement(userProfileButton);
        showElement(openCreateItemButton);
    } else {
        showElement(userConnectButton);
        hideElement(userProfileButton);
        hideElement(openCreateItemButton);
    }
}

login = async () => {
    try {
        Moralis.Web3.authenticate();
        initUser();
    } catch (error) {
        alert(error);
    }
}

logout = async () => {
    await Moralis.User.logOut();
    hideElement(userInfo);
    init();
}

openUserInfo = async () => {
    user = await Moralis.User.current();
    if (user){
        const email = user.get('email');
        if(email) {
            userEmailField.value = email;
        } else {
            userEmailField.value = "";
        }

        userUsernameField.value = user.get('username');

        const userAvatar = user.get('avatar');
        if(userAvatar) {
            userAvatarImg.src = userAvatar.url();
            showElement(userAvatarImg);
        } else {
            hideElement(userAvatarImg);
        }

        showElement(userInfo);
    }else{
        login();
    }


} 

saveUserInfo = async () => {
    user.set('email', userEmailField.value);
    user.set('username', userUsernameField.value); //There is a check behind the scenes for username duplicate in moralis backend, but is not deep dived here in tutorial!

    if (userAvatarFile.files.length > 0) {
        const avatar = new Moralis.File("avatar.jpg", userAvatarFile.files[0]);
        user.set('avatar', avatar);
      }

    await user.save();
    alert("User info saved successfully!");
    openUserInfo();

}

createItem = async () => {
    if (createItemFile.files.length == 0){
        alert("Please select a file!");
        return;
    } else if (createItemNameField.value.length == 0) {
        alert("Please give the item a name!");
        return;
    }

    const nftFile = new Moralis.File("nftFile.jpg", createItemFile.files[0]);
    await nftFile.saveIPFS();

    const nftFilePath = nftFile.ipfs();
    const nftFileHash = nftFile.hash();

    const metadata = {
        name: createItemNameField.value,
        description: createItemDescriptionField.value,
        image: nftFilePath,
    };

    const nftFileMetadataFile = new Moralis.File("metadata.json", {base64 : btoa(JSON.stringify(metadata))});
    await nftFileMetadataFile.saveIPFS();

    const nftFileMetadataFilePath = nftFileMetadataFile.ipfs();
    const nftFileMetadataFileHash = nftFileMetadataFile.hash();

    const nftId = await mintNft(nftFileMetadataFilePath);

    const Item = Moralis.Object.extend("Item");

    //create new Instance of Item Class
    const item = new Item();
    item.set('name', createItemNameField.value);
    item.set('description', createItemDescriptionField.value);
    item.set('nftFilePath', nftFilePath);
    item.set('nftFileHash', nftFileHash);
    item.set('metadataFilePath', nftFileMetadataFilePath);
    item.set('metadataFileHash', nftFileMetadataFileHash);
    item.set('nftId', nftId);
    item.set('nftContractAddress', TOKEN_CONTRACT_ADDRESS);

    await item.save();
    console.log(item);
}

mintNft = async (metaDataUrl) => {
    const receipt = await tokenContract.methods.createItem(metaDataUrl).send({from: ethereum.selectedAddress});
    console.log(receipt);
    return receipt.events.Transfer.returnValues.tokenId;
}

hideElement = (element) => element.style.display = "none";
showElement = (element) => element.style.display = "block";

// NAVBAR ------------------------------------------------------------
const userConnectButton = document.getElementById("btnConnect");
userConnectButton.onclick = login;

const userProfileButton = document.getElementById("btnUserInfo");
userProfileButton.onclick = openUserInfo;

const openCreateItemButton = document.getElementById("btnOpenCreateItem");
openCreateItemButton.onclick = () => showElement(createItemForm);

//USER PROFILE ------------------------------------------------------------
const userInfo = document.getElementById("userInfo");
const userUsernameField = document.getElementById("txtUserName");
const userEmailField = document.getElementById("txtEmail");
const userAvatarImg = document.getElementById("imgAvatar");
const userAvatarFile = document.getElementById("fileAvatar");

document.getElementById('btnCloseUserInfo').onclick = () => hideElement(userInfo);
document.getElementById('btnLogout').onclick = logout;
document.getElementById('btnSaveUserInfo').onclick = saveUserInfo;

//ITEM CREATION ------------------------------------------------------------
const createItemForm = document.getElementById("createItem");

const createItemNameField = document.getElementById("txtCreateItemName");
const createItemDescriptionField = document.getElementById("txtCreateItemDescription");
const createItemPriceField = document.getElementById("numberCreateItemPrice");
const createItemStatusField = document.getElementById("selectCreateItemStatus");
const createItemFile = document.getElementById("fileCreateItemFile");

document.getElementById('btnCloseCreateItem').onclick = () => hideElement(createItemForm);
document.getElementById('btnCreateItem').onclick = createItem;

init();
token.sol
pragma solidity ^0.8.0;

import "../node_modules/@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "../node_modules/@openzeppelin/contracts/utils/Counters.sol";

contract MyRaribleToken is ERC721 {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;

    constructor () ERC721("MyRarible", "MYRA"){

    }

    struct Item {
        uint256 id;
        address creator;
        string uri;
    }

    mapping (uint256 => Item) public Items;

    function createItem(string memory uri) public returns(uint256) {
        _tokenIds.increment();
        uint256 newItemId = _tokenIds.current();
        _safeMint(msg.sender, newItemId);

        Items[newItemId] = Item(newItemId, msg.sender, uri);

        return newItemId;
    }

     function tokenURI(uint256 tokenId) public view override returns (string memory) {
        require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");

        return Items[tokenId].uri;
    }



}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Morarible</title>
</head>
<body>

    <div>
        <button id="btnConnect">Connect Wallet</button>
        <button id="btnUserInfo">Profile</button>
        <button id="btnOpenCreateItem">Create</button>
    </div>

    <div id="userInfo">
        <h4>User Profile</h4>
        <input type="text" id="txtUserName" required placeholder="Enter Username">
        <input type="text" id="txtEmail" placeholder="Enter Email">
        <small>Optional</small>

        <img width="50px" height="50px" src="" id="imgAvatar" alt="Avatar Image">
        <label for="fileAvatar">Select Avatar</label>
        <input type="file" id="fileAvatar">

        <button id="btnLogout">Logout</button>
        <button id="btnCloseUserInfo">Close</button>
        <button id="btnSaveUserInfo">Save</button> 
    </div>

    <div id="createItem">
        <h4>Create Item</h4>
        <input type="text" id="txtCreateItemName" required placeholder="Enter name">
        <textarea id="txtCreateItemDescription" cols="30" rows="5" placeholder="Enter description" ></textarea>
        <input type="number" min="1" step="1" id="numberCreateItemPrice" placeholder="Enter price" required>

        <label for="selectCreateItemStatus">Status</label>
        <select id="selectCreateItemStatus">
            <option value="0">Not for sale</option>
            <option value="1">Instant buy</option>
            <option value="2">Accept offers</option>
        </select>

        <label for="fileCreateItemFile">Select a file</label>
        <input type="file" id="fileCreateItemFile">

        <button id="btnCloseCreateItem">Close</button>
        <button id="btnCreateItem">Create!</button> 
    </div>

  <script src="https://cdn.jsdelivr.net/npm/web3@latest/dist/web3.min.js"></script>
  <script src="https://unpkg.com/moralis/dist/moralis.js"></script>
  <script src="abi.js"></script>
  <script src="main.js"></script>
    
</body>
</html>

Any help much appreciated. I triple checked with the code in the videos, but can’t find any difference. Ganache is setup correctly, Metamask pops up. FRPC server is running as well and connected to moralis.

I think the first error is that you are trying to register a user with an email that is already used (needs to be unique as the error message says). Let me know if this was the problem.

The reason why it says unauthorized when you browse the URL is that it expects some authentication headers to be present.

1 Like

yes this was the exact reason.
Do you have any idea why the minting is reverting?

1 Like

Hi @Thomas

I was able to use your code and run it form my side.

It’s a error related to metamask not connecting to the right network. Can you confirm that metamask is configured with ganache and also can you confirm that you have followed the reverse proxy setup that was done in PART 7 of the variable clone series video? (Perhaps a screenshot of the frcp.ini configuration file)

Do let me know.

Thanks.

1 Like

Thank you @Malik I will try again. Metamask is configured with Ganache and I do use the frcp.ini, it is connecting successfully to moralis.

frcp.ini
[common]
  server_addr = bfsym9vvcv4b.moralis.io
  server_port = 7000
  token = 2WR2VLDTZX
[ganache]
  type = http
  local_port = 7545
  custom_domains = bfsym9vvcv4b.moralis.io
MetaMaskGanacheConfig

truffle.config
/**
 * Use this file to configure your truffle project. It's seeded with some
 * common settings for different networks and features like migrations,
 * compilation and testing. Uncomment the ones you need or modify
 * them to suit your project as necessary.
 *
 * More information about configuration can be found at:
 *
 * trufflesuite.com/docs/advanced/configuration
 *
 * To deploy via Infura you'll need a wallet provider (like @truffle/hdwallet-provider)
 * to sign your transactions before they're sent to a remote public node. Infura accounts
 * are available for free at: infura.io/register.
 *
 * You'll also need a mnemonic - the twelve word phrase the wallet uses to generate
 * public/private key pairs. If you're publishing your code to GitHub make sure you load this
 * phrase from a file you've .gitignored so it doesn't accidentally become public.
 *
 */

// const HDWalletProvider = require('@truffle/hdwallet-provider');
// const infuraKey = "fj4jll3k.....";
//
// const fs = require('fs');
// const mnemonic = fs.readFileSync(".secret").toString().trim();

module.exports = {
  /**
   * Networks define how you connect to your ethereum client and let you set the
   * defaults web3 uses to send transactions. If you don't specify one truffle
   * will spin up a development blockchain for you on port 9545 when you
   * run `develop` or `test`. You can ask a truffle command to use a specific
   * network from the command line, e.g
   *
   * $ truffle test --network <network-name>
   */

  networks: {
    // Useful for testing. The `development` name is special - truffle uses it by default
    // if it's defined here and no other network is specified at the command line.
    // You should run a client (like ganache-cli, geth or parity) in a separate terminal
    // tab if you use this network and you must also set the `host`, `port` and `network_id`
    // options below to some value.
    //
    development: {
     host: "127.0.0.1",     // Localhost (default: none)
     port: 7545,            // Standard Ethereum port (default: none)
     network_id: "*",       // Any network (default: none)
    },
    // Another network with more advanced options...
    // advanced: {
    // port: 8777,             // Custom port
    // network_id: 1342,       // Custom network
    // gas: 8500000,           // Gas sent with each transaction (default: ~6700000)
    // gasPrice: 20000000000,  // 20 gwei (in wei) (default: 100 gwei)
    // from: <address>,        // Account to send txs from (default: accounts[0])
    // websocket: true        // Enable EventEmitter interface for web3 (default: false)
    // },
    // Useful for deploying to a public network.
    // NB: It's important to wrap the provider as a function.
    // ropsten: {
    // provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/YOUR-PROJECT-ID`),
    // network_id: 3,       // Ropsten's id
    // gas: 5500000,        // Ropsten has a lower block limit than mainnet
    // confirmations: 2,    // # of confs to wait between deployments. (default: 0)
    // timeoutBlocks: 200,  // # of blocks before a deployment times out  (minimum/default: 50)
    // skipDryRun: true     // Skip dry run before migrations? (default: false for public nets )
    // },
    // Useful for private networks
    // private: {
    // provider: () => new HDWalletProvider(mnemonic, `https://network.io`),
    // network_id: 2111,   // This network is yours, in the cloud.
    // production: true    // Treats this network as if it was a public net. (default: false)
    // }
  },

  // Set default mocha options here, use special reporters etc.
  mocha: {
    // timeout: 100000
  },

  // Configure your compilers
  compilers: {
    solc: {
      version: "^0.8.0",    // Fetch exact version from solc-bin (default: truffle's version)
      // docker: true,        // Use "0.5.1" you've installed locally with docker (default: false)
      // settings: {          // See the solidity docs for advice about optimization and evmVersion
      //  optimizer: {
      //    enabled: false,
      //    runs: 200
      //  },
      //  evmVersion: "byzantium"
      // }
    }
  },

  // Truffle DB is currently disabled by default; to enable it, change enabled: false to enabled: true
  //
  // Note: if you migrated your contracts prior to enabling this field in your Truffle project and want
  // those previously migrated contracts available in the .db directory, you will need to run the following:
  // $ truffle migrate --reset --compile-all

  db: {
    enabled: false
  }
};

Everything does look fine. :confused: Can you check out this thread - https://forum.openzeppelin.com/t/metamask-returns-rpc-error-error-ethjs-rpc-rpc-error-with-payload-metamask-shows-failed-object-object/3057/5.

Also, you remembered to import the private key from ganache into your metamask wallet ? Just making sure.

Thanks.

1 Like

I am redoing the whole tutorial, will let you know if it works out.

After starting from fresh and keeping the Dev Environment clean and as organized as can be:

Using the same Ganache Workspace
Using the same localhost port
Using a fresh instance of MetaMask

and following everything in as much detail as possible, and still having patience, as CORS will just pop up like a joker from time to time and try to make your life miserable, haha.

I am very happy to let you know @Malik that I finally was able to mint successfully! Will continue with the tutorial and enjoy every little bit of the ride.

Thanks for helping, I really appreciate the amazing support of all of you!

1 Like