I’m trying to create a contract that lets people choose to pay for their transactions with either ETH, or another ERC20 Token - one that I’m creating and for now calling “DummyCoin”.

So basically when the user makes a transaction they can choose to pay for it with either ETH or DummyCoin.

Note that DummyCoin is a totally standard ERC20 Token created using OpenZeppelin’s library - it’s basic cookie-cutter stuff, easy-peasy, works perfectly.

I’ve got most of this working thus far:
-When a User selects DummyCoin as their method of payment, they of course first need to grant permission to the Main-Contract to spend DummyCoin on their (the User’s) behalf - and I’ve got that part working.
Here's the MetaMask screen showing the approval process for DummyCoin:

enter image description here

The problem is that when I then call a function from the Main contract, MetaMask opens but this time it does NOT show DummyCoin as the method of payment - it shows ETH as the method of payment - and I don’t understand why, as I think I’ve done everything correctly:

enter image description here

Here’s the code of function that's executing the transaction - note that I’m simply passing in a currencyID argument to it so it knows which currency the user chose to pay with:

function mintNFT(uint tokenID, uint numTokensRequested, uint8 currencyID) public payable whenNotPaused {
    if(currencyID == 1) {
      // Paying with ETH:       
      require(msg.value >= (basePriceInETH * numTokensRequested), "'mintNFT()' ERROR: INSUFFICIENT amount of ETH sent!");
   else {
      // Paying with DummyCoin: 
      require(msg.value >= (basePriceInDummies * numTokensRequested), "'mintNFT()' ERROR: INSUFFICIENT amount of DUMMY sent!"); 
      dummyCoinContract.transferFrom(msg.sender, address(this), basePriceInDummies * numTokensRequested);

That last line -> dummyCoinContract.transferFrom(...) - that's the one that's supposed to be triggering the DummyCoin transaction.
I've been researching this for a while and have seen other versions, where it's suggested to use IERC20 in the code:

IERC20(dummyCoinContractAddress).transferFrom(msg.sender, address(this), basePriceInDummies * numTokensRequested);

But that doesn't work either - MetaMask still shows ETH as the method of payment.

I guess the last piece of this is that I'm importing DummyCoin.sol into the main contract:

import "./DummyCoin.sol";

I then create a reference variable to the deployed Dummy-Contract:

DummyCoin public dummyCoinContract;

And then assign it a value in the main-contract's constructor:

  dummyCoinContract = DummyCoin(dummyCoinContractAddress);

Where-in dummyCoinContractAddress gets passed into the main contract by the deployment code of both contracts: (I'm using HARDHAT + Ethers.js)

async function main () {   
   // DEPLOYING the ERC-20 Contract first, then passing it's ADDRESS to the MAIN-Contract:
   // 1. DummyCoin Deployment:
   const DummyCoinContract = await ethers.getContractFactory("DummyCoin");
   const DummyCoinContractInstance = await DummyCoinContract.deploy();
   await DummyCoinContractInstance.deployed();
   console.log(">FINISHED!\n\n-'DummyCoinContractInstance' deployed to:", DummyCoinContractInstance.address, "\n\n.");

   // 2. Main-Contract Deployment:
   const MinterContract = await ethers.getContractFactory("Minter");
   const MinterContractInstance = await MinterContract.deploy(DummyCoinContractInstance.address);
   await MinterContractInstance.deployed();
   console.log(">FINISHED!\n\n-'MinterContractInstance' deployed to:", MinterContractInstance.address, "\n\n.");

.then(() => process.exit(0))
.catch(error => {

So, any idea what's going on here and how I might trouble-shoot this?

  • In the else statement, you should require that msg.value == 0 because they are not paying with eth in that case.
    – Ape Toshi
    Commented May 29, 2023 at 23:47
  • OK - I tried it and it didn't make a difference. I then commented out that require statement altogether to just take it out of the mix entirely - made no difference. The same thing keeps happening: the Tx defaults to using ETH...
    – Mark55
    Commented May 30, 2023 at 0:29
  • Did you actually check the token balances afterwards? Got any txn details to share on some public testnet?
    – Ape Toshi
    Commented May 30, 2023 at 2:31
  • I don't think This method is correct for paying gas fees. Metamask only knows to initiate a transaction with ETH no matter what's written in the contract, it follows the Ethereum code anyways. Triggering a transaction will ask for ETH. You are transferring dummy tokens to the contract itself and it doesn't mean anything to a transaction. Commented May 30, 2023 at 3:30
  • @ApeToshi The token Balance do decrease - the ETH Token balances, not the DummyCoin balance. Because the Tx happens in ETH. Which is the problem. As far as Txns on public testnets - nope, I'm doing all this on localhost. Just spinning up 2 contracts that work together. And they are - remember, the Approval of my Token works (see screenshot), so the sending (or transferring) of the coin should works as well.
    – Mark55
    Commented May 30, 2023 at 3:46

2 Answers 2

  • First of all ethereum require to pay transactions in ETH and not in any ERC tokens.
  • So in your case you want to collect Fee for invoking your contract by ETH or ERC token so what you have to do is use paymaster.
  • You can see the opengsn, you can check the code and demo here and if you want to implement this then you have to modify to your requirements.
  • GSN is beyond the scope of this answer but there is tutorials available , you can check out them.

OK I finally got the thing working so here are some major takeaways that I think & hope future coders will find very helpful if they're trying to achieve the same functionality:

  1. While Metamask (MM) will show the name of your custom ERC20 Token during the approval process (meaning when you're literally calling approve() on your custom Token) MM will NOT show your Token's name when you're using it to pay for a Transaction. Make sure you understand this.
  2. The only thing MM will show when you're using your custom ERC20 Token to pay for a Tx is the amount of Gas needed in order to execute that Tx - and it'll show that in ETH.
    -For example: if you're minting an NFT and asking people to pay for that minting with your custom ERC20 Token, MM will NOT show the name of your custom ERC20 Token in the it's window (like it did during the approve() call), NOR will it show the AMOUNT of Tokens you're about to send as payment for the minting of this NFT. It'll only show the Gas needed, and it'll show that in ETH.

I personally found this to be ridiculously counter-intuitive, but I do get it - and it is what it is. At least now you know this ahead of time so you know what behavior to expect.

  1. In your JS code, when you call your smart-contract's mint function, it's best to NOT include and pass-in a { value: xxxx } argument to it as you would normally. Because that'll cause your Solidity code to EXPECT that it's about to receive ETH - which is exactly what you DON'T want to happen. You're trying to send it tokens from your custom ERC20 Token contract, not ETH. So just omit the { value: xxxx } business altogether.

  2. In your solidity contract, you'd normally want to have some sort of require statement to make sure that the amount of ETH that the caller sent is enough to cover the Minting (or w'ever Tx you're trying to execute) - well here you should NOT do that. Again, it'll sorta "confuse" your Dapp into thinking it's receiving and working with ETH - when it's NOT. If anything, you can put in a require statement that makes sure the msg.value == 0, to ensure that NO ETH was received by your contract.

I think that's about it.

Note that as far as any security concerns go, I am NOT vouching 100% for everything I've just spelled out above. I just haven't gotten that far. But I do know that not following all those steps I just listed above simply caused things to not work. So hopefully this will shorten your journey and help you get further much faster.

Happy to have others chime in here and offer w'ever lessons or tips they might have!

(And thank you to @NalLuksic, @ZartajAfser, and @ApeToshi for their comments!)

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.