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 address
  • nonces[owner][tokenId] (before state update) is equal to nonce
  • sig is a valid secp256k1, ERC-2098, or ERC-1271 signature from owner 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 Permits 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 and related rights waived via CC0.