How to Troubleshoot "!BAL20" Error When Buying NFTs on Marketplace v3

Overview

When purchasing NFTs from a thirdweb Marketplace v3 contract, you might encounter a TransactionError: Error - !BAL20 error. This guide explains what causes this error and how to resolve it.

Understanding the Error

The !BAL20 error occurs during transaction validation when either:

  1. The buyer's wallet doesn't have enough token balance, OR
  2. The buyer hasn't provided sufficient allowance for the marketplace contract to spend their tokens

Root Cause

The error is thrown from this contract function:

solidity

function _validateERC20BalAndAllowance(address _tokenOwner, address _currency, uint256 _amount) internal view {
    require(
        IERC20(_currency).balanceOf(_tokenOwner) >= _amount &&
            IERC20(_currency).allowance(_tokenOwner, address(this)) >= _amount,
        "!BAL20"
    );
}

Common Pitfalls

  1. Token Decimal Mismatch: The most common cause is incorrectly calculating the token amount, especially when converting between human-readable values and on-chain values.
  2. TransactionButton Timing: When using the TransactionButton component, approval transactions and purchase transactions may have timing issues.
  3. Contract Addresses: Using incorrect spender addresses for approvals.

Step-by-Step Troubleshooting

1. Verify Token Decimals

Always explicitly get the token decimals and use them for calculations:

javascript

// Get token decimals
const tokenDecimals = await decimals({ contract: tokenContract });
console.log(`Token decimals: ${tokenDecimals}`);

// Convert price to raw amount
const rawAmount = BigInt(Math.floor(price * 10**tokenDecimals));

2. Check Balance and Allowance

Explicitly check both balance and allowance before proceeding:

javascript

// Check wallet balance
const walletBalance = await balanceOf({
  contract: tokenContract,
  address: walletAddress,
});

// Check current allowance
const currentAllowance = await allowance({
  contract: tokenContract,
  owner: walletAddress,
  spender: marketplaceAddress,
});

// Log values for debugging
console.log(`Price: ${rawAmount} raw units`);
console.log(`Wallet balance: ${walletBalance} raw units`);
console.log(`Current allowance: ${currentAllowance} raw units`);

// Verify both conditions
if (walletBalance < rawAmount) {
  throw new Error(`Insufficient token balance. Need ${rawAmount} but have ${walletBalance}`);
}

if (currentAllowance < rawAmount) {
  // Need to increase allowance
}

3. Separate Approval and Purchase

For more reliable transactions, separate the approval and purchase steps:

javascript

// 1. First handle approval if needed
if (currentAllowance < rawAmount) {
  const approveTx = await approve({
    contract: tokenContract,
    spender: marketplaceAddress,
    amount: rawAmount,
  });
  
  // Wait for approval to be confirmed
  const approvalReceipt = await sendAndConfirmTransaction({
    transaction: approveTx,
    account: activeAccount,
  });
  
  console.log('Approval confirmed in block', approvalReceipt.blockNumber);
}

// 2. Then handle the purchase separately
console.log('Creating purchase transaction');
const purchaseTx = await buyFromListing({
  contract: marketplaceContract,
  listingId: BigInt(listingId),
  quantity: quantity,
  recipient: walletAddress,
});

const purchaseReceipt = await sendAndConfirmTransaction({
  transaction: purchaseTx,
  account: activeAccount,
});

4. Verify Listing Details

Ensure you're working with valid listing data:

javascript

// Get listing details
const listing = await getListing({
  contract: marketplaceContract,
  listingId: BigInt(listingId),
});

console.log('Listing details:', {
  currencyAddress: listing.currencyValuePerToken.currency,
  pricePerToken: listing.currencyValuePerToken.value,
  quantity: listing.quantity,
});

Additional Tips

  1. Use Larger Allowances: Consider approving more tokens than needed to prevent future approval transactions.
  2. Check Listing Status: Ensure the listing is still active and has available quantity.
  3. Network Consistency: Verify all operations are on the same network.
  4. Test with Manual Transactions: If TransactionButton causes issues, try manual transactions first to isolate the problem.

Conclusion

The !BAL20 error is most commonly caused by insufficient balance or allowance issues. By carefully verifying token amounts, ensuring proper approvals, and separating approval from purchase transactions, you can resolve this error and successfully complete NFT purchases on thirdweb Marketplace v3.