Radim
Radim

Reputation: 85

Ethers js transferring ERC20 between contracts

I have 2 contracts, first one is openzeppelin ERC20 token and second one is a lottery contract where players can bet on a number.

lottery.sol

pragma solidity ^0.8.4;
import "./Token.sol"; //import ERC20 token
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract Lottery is Ownable {

Token token;
constructor(Token _token) public {
   token = _token;
}

// store information about player's bet
struct PlayersStruct {
   uint betAmount;
   uint betOnNumber;
}

mapping(address => PlayersStruct) public balances;

function enterLottery(string memory _betOnNumber) public payable {

    address player = msg.sender;
    uint amount = msg.value;

    // transfer token from player's wallet to lottery contract
    token.transferFrom(player, address(this), betAmount);

    balances[player].betAmount += amount ;
    balances[player].betOnNumber = _betOnNumber;

}

And this is how I call it from ReactJS

async function stakeBet() {
   const amount = ethers.utils.parseEther("10");
   const maxAmount = ethers.utils.parseEther("1000000");

  // approve token once so player can save on gas in future
  await token.approve(stakingContract.address, maxAmount);

  // bet 10 tokens on number 20
  await lottery.enterLottery(20, {value: amount,});
}

There are 2 problems with this code:

  1. I have to approve the contract every time, even I'm approving maxAmount higher than betting amount. How do I let Metamask know that contract was already approved?
  2. After approving the ERC20 token, the token for transfer is actually ETH and not ERC20 token defined in Token.sol, how do I specify that ERC20 is the one to transfer?

I'm testing on Kovan test net.

Upvotes: 0

Views: 3728

Answers (2)

Slok Aks
Slok Aks

Reputation: 29

For the 1st part, you have done what you intended to do, i.e, you have set the allowance to the maximum amount so that the user doesn't have to pay for calling transaction for allow() each time.

The MetaMask is asking your permission for gas fees (in ETH) for sending "betAmount" amount of tokens to the contract.

I am new to ethereum, but I have faced similar circumstances in my projects. So, this is according to my understanding.

Also, for the 2nd problem, as I said earlier, MetaMask is asking for your permission for paying the gas fees (which it takes in ETH, but actual transfer of tokens must also be taking place. Only, some amount of ETH is spent for the "Gas Fees".

I got a real good article discussing the same thing. Link: https://medium.com/ethex-market/erc20-approve-allow-explained-88d6de921ce9

You can see the demonstration of what I just said. You can see that the gas fees are taken in ETH. Also, if you had not set the allowance earlier to maximum, you would have to pay gas fees two times for a transaction to your contract to happen, first to call approve() for allowance, then to get the "betAmount" amount of tokens transferred to the contract.

Upvotes: 1

Yilmaz
Yilmaz

Reputation: 49261

In your stakeBet function, you are calling those functions in order:

 await token.approve(stakingContract.address, maxAmount);

  // bet 10 tokens on number 20
  await lottery.enterLottery(20, {value: amount,});

When you call approve, you are actually updating the allowance mapping. Let the contract know that, you are allowing certain amount for the allowed address. It should be implemented like this:

function approve(address _spender, uint _value)public returns (bool success){
        // allowance  tells how many tokens can be sent
        allowance[msg.sender][_spender]=_value;
        // This event must trigger when a successful call is made to the approve function.    
        emit Approval(msg.sender,_spender,_value);
        return true;
 }

Token transfer or coin transfer is actually updating the state inside the contracts. With the approve function you updated the allowance. Now token.transferFrom should be implemented like this:

// my address is allowing your address for this much token
mapping(address=>mapping(address=>uint)) public allowance;

function transferFrom(address _from, address _to, uint256 _value) public returns (bool success){
        // check the allowance
        require(_value <=allowance[_from][msg.sender]);
        // update the balances
        balanceOf[_to]+=_value;
        balanceOf[_from]-=_value;
        allowance[_from][msg.sender]-=_value;
        // emitting an event
        emit Transfer(_from,_to,_value);
        return true;
    }
  • ETH is not ERC20 token. Instead you have to implement WETH token and transfer WETH token. WETH is wrapped version of eth. https://weth.io/

All Ethereum wallet addresses are ERC20 compatible. Moreover, this means that every ERC20 transfer can happen between two Ethereum wallet addresses, or ERC20-compatible addresses. This typically includes all EVM-compatible blockchains. You send weth token and user can swap in metamask:

enter image description here

Upvotes: 3

Related Questions