Skip to content

ERC-1776 Native Meta Transactions #1776

@wighawag

Description

@wighawag

Introduction

Native Meta transactions (a term first coined by Austin Thomas Griffith here : https://medium.com/gitcoin/native-meta-transactions-e509d91a8482) allows users that simply own ERC20 or ERC777 (ERC1155 could be added too) tokens to perform operations on the ethereum network without needing to own ether themselves by letting a third party, the relayer, the responsibility to execute a transaction on the ethereum network carrying the desired operations (the so called meta transaction) in exchange of a token reward to cover the relayer's gas cost. They differ from traditional meta transactions (https://medium.com/uport/making-uport-smart-contracts-smarter-part-3-fixing-user-experience-with-meta-transactions-105209ed43e0) in that they only require support from the meta transaction processor contract itself and do not need the user wallet to be contract based.

The proposal here define the message format and meta transaction processor contract interface required for web3 wallets and browsers to provide a meaningful meta-transaction display when users are requested to approve. This could even allow wallets/browsers to not bother users by displaying an ether balance when such users do not have any ether or when the application being used does not require it.

It does not dictate how such signed message get injected on the network except for the meaning of each message parameters and their security requirements. More precisely, it does not dictate the ABI signature for the smart contract function executing the meta-transaction (the one signed and broadcast by the relayer). This is left as work for another standard.

Nevertheless due to nature of ERC20 this proposal also need to define how smart contract recipient of such meta transaction need to behave when being called. In particular it specify how the from field is to be verified.

Why native meta transactions?

It is common today for users to own ERC20 tokens without owning any ether. More and more applications reward their users without requiring prior on-chain interactions. It is thus possible for users to have been given tokens without them ever owning ether. With native meta transactions and a willing relayer (the company behind the token for example), such users can now interact with ethereum.

Without meta transactions, it would be impossible for them to interact with the ethereum network when required. Indeed, unless they have ether they can't interact with the ethereum network, requiring them to go through a difficult and costly process to acquire it. They can't even exchange their token for ether without having ether to pay the gas associated with such transaction.

While normal meta-transactions are possible using smart contract based wallet (like gnosis safe: https://safe.gnosis.io), there are currently more users with EOA (Externally Owned Account) based wallet and this might be the case for a while as there is an inherent cost to smart contract wallet. Native meta transactions also allow new tokens to be used without requiring generic relayer support on the part of the smart contract wallet. They thus offer native support for meta-transactions without any requirement from the users except to have a private key and enough tokens to pay for the gas.

As mentioned, Native Meta Transactions are great for applications whose users do not necessarily own or even know about ether. This is also useful for applications that want to distribute their tokens to new non-crypto users. Indeed, with such meta-transactions they can then start interacting in ethereum without having to think about ether. Application with the support of web3 wallet and browsers can then provide a less confusing experience where users can simply operate on one currency, at least until their horizon expands.

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.

For operability and the ability for wallet to display a more meaningful UI the following need to be defined:

  1. a signed message format that include all the required information for performing a meta transaction and protecting the user
  2. a public getter on the meta transaction processor smart Contract for the current nonce
  3. a way to verifiy the meta transaction signer.

Message format

The proposal is using a message format based on ERC712 so that wallet that support ERC712 but do not support the proposal described here can still offer an approval display showing all the information albeit in a less than ideal presentation.

Here is the proposed ERC712 message format :
typeHash = keccak256("ERC20MetaTransaction(address from,address to,address tokenContract,uint256 amount,bytes data,uint256 batchId,uint256 batchNonce,uint256 expiry,uint256 txGas,uint256 baseGas,uint256 tokenGasPrice,address relayer)");

The meaning of each field is as follow:

  • from: the account from which the meta-transaction is executed for and from which the token will be deduced. It MUST be either equal to the resulting message signer or have been given execution rights to the signer (via ERC1271 or ERC1654)
  • to: the target that will receive the token amount (if any, see below). If it is a contract's address the data(see below) passed will be executed on it.
  • tokenContract: token address that will be used for relayer payment and the amount field if non-zero. This allows the standard to work for both per-token metatx implementation and general implementation that would support multiple token.
  • amount: the amount in token to be sent to to
  • data: the bytes to be executed at to (if empty, only a transfer will be executed)
  • batchId: Nonces are 2 dimensional, the batchId part can be randomly generated so that meta-transaction that have no prior requirement can be included without waiting for previous meta-tx
  • batchNonce: the second part of the nonce, is the batchNonce, that allow a user to batch multiple meta-transaction and ensure they get included in order
  • expiry: the time limit at which the meta-transaction must be executed. If that meta-tx do not get executed in time, it reverts at the expense of the relayer.
  • txGas: the exact amount of gas to be used to execute the meta-transaction. This field also prevents attacks that could happen if the logic of the call relies on gas provided for whatever reason. (note though that the total cost of the transaction executing the meta-transaction will obviously have a higher cost)
  • baseGas: As meta tx has extra operation to be performed on top of calling the receiving address, this value determine the minimum gas the relayer will be paid on top. This is thus independent of opcode pricing and relayer are free to reject meta tx with a too low baseGas
  • tokenGasPrice the gasPrice set in token, (the relayer will decide to accept meta-transaction or not depending on the value of such).
  • relayer (optionally set to zero) used to protect the chosen relayer from front running attacks where the intended executor would lose its gas by someone inserting the transaction before. Letting it to be zero (acts as a wildcard) could still be worthwhile for situations where the users would benefit of having different relayers. This is out of scope of this proposal to define how such relayers network would work together.

batch and nonce

In order for the wallet or application to request a valid meta transaction it needs to be able to know the current nonce

The token contract MUST implement a getter for the current nonce as follow:

function meta_nonce(address from, uint256 batchId) external view returns(uint256);

Nonces work similarly to normal ethereum transactions: a transaction can only be executed if it matches the last nonce + 1, and once a transaction has occurred, the lastNonce will be updated to the current one. This prevents transactions to be executed out of order or more than once.

But instead of being one-dimensional, each nonce is actually associated to a batchId. This is offer great flexibility to the user and allow them to batch meta-transaction together if so desried while still allowing them to submit other meta-tx in paralel.

expiry and EIP-1681

While a previous version of this standard was using a minGasPrice to ensure that the user meta-transaction get included at a minimum price, such field becomes unecessary if we have an expiry field that have also a more meaningful purpose.

One of the danger, minGasPrice were protecting against, was relayers that would include the tx at a very low gas price to get a higher profit. With an expiry field, the relayer has to ensure the gasPrice is adequate so that the meta-tx is included in time.

On the other hand, the relayer is now risking to submit the transaction just a bit too late. To solve this, we can rely on EIP-1681 that can ensure the relayers that if the cannot get included after the expiry time-limit.

execute transaction and receiver verification

When executing the meta-transaction the contract must then verify the signature and if the nonce matches as specified above.

Then for the case of ERC20 the implementation need to ensure that the first parameter of the call being made is equal to from. The receiver will thus be able to accept calls from the token by knowing that the first parameter is indeed the from and not some arbitrary address. This means only such receiver will be able to accept such meta transactions securely.

In order to do it, the receiver simply check if msg.sender is the meta transaction processor contract itself and if so can assume that first parameter is equal to the from specified as part of the meta transaction message,

Remember, such ERC20 meta transaction receiver need to have as first parameters the "sender" whose token will be deduced.

for example:

function anyMethod(address sender, ERC20 token, uint256 value) external {
  require(sender ==  msg.sender ||  msg.sender ==  <metaTransactionProcessor>, "sender has to be the actual sender or the meta transaction processor contract itself");
  require(value == price, "value != price");
  token.transferFrom(sender, address(this), value);
  balance += value;
  ...
}

The meta transaction processor will ensure the ERC20 token as the permitted allowance and

Balance checks

To protect from malicious user the relayers also need to ensure the user (from) has enough balance. While it is technically possible for the user to withdraw token just in time (between the meta-tx is send and mined), it is unlikely to happen since it is unlikely to benefit the user unless it wishes to cancel the last minute. They could achieve a similar feat anyway by publishing a different signed message with the same nonce (albeit at a higher gas cost than a simple transfer). This is a risk that need to be taken by the relayer.

Gas accounting and refund

The txGas set as part of the message represent the gas passed to the contract call made (the meta transaction). This ensure the signer that its call will be executed as intended with as many gas as it asked for. This means though that the total gas cost of the realyer's transaction will be higher than that.

As mentioned above, this is solved with baseGas parameter that can be updated if opcode pricing changes. While this might feel like yet another extra field, it is important to note that the alternative (not having it) is worse since either the smart contract hard code the extra gas or the relayer have to pay the cost.

Gas estimate

In order to estimate the txGas to use for the meta transaction, the meta transaction call data can be used. The behaviour will be identical. As such there is no need to expose an estimateGas function for that.

As for the extra gas required by the relayer, the whole meta transaction call can be estimated as usual.

Relayer cooperations

One possibility that remains a problem for relayers is that a user could submit the meta-tx message and signature to multiple relayer at once.
This possibility pose a problem to relayers as they run the risk that their tx get included after another.

With the addition of expiry, we could imagine a simple method to avoid such issues:

Every meta-tx that include both an expiry field and relayer field, can always be included in a block if it reaches before expiry. If the nonce is already used, the actual meta-tx is not executed, but the relayer is getting rewarded for the gas spent (+ baseGas).

This would ensure relayers that they get paid for the work they do, while still allowing users to choose which relayer they want. The expiry field would also allow.

Example Implementation

see https://github.com/wighawag/singleton-1776-meta-transaction/blob/master/contracts/src/GenericMetaTxProcessor.sol

wallet / browser

Web3 wallet and browsers MUST provide a meaningful interface when such signed message is being requested. They could for example show a similar UI to traditional ether transaction except it should clearly state the token being used as well as the other parameters. For the data field, they should also be able to use function signature registry to display the function being called and the arguments.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions