I'm building a presale dApp as an experiment, and when I hit the buyButton, MetaMask successfully opens for me to approve spending my USDT (labelled a contract interaction). However the transaction is empty aside from showing gas fees. I'm able to approve this without any errors even though it's essentially empty. Shortly after, the buyTokens interaction opens in MM, same again - empty aside from gas fees. This one fails, as expected.

I know the contract is being detected to a certain point because my IDE, console log and MM all recognise the functions I'm trying to call. I'm also managing to pull a number of datapoints from the contract to display in my dApp.

Thanks in adv!

document.getElementById("buyButton").addEventListener("click", async () => {

    try {
      const usdtEther = document.getElementById("usdtAmount");
      // Convert the input amount to Wei (assuming USDT has 18 decimal places)
      const usdtX = usdtEther.value;
      const usdtAmount = ethers.parseUnits(usdtX, 6);
      const usdtString = usdtAmount.toString();
      console.log('USDT STRING:', usdtString);
      const buyProv = new ethers.BrowserProvider(window.ethereum);
      const buySign = await buyProv.getSigner();
      const buyContract = new ethers.Contract(presaleContractAddress, presaleContractAbi, buySign);
      const userAddress = await buySign.getAddress();
      const nonce = await provider.getTransactionCount(userAddress, 'latest');
      console.log('userAdd', userAddress);
      // Transaction options (gasPrice, gasLimit, etc.)
      const txOptions = {
        gasLimit: 200000, 
        gasPrice: ethers.parseUnits("20", "gwei"), // Set the gas price in Gwei
        chainId: 97, 
        value: 0,
        nonce: nonce,
        sendTransaction: true,
      console.log('txOptions:', txOptions);

      const approveUsdt = await buyContract.approveUSDT(usdtAmount, { ...txOptions, });
      await approveUsdt.wait();
      const buyTokens = await buyContract.buyTokens(usdtAmount, {
      await buyTokens.wait();
      // Send the transaction to the 'buyTokens' function
      alert("Tokens purchased successfully!");
    } catch (error) {
      console.error("Error purchasing tokens:", error);
      alert("Error purchasing tokens. See console for details.");
 // SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract Presale is Ownable, Pausable, ReentrancyGuard {
    IERC20 public kooToken;
    IERC20 public usdtToken;

    uint256 public constant presalePrice = 793700000000000; // This represents 0.0007937 USDT with 7 decimal precision
    uint256 public constant tokensToSell = 6300000000000000000000000000; // 6.3 billion KOO with 18 decimals

    uint256 public totalTokensSold;

    event TokensPurchased(address indexed buyer, uint256 amount);
    event TokensWithdrawn(address indexed owner, uint256 amount);
    event FundsWithdrawn(address indexed owner, uint256 amount);

    constructor(address _kooToken, address _usdtToken) {
        kooToken = IERC20(_kooToken);
        usdtToken = IERC20(_usdtToken);

    // Approval function to allow the contract to spend the buyer's USDT
   function approveUSDT(uint256 usdtAmount) external whenNotPaused {
    require(usdtAmount > 0, "Amount must be greater than 0");

    // Check the current allowance
    uint256 currentAllowance = usdtToken.allowance(msg.sender, address(this));

    // Calculate the new allowance needed
    uint256 newAllowance = usdtAmount;
    // If the current allowance is already sufficient, no need to change it
    if (newAllowance > currentAllowance) {
        // Approve the contract to spend the buyer's USDT
        require(usdtToken.approve(address(this), newAllowance - currentAllowance), "USDT approval failed");
    function buyTokens(uint256 usdtAmount) external whenNotPaused nonReentrant {
        require(usdtAmount > 0, "Amount must be greater than 0");

        uint256 tokensToBuy = usdtAmount * 10**6 / presalePrice; 
        uint256 remainingTokens = tokensToSell - totalTokensSold;

        require(tokensToBuy <= remainingTokens, "Not enough tokens left for sale");

        // Transfer USDT from buyer to this contract using transferFrom
        require(usdtToken.transferFrom(msg.sender, address(this), usdtAmount), "USDT transfer failed");

        // Transfer KOO tokens from this contract to the buyer
        kooToken.transfer(msg.sender, tokensToBuy);

        totalTokensSold += tokensToBuy;

        emit TokensPurchased(msg.sender, tokensToBuy);

      function withdrawUnsoldTokens() external onlyOwner {
        uint256 unsoldTokens = tokensToSell - totalTokensSold;
        require(unsoldTokens > 0, "No unsold tokens left");

        kooToken.transfer(owner(), unsoldTokens);

        emit TokensWithdrawn(owner(), unsoldTokens);

    function withdrawUSDT() external onlyOwner {
        uint256 contractBalance = usdtToken.balanceOf(address(this));
        require(contractBalance > 0, "No USDT to withdraw");

        usdtToken.transfer(owner(), contractBalance);

        emit FundsWithdrawn(owner(), contractBalance);

    function withdrawAccidentallySentTokens(address _tokenAddress) external onlyOwner {
        IERC20 token = IERC20(_tokenAddress);
        uint256 contractBalance = token.balanceOf(address(this));
        require(contractBalance > 0, "No tokens to withdraw");

        token.transfer(owner(), contractBalance);

        emit TokensWithdrawn(owner(), contractBalance);

    function pause() external onlyOwner {

    function unpause() external onlyOwner {

    // Fallback function to receive Ether (if needed)
    receive() external payable {}


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.