I use ERC20Permit token extension contract from openzeppelin for approval via signatures. In tests (hardhat testnet), I use ethers and signing typed data as described here: Ethers Signers. So the problem is that when I trying to use permit function, it gives me 'Invalid signature' error. Permit function is called from standard erc721 contract and actually approves from account of erc20 tokens owner to erc721 contract address.

Relevant parts of code:

From ERC20Permit:

function permit(
    address owner,
    address spender,
    uint256 value,
    uint256 deadline,
    uint8 v,
    bytes32 r,
    bytes32 s
) public virtual override {
    require(block.timestamp <= deadline, "ERC20Permit: expired deadline");

    bytes32 structHash = keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline));

    bytes32 hash = _hashTypedDataV4(structHash);

    address signer = ECDSA.recover(hash, v, r, s);
    require(signer == owner, "ERC20Permit: invalid signature");

    _approve(owner, spender, value);

From test.js file:

const types = {
        Permit: [
            { name: 'owner', type: 'address' },
            { name: 'spender', type: 'address' },
            { name: 'value', type: 'uint256' },
            { name: 'nonce', type: 'uint256' },
            { name: 'deadline', type: 'uint256' } 

const domain = {
    name: "ERC20Permit-Token",
    version: '1',
    chainId: "31337",
    verifyingContract: verifyAddr

const value = {
    owner: _owner,
    spender: _spender, 
    value: _value, 
    nonce: _nonce, 
    deadline: _deadline

const signer = _signer
const signature = await signer._signTypedData(domain, types, value)

All the arguments on chain and from js are equal (I compared it with hardhat/console.sol). BUT the addresses of recovered signer and owner differ (from where the error actually comes).

I suppose that data signing with ethers and hashing in ERCPermit somehow give different results, so the addresses are not equal. But I'm not sure.

What can be the problem here? Any help appreciated.

2 Answers 2


Is the chain Id correct?

Check with this one: 1337 or others IDK

I had done some research on permits and chainid is important too.

  • I provided chainId in hardhat.config.js though it is default (31337) and double-checked it via assembly inside contract, so i don't think that's the reason. But thanks for reply anyway May 26 at 17:19

After some debugging i decided to use ethers-eip712' typeData generation and signing via metamask and everything worked as it should.

Don't now what was the problem exactly... Check out this solution: codesandbox. Hope it will help

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.