1

I have a problem with the execution of a metatransaction EIP-712 compliant, I show you the backend code with ethers.js and the link of the contract on mainnet :slight_smile:

Contract : https://polygonscan.com/address/0xc2132d05d31c914a87c6611c10748aeb04b58e8f

Code :

    
const ethers = require("ethers");

/**************************** SETUP ****************************/

const provider = new ethers.providers.JsonRpcProvider("RPC URL");

// Tether USDT address
const address = "0xc2132d05d31c914a87c6611c10748aeb04b58e8f";

const abi = [
  {
    inputs: [
      { internalType: "address", name: "spender", type: "address" },
      { internalType: "uint256", name: "amount", type: "uint256" },
    ],
    name: "approve",
    outputs: [{ internalType: "bool", name: "", type: "bool" }],
    stateMutability: "nonpayable",
    type: "function",
  },
];

const signer = new ethers.Wallet("Approve Sender Priv Key", provider);

const contract = new ethers.Contract(address, abi, signer);

async function getSignature() {
  // Create transaction data
  const transaction = await contract.populateTransaction.approve(
    "Approve Receiver",
    "Amount"
  );

  // Get signer nonce
  const nonce = await contract.getNonce(signer.address);

  // Get Polygon Mainnet Chain ID
  const salt = ethers.utils.hexZeroPad(ethers.utils.hexValue(137 ?? 0), 32);

  // Create domain object
  const domain = {
    name: "UChildERC20Proxy",
    version: "1",
    verifyingContract: transaction.to,
    salt: salt,
  };

  // Create object according to MetaTransaction struct
  const types = {
    MetaTransaction: [
      {
        name: "nonce",
        type: "uint256",
      },
      {
        name: "from",
        type: "address",
      },
      {
        name: "functionSignature",
        type: "bytes",
      },
    ],
  };

  // Create message to be signed
  const message = {
    nonce: nonce,
    from: signer.address,
    functionSignature: transaction.data ?? "",
  };

  // Sign message
  const signature = await signer._signTypedData(domain, types, message);

  console.log(signature);

  // Retrieve v, r, s from previous signature
  const split = ethers.utils.splitSignature(signature);

  console.log(split);

  // Check address retrieved from the previous data
  const recoveredAddress = ethers.utils.verifyTypedData(
    domain,
    types,
    message,
    signature
  );

  console.log(recoveredAddress);
}

getSignature();

I have followed the EIP-712 and NativeMetaTransaction flux to populate the inputs of the executeMetaTransaction function, which is:

function executeMetaTransaction(
    address userAddress,
    bytes memory functionSignature,
    bytes32 sigR,
    bytes32 sigS,
    uint8 sigV
) public payable returns (bytes memory) {
    MetaTransaction memory metaTx = MetaTransaction({
        nonce: nonces[userAddress],
        from: userAddress,
        functionSignature: functionSignature
    });

    require(
        verify(userAddress, metaTx, sigR, sigS, sigV),
        "Signer and signature do not match"
    );

    // increase nonce for user (to avoid re-use)
    nonces[userAddress] = nonces[userAddress].add(1);

    emit MetaTransactionExecuted(
        userAddress,
        msg.sender,
        functionSignature
    );

    // Append userAddress and relayer address at the end to extract it from calling context
    (bool success, bytes memory returnData) = address(this).call(
        abi.encodePacked(functionSignature, userAddress)
    );
    require(success, "Function call not successful");

    return returnData;
}    function executeMetaTransaction(
    address userAddress,
    bytes memory functionSignature,
    bytes32 sigR,
    bytes32 sigS,
    uint8 sigV
) public payable returns (bytes memory) {
    MetaTransaction memory metaTx = MetaTransaction({
        nonce: nonces[userAddress],
        from: userAddress,
        functionSignature: functionSignature
    });

    require(
        verify(userAddress, metaTx, sigR, sigS, sigV),
        "Signer and signature do not match"
    );

    // increase nonce for user (to avoid re-use)
    nonces[userAddress] = nonces[userAddress].add(1);

    emit MetaTransactionExecuted(
        userAddress,
        msg.sender,
        functionSignature
    );

    // Append userAddress and relayer address at the end to extract it from calling context
    (bool success, bytes memory returnData) = address(this).call(
        abi.encodePacked(functionSignature, userAddress)
    );
    require(success, "Function call not successful");

    return returnData;
}

When I go on the WriteProxy tab of the contract, I set all the inputs in this way:

userAddress (address) : address who approves and needs the metatransaction to be paid by the relayer (the approve sender) functionSignature (bytes) : function signature got from the backend script (signature) sigR (bytes32), sigS (bytes32), sigV (uint8) : values got from the backend script (split)

Then I connect another wallet with funds but on Metamask says the transaction will revert, I don't know why because I think everything is fine...

Thanks to everyone who will help me :)

1 Answer 1

0

We had to get a similar workflow working correctly at our start-up recently. One definite issue with your code is:

name: "UChildERC20Proxy",

should be:

name: "(PoS) Tether USD",

This field should be the result of calling the name method of the Polygon USDT contract. Apart from that, I believe the rest of your code is correct: in particular, you are forming domain, types, and message correctly.

As a suggestion, I would consider switching to human-readable abi and to ethers v6, to simplify things a bit, if possible.

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.