ERC20: transfer amount exceeds allowance with approve

Hello

I want to use transferFrom() in my application to add Liquidity. I approve my contract and it works in my local machine with hardhat and metamask. After I switched to Rinkeby I am getting following Error:

inpage.js:1 MetaMask - RPC Error: execution reverted: ERC20: transfer amount exceeds allowance {code: -32603, message: "execution reverted: ERC20: transfer amount exceeds allowance"

My code on the frontend which is responsible for that is:

approveAddLiquidity = async (
    dexAddress: string,
    tokenAmount: string,
    ethAmount: string
  ) => {
    const exchange = await this.getExchange(dexAddress);

    await this.state.token.approve(exchange.address, tokenAmount); //<-- here I approve the contract
    const tx = await exchange.addLiquidity(tokenAmount, {
      value: ethAmount,
      from: this.state.account,
    });
    await tx.wait(1);
    return tx;
  };

I am approving via ERC20 token and then calling the addLiquidity which call the transferFrom() function. Don't understand why it is working on my localmachine but not on Rinkeby. Maybe something is missing?

One possibility is on the local machine, you don't get to interact with a deployed dex contract. What is dexAddress in your code? Is it the router or the pair?

It is a pair address or Exchange address: 0x575e9c7aE8e063200893DA727785a5a4b212c61B

Overall, in my local node from hardhat it is working but if I switch to rinkeby or other network I am getting these kind of errors. I am getting not these errors on hardhat local node.

Try providing the from parameter to the approval transaction as well (with the same account that is adding the liquidity later).

Also wouldn't hurt to wait for the approve transaction to be mined. Locally each Tx is generating a new block for you, so everything works nicely. But real blockchains do not behave like that and you want to make sure the approval is processed correctly before sending the second transaction.

The function looks like this:

 approveAddLiquidity = async (
    dexAddress: string,
    tokenAmount: string,
    ethAmount: string
  ) => {

    // getting the exchange contract instance
    const exchange = await this.getExchange(dexAddress);

    // approving the spender , e.g. exchange address (dexAddress)
    await this.state.token.approve(dexAddress, tokenAmount, {
      from: this.state.account,
    }); 

    const tx = await exchange.addLiquidity(tokenAmount, {
      value: ethAmount,
      from: this.state.account,
    });
    await tx.wait(1);
    return tx;
  };

I add the {from: this.state.account} but getting same error. What else I am missing? Is there a better way to do it?

i don't really use node for dev and testing, but it looks like you need to wait for approve transaction (i'm assuming) await this.state.token.approve returns a tx like the await exchange.addLiquidity below. something like this:

    // approving the spender , e.g. exchange address (dexAddress)
    let tx = await this.state.token.approve(dexAddress, tokenAmount, {
      from: this.state.account,
    }); 

    await tx.wait(1);

    tx = await exchange.addLiquidity(tokenAmount, {
      value: ethAmount,
      from: this.state.account,
    });
    await tx.wait(1);
    return tx;

Yes, thanks. await tx.wait(1) was missing but it takes long time and the user need wait. I guess on mainnet it is faster? Is there a better way to do it, e.g. with ERC20-Permit

the permit implementation on the pair contract only "works" for the lp tokens. You would need a addLiquidityWithPermit (or something like that) method on the router. The permit is not part ERC20, so most tokens don't implement it.

Don't see a way around the approve > wait > addLiquidity > wait flow.

Increasing gas might help on mainnet

That was also my thoughts that most ERC20 token have not permit implemented. But it helps a lot to know it. I was already frustrated. At least I know that I can't rely on the local code. The error never appeared because the functions, e.g. approve() runs extremely fast. I am building my exchange based on Uniswap V1 https://github.com/melonecrypto/meloneswap
Step by step I am adding more features and improving the security

Adjust the gasPrice and gasLimit parameters for the transactions. You need to set them correctly for all transactions so they get processed correctly. Keep in mind in production blockchains (testnet and mainnet) you are competing with other transactions in order to get mined, so these parameters are a must

I have extend my function and can add the gasPrice and gasLimit to other transactions functions as well. Do you mean like this?

 approveAddLiquidity = async (
    dexAddress: string,
    tokenAmount: string,
    ethAmount: string
  ) => {
    const exchange = await this.getExchange(dexAddress);
    const gasPrice = await this.state.provider.getGasPrice();
    const options = { gasPrice: gasPrice.toString(), gasLimit: 250000 };

    let tx: any;
    tx = await this.state.token.approve(exchange.address, tokenAmount, {
      from: this.state.account,
      options,
    });

    await tx.wait(1);
    tx = await exchange.addLiquidity(tokenAmount, {
      value: ethAmount,
      from: this.state.account,
    });
    await tx.wait(1);
    return tx;
  };

I tried it out getting error:

Error: cannot estimate gas; transaction may fail or may require manual gas limit (error={"code":-32603,"message":"execution reverted: invalid reserves","data":{"originalError":

I am working with ethers.js and tried with

 tx = await exchange.**estimateGas**.addLiquidity(tokenAmount, {
      value: ethAmount,
      from: this.state.account,
    });

But it does not add token or eth only without estimageGas(). Tried also passing gasPrice and gasLimit but also not working. I read in some posts in internet that ethers.js is calculating it automatically but on ethers.js I could nothing found about it.