ERC20Permit Reverts on Permit

I deployed this ERC20 + ERC20Permit contract on Avalanche Fuji(43313) and pre-minted 900000 tokens.

  • I prepare a permit request to be signed by the owner.
// Prepare Contract
  let tokenContract = IERC20Permit__factory.connect(tokenAddress, provider);
  let tokenChainId = await tokenContract.provider.getNetwork().then((n) => n.chainId);

  const getPermitNonce = await tokenContract.nonces(playerPubkey);
  const permitNonce = getPermitNonce.toString();
  //  Grabs the current timestamp
  const blockNumber = await provider.getBlockNumber();
  const block = await provider.getBlock(blockNumber);
  const timestamp = block.timestamp;
  // set the Permit type values
  const permitRequest = {
    owner: '0x469443b1A8e764543C46F304272273ECBBB1d5E9',
    spender: '0x83bF103738373B3565d75C9c0C32894B563bA234',
    value: '3000',
    nonce: permitNonce, // '0'
    deadline: timestamp + 600, // Valid for 10 minutes  
  };
  // set the Permit type parameters
  const PermitRequestType = [
    {
      name: "owner",
      type: "address"
    },
    {
      name: "spender",
      type: "address"
    },
    {
      name: "value",
      type: "uint256"
    },
    {
      name: "nonce",
      type: "uint256"
    },
    {
      name: "deadline",
      type: "uint256"
    },
  ];
  // set the domain parameters
  const PermitTypedData = {
    domain: {
      name: tokenName,
      version: "1",
      chainId: tokenChainId,
      verifyingContract: tokenContract.address
    },
    primaryType: "PermitRequest",
    types: {
      PermitRequest: PermitRequestType
    }
  };

  const toSignPermit = { ...PermitTypedData, message: permitRequest };
  • I then sign the request using the owner signer 0x469443b1A8e764543C46F304272273ECBBB1d5E9

  • Split signature:

  // sig
  r: '0xe8f78de1c311183e3d931c1cd7a55154c281df301a1f6852f0105b2b1f0d483f',
  s: '0x5efcea0b9778818daae2c9b89bc5d395321fe42a546fb75c602126cc507945ef',
  _vs: '0xdefcea0b9778818daae2c9b89bc5d395321fe42a546fb75c602126cc507945ef',
  recoveryParam: 1,
  v: 28,
  yParityAndS: '0xdefcea0b9778818daae2c9b89bc5d395321fe42a546fb75c602126cc507945ef',
  compact: '0xe8f78de1c311183e3d931c1cd7a55154c281df301a1f6852f0105b2b1f0d483fdefcea0b9778818daae2c9b89bc5d395321fe42a546fb75c602126cc507945ef'
 const permit = await tokenContract.permit(toSignPermit.message.owner, toSignPermit.message.spender, toSignPermit.message.value, toSignPermit.message.deadline, sig.v, sig.r, sig.s, { gasLimit: 1000000 });

Here is the failed transaction you can decode input data and double check out my request params,

Hey @Ibrahim96

The transaction is reverting due to an invalid address derivation. It reverts with an ERC2616InvalidSigner error with the following parameters:

  • Owner: 0x469443b1a8e764543c46f304272273ecbbb1d5e9
  • Signer: 0xd820ac0a53442f3fb5a230e4bf4782dd3d147fd4

I think the signature generation is failing at some point. It'd be nice if you share the part of the code where you produce the signature.

I'm stringifying two transactions. Interestingly when I verify the typedData I get back the correct address.

const transaction = "W3siZG9tYWluIjp7Im5hbWUiOiJNeVRva2VuIiwidmVyc2lvbiI6IjEiLCJjaGFpbklkIjo0MzExMywidmVyaWZ5aW5nQ29udHJhY3QiOiIweDZCMzI5NGE3MEQyYTJBNjZiYjEzMjNBMTc3Q2FiMDAwYmQxQjU0NTIifSwicHJpbWFyeVR5cGUiOiJQZXJtaXRSZXF1ZXN0IiwidHlwZXMiOnsiUGVybWl0UmVxdWVzdCI6W3sibmFtZSI6Im93bmVyIiwidHlwZSI6ImFkZHJlc3MifSx7Im5hbWUiOiJzcGVuZGVyIiwidHlwZSI6ImFkZHJlc3MifSx7Im5hbWUiOiJ2YWx1ZSIsInR5cGUiOiJ1aW50MjU2In0seyJuYW1lIjoibm9uY2UiLCJ0eXBlIjoidWludDI1NiJ9LHsibmFtZSI6ImRlYWRsaW5lIiwidHlwZSI6InVpbnQyNTYifV19LCJtZXNzYWdlIjp7Im93bmVyIjoiMHg0Njk0NDNiMUE4ZTc2NDU0M0M0NkYzMDQyNzIyNzNFQ0JCQjFkNUU5Iiwic3BlbmRlciI6IjB4ODNiRjEwMzczODM3M0IzNTY1ZDc1QzljMEMzMjg5NEI1NjNiQTIzNCIsInZhbHVlIjoiMzAwMCIsIm5vbmNlIjoiMCIsImRlYWRsaW5lIjoxNzA2ODI4OTUyfX0seyJkb21haW4iOnsibmFtZSI6Ikx1ZGV4IiwidmVyc2lvbiI6IjEiLCJjaGFpbklkIjo0MzExMywidmVyaWZ5aW5nQ29udHJhY3QiOiIweGM0NzU1RjI4Mjk0Nzk0MzkxM2JhODRhNDQ0NkQ3MDU1NzE5NmUwQUEifSwicHJpbWFyeVR5cGUiOiJGb3J3YXJkUmVxdWVzdCIsInR5cGVzIjp7IkZvcndhcmRSZXF1ZXN0IjpbeyJuYW1lIjoiZnJvbSIsInR5cGUiOiJhZGRyZXNzIn0seyJuYW1lIjoidG8iLCJ0eXBlIjoiYWRkcmVzcyJ9LHsibmFtZSI6InZhbHVlIiwidHlwZSI6InVpbnQyNTYifSx7Im5hbWUiOiJnYXMiLCJ0eXBlIjoidWludDI1NiJ9LHsibmFtZSI6Im5vbmNlIiwidHlwZSI6InVpbnQyNTYifSx7Im5hbWUiOiJkZWFkbGluZSIsInR5cGUiOiJ1aW50NDgifSx7Im5hbWUiOiJkYXRhIiwidHlwZSI6ImJ5dGVzIn1dfSwibWVzc2FnZSI6eyJmcm9tIjoiMHg0Njk0NDNiMUE4ZTc2NDU0M0M0NkYzMDQyNzIyNzNFQ0JCQjFkNUU5IiwidG8iOiIweDgzYkYxMDM3MzgzNzNCMzU2NWQ3NUM5YzBDMzI4OTRCNTYzYkEyMzQiLCJ2YWx1ZSI6IjMwMDAiLCJnYXMiOiIxNTAwMDAiLCJub25jZSI6IjAiLCJkZWFkbGluZSI6MTcwNjgyMzU1MiwiZGF0YSI6IjB4MjA3N2M1NGIyZWZmODQwYTNiNThlOGRhZWY3ZmRlMmY1MGE0OGI2MjgyZjU0MDZjMTUxMTAxYTcxOWY0MDY4MjE3OTEzYjMwMTQyYmU3NjRkYjllZjczYTkwZWIyNjEyNzM4MjRkYWM1ODgxYjA0ZWRmNzFkYzVhNGNlMWNjM2I3MjcyMWY4ZTAzOGQ3ZGI2OWJkOWRlMTgwMTE5OTJmOGM5YTMxZDUxZjUzMWY1OGMyMWNkNTI1NjY1NzYzNWRhYzI5ZDU5ZDMyYzE2MTgwMDNiZjhjZmUyNDAxMjlhMmRmNjk0OWE1ZDAxODAwOWUyN2E4OWZkMzE5MzgyNDYyOTc3ZjBmZGRlMmNhNDRhMDQ2NDk1NWFkMzM4NzViODQ4NTYwMWY4YmE5ODMxMzBkYWMwMGYxYWEyMjk4OWVhODY2NDU4ZjUwZTAyZGI1OGQ1M2VlOWI3NTlhZWFkZDM0YjcwNjk2OGZlNzY2N2U3M2I1NmVlMzRhZWQ3ZDBhMjg2ODdmOGE4MDQwMDk2OTllNTc3YTY4M2RiMWUyZDM4ZTdhMzZiMTlhM2IzYjlmMjA1Y2Y3YTdmMWM0NTc3OGE4ZmFlM2U5NGFjMTIxZWQ1MmUxYzE1ZDU3MmMzZTkxZjRmZDg0YTkzY2I1MzRkZTI1YjE2Y2ZkYjM2ZGE3NTI1ODVhMjNjMDhiZTJmNDUxNzMwYjRhZDM2NjE5MzI2YzdlODkwMzk0Mjk4ODg1YzY3MWQxMTgwMGMzMDRhMmQ4YTRkYzFjMjk5M2QxMGUwMzE4YzkxYWY0ZGVlNTk1NjlmMTBlOWYyMjRmZmIyZGNmNWIwNWJjMGI0ZTkzZDJlYzVmM2U1NmFmZjI3MDI1YjdlNzU4ZjZjMjM4ZTBlYzk2YWU2YTRlMWY3MjEyMTcxNjI5OWZlZGMxODU2NTExMTk1OWY3NDJjMTQ5MSJ9fV0=";
        const decodedTx = Buffer.from(transaction, "base64").toString("utf-8");
        const serializedTransaction = JSON.parse(decodedTx);

        const PermitRequestToSign = {
            ...serializedTransaction[0],
            account: wallet.address!,
        }

        const forwarderRequestToSign = {
            ...serializedTransaction[1],
            account: wallet.address!,
        }

        walletClient.signTypedData(PermitRequestToSign).then(async (signature) => {
            console.log(signature);
            //0xe8f78de1c311183e3d931c1cd7a55154c281df301a1f6852f0105b2b1f0d483f5efcea0b9778818daae2c9b89bc5d395321fe42a546fb75c602126cc507945ef1c

            const recovery = ethers.verifyTypedData(PermitRequestToSign.domain, PermitRequestToSign.types, PermitRequestToSign.message, signature);

            // 0x469443b1A8e764543C46F304272273ECBBB1d5E9
            console.log(recover);

        }).catch((error) => {
            console.log(error);
        });

Yeah the signature matches because it was signed by the verifying address. The problem may be a mismatch in some of the elements or values to sign. It's difficult to tell without having the full Solidity code and tests.

Got it to work by checking my permit request 100x times.

Curious to know how did you find the exact error, I'm just seeing a Warning! Error encountered during contract execution **[]**