2

This is a pretty noob question which I thought wouldn't take too much time to answer through research. I am working on my first set of dapp building with basic javascript and solidity. Through it, I've been able to connect a wallet and send eth via javascript to a contract having a payable donate function. Yay.

I am now trying to send an ERC20 token to my contract. I have been able to get approval to spend the ERC20 token through my javascript. I am now attempting to send the token to my solidity contract (using ethers) but for the life of me am failing. I have tried every which way I can think of (via google-fu) but am constantly getting " Cannot read properties of undefined" as an error or "TypeError: contract.transferTokens is not a function". Essentially, I can't seem to get it to recognize the function of the contract. This is the full solidity contract:

//SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract TransferContract {
    function transferFrom(address recipient, uint256 amount) public {
        address token = 0x78867BbEeF44f2326bF8DDd1941a4439382EF2A7; IERC20(token).transferFrom(msg.sender, recipient, amount);
    }

   function transferTokens(uint256 _amount) public {
       require(_amount > 0);
       uint256 input = _amount; //It's reverting here?? I don't see the issue
       address token = 0x78867BbEeF44f2326bF8DDd1941a4439382EF2A7;
       IERC20(token).transferFrom(msg.sender, 0x4B8C40757A00eD0479e4B8293C61d8178E23d2f1, input);
   }
}

Here is the javascript line where I am trying to get something from it.

const _return = await contract.transferTokens(10000);

Please have mercy on this poor soul as I can't seem to find anything to help me. LOL.

Thank you!

  • Can you please provide the full js code, and also network where the contract `0x78867BbEeF44f2326bF8DDd1941a4439382EF2A7` is deployed? Pretty sure there's something bad with your js, since I've just tried deploying and interacting on a small project (https://github.com/Frenzoid/labs/tree/master/SOLIDITY/dapp) , and it works for me. I haven't tested calling the token contract `IERC20(token).transferFrom`, since idk on which network is deployed. – MrFrenzoid Jun 06 '22 at 23:54
  • Thank you soooo much! The javascript is on pastebin at https://pastebin.com/kg9mKqH7 It's the BUSD on the testnet binance chain. Fairly simple and I can approve the BUSD to allow the contract to spend it. The first and second button works but the last one is where it dies. – DevSolidityDude Jun 08 '22 at 00:49
  • I just tried the code on your github. The result was a thorough laugh by the Solidity and Javascript gods at my frustration as I failed. As a note of differences, I am using online Remix IDE for my contract development and VS Code for my html/js IDE with IIS as my server. With that said, I have 0 server side stuff in this code and only the html/js in the file structure that you used. I doubt that bears anything to what is going on but at this point I might try to find some small animal to sacrifice and do a dance around my computer in a loincloth to appease the gods that I've offended. – DevSolidityDude Jun 08 '22 at 01:33

2 Answers2

1

From your Pastebin, there seems to be something wrong with your abi, I recompiled the contract and used the abi produced by hardhat, and now it works!

Also, I've changed the place where you initialize the signer since getting it when you approve would force anyone who transfers to approve first or the second method won't work, even if that user has BUSD approved from beforehand.

I couldn't get to run it since I don't have any BUSD or BNB on my acc, but from what I could see, the execution was successful, since I get an error telling me that the transfer amount exceeds the allowance :)

enter image description here

This is triggered by calling the busd contract from your contract.

IERC20(token).transferFrom(
        msg.sender,
        0x4B8C40757A00eD0479e4B8293C61d8178E23d2f1,
        input
    );

<!DOCTYPE html>
<html lang="en">

<head>
  <script src="https://cdn.ethers.io/lib/ethers-5.2.umd.min.js" type="application/javascript"></script>
  <script>
    //const { ethers } = require("ethers");
    const busdABI = [
      {
        "constant": true,
        "inputs": [],
        "name": "name",
        "outputs": [{
          "name": "",
          "type": "string"
        }],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
      },
      {
        "constant": true,
        "inputs": [],
        "name": "assetProtectionRole",
        "outputs": [{
          "name": "",
          "type": "address"
        }],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
      },
      {
        "constant": true,
        "inputs": [],
        "name": "decimals",
        "outputs": [{
          "name": "",
          "type": "uint8"
        }],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
      },
      {
        "constant": true,
        "inputs": [],
        "name": "paused",
        "outputs": [{
          "name": "",
          "type": "bool"
        }],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
      },
      {
        "constant": true,
        "inputs": [],
        "name": "owner",
        "outputs": [{
          "name": "",
          "type": "address"
        }],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
      },
      {
        "constant": true,
        "inputs": [],
        "name": "symbol",
        "outputs": [{
          "name": "",
          "type": "string"
        }],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
      },
      {
        "constant": true,
        "inputs": [],
        "name": "betaDelegateWhitelister",
        "outputs": [{
          "name": "",
          "type": "address"
        }],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
      },
      {
        "constant": true,
        "inputs": [],
        "name": "proposedOwner",
        "outputs": [{
          "name": "",
          "type": "address"
        }],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
      },
      {
        "constant": true,
        "inputs": [],
        "name": "EIP712_DOMAIN_HASH",
        "outputs": [{
          "name": "",
          "type": "bytes32"
        }],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
      },
      {
        "constant": true,
        "inputs": [],
        "name": "supplyController",
        "outputs": [{
          "name": "",
          "type": "address"
        }],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
      },
      {
        "inputs": [],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "constructor"
      },
      {
        "anonymous": false,
        "inputs": [{
          "indexed": true,
          "name": "from",
          "type": "address"
        },
        {
          "indexed": true,
          "name": "to",
          "type": "address"
        },
        {
          "indexed": false,
          "name": "value",
          "type": "uint256"
        }],
        "name": "Transfer",
        "type": "event"
      },
      {
        "anonymous": false,
        "inputs": [{
          "indexed": true,
          "name": "owner",
          "type": "address"
        },
        {
          "indexed": true,
          "name": "spender",
          "type": "address"
        },
        {
          "indexed": false,
          "name": "value",
          "type": "uint256"
        }],
        "name": "Approval",
        "type": "event"
      },
      {
        "anonymous": false,
        "inputs": [{
          "indexed": true,
          "name": "currentOwner",
          "type": "address"
        },
        {
          "indexed": true,
          "name": "proposedOwner",
          "type": "address"
        }],
        "name": "OwnershipTransferProposed",
        "type": "event"
      },
      {
        "anonymous": false,
        "inputs": [{
          "indexed": true,
          "name": "oldProposedOwner",
          "type": "address"
        }],
        "name": "OwnershipTransferDisregarded",
        "type": "event"
      },
      {
        "anonymous": false,
        "inputs": [{
          "indexed": true,
          "name": "oldOwner",
          "type": "address"
        },
        {
          "indexed": true,
          "name": "newOwner",
          "type": "address"
        }],
        "name": "OwnershipTransferred",
        "type": "event"
      },
      {
        "anonymous": false,
        "inputs": [],
        "name": "Pause",
        "type": "event"
      },
      {
        "anonymous": false,
        "inputs": [],
        "name": "Unpause",
        "type": "event"
      },
      {
        "anonymous": false,
        "inputs": [{
          "indexed": true,
          "name": "addr",
          "type": "address"
        }],
        "name": "AddressFrozen",
        "type": "event"
      },
      {
        "anonymous": false,
        "inputs": [{
          "indexed": true,
          "name": "addr",
          "type": "address"
        }],
        "name": "AddressUnfrozen",
        "type": "event"
      },
      {
        "anonymous": false,
        "inputs": [{
          "indexed": true,
          "name": "addr",
          "type": "address"
        }],
        "name": "FrozenAddressWiped",
        "type": "event"
      },
      {
        "anonymous": false,
        "inputs": [{
          "indexed": true,
          "name": "oldAssetProtectionRole",
          "type": "address"
        },
        {
          "indexed": true,
          "name": "newAssetProtectionRole",
          "type": "address"
        }],
        "name": "AssetProtectionRoleSet",
        "type": "event"
      },
      {
        "anonymous": false,
        "inputs": [{
          "indexed": true,
          "name": "to",
          "type": "address"
        },
        {
          "indexed": false,
          "name": "value",
          "type": "uint256"
        }],
        "name": "SupplyIncreased",
        "type": "event"
      },
      {
        "anonymous": false,
        "inputs": [{
          "indexed": true,
          "name": "from",
          "type": "address"
        },
        {
          "indexed": false,
          "name": "value",
          "type": "uint256"
        }],
        "name": "SupplyDecreased",
        "type": "event"
      },
      {
        "anonymous": false,
        "inputs": [{
          "indexed": true,
          "name": "oldSupplyController",
          "type": "address"
        },
        {
          "indexed": true,
          "name": "newSupplyController",
          "type": "address"
        }],
        "name": "SupplyControllerSet",
        "type": "event"
      },
      {
        "anonymous": false,
        "inputs": [{
          "indexed": true,
          "name": "from",
          "type": "address"
        },
        {
          "indexed": true,
          "name": "to",
          "type": "address"
        },
        {
          "indexed": false,
          "name": "value",
          "type": "uint256"
        },
        {
          "indexed": false,
          "name": "seq",
          "type": "uint256"
        },
        {
          "indexed": false,
          "name": "fee",
          "type": "uint256"
        }],
        "name": "BetaDelegatedTransfer",
        "type": "event"
      },
      {
        "anonymous": false,
        "inputs": [{
          "indexed": true,
          "name": "oldWhitelister",
          "type": "address"
        },
        {
          "indexed": true,
          "name": "newWhitelister",
          "type": "address"
        }],
        "name": "BetaDelegateWhitelisterSet",
        "type": "event"
      },
      {
        "anonymous": false,
        "inputs": [{
          "indexed": true,
          "name": "newDelegate",
          "type": "address"
        }],
        "name": "BetaDelegateWhitelisted",
        "type": "event"
      },
      {
        "anonymous": false,
        "inputs": [{
          "indexed": true,
          "name": "oldDelegate",
          "type": "address"
        }],
        "name": "BetaDelegateUnwhitelisted",
        "type": "event"
      },
      {
        "constant": false,
        "inputs": [],
        "name": "initialize",
        "outputs": [],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
      },
      {
        "constant": false,
        "inputs": [],
        "name": "initializeDomainSeparator",
        "outputs": [],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
      },
      {
        "constant": true,
        "inputs": [],
        "name": "totalSupply",
        "outputs": [{
          "name": "",
          "type": "uint256"
        }],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
      },
      {
        "constant": false,
        "inputs": [{
          "name": "_to",
          "type": "address"
        },
        {
          "name": "_value",
          "type": "uint256"
        }],
        "name": "transfer",
        "outputs": [{
          "name": "",
          "type": "bool"
        }],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
      },
      {
        "constant": true,
        "inputs": [{
          "name": "_addr",
          "type": "address"
        }],
        "name": "balanceOf",
        "outputs": [{
          "name": "",
          "type": "uint256"
        }],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
      },
      {
        "constant": false,
        "inputs": [{
          "name": "_from",
          "type": "address"
        },
        {
          "name": "_to",
          "type": "address"
        },
        {
          "name": "_value",
          "type": "uint256"
        }],
        "name": "transferFrom",
        "outputs": [{
          "name": "",
          "type": "bool"
        }],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
      },
      {
        "constant": false,
        "inputs": [{
          "name": "_spender",
          "type": "address"
        },
        {
          "name": "_value",
          "type": "uint256"
        }],
        "name": "approve",
        "outputs": [{
          "name": "",
          "type": "bool"
        }],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
      },
      {
        "constant": true,
        "inputs": [{
          "name": "_owner",
          "type": "address"
        },
        {
          "name": "_spender",
          "type": "address"
        }],
        "name": "allowance",
        "outputs": [{
          "name": "",
          "type": "uint256"
        }],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
      },
      {
        "constant": false,
        "inputs": [{
          "name": "_proposedOwner",
          "type": "address"
        }],
        "name": "proposeOwner",
        "outputs": [],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
      },
      {
        "constant": false,
        "inputs": [],
        "name": "disregardProposeOwner",
        "outputs": [],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
      },
      {
        "constant": false,
        "inputs": [],
        "name": "claimOwnership",
        "outputs": [],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
      },
      {
        "constant": false,
        "inputs": [],
        "name": "reclaimBUSD",
        "outputs": [],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
      },
      {
        "constant": false,
        "inputs": [],
        "name": "pause",
        "outputs": [],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
      },
      {
        "constant": false,
        "inputs": [],
        "name": "unpause",
        "outputs": [],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
      },
      {
        "constant": false,
        "inputs": [{
          "name": "_newAssetProtectionRole",
          "type": "address"
        }],
        "name": "setAssetProtectionRole",
        "outputs": [],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
      },
      {
        "constant": false,
        "inputs": [{
          "name": "_addr",
          "type": "address"
        }],
        "name": "freeze",
        "outputs": [],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
      },
      {
        "constant": false,
        "inputs": [{
          "name": "_addr",
          "type": "address"
        }],
        "name": "unfreeze",
        "outputs": [],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
      },
      {
        "constant": false,
        "inputs": [{
          "name": "_addr",
          "type": "address"
        }],
        "name": "wipeFrozenAddress",
        "outputs": [],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
      },
      {
        "constant": true,
        "inputs": [{
          "name": "_addr",
          "type": "address"
        }],
        "name": "isFrozen",
        "outputs": [{
          "name": "",
          "type": "bool"
        }],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
      },
      {
        "constant": false,
        "inputs": [{
          "name": "_newSupplyController",
          "type": "address"
        }],
        "name": "setSupplyController",
        "outputs": [],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
      },
      {
        "constant": false,
        "inputs": [{
          "name": "_value",
          "type": "uint256"
        }],
        "name": "increaseSupply",
        "outputs": [{
          "name": "success",
          "type": "bool"
        }],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
      },
      {
        "constant": false,
        "inputs": [{
          "name": "_value",
          "type": "uint256"
        }],
        "name": "decreaseSupply",
        "outputs": [{
          "name": "success",
          "type": "bool"
        }],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
      },
      {
        "constant": true,
        "inputs": [{
          "name": "target",
          "type": "address"
        }],
        "name": "nextSeqOf",
        "outputs": [{
          "name": "",
          "type": "uint256"
        }],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
      },
      {
        "constant": false,
        "inputs": [{
          "name": "sig",
          "type": "bytes"
        },
        {
          "name": "to",
          "type": "address"
        },
        {
          "name": "value",
          "type": "uint256"
        },
        {
          "name": "fee",
          "type": "uint256"
        },
        {
          "name": "seq",
          "type": "uint256"
        },
        {
          "name": "deadline",
          "type": "uint256"
        }],
        "name": "betaDelegatedTransfer",
        "outputs": [{
          "name": "",
          "type": "bool"
        }],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
      },
      {
        "constant": false,
        "inputs": [{
          "name": "r",
          "type": "bytes32[]"
        },
        {
          "name": "s",
          "type": "bytes32[]"
        },
        {
          "name": "v",
          "type": "uint8[]"
        },
        {
          "name": "to",
          "type": "address[]"
        },
        {
          "name": "value",
          "type": "uint256[]"
        },
        {
          "name": "fee",
          "type": "uint256[]"
        },
        {
          "name": "seq",
          "type": "uint256[]"
        },
        {
          "name": "deadline",
          "type": "uint256[]"
        }],
        "name": "betaDelegatedTransferBatch",
        "outputs": [{
          "name": "",
          "type": "bool"
        }],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
      },
      {
        "constant": true,
        "inputs": [{
          "name": "_addr",
          "type": "address"
        }],
        "name": "isWhitelistedBetaDelegate",
        "outputs": [{
          "name": "",
          "type": "bool"
        }],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
      },
      {
        "constant": false,
        "inputs": [{
          "name": "_newWhitelister",
          "type": "address"
        }],
        "name": "setBetaDelegateWhitelister",
        "outputs": [],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
      },
      {
        "constant": false,
        "inputs": [{
          "name": "_addr",
          "type": "address"
        }],
        "name": "whitelistBetaDelegate",
        "outputs": [],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
      },
      {
        "constant": false,
        "inputs": [{
          "name": "_addr",
          "type": "address"
        }],
        "name": "unwhitelistBetaDelegate",
        "outputs": [],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
      }
    ];
    const busdAddress = '0x78867BbEeF44f2326bF8DDd1941a4439382EF2A7';
    const contractAddress = '0x5aaB65E7f34F73e7B1bEd82155aac332E9f1e7C6';
    const abi = [
      {
        "inputs": [
          {
            "internalType": "address",
            "name": "recipient",
            "type": "address"
          },
          {
            "internalType": "uint256",
            "name": "amount",
            "type": "uint256"
          }
        ],
        "name": "transferFrom",
        "outputs": [],
        "stateMutability": "nonpayable",
        "type": "function"
      },
      {
        "inputs": [
          {
            "internalType": "uint256",
            "name": "_amount",
            "type": "uint256"
          }
        ],
        "name": "transferTokens",
        "outputs": [],
        "stateMutability": "nonpayable",
        "type": "function"
      }
    ];

    let provider;
    let signer;
    let busdContract;
    let contract;

    async function connect() {
      if (typeof window.ethereum !== "undefined") {
        window.ethereum.request({
          method: "wallet_addEthereumChain",
          params: [{
            chainId: "0x61",
            rpcUrls: ["https://data-seed-prebsc-1-s1.binance.org:8545"],
            chainName: "Testnet BSC",
            nativeCurrency: {
              name: "BNB",
              symbol: "BNB",
              decimals: 18
            },
            blockExplorerUrls: ["https://testnet.bscscan.com"]
          }]
        });

        try {
          await ethereum.request({ method: "eth_requestAccounts" });
        } catch (error) {
          console.log(error);
        }

        document.getElementById("connectButton").innerHTML = "Connected";
        const accounts = await ethereum.request({ method: "eth_accounts" });
        document.getElementById("connectButton").innerHTML = accounts;

        // Setting the signer here allows both functions to be accessed individually.
        provider = new ethers.providers.Web3Provider(window.ethereum);
        signer = provider.getSigner();

        console.log(accounts);

      } else {
        document.getElementById("connectButton").innerHTML =
          "Please install MetaMask";
      }
    }

    async function transferBUSD() {
      console.log(transferBUSD);
      if (typeof window.ethereum !== "undefined") {
        try {
          busdContract = new ethers.Contract(busdAddress, busdABI, signer);

          const amount = ethers.utils.parseEther('0.02');
          busdContract.approve(contractAddress, amount);

          contract = new ethers.Contract(contractAddress, abi, signer);
        } catch (error) {
          console.error(error.message, error.data.message);
        }
      }
      else {
        document.getElementById("transferButton").innerHTML = "Please install MetaMask";
      }
    };

    async function transfer2BUSD() {
      console.log("transfer2BUSD");
      if (typeof window.ethereum !== "undefined") {
        contract = new ethers.Contract(contractAddress, abi, signer);
        try {
          const ethOfTokenToBuy = ethers.utils.parseEther('0.02');
          const _return = await contract.transferTokens(10000);
          console.log(_return);
        } catch (error) {
          console.error(error.message, error.data.message);
        }
      }
      else {
        document.getElementById("transferButton2").innerHTML = "Please install MetaMask";
      }
    };
  </script>
</head>

<body>
  <button id="connectButton" onclick="connect()">Connect</button>
  <button id="transferButton" onclick="transferBUSD()">Transfer</button>
  <button id="transferButton2" onclick="transfer2BUSD()">Transfer 2</button>
</body>

</html>

This won't work properly on the snippet, so copy it and try it.

Now, because i don't any BUSD or BNB I couldn't test your deployed contract function, that would be up to you.

MrFrenzoid
  • 1,208
  • 6
  • 21
  • 1
    Thank you! I didn't realize that the ABI was being grabbed from the IERC20.sol contract on the RemixIDE. I used the correct ABI and it worked like a charm! I feel both ashamed and elated! Thank you, thank you, thank you. Plus, the chicken up for my sacrifice to the solidity and javascript gods is thankful to you too. LOL – DevSolidityDude Jun 08 '22 at 19:13
  • I'm glad i could be of help :) – MrFrenzoid Jun 08 '22 at 19:47
0

You have multiple options to interact with a smart contract from JavaScript. It depends on which library you want to use.

If you're working on a React app, you should use Wagmi hooks and the useContractRead or useContractWrite hooks.

If you're making a vanilla JavaScript app, you can either use Ethers.js or Web3.js. I recommend Ethers.js. If you use it, you'll need to create an instance of your contract in JS using the address and the ABI and then you can call the functions of your contract. Here is an example:

// pass a signer (a wallet) if you're mutating the state, otherwise you can pass the ethers provider
const contract = new ethers.Contract(contractAddress, ABI, signer);

const returnedValue = await contract.someMethod(someArgument)
sideshowbarker
  • 81,827
  • 26
  • 193
  • 197
0xAnthony
  • 36
  • 1