Hardhat Polygon Deployment Issues

I've heavily piggy-backed off of OpenZeppelin's NFT contract to create a custom contract. I don't want to go into too much details around what the contract does.

The issue that I'm having is with deployment and contract calls.
I'm able to deploy the contract to both Ganache and HardHat's localhost. After which I can make contract calls with no problems.

The issue comes up when I try to deploy to Polygon.
Here's a link to the most recent contract I deployed. https://polygonscan.com/address/0x04b4bA0CCd2970780d12605Db30bBC234D2734BA
It seems to be empty?

I then try and run a contract function batchSafeMint. Which, again, works completely fine on localhost and ganache. On Polygon, it seems to be working fine as well.

  function batchSafeMint(address[] memory receivers, string[] memory uris) public radicalWalletOnly {
    uint num_receivers = receivers.length;
    for (uint i = 0; i < num_receivers; i++) {
      if (receivers[i] != deployer && receivers[i] != vendor) {
        if (balanceOf(receivers[i]) > maxPerks) {
          continue;
        }
      }
      uint256 tokenId = _nextTokenId++;
      _safeMint(receivers[i], tokenId);
      _setTokenURI(tokenId, uris[i]);
    }
  }

After this call I try calling the token uri function tokenURI which then returns 2023-12-18T23:39:45.156Z error: Error: could not decode result data (value="0x", info={ "method": "tokenURI", "signature": "tokenURI(uint256)" }, code=BAD_DATA, version=6.9.0)

I'm not sure what I'm doing wrong here. After Googling around, seems like most people who hit this issue was due to selecting the wrong network. I double checked that the network on the ethers objects was all set to Polygon. I also set the environment variable HARDHAT_NETWORK to polygon.

I'm calling the scripts to deploy, mint, etc... with HardHat's run command.

Here' the deploy function

require('dotenv').config()
const { getOwner } = require('./helpers/getContractOwner')
const getTxOptions = require('./helpers/getTxOptions')
const logger = require('../../lib/logger')

const deployPerk = async (hre, contractName, contractSymbol, contractReward, baseUri, vendorAddress) => {
  logger.info(`contract name: ${contractName}`)
  logger.info(`contract symbol: ${contractSymbol}`)
  logger.info(`contract reward: ${contractReward}`)
  logger.info(`baseUri: ${baseUri}`)
  logger.info(`vendor address: ${vendorAddress}`)

  try {
    const owner = await getOwner(hre)
    const contract = await hre.ethers.deployContract(
      'Perks', [vendorAddress, contractName, contractSymbol, baseUri, contractReward], owner)
    const contractAddress = contract.target
    const txHash = contract.deploymentTransaction().hash

    return { contractAddress, txHash, rType: 'deploy-perk' }
  } catch (error) {
    logger.error(error.toString())
    return { contractAddress: null, txHash: null, rType: 'deploy-perk', error: error.toString() }
  }
}

module.exports = deployPerk

And the batchSafeMint script...

require('dotenv').config()
const getTxOptions = require('./helpers/getTxOptions')
const { getOwner, getContract } = require('./helpers/getContractOwner')
const logger = require('../../lib/logger')

const mintPerks = async (hre, contractAddress, toAddresses, uris) => {
  logger.info(`contractAddress: ${contractAddress}`)
  logger.info(`toAddresses: ${JSON.stringify(toAddresses)}`)
  logger.info(`uris: ${JSON.stringify(uris)}`)
  try {
    const owner = await getOwner(hre)
    const contract = await getContract(hre, contractAddress, owner)
  
    const txOptions = await getTxOptions(10000000)
    txOptions.nonce = await hre.ethers.provider.getTransactionCount(owner.address)
  
    let tx = await contract.connect(owner).batchSafeMint(toAddresses, uris, txOptions)
    const txHash = tx.hash

    // let tx = await contract.tokenURI(0)
    // console.log(tx)

    // let wtx = await tx.wait()
    // console.log(wtx)
  
    return { txHash, rType: 'mint-perks' }
  } catch (error) {
    logger.error(error.toString())
    return { txHash: null, rType: 'mint-perks', error: error.toString() }
  }
}

module.exports = mintPerks

and this is how I'm getting the contract for contract calls

const getContract = async (hre, contractAddress, owner) => {
  try {
    const contractABI = await JSON.parse(fs.readFileSync(path.join(dirname, `/factory/artifacts/contracts/Perks.sol/Perks.json`), 'utf-8'))
    // console.log(contractABI.abi)
    const contract = await new hre.ethers.Contract(contractAddress, contractABI.abi, owner)
    // const contract = await hre.ethers.getContract(hre, contractAddress, owner)
    // console.log(owner)
    // const contract = await hre.ethers.getContractAt(contractABI.abi, contractAddress, owner)
    return contract
  } catch (error) {
    logger.error(error.toString())
    return null
  }
}

Any ideas around this would be much appreciated!

The linked webpage shows that there is no contract deployed at this address.

In order to double-verify this, I tried:

const Web3 = require("web3");

async function run() {
    const web3 = new Web3("https://polygon-mainnet.infura.io/v3/MY_API_KEY");
    const code = await web3.eth.getCode("0x04b4bA0CCd2970780d12605Db30bBC234D2734BA");
    console.log(code);
}

run();

And I got "0x", which means that there is no byte-code deployed at this address.


I've found only one contract deployed by the wallet from the link above, and it's here.

In fact, this contract does seem to implement several ERC721 functions, such as:

  • Function name, which returns the string "RadicalTestNFT2"
  • Function symbol, which returns the string "RTN2"

Unfortunately, assuming that you've inherited (or copied) OpenZeppelin's implementation of ERC721, function tokenURI can only be called by the owner of the input token ID, so I am unable to verify that this contract indeed implements that function.

Hmm really hard to tell why this isn't going through.
Would anyone know if there's a way to set the gasLimit and gasPrice with ethers.deployContract?
I've looked into it and I don't think there's a way. Only thing I know to do is to set the values in HardHat's config file. However, I can't get the most up-to-date gas prices this way. When I make subsequent contract calls, I get the gas prices using https://api.polygonscan.com/api?${queryParams}apikey=${process.env.POLYGON_SCAN_API_KEY} Where queryParams is module=gastracker&action=gasoracle&

  1. How is that related to any of what you asked in your original question?
  2. Did you bother at all to read anything of what I wrote in the response?

Apologies, I jumped the gun here.
Yes I did read your response, thank you for replying and trying to help me figure this out.

Based on what's been observed, it seems like the contract did not deploy after making the call. (Since there is no bytecode) I'm not sure why. I have assumptions... Maybe the transaction never got picked up by a validator? Perhaps const contractAddress = contract.target changed after the transaction was validated? Maybe I need to specify the gas price in HardHat's config file?

The contract should be implementing name and symbol as it is inheriting ERC721 from OpenZeppelin. contract Perks is IERC4906, ERC721, ERC721Enumerable, Ownable {

  constructor(
    address initialOwner,
    string memory _name,
    string memory _symbol,
    string memory _baseURI,
    string memory _reward
  )
    ERC721(_name, _symbol)
    Ownable(initialOwner)
  {
    deployer = _msgSender();
    vendor = initialOwner;
    baseUri = _baseURI;
    reward = _reward;
    revoked = false;
    claimUri = "";
  }

The reason why I brought up the post above is because I was having issues previously with Polygon where sometimes it would take more than half a day for the transaction to complete. I thought maybe this issue could be circumvented by specifying the gasLimit and the gasPrice when deploying the contract as this is how I've sped up contract calls.

The issue that I'm trying to solve here is the inconsistency of the contract deployment to Polygon. I need this system to be as reliable as possible. Currently I'm handling the contract deployment and subsequent contract calls by querying the transaction hash and checking to see if it's been set to a block.

I deployed another contract earlier today, but this time I called await contract.waitForDeployment. This worked, however, this is not going to work for what we're trying to build. Hence, I've opted to get the transaction hash and run a cronjob loop to wait for the transaction to complete before making contract calls.