Skip to content

Amxx/Permit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Permit Singleton (for backward-compatible signed ERC20/721/1155 transfers)

Introduction

Meta-transactions are becoming more and more widely adopted as they do have the ability to improve the UX of many dapps. This can be seen in the adoption of smart wallets such as Argent and Gnosis, but also in the apparition of standards such as EIP2612. This extension to the ERC20 standard allows users to approve token transfers using a signed message. Similarly, Uniswap V3 contracts include a mechanism to approve the transfer of ERC721 assets using a similar signed message.

While these evolutions are interesting, they must be part of the token contract. New tokens can choose to include these extensions, but older tokens, which are already deployed and are, in most cases, not upgradeable, have no way to benefit from them. This creates a gap between old tokens, with big community and valuation but limited features, and new tokens with many features but no adoption. Token swaps are possible, but are complex to carry out, particularly when the token is well recognized has is supported by many centralized and decentralized applications.

The permit singleton is a smart contract, once allowed to manage a user's tokens, is able to perform transfers that have been signed by the user. This is an opt-in solution, that doesn't impose itself on users. It being an external contract, that interacts with the tokens using their standard interfaces, it is compatible with any ERC20, 721, or 1155 tokens already deployed. This means that anyone holding tokens that do not support EIP2612 can use this singleton to achieve similar, signature-based, workflows.

Deploying the Permit Singleton

The very nature of the singleton is that it only has to be deployed once per blockchain. This instance will support all users and all tokens. This instance must be secure and trustless, so it shouldn't be deployed through an upgradeable mechanism, as the very governance of this upgrade would be an issue. In case new features are needed, it would be required the deploy a new version, and each user would then have to explicitly authorize this new version to operate on their assets.

Using the Permit Singleton

A user willing to use the Permit Singleton will have to grant it the authorization to manage his or her assets. This authorization is performed on a token per token basis, either by approving a balance to it (by calling ERC20's approve) or by making it an operator (by calling ERC721 and ERC1155's setApprovalForAll function).

Once approved, the singleton will be able to transfer the user's assets. For that, it requires the user to sign an authorization (following ERC712 standard). There is one message format per token standard:

Permit20(address registry,address to,uint256 value,uint256 nonce,uint256 deadline,address relayer)
Permit721(address registry,uint256 tokenid,address to,uint256 nonce,uint256 deadline,address relayer)
Permit1155(address registry,uint256 tokenid,address to,uint256 value,uint256 nonce,uint256 deadline,address relayer,bytes data)

All signed messages come with a nonce (for replay protection), and a deadline. Once an order is signed, it can be shared across any communication channel (on-chain or off-chain). Thanks to the signature, we can safely allow anyone to relay it to the singleton that will verify the signature and execute it. This relaying is done through the following functions:

function transfer20WithSign(
  address registry,
  address from,
  address to,
  uint256 value,
  uint256 nonce,
  uint256 deadline,
  address relayer,
  bytes memory signature
) external;

function transfer721WithSign(
  address registry,
  uint256 tokenId,
  address to,
  uint256 nonce,
  uint256 deadline,
  address relayer,
  bytes memory signature
) external;

function transfer1155WithSign(
  address registry,
  uint256 tokenId,
  address from,
  address to,
  uint256 value,
  uint256 nonce,
  uint256 deadline,
  address relayer,
  bytes memory data,
  bytes memory signature
) external;

These functions will revert if anything goes wrong (invalid nonce, deadline reached, invalid signature, error executing the token transfer, ...).

I encourage you to read the test/main.test.js file to see this workflow unfold.

Out of order execution

Just like native ethereum transactions, it is essential to prevent signed messages from being replayed (executed multiple times). This is achieved using a nonce system. However, unlike the native transaction in which nonces are strictly sequential, the Permit Singleton uses a multi-timeline nonce system to allow out-of-order execution of some messages.

Messages with sequential nonces must be executed in order, according to the nonce they are given. This means order number 6 must be executed before order number 7 can be executed. Some users may want to sign multiple orders, allowing the transfer of different tokens to different users. In that case, the order recipient will want to be able to execute the order whenever they want, and will certainly not want to wait on anyone executing their order before they can execute theirs. This is where out-of-order execution comes in.

Out-of-order execution is achieved by using multiple independent timelines. Each timeline's nonce behaves as expected, but different timelines are independent. This means that messages 5, 6, and 7 of timeline 0 must be executed sequentially, but order 6 or timeline 1 is independent, and only depends on order 5 of timeline 1.

The Permit Singleton's nonces, which are represented as uint256 should be seen as the concatenation of two uint128. The first one is the timeline id, while the second one is the nonce id within this timeline.

This means that regular nounce, which are smaller then 2128 are living on timeline 0, and match the exepcted behaviour. However, messages with nonce 2128, 2*2128 and 3*2128 are on 3 independant timeline and have no dependency (they are the first nonce in their respective timeline).

The current nonce of a user (on the trivial timeline) can be queried using function nonce(address) public view returns (uint256). The current nonce on a specific timeline can be queried using function nonce(address,uint256) public view returns (uint256).

Important notes

  • Having a signed message does NOT guarantee the token can be transferred. There are many cases in which the message cannot be executed:

    • The signer isn't the owner of the token or has an insufficient balance.
    • The singleton is not approved by the user (approval can be revocated).
    • Another order with a similar nonce has been executed before yours, thus "consuming" the nonce.
  • In order to "cancel" an order, the signer should replace it with another one of the same nonce (for example with an order transferring 0 tokens) and execute it before. This is similar to replacing an ethereum transaction.

Disclaimer

Contracts in this repository are prototypes that have NOT been audited. While I encourage trying them out, they should not be considered safe without a proper audit, and should not the used in production. Please audit your contract before trusting them with any real assets. Also, don't assume other people's contracts, which you can find on a repository like this one, are safe!

Relevant reading material

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published