Payable NFT minting function failing in dual mint smart contract

Payment Transfer Issue in NFT Minting Contract

I’m working on a Solidity NFT contract that has two different minting mechanisms. One mint requires ownership of a specific soulbound token, and the other is a paid mint where users pay in ETH or MATIC. The soulbound minting works fine, but I’m having trouble with the paid version.

Here’s my paid minting function:

function mintPaidNft() public payable whenNotPaused {
    require(
        tokenPrice > 0, "Token price not configured"
    );

    require(
        keccak256(bytes(approvedUsers[msg.sender].username)) !=
            keccak256(""),
        "User not in approved list"
    );

    require(
        keccak256(bytes(approvedUsers[msg.sender].account_type)) == keccak256(bytes("purchaser")),
        "Only purchasers allowed to mint"
    );
    uint256 userBalance = address(msg.sender).balance;
    require(
        userBalance >= tokenPrice,
        "insufficient funds for NFT purchase"
    );

    
    uint256 newTokenId = _idCounter.current();
    _idCounter.increment();
    tokenOwners[newTokenId] = msg.sender;

    address payable contractWallet = payable(address(this));
    contractWallet.transfer(tokenPrice);

    _safeMint(msg.sender, newTokenId);
}

My test setup looks like this:

describe("User has sufficient balance for minting", async function() {
     beforeEach(async function() {
         await this.nftContract.setTokenPrice(ethers.utils.parseEther("0.1"));
      });
      it("should successfully mint token", async function() {
         const transaction = await this.nftContract.connect(this.testUser).mintPaidNft();
         const receipt = await transaction.wait();
         expect(receipt.status).to.equal(1);
      });
});

The test account has plenty of ETH (999+ from Hardhat), and the price is set to 0.1 ETH. But when the test runs, I get a revert error at the contractWallet.transfer(tokenPrice); line.

What could be causing this transfer to fail? The user has enough balance and the amount seems correct.

Hmm, this is interesting! I’m curious about something nobody else mentioned - why check address(msg.sender).balance instead of msg.value?

Yeah everyone’s right about the transfer issue, but there’s another weird thing. You’re checking if the user has enough ETH in their wallet, but not requiring them to actually send it to your contract. What if someone has 10 ETH but only sends 0.01 ETH with the transaction? Your check would pass even though they didn’t pay the full price.

Also - what happens when that transfer line fails? Do you get a specific error or just a generic revert? Have you tried logging the contract’s balance before the transfer attempt?

One more thing - why do you have that tokenOwners[newTokenId] = msg.sender; mapping? Doesn’t ERC721 already track ownership through _owners? Are you using that mapping for something specific later?

Just trying to understand the full picture because there might be other issues lurking around!

you’re not sending any ETH with your transaction. your function needs payment through msg.value, but your test doesn’t include the value parameter. add {value: ethers.utils.parseEther("0.1")} to your test call and ditch that transfer line - it’s trying to send money from the contract to itself, which is pointless.

Your transfer logic is wrong. You’re checking the sender’s balance but then trying to transfer from the contract to itself - that’s pointless. msg.value is the ETH sent with the transaction, not some balance you need to transfer separately. Fix your function by changing the balance check to require(msg.value >= tokenPrice, "insufficient payment"); and delete that transfer line completely. The ETH gets sent to the contract automatically when someone calls the function. For your test, make sure you’re passing the value right: await this.nftContract.connect(this.testUser).mintPaidNft({value: ethers.utils.parseEther("0.1")});. That’s how payable functions work - they handle the ETH transfer when you include the value.