-
Notifications
You must be signed in to change notification settings - Fork 264
Description
Summary
I'm attempting to use permitWitnessTransferFrom in my dApp using the same parameters and EOA as a successful Solidity test, but the generated signature fails with InvalidSigner. The issue appears to be due to an inconsistency in how the EIP712 signature is being generated or hashed.
Context
I adapted the test testPermitTransferFromTypedWitness to use the same values as my frontend (using the same EOA private key), and it passes successfully in Solidity:
function testPermitTransferFromTypedWitness() public {
vm.chainId(1);
uint256 nonce = 1;
MockWitness memory witnessData = MockWitness(10000000, address(5), true);
address usdcAddress = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
bytes32 witness = keccak256(abi.encode(witnessData));
ISignatureTransfer.PermitTransferFrom memory permit = ISignatureTransfer.PermitTransferFrom({
permitted: ISignatureTransfer.TokenPermissions({token: usdcAddress, amount: 1_000_000}),
nonce: nonce,
deadline: 1747147831
});
// Signing and asserting success
bytes memory sig = getPermitWitnessTransferSignature(
permit, fromPrivateKey, FULL_EXAMPLE_WITNESS_TYPEHASH, witness, DOMAIN_SEPARATOR
);
...
permit2.permitWitnessTransferFrom(permit, transferDetails, from, witness, WITNESS_TYPE_STRING, sig);
}In my app, I use Ethers.js to construct and sign the same EIP-712 message. When calling permit2.permitWitnessTransferFrom() I ensured the same permit, transferDetails, userAddress (from), witness and witnessTypeString.
However, despite using the same values, the signatures differ, and the simulation fails with:
Revert data: 0x815e1d64 (InvalidSigner)
In Tenderly, the claimedSigner is decoded as 0x0000000000000000000000000000000000000041, which seems totally off. The expected signer is my EOA: 0x5bb21B30E912871D27182E7b7F9C37C888269cb2.
Link to simulation
Tenderly simulation (fails at claimedSigner != signer)
Typed Data Details
Here’s the exact EIP-712 data I use in the dApp:
const domain = {
name: "Permit2",
chainId: connectedChainId, // using 1 (mainnet)
verifyingContract: "0x000000000022d473030f116ddee9f6b43ac78ba3"
};
const types = {
PermitWitnessTransferFrom: [
{ name: "permitted", type: "TokenPermissions" },
{ name: "nonce", type: "uint256" },
{ name: "deadline", type: "uint256" },
{ name: "witness", type: "MockWitness" }
],
TokenPermissions: [
{ name: "token", type: "address" },
{ name: "amount", type: "uint256" }
],
MockWitness: [
{ name: "value", type: "uint256" },
{ name: "person", type: "address" },
{ name: "test", type: "bool" }
]
};
const message = {
permitted: {
token: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // USDC
amount: "1000000"
},
nonce: "1",
deadline: "1747147831",
witness: {
value: "10000000",
person: "0x0000000000000000000000000000000000000005",
test: true
}
};I generate the signature like this:
const payload = ethers.TypedDataEncoder.getPayload(domain, types, message);
const signature = await provider.send("eth_signTypedData_v4", [
userAddress,
JSON.stringify(payload)
]);When using Rabby Wallet, I see the signing prompt exactly showing:
{
"types": {
"PermitWitnessTransferFrom": [
{ "name": "permitted", "type": "TokenPermissions" },
{ "name": "nonce", "type": "uint256" },
{ "name": "deadline", "type": "uint256" },
{ "name": "witness", "type": "MockWitness" }
],
"TokenPermissions": [
{ "name": "token", "type": "address" },
{ "name": "amount", "type": "uint256" }
],
"MockWitness": [
{ "name": "value", "type": "uint256" },
{ "name": "person", "type": "address" },
{ "name": "test", "type": "bool" }
],
"EIP712Domain": [
{ "name": "name", "type": "string" },
{ "name": "chainId", "type": "uint256" },
{ "name": "verifyingContract", "type": "address" }
]
},
"domain": {
"name": "Permit2",
"chainId": "0x1",
"verifyingContract": "0x000000000022d473030f116ddee9f6b43ac78ba3"
},
"primaryType": "PermitWitnessTransferFrom",
"message": {
"permitted": {
"token": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"amount": "1000000"
},
"nonce": "1",
"deadline": "1747147831",
"witness": {
"value": "10000000",
"person": "0x0000000000000000000000000000000000000005",
"test": true
}
}
}
This looks correct to me but there must be something wrong, are there reference examples where a frontend allows to generate a witnesses permit2 and the corresponding code to spend it?