soliditypolygonchainlink

Error in Minting NFTs on Polygon Network: Suspecting Price Conversion Issue


I've deployed a contract on the Polygon network to mint NFTs, but I'm encountering an issue where only one of the transactions succeeds, while subsequent attempts result in errors. Here are the transaction hashes:

Upon analysis, I suspect the issue might be related to how I'm fetching the price of MATIC in USD. Interestingly, I haven't made any changes to this process during the successful minting. It's worth mentioning that the contract's mint function operates flawlessly in a local development environment as well as on the testnet.

Here's the relevant section of my PriceConverter file, where I handle price conversion:

/**
 * @dev Get the latest price from a Chainlink Price Feed and convert it to a fixed-point integer.
 * @param _priceFeed The Chainlink Price Feed interface.
 * @return The latest price in fixed-point integer format (with 10 additional decimal places).
 */
function getPrice(AggregatorV3Interface _priceFeed) internal view returns (uint256) {
    unchecked {
        try _priceFeed.latestRoundData() returns   
        (
            uint80 roundId,
            int256 answer,
            uint256,
            uint256 ,
            uint80 answeredInRound
        ){
            if(answer <= 0)  revert AnswerIsNotGreaterThanZero();
            if(roundId != answeredInRound) revert InconsistentRoundId();
            return uint256(answer);
        } catch {
            revert CantFetchLatestRoundData();
        }
    }
}

/**
 * @dev Convert an amount in MATIC to its equivalent value in USD using a Chainlink Price Feed.
 * @param _maticAmount The amount in MATIC to convert.
 * @param _priceFeed The Chainlink Price Feed interface.
 * @return The equivalent value in USD (with 10 additional decimal places).
 */
function getConversionRate(uint256 _maticAmount, AggregatorV3Interface _priceFeed) internal view returns (uint256) {
    unchecked {
        uint256 maticPrice = getPrice(_priceFeed);
        uint256 decimals = _priceFeed.decimals();
        uint256 maticAmountInUsd = (maticPrice * _maticAmount) / 10 ** decimals;
        // The actual MATIC/USD conversion rate, after adjusting the extra 0s.
        return maticAmountInUsd;
    }
}

/**
 * @dev Convert an amount in USD to its equivalent value in MATIC using a Chainlink Price Feed.
 * @param _usdAmount The amount in USD to convert.
 * @param _priceFeed The Chainlink Price Feed interface.
 * @return The equivalent value in MATIC (with 10 additional decimal places).
 */
function getMaticValueFromUsd(uint256 _usdAmount, AggregatorV3Interface _priceFeed) internal view returns (uint256) {
    unchecked {
        uint256 maticPrice = getPrice(_priceFeed);
        uint256 decimals = _priceFeed.decimals();
        uint256 maticValue = (_usdAmount * 10 ** decimals) / maticPrice;
        // The actual USD/MATIC conversion rate, after adjusting the extra 0s.
        return maticValue; 
    }
}

And this is the Mint function:

function mint() external payable {
    uint16 localNftQuantityMinted = nftQuantityMinted;
    uint256 nftPriceInMatic = nftPriceUsd.getMaticValueFromUsd(priceFeed);
    // CHECKS
    if(msg.sender != owner()){
        unchecked { if(nftCountPerAddress[msg.sender] >= maxNftsAllowedPerUser) revert Only10NFTsPerAddress(); }
    }
    unchecked { if(localNftQuantityMinted >= maxNftsAllowed) revert AllNFTsMinted(); }
    unchecked { if(msg.value < nftPriceInMatic) revert InsufficientFunds({required: nftPriceUsd}); }
    unchecked { if(msg.value > nftPriceInMatic) revert UserIsOverpaying({required: nftPriceUsd}); }

    // EFFECTS 

    // Mark the ID as minted
    _mintedIds[localNftQuantityMinted] = true ; 
    unchecked { ++nftCountPerAddress[msg.sender]; }
    ++nftQuantityMinted;

    // INTERACTIONS 

    // Transfer MATIC to this contract from the sender account

    bytes memory data = bytes("0x");

    _mint(msg.sender, localNftQuantityMinted, 1, data);
    emit NFTMinted(msg.sender, localNftQuantityMinted);

    // Set royalty for the token minted
    _setTokenRoyalty(localNftQuantityMinted, safe, 1000);
}

Could someone please help me identify what might be causing this issue?


Solution

  • As, you've mentioned in the discussion, the conversion rate on testnet is not changing rapidly as compared to mainnet, and that's mainly because of the speed of the rounds possibly due to the submission of round data by higher number of nodes and faster confirmations in case of mainnet.

    One way to handle with this is to remove the revert of UserIsOverpaying error:

    unchecked { if(msg.value > nftPriceInMatic) revert UserIsOverpaying({required: nftPriceUsd}); }
    

    Instead, replace it with the logic to refund the user with the overpaid amount, like:

    if (msg.value > nftPriceInMatic) {
        // Transfer MATIC equivalent to (msg.value - nftPriceInMatic) back to the sender account
        (bool callSuccess, ) = payable(msg.sender).call{
            value: msg.value - nftPriceInMatic
        }("");
        require(callSuccess, "Call failed");
    }
    

    So, in this case, you can encourage the user to pay slightly higher than expected value (i.e., showing at the time of when they're checking the equivalent value of MATIC in USD), so as to avoid the discrepancy of changing in the value at the time of function execution. And, moreover, the buffer amount will always be refunded to the user.

    I've created the gist for the minimum reproducible code. And here is the transaction when I called the mintTest() function in which I passed 0.02 MATIC as value, but the getMaticValueFromUsd(10000000000000000) was showing around 0.014 MATIC, so it refunded the remaining 0.006 MATIC to my wallet.