Arguably one of the main reasons for the success of EIP-20 tokens lies in the interplay between approve
and transferFrom
, which allows for tokens to not only be transferred between externally owned accounts (EOA), but to be used in other contracts under application specific conditions by abstracting away msg.sender
as the defining mechanism for token access control.
However, a limiting factor in this design stems from the fact that the EIP-20 approve
function itself is defined in terms of msg.sender
. This means that user's initial action involving EIP-20 tokens must be performed by an EOA (but see Note below). If the user needs to interact with a smart contract, then they need to make 2 transactions (approve
and the smart contract call which will internally call transferFrom
). Even in the simple use case of paying another person, they need to hold ETH to pay for transaction gas costs.
This ERC extends the EIP-20 standard with a new function permit
, which allows users to modify the allowance
mapping using a signed message, instead of through msg.sender
.
For an improved user experience, the signed data is structured following EIP-712, which already has wide spread adoption in major RPC providers.
Note: EIP-20 must be performed by an EOA unless the address owning the token is actually a contract wallet. Although contract wallets solves many of the same problems that motivates this EIP, they are currently only scarcely adopted in the ecosystem. Contract wallets suffer from a UX problem -- since they separate the EOA owner
of the contract wallet from the contract wallet itself (which is meant to carry out actions on the owner
s behalf and holds all of their funds), user interfaces need to be specifically designed to support them. The permit
pattern reaps many of the same benefits while requiring little to no change in user interfaces.
While EIP-20 tokens have become ubiquitous in the Ethereum ecosystem, their status remains that of second class tokens from the perspective of the protocol. The ability for users to interact with Ethereum without holding any ETH has been a long outstanding goal and the subject of many EIPs.
So far, many of these proposals have seen very little adoption, and the ones that have been adopted (such as EIP-777), introduce a lot of additional functionality, causing unexpected behavior in mainstream contracts.
This ERC proposes an alternative solution which is designed to be as minimal as possible and to only address one problem: the lack of abstraction in the EIP-20 approve
method.
While it may be tempting to introduce *_by_signature
counterparts for every EIP-20 function, they are intentionally left out of this EIP-20 for two reasons:
transfer_by_signature
, possible batching algorithms, varies depending on the use case, and,permit
and additional helper contracts without loss of generality.Compliant contracts must implement 3 new functions in addition to EIP-20:
function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external
function nonces(address owner) external view returns (uint)
function DOMAIN_SEPARATOR() external view returns (bytes32)
The semantics of which are as follows:
For all addresses owner
, spender
, uint256s value
, deadline
and nonce
, uint8 v
, bytes32 r
and s
,
a call to permit(owner, spender, value, deadline, v, r, s)
will set
allowance[owner][spender]
to value
,
increment nonces[owner]
by 1,
and emit a corresponding Approval
event,
if and only if the following conditions are met:
deadline
.owner
is not the zero address.nonces[owner]
(before the state update) is equal to nonce
.r
, s
and v
is a valid secp256k1
signature from owner
of the message:If any of these conditions are not met, the permit
call must revert.
keccak256(abi.encodePacked(
hex"1901",
DOMAIN_SEPARATOR,
keccak256(abi.encode(
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"),
owner,
spender,
value,
nonce,
deadline))
))
where DOMAIN_SEPARATOR
is defined according to EIP-712. The DOMAIN_SEPARATOR
should be unique to the contract and chain to prevent replay attacks from other domains,
and satisfy the requirements of EIP-712, but is otherwise unconstrained.
A common choice for DOMAIN_SEPARATOR
is:
DOMAIN_SEPARATOR = keccak256(
abi.encode(
keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
keccak256(bytes(name)),
keccak256(bytes(version)),
chainid,
address(this)
));
In other words, the message is the EIP-712 typed structure:
{
"types": {
"EIP712Domain": [
{
"name": "name",
"type": "string"
},
{
"name": "version",
"type": "string"
},
{
"name": "chainId",
"type": "uint256"
},
{
"name": "verifyingContract",
"type": "address"
}
],
"Permit": [
{
"name": "owner",
"type": "address"
},
{
"name": "spender",
"type": "address"
},
{
"name": "value",
"type": "uint256"
},
{
"name": "nonce",
"type": "uint256"
},
{
"name": "deadline",
"type": "uint256"
}
],
},
"primaryType": "Permit",
"domain": {
"name": erc20name,
"version": version,
"chainId": chainid,
"verifyingContract": tokenAddress
},
"message": {
"owner": owner,
"spender": spender,
"value": value,
"nonce": nonce,
"deadline": deadline
}
}
Note that nowhere in this definition we refer to msg.sender
. The caller of the permit
function can be any address.
The permit
function is sufficient for enabling any operation involving EIP-20 tokens to be paid for using the token itself, rather than using ETH.
The nonces
mapping is given for replay protection.
A common use case of permit
has a relayer submit a Permit
on behalf of the owner
. In this scenario, the relaying party is essentially given a free option to submit or withhold the Permit
. If this is a cause of concern, the owner
can limit the time a Permit
is valid for by setting deadline
to a value in the near future. The deadline
argument can be set to uint(-1)
to create Permit
s that effectively never expire.
EIP-712 typed messages are included because of its wide spread adoption in many wallet providers.
There are already a couple of permit
functions in token contracts implemented in contracts in the wild, most notably the one introduced in the dai.sol
.
Its implementation differs slightly from the presentation here in that:
value
argument, it takes a bool allowed
, setting approval to 0 or uint(-1)
.deadline
argument is instead called expiry
. This is not just a syntactic change, as it effects the contents of the signed message.There is also an implementation in the token Stake
(Ethereum address 0x0Ae055097C6d159879521C384F1D2123D1f195e6
) with the same ABI as dai
but with different semantics: it lets users issue "expiring approvals", that only allow transferFrom
to occur while expiry >= block.timestamp
.
The specification presented here is in line with the implementation in Uniswap V2.
The requirement to revert if the permit is invalid was added when the EIP was already widely deployed, but at the moment it was consistent with all found implementations.
Though the signer of a Permit
may have a certain party in mind to submit their transaction, another party can always front run this transaction and call permit
before the intended party. The end result is the same for the Permit
signer, however.
Since the ecrecover precompile fails silently and just returns the zero address as signer
when given malformed messages, it is important to ensure owner != address(0)
to avoid permit
from creating an approval to spend "zombie funds" belong to the zero address.
Signed Permit
messages are censorable. The relaying party can always choose to not submit the Permit
after having received it, withholding the option to submit it. The deadline
parameter is one mitigation to this. If the signing party holds ETH they can also just submit the Permit
themselves, which can render previously signed Permit
s invalid.
The standard EIP-20 race condition for approvals (SWC-114) applies to permit
as well.
If the DOMAIN_SEPARATOR
contains the chainId
and is defined at contract deployment instead of reconstructed for every signature, there is a risk of possible replay attacks between chains in the event of a future chain split.
Copyright and related rights waived via CC0.