ERC-1155 Permit Approvals
Abstract
The “permit” approval flow for both ERC-20 and ERC-721 are large improvements for the existing UX of the token underlying each ERC. This ERC extends the “permit” pattern to ERC-1155 tokens, borrowing heavily upon both ERC-4494 and ERC-2612.
The structure of ERC-1155 tokens requires a new ERC to account for the token standard’s use of both token IDs and balances (also why this ERC requires ERC-5216).
Motivation
The permit structures outlined in both ERC-4494 and ERC-2612 allows a signed message to create an approval, but are only applicable to their respective underlying tokens (ERC-721 and ERC-20).
Specification
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “NOT RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119 and RFC 8174.
Three new functions must be added to ERC-1155 and ERC-5216.
interface IERC1155Permit {
function permit(address owner, address operator, uint256 tokenId, uint256 value, uint256 deadline, bytes memory sig) external;
function nonces(address owner, uint256 tokenId) external view returns (uint256);
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
The semantics of which are as follows:
For all addresses owner
, spender
, uint256’s tokenId
, value
, deadline
, and nonce
, bytes sig
, a call to permit(owner, spender, tokenId, value, deadline, sig)
MUST set allowance(owner, spender, tokenId)
to value
, increment nonces(owner, tokenId)
by 1, and emit a corresponding Approval
event defined by ERC-5216, if and only if the following conditions are met:
- The current blocktime is less than or equal to
deadline
owner
is not the zero addressnonces[owner][tokenId]
(before state update) is equal tononce
sig
is a validsecp256k1
, ERC-2098, or ERC-1271 signature fromowner
of the message:keccak256(abi.encodePacked( hex"1901", DOMAIN_SEPARATOR, keccak256(abi.encode( keccak256("Permit(address owner,address spender,uint256 tokenId,uint256 value,uint256 nonce,uint256 deadline)"), owner, spender, tokenId, value, nonce, deadline)) ));
If any of these conditions are not met the permit
call MUST revert.
Where DOMAIN_SEPARATOR
MUST be 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 following 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": "tokenId",
"type": "uint256"
},
{
"name": "value",
"type": "uint256"
},
{
"name": "nonce",
"type": "uint256"
},
{
"name": "deadline",
"type": "uint256"
}
],
"primaryType": "Permit",
"domain": {
"name": erc1155name,
"version": version,
"chainId": chainid,
"verifyingContract": tokenAddress
},
"message": {
"owner": owner,
"spender": spender,
"tokenId": tokenId,
"value": value,
"nonce": nonce,
"deadline": deadline
}
}}
The permit
function MUST check that the signer is not the zero address.
Note that nowhere in this definition do we refer to msg.sender
. The caller of the permit
function can be any address.
This EIP requires ERC-165. ERC-165 is already required in ERC-1155, but is further necessary here in order to register the interface of this ERC. Doing so will allow easy verification if an NFT contract has implemented this ERC or not, enabling them to interact accordingly. The ERC-165 interface of this ERC is 0x7409106d
. Contracts implementing this ERC MUST have the supportsInterface
function return true
when called with 0x7409106d
.
Rationale
The permit
function is sufficient for enabling a safeTransferFrom
transaction to be made without the need for an additional transaction.
The format avoids any calls to unknown code.
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 Permits that effectively never expire. Likewise, the value
argument can be set to uint(-1)
to create Permits with effectively unlimited allowances.
EIP-712 typed messages are included because of its use in ERC-4494 and ERC-2612, which in turn cites widespread adoption in many wallet providers.
This ERC focuses on both the value
and tokenId
being approved, ERC-4494 focuses only on the tokenId
, while ERC-2612 focuses primarily on the value
. ERC-1155 does not natively support approvals by amount, thus this ERC requires ERC-5216, otherwise a permit
would grant approval for an account’s entire tokenId
balance.
Whereas ERC-2612 splits signatures into their v,r,s
components, this ERC opts to instead take a bytes
array of variable length in order to support ERC-2098 signatures, which may not be easily separated or reconstructed from r,s,v
components (65 bytes).
Backwards Compatibility
No backward compatibility issues found.
Security Considerations
The below considerations have been copied from ERC-4494.
Extra care should be taken when creating transfer functions in which permit
and a transfer function can be used in one function to make sure that invalid permits cannot be used in any way. This is especially relevant for automated NFT platforms, in which a careless implementation can result in the compromise of a number of user assets.
The remaining considerations have been copied from ERC-2612 with minor adaptation, and are equally relevant here:
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 ownerOf(tokenId) != address(0)
to avoid permit
from creating an approval to any tokenId
which does not have an approval set.
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 ERC-20 race condition for approvals 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
Copyright and related rights waived via CC0.