logo
A Guide To Meta Transactions and Atomic Interactions
by John Oba - AfrodevOct 31, 2022 • 6 min read
Image Banner

The current state of the Ethereum blockchain requires GAS to run transactions and the ability for users to interact with Ethereum without holding any ETH has been a long outstanding goal and the subject of many EIPs.

In this article, we will be looking at the Ethereum Improvement Proposal no. 3009 (EIP-3009) whose adoption is growing rapidly. This comes with a gas optimization strategy where token transfers can be made without holding any ETH to pay GAS.

How does it work?

Before we jump into this section let’s look into the motivation and what this Improvement proposal has to offer. This enables the user to

  • Delegate the gas payment to someone else
  • Pay for gas in the token itself rather than in ETH
  • Perform one or more token transfers and other operations in a single atomic transaction
  • Transfer ERC-20 tokens to another address, and have the recipient submit the transaction
  • Batch multiple transactions with minimal overhead, and
  • Create and perform multiple transactions without having to worry about them failing due to accidental nonce-reuse or improper ordering by the miner

The user signs an off-chain message (not transaction) containing the transfer details conforming to the EIP-712 typed message signing specifications. This signature (signed message) can be sent to the blockchain with another wallet holding ETH and the GAS is paid on behalf of the user.

Why it is important to implement EIP-3009

This EIP is most beneficial to (d)Application developers, it attempts to solve the user onboarding problems with respect to GAS fee optimizations, batch transfers, and even recurring payments. It can help dApp developers collect GAS fees in ERC-20 tokens, and help DAO members operate and make on-chain transactions purely in DAO tokens. Usually, the transactions are delegated to an address to pay the GAS fees. Such wallets are often called Paymaster or MasterWallet.

A truly gasless exchange experience would yield the following benefits:

  • someone who is paid exclusively in a token (a common use case is stablecoin payments) wouldn’t need to turn to a third party to additionally procure ETH to be able to use their funds. they could just exchange some of it for ETH and then proceed to use any functionality on Ethereum.
  • the same scenario, but even if the user has ETH on a different address, they wouldn’t need to link their addresses via on-chain transaction trails. this is highly desirable because it preserves privacy. e.g. when Bob is paid in LUSD by Alice for his work, he wouldn’t have to transfer ETH from his spending address to his salary payout address and thus reveal his spending habits to his employer.

Does it work for every contract?

To delegate the gas payment to another wallet as specified in EIP-3009, it is required that the contract with which the user is interacting implements the EIP standards. A contract implementing the EIP-3009 inherits the following set of functions and specifications:


event AuthorizationUsed(
    address indexed authorizer,
    bytes32 indexed nonce
);

// keccak256("TransferWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)")
bytes32 public constant TRANSFER_WITH_AUTHORIZATION_TYPEHASH = 0x7c7c6cdb67a18743f49ec6fa9b35f50d52ed05cbed4cc592e13b44501c1a2267;

// keccak256("ReceiveWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)")
bytes32 public constant RECEIVE_WITH_AUTHORIZATION_TYPEHASH = 0xd099cc98ef71107a616c4f0f941f04c322d8e254fe26b3c6668db87aae413de8;

/**
 * @notice Returns the state of an authorization
 * @dev Nonces are randomly generated 32-byte data unique to the authorizer's
 * address
 * @param authorizer    Authorizer's address
 * @param nonce         Nonce of the authorization
 * @return True if the nonce is used
 */
function authorizationState(
    address authorizer,
    bytes32 nonce
) external view returns (bool);

/**
 * @notice Execute a transfer with a signed authorization
 * @param from          Payer's address (Authorizer)
 * @param to            Payee's address
 * @param value         Amount to be transferred
 * @param validAfter    The time after which this is valid (unix time)
 * @param validBefore   The time before which this is valid (unix time)
 * @param nonce         Unique nonce
 * @param v             v of the signature
 * @param r             r of the signature
 * @param s             s of the signature
 */
function transferWithAuthorization(
    address from,
    address to,
    uint256 value,
    uint256 validAfter,
    uint256 validBefore,
    bytes32 nonce,
    uint8 v,
    bytes32 r,
    bytes32 s
) external;

/**
 * @notice Receive a transfer with a signed authorization from the payer
 * @dev This has an additional check to ensure that the payee's address matches
 * the caller of this function to prevent front-running attacks. (See security
 * considerations)
 * @param from          Payer's address (Authorizer)
 * @param to            Payee's address
 * @param value         Amount to be transferred
 * @param validAfter    The time after which this is valid (unix time)
 * @param validBefore   The time before which this is valid (unix time)
 * @param nonce         Unique nonce
 * @param v             v of the signature
 * @param r             r of the signature
 * @param s             s of the signature
 */
function receiveWithAuthorization(
    address from,
    address to,
    uint256 value,
    uint256 validAfter,
    uint256 validBefore,
    bytes32 nonce,
    uint8 v,
    bytes32 r,
    bytes32 s
) external;

optionally:


event AuthorizationCanceled(
    address indexed authorizer,
    bytes32 indexed nonce
);

// keccak256("CancelAuthorization(address authorizer,bytes32 nonce)")
bytes32 public constant CANCEL_AUTHORIZATION_TYPEHASH = 0x158b0a9edf7a828aad02f63cd515c68ef2f50ba807396f6d12842833a1597429;

/**
 * @notice Attempt to cancel an authorization
 * @param authorizer    Authorizer's address
 * @param nonce         Nonce of the authorization
 * @param v             v of the signature
 * @param r             r of the signature
 * @param s             s of the signature
 */
function cancelAuthorization(
    address authorizer,
    bytes32 nonce,
    uint8 v,
    bytes32 r,
    bytes32 s
) external;

As of the time of writing, contracts implementing this EIP include USDC v2 and other contracts deriving FiatTokenV2 code bases.

Does it work with Metamask?

Yes, it does. This EIP uses EIP-712 message specification and works on any provider that exposes eth_signTypedData RPC method.

Although for dApp developers running a custodial wallet for its users, this feature can be abstracted separately since wallet providers like metamask do not work in your backend services (non-browser).

Conclusion

Gasless transactions and meta-transactions have been the subject of many EIPs. Some similar EIPs leveraging on permit (off-chain message signing) include:

  • EIP-2612 extends the EIP-20 standard with a new function permit, which allows users to modify the allowance mapping using a signed message, instead of through msg.sender.
  • EIP-1613 makes smart contracts (e.g. dApps) accessible to non-ether users by allowing contracts to accept “collect-calls”, paying for incoming calls. Let contracts “listen” on publicly accessible channels.
  • EIP-1077 Allows users to sign messages to show the intent of execution but allows a third-party relayer to execute them. Here the signature standard follows the EIP-191.

In the next article, we will make a practical implementation of EIP-3009.

Cheers

Useful Resources


More Stories from Afrodev

2023 AfroDev