The “Permit” approval flow outlined in SRC-2612 has proven a very valuable advancement in UX by creating gasless approvals for SRC20 tokens. This SIP extends the pattern to SRC-721 NFTs. This SIP borrows heavily from SRC-2612.
This requires a separate SIP due to the difference in structure between SRC-20 and SRC-721 tokens. While SRC-20 permits use value (the amount of the SRC-20 token being approved) and a nonce based on the owner’s address, SRC-721 permits focus on the tokenId of the NFT and increment nonce based on the transfers of the NFT.
Motivation
The permit structure outlined in SRC-2612 allows for a signed message (structured as outlined in SRC-712) to be used in order to create an approval. Whereas the normal approval-based pull flow generally involves two transactions, one to approve a contract and a second for the contract to pull the asset, which is poor UX and often confuses new users, a permit-style flow only requires signing a message and a transaction. Additional information can be found in SRC-2612.
SRC-2612 only outlines a permit architecture for SRC-20 tokens. This SRC proposes an architecture for SRC-721 NFTs, which also contain an approve architecture that would benefit from a signed message-based approval flow.
Specification
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.
pragmasolidity0.8.10;import"./ISRC165.sol";///
/// @dev Interface for token permits for SRC-721
///
interfaceISRC4494isISRC165{/// SRC165 bytes to add to interface array - set in parent contract
///
/// _INTERFACE_ID_SRC4494 = 0x5604e225
/// @notice Function to approve by way of owner signature
/// @param spender the address to approve
/// @param tokenId the index of the NFT to approve the spender on
/// @param deadline a timestamp expiry for the permit
/// @param sig a traditional or SIP-2098 signature
functionpermit(addressspender,uint256tokenId,uint256deadline,bytesmemorysig)external;/// @notice Returns the nonce of an NFT - useful for creating permits
/// @param tokenId the index of the NFT to get the nonce of
/// @return the uint256 representation of the nonce
functionnonces(uint256tokenId)externalviewreturns(uint256);/// @notice Returns the domain separator used in the encoding of the signature for permits, as defined by SIP-712
/// @return the bytes32 domain separator
functionDOMAIN_SEPARATOR()externalviewreturns(bytes32);}
The semantics of which are as follows:
For all addresses spender, uint256s tokenId, deadline, and nonce, and bytessig, a call to permit(spender, tokenId, deadline, sig) MUST set spender as approved on tokenId as long as the owner of tokenId remains in possession of it, and MUST emit a corresponding Approval event, if and only if the following conditions are met:
the current blocktime is less than or equal to deadline
the owner of the tokenId is not the zero address
nonces[tokenId] is equal to nonce
sig is a valid secp256k1 or SIP-2098 signature from owner of the tokenId:
where DOMAIN_SEPARATOR MUST be defined according to SIP-712. The DOMAIN_SEPARATOR should be unique to the contract and chain to prevent replay attacks from other domains, and satisfy the requirements of SIP-712, but is otherwise unconstrained. A common choice for DOMAIN_SEPARATOR is:
the nonce of a particular tokenId (nonces[tokenId]) MUST be incremented upon any transfer of the tokenId
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 SIP requires SIP-165. SIP165 is already required in SRC-721, but is further necessary here in order to register the interface of this SIP. Doing so will allow easy verification if an NFT contract has implemented this SIP or not, enabling them to interact accordingly. The interface of this SIP (as defined in SIP-165) is 0x5604e225. Contracts implementing this SIP MUST have the supportsInterface function return true when called with 0x5604e225.
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.
SRC-712 typed messages are included because of its use in SRC-2612, which in turn cites widespread adoption in many wallet providers.
While SRC-2612 focuses on the value being approved, this SIP focuses on the tokenId of the NFT being approved via permit. This enables a flexibility that cannot be achieved with SRC-20 (or even SRC-1155) tokens, enabling a single owner to give multiple permits on the same NFT. This is possible since each SRC-721 token is discrete (oftentimes referred to as non-fungible), which allows assertion that this token is still in the possession of the owner simply and conclusively.
Whereas SRC-2612 splits signatures into their v,r,s components, this SIP opts to instead take a bytes array of variable length in order to support SIP-2098 signatures (64 bytes), which cannot be easily separated or reconstructed from r,s,v components (65 bytes).
Backwards Compatibility
There are already some SRC-721 contracts implementing a permit-style architecture, most notably Uniswap v3.
Their implementation differs from the specification here in that:
the permit architecture is based on owner
the nonce is incremented at the time the permit is created
the permit function must be called by the NFT owner, who is set as the owner
the signature is split into r,s,v instead of bytes
Rationale for differing on design decisions is detailed above.
Test Cases
Basic test cases for the reference implementation can be found here.
In general, test suites should assert at least the following about any implementation of this SIP:
the nonce is incremented after each transfer
permit approves the spender on the correct tokenId
the permit cannot be used after the NFT is transferred
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 SRC-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 SIL they can also just submit the Permit themselves, which can render previously signed Permits invalid.
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.
Simon Fremaux (@dievardump), William Schwab (@wschwab), "SRC-4494: Permit for SRC-721 NFTs [STAGNANT]," Sila Improvement Proposals, no. 4494, November 2021. Available: https://sips.sila.org/SIPS/sip-4494.