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.

1 Like

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.

1 Like

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 **[]**