Hardhat and Ethers.js Mint Slow with 25 RPC Requests Per Minted Token

We've successfully deployed an ERC-721 contract to testnet and mainnet BSC, but when calling our Mint function, followed by a contract.tokenURI(tokenId) call, the request times are quite long, and we are limited to about 8 or 9 NFT tokens minted before our script (and our RPC requests) freeze (Hardhat was timing out, but we increased the timeout value).

We're connecting via Hardhat and Ethers.js.

We've also tried using an API service provider and have the same problem. Stats from our API service provider tell us that for every token we mint, followed by the contract.tokenURI(tokenId) call - we are sending 25 API requests!

Can anyone suggest what might be going on here? Or where we could look? At first we thought we were hitting testnet and mainnet RPC limits, but our API service provider does not impose any limits.

This is our 'Mint' function...

async function mintToken (ownerAddress, metadataURI) {
  const contract = await getContract()
  // the smart contract adds the baseUri prefix to all URIs, so make sure it doesn't get added twice
  // this should work whether in ipfs or gateway mode
  metadataURI = stripIpfsUriPrefixOrGateway(metadataURI)

  // Call the mintToken method to issue a new token to the given address
  // This returns a transaction object, but the transaction hasn't been confirmed
  // yet, so it doesn't have our token id.
  const tx = await contract.mintToken(ownerAddress, metadataURI)

  // The OpenZeppelin base ERC721 contract emits a Transfer event when a token is issued.
  // tx.wait() will wait until a block containing our transaction has been mined and confirmed.
  // The transaction receipt contains events emitted while processing the transaction.
  const receipt = await tx.wait()
  for (const event of receipt.events) {
    if (event.event !== 'Transfer') {
      console.log('ignoring unknown event type ', event.event)
      continue
    }
    return event.args.tokenId.toString()
  }

  throw new Error('unable to get token id')
}

@bluu_waters Can you share your contract mint function?
About those 25RPC requests, which methods are being invoked and how many times?

Hi @guyo13 - below are the script and mint functions. As for which methods are being called 25 times for every token, looking at our deploy scripts... we're calling the following vial a set of helper scripts. For each rpc call, we have to 'get the Contract'. We could have optimized this so that the contract was instatiated only one, but the below, as far as I understand, should only have resulted in 4 RPC calls, per token, and not 25! Unfortunately the API service only tells us the number of RPC requests, and not the actual method or endpoint calls.We're using https://web3api.com

'Mint'

  1. const await hardhat.ethers.getContractAt(abi, address, signer)
  2. const tokenId = await contract.mintToken(ownerAddress, metadataURI)

'Retrieve Token URI'

  1. const await hardhat.ethers.getContractAt(abi, address, signer)
  2. return await contract.tokenURI(tokenId)

Solidity mint function...

function mintToken(address ownerAddress, string memory metadataURI)
        public
        onlyOwner
        returns (uint256)
    {
        require(
            _tokenIds.current() < editionLimit,
            "NFT Edition Limit reached."
        );

        _tokenIds.increment();

        uint256 newTokenId = _tokenIds.current();
        _mint(ownerAddress, newTokenId);
        _setTokenURI(newTokenId, metadataURI);
        
        return newTokenId;
    }

Any ideas?

Okay so it looks like hardhat.ethers.getContractAt(abi, address, signer) is making a series of requests.

And so for a starter, we should create a single 'connection' to our contract, or 'Contract' instance, and then issue the subsequent mint token requests from there. We'll try this and then see how many requests are being made per token mint

Okay we have a much better understanding of what's going on here.

For starters, the docs at ethers.js are terrific... this in particular...

An ethers Provider will execute frequent getNetwork calls to ensure the network calls and network being communicated with are consistent.

In the case of a client like MetaMask, this is desired as the network may be changed by the user at any time, in such cases the cost of checking the chainId is local and therefore cheap.

However, there are also many times where it is known the network cannot change, such as when connecting to an INFURA endpoint, in which case, the StaticJsonRpcProvider can be used which will indefinitely cache the chain ID, which can reduce network traffic and reduce round-trip queries for the chain ID.

And so by reducing our 'contractAtAddress calls, and using the StaticJsonRpcProvider, we've significantly reduced our 'per mint' RPC requests.

The next thing we did was use https://github.com/SGrondin/bottleneck to rate limit our requests to ensure that we weren't exceeding limits, or at least set our script to a reasonable number of 'mint' requests per second...

But how much will you pay to mint all your NFTs ?

Hi @Coineo , we won't pay anything, as we're using a 'Lazy Minting' strategy, where the buyer pays to mint the NFT at the time of the purchase. https://nftschool.dev/how-to/lazy-minting/

Yes that's right.
But you still need to deploy your smartcontract ERC-721.
How much will it cost on avg ?