Implementing percentage-based commission system in NFT trading contract

I’m working on an NFT trading platform and need help with switching from a fixed fee structure to a percentage-based one. Originally I had a static 0.1 ETH fee for each transaction, but now I want to implement a 2.5% commission on all sales.

Here’s my current smart contract function:

function executeSale(address tokenContract, uint256 marketId, uint256 platformFee)  
    public  
    payable  
    nonReentrant  
{
    uint256 salePrice = marketItems[marketId].price;
    uint256 nftId = marketItems[marketId].tokenId;
    require(
        msg.value == salePrice,
        "Payment amount must match the listed price"
    );
    marketItems[marketId].seller.transfer(msg.value);
    IERC721(tokenContract).transferFrom(address(this), msg.sender, nftId);
    marketItems[marketId].owner = payable(msg.sender);
    marketItems[marketId].sold = true;
    totalSold.increment();
    payable(owner).transfer(platformFee);
}

And here’s my frontend code that calls the contract:

async function purchaseToken() {
    const modal = new Web3Modal();
    const connection = await modal.connect();
    const web3Provider = new ethers.providers.Web3Provider(connection);
    
    const userSigner = web3Provider.getSigner();
    const marketContract = new ethers.Contract(marketplaceAddress, MarketABI.abi, userSigner);
    
    const tokenPrice = ethers.utils.parseUnits(token.price.toString(), "ether");
    const commissionRate = +((2.5 / 100) * token.price);
    let serviceFee = commissionRate.toString();
    
    let txn = await marketContract.executeSale(tokenAddress, token.marketId, serviceFee, {
        value: tokenPrice,
    });
    
    await txn.wait();
    window.location.replace("/dashboard");
}

The transaction keeps failing at the commission transfer line with “function call failed to execute” error. What am I missing here?

Wait, I think I see another issue causing your headaches… does your contract balance have enough ETH to cover both transfers?

You’re checking msg.value == salePrice but then trying to transfer the full salePrice to the seller AND send a separate platformFee to the owner. That’s spending more than what was sent to the contract, right?

How did the fixed 0.1 ETH fee work before? Did buyers send salePrice + 0.1 ETH total, or was the 0.1 ETH deducted from the salePrice?

If buyers sent extra ETH for fees before, you’ll need to adjust your frontend to send tokenPrice + serviceFee instead of just tokenPrice. If the fee was always deducted from sale price, then elias87’s approach makes more sense.

Check what the actual msg.value vs your transfer amounts are when the transaction fails. That’ll help narrow down if it’s a calculation or balance issue. Also wondering if your marketItems[marketId].price is stored in wei or ether units - that could mess up the math.

another problem: you’re paying the seller before taking out fees. if the seller’s transfer fails, you’ll lose the platform fee too. always grab your fees first, then pay the seller. and throw some reentrancy protection around those transfers.

Your fee calculation and contract logic are the problem. You’re calculating commission in JavaScript as a decimal, but your contract expects wei values. When you do +((2.5 / 100) * token.price), you get a decimal that doesn’t convert to wei properly.

I hit this exact issue building my own marketplace. Fix it by handling percentage calculations entirely in Solidity instead of JavaScript. Modify your contract to calculate commission internally:

function executeSale(address tokenContract, uint256 marketId) public payable nonReentrant {
    uint256 salePrice = marketItems[marketId].price;
    uint256 platformFee = (salePrice * 250) / 10000; // 2.5% commission
    uint256 sellerAmount = salePrice - platformFee;
    
    require(msg.value == salePrice, "Payment amount must match the listed price");
    
    marketItems[marketId].seller.transfer(sellerAmount);
    payable(owner).transfer(platformFee);
    // ... rest of your logic
}

Then just remove the commission parameter from your frontend call completely. This kills the floating-point precision issues and keeps fee calculation transparent on-chain.