May I introduce you to ERC-2612? Permit. Maybe you’ve heard of it?
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.
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).
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.
Note: because Permit2 is a smart contract, it might not be available on the chains you want.
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
.
Hope you liked, give me a follow, mint my stuff, give me monies.
kthxbye.