I am trying to call transfer of an ERC20 token from another "escrow" smart contract in remix JavaScript VM (London). I have 2 contracts like so:

MyToken.sol (an ERC20Votes.sol, but "votes" should not affect anything here?):

//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";

contract MyToken is ERC20Votes {
    uint256 public maxSupply = 555; // * 10**18;

    constructor() ERC20("MyToken", "MT") ERC20Permit("MyToken") {
        _mint(msg.sender, maxSupply);

    // The functions below are overrides required by Solidity.

    function _afterTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal override(ERC20Votes) {
        super._afterTokenTransfer(from, to, amount);

    function _mint(address to, uint256 amount) internal override(ERC20Votes) {
        super._mint(to, amount);

    function _burn(address account, uint256 amount)
        super._burn(account, amount);


//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

import "hardhat/console.sol";

contract Escrow3 {
    ERC20 token;
    constructor(address _token) {
        token = ERC20(_token);

    function confirm(
        address _recipient,
        uint256 _amount
    ) external {
        require(msg.sender != _recipient, "Caller cannot be recipient");
        require(token.balanceOf(msg.sender) >= _amount, "Insufficient funds");

        bool sent = token.transfer(_recipient, _amount);
        require(sent, "transfer fail");
    function getBalanceToken() external view returns(uint256) {
        return token.balanceOf(msg.sender);

In remix, I first deploy MyToken.sol, then deploy Escrow3.sol with MyToken's address in its constructor. With the deployer's address, I then call getBalanceToken in remix and it returns 555 as expected. In remix, I then call (via deployer's address) confirm with _recipient = another address and _amount = 5. I get this error:

ERC20: transfer amount exceeds balance

This is strange because getBalanceToken clearly shows deployer's address with enough tokens in balance and the check for Insufficient funds did not revert in the confirm function.

I have tried everything and looked everywhere and I still do not understand how to solve this.

  • You are using transfer but it will not work in your contract. See for an explanation here: ethereum.stackexchange.com/questions/46457/….
    – Ismael
    Nov 6, 2021 at 5:30
  • @Ismael ok but why does the first check of "Insufficient funds" pass but the second one fail? I have also tried approve _ transferFrom, but I can get it to work in the same contract.
    – user82768
    Nov 6, 2021 at 5:35
  • Because transfer uses the caller's funds, in this case the caller is the contract.
    – Ismael
    Nov 6, 2021 at 5:39
  • @Ismael I am confused on the caller/msg.sender. My understanding is the "caller" is the address that deployed the Escrow3.sol contract, which is also msg.sender. How does the caller become the contract's address, but msg.sender still being the deployer's address?
    – user82768
    Nov 6, 2021 at 5:46
  • Read ethereum.stackexchange.com/questions/98892/…
    – Ismael
    Nov 6, 2021 at 5:59


Your Answer

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