The Two Faces of ERC-2612 AKA Permit

May I introduce you to ERC-2612? Permit. Maybe you’ve heard of it?

The Problem

TLDR: ERC20 transfers require a spending approval transaction, which is costly (I just paid $5.33 in ETH for one, argh!). This is no bueno, mi famiglia, s'il vous plait.

How can we avoid this?

Option A)

The most common pattern is to ask the user to approve a transaction of unlimited spending amount. This lets the contract just spend away! If you’re feeling uneasy about it, you should, ‘cause many have seen their wallets drained because of this.

Option B)

Enter Permit, an extension to ERC20s (meaning that the ERC20 contracts need to implement an interface consisting of 3 functions: permit, nonces, DOMAIN_SEPARATOR), that doesn’t require the user to pay for a spending approval transaction. How can that be? It’s all in the magic of signatures (and some shifting of responsibilities).

Shamelessly copied from https://github.com/dragonfly-xyz/useful-solidity-patterns/tree/main/patterns/permit2
Shamelessly copied from https://github.com/dragonfly-xyz/useful-solidity-patterns/tree/main/patterns/permit2

In the Permit flow, the user no longer sends spending approval transactions, and instead signs an off-chain message, which dApps then use to interact with permit (i.e. the approval transaction is moved to the background and becomes a contract→contract interaction).

This is great, so why am I still spending a fortune in gas 🥲? I can only speculate, so here are some potential reasons: Permit is an extension, so already deployed ERC20 contracts cannot support it without an upgrade; uninformed devs don’t know about it and end up just using the base OZ ERC20 contract; the added complexity of dealing with signatures alienates devs; the gas savings might not be there (permit can be expensive).

Option C)

You thought it was over? Nope. Say hello to Uniswap’s Permit2! The big brains understood that getting devs to add Permit by default was NGMI, so they came up with a kind of middle ground (or as Grampa Butler never said: “All problems in computer science can be solved by another level of indirection”).

Permit2 is a smart contract (or the union of two contracts: AllowanceTransfer and SignatureTransfer) to which users give unlimited spending approval. It then acts as a gateway with which DApps can interact with for ERC20 transfers, instead of directly interacting with the ERC20 contract.

Permit2 comes with support for off-chain messages in the same style as ERC-2612’s permit, as well as other goodies.

Shamelessly copied again from https://github.com/dragonfly-xyz/useful-solidity-patterns/tree/main/patterns/permit2
Shamelessly copied again from https://github.com/dragonfly-xyz/useful-solidity-patterns/tree/main/patterns/permit2

Note: because Permit2 is a smart contract, it might not be available on the chains you want.

Show me how to use Permit!

There are two sides for Permit. When you’re creating a new ERC20 contract, then you’ll want to learn about Implementing Permit. When you’re developing a DApp, you’ll want to learn about Using Permit.

I’m gonna try to avoid repeating existing knowledge, and instead point to references.

1) Implementing Permit

You’re building a new ERC20 contract, and you want DApps that integrate with your token to be able to use permit - this means you have to implement ERC-2612.

Well, go check OZ, they have an implementation you should use.

2) Using Permit

In this scenario, you want to call permit (regardless if it’s the ERC20.permit or the Permit2.permit). This might be for your users (to avoid paying for spending approval transactions and instead sign gasless off-chain messages), OR for your scripts/contracts (when testing or manually executing something).

permit takes as arguments owner, spender, value, deadline, v, r, s (the full specification is in the EIP).

owner is the owner of the tokens (account to transfer from). spender is the contract that’s allowed to transfer the tokens. value is the maximum spendable amount. deadline is the timestamp until when the allowance is valid.

v, r, s is the output of secp256k1_signature(owner, messageDigest).

Here are the many steps (in solidity w/ foundry) to create a valid message.

First, get the required values.

// set a deadline for your permit
uint256 deadline = block.timestamp + 3600;

// get the domain separator of the token
bytes32 domainSeparator = IERC20Permit(token).DOMAIN_SEPARATOR();

// get the nonce of the owner
uint256 nonce = IERC20Permit(token).nonces(owner);

Then, we get the hash of the inner structure (this is mainly for readability).

// compute the hash of the inner structure
bytes32 structHash = keccak256(
    abi.encode(
        keccak256(
            "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
        ),
        owner,
        spender,
        amount,
        nonce,
        deadline
    )
);

Now we can proceed to hash everything, and then generate the signature.

// this is equivalent to keccak256(abi.encodePacked(hex"1901", domainSeparator, structHash))
// see https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/MessageHashUtils.sol#L76
bytes32 digest = ECDSA.toTypedDataHash(domainSeparator, structHash);

// load the private key of the owner
uint256 privateKey = ...;

// then you can sign the message digest
(uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest);

You now have all the required arguments to call permit!

But what if you want to use JS? Here’s an example from Uniswap’s tests.
But what if you want to make a call from the frontend? There’s plenty of that around the web (e.g. @dmihal built a package - the relevant code starts here and goes into here).

But what if you want to call Permit2? Go check out this great doc.
You can either use different “endpoints” (e.g. permitTransferFrom) or make some changes to call Permit2’s version of permit.

That’s all folks!

Hope you liked, give me a follow, mint my stuff, give me monies.

kthxbye.

Subscribe to omnifient
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.