Onboarding without friction — the "gas" challenge (EIP-2771)

Nilton Heck
GasBit
Published in
11 min readNov 1, 2022

According to the most recent developer report by Alchemy, the Web3 environment just reached more than 12.000 dApps running across all chains, and, along with that, the number of Smart Contracts verified has grown at least 160% YoY. These are astonishing numbers and do reinforce that decentralized solutions seem to be getting attention but, in parallel, as we expect more users to join the vanguard, there are plenty of challenges to tackle.

Thus, especially considering dApps, it’s quite safe to say that gas is, by no means, a strong generator of friction for new users. Since gas is required for every single transaction, and usually the process to buy Ether requires some annoying and time-consuming steps, it’s very unlikely that new users will spend their time fighting the barriers of the crypto entrance just to enjoy your app — and more than that, think about the UX challenge to create a seamless integration between different companies and products, or how limited your app is in terms of automated tasks since it would require users to consistently fuel their gas tanks. This is a mess.

Trying to reduce friction and create a more realistic approach to onboarding new users, the Ethereum community come up with a set of Improvement Propositions (EIP) to define a design pattern that allows for one wallet to pay the gas for another wallet’s transaction while keeping the data intact. This is what Meta Transactions are about.

Additionally, as we unfold the Meta Transaction concept you’ll see that we will focus on the so-called EIP-2771. We’ll, obviously, get into more details about what it actually means, but it’s import to reinforce that there are nearly inifnity different ways to achieve the same goal.

A bit more detailed view of Meta Transactions

So, as previously said Meta Transaction are transactions compound in such a way that the user who has the intention to fulfill a transaction does not have to pay for the transaction execution, leveraging the responsibility for a third-party entity that will verify the content of the transaction and, if approved, proceed to forward it to its final destination (see image below).

Simplified Flow of a Meta-Transaction Implementation
Simplified flow of a Meta-Transaction Implementation

Technically, this idea is composed of three different design patterns, as defined by their own EIPs (EIPs stand for Ethereum Improvement Proposition and are defined and discussed inside the community until it is fully published). In this case, the following EIPs compose the Meta Transactions technique: EIP-712, EIP-2770, and EIP-2771.

In the following sections, we’ll get into more detail about each EIP and how it impacts the whole solution. For now, what you need to understand is the role of each actor represented in the flow previously presented.

  • The Third-party Wallet also called "Relayer": is the wallet that will actually pay for the fees;
  • The User: the person who wants to execute some transaction and who’ll sign his intended action;
  • The Trusted Forwarder Contract: a “proxy” contract responsible to validate inputs coming from the requester and, finally, forward it toward its final destination;
  • The Recipient Contract: the contract whom which the user wants to interact with.

In order to better represent how EIPs and the steps in the flow converge we can slightly change the previous image to split each responsibility accordingly:

Meta Transaction Flow by EIP

Beginning to Build

Ok, enough with theory (for now!). From this segment on, we'll walk through the necessary steps required to build our own Meta Transaction solution. To avoid being too boring we'll avoid getting into much detail about every EIP, rather we're going to focus on the suggested proposition and how to implement it, considering the desired outcome: allow a third-party wallet to pay the gas fees.

Also, to make it easier to digest we're going to begin the development of our solution from the very end, then move toward the beginning of the flow, so we assure that all requirements are properly fulfilled. Having said that, the first things that need to be addressed are the necessary changes to the recipient contract. So, let's get right into it.

Stage 1: Crafting our Recipient Contract

To summarize, when we remove the veils of complexity, what happens during a Meta Transaction is a “trusted impersonation”. And this is so because what the “Relayer” does is to call a “generic function” (we’ll talk more about this concept in the sections ahead) on a “Trusted Forwarder Contract” explicitly asking to forward some payload to the Recipient Contract.

Being so, since the proposed approach of Meta Trsanctions uses a third-party Relayer to pay the gas, and a forwarder to forward the transaction, some aspects of a regular transaction are fundamentally different: a) the sender (usually found in the msg.sender attribute) won't be the real sender, since this is a relayed transaction; also b) the Trusted Forwarder Contract, who's responsible for forwarding the transaction, should be explicitly allowed to execute those functions — this is mandatory to assure the safety of the protocol. These definitions, as well as the proposed solutions, are precisely described on the EIP-2771, as previously mentioned.

To demonstrate in practice, the following code is a simplified contract that only stores the address of the last caller. Thus, to add support for meta transactions we took advantage of the templates from the @openzepellin/contracts library. Getting this final result:

It's important to note that, out-of-the-box the ERC2771Context import already assures compliance with the EIP-2771 by both allowing for a definition of the Trusted Forwarder (line 3), and also by exporting a method to extract the requester address (line 15). The last one is the only way in which someone could find the original sender, since, as mentioned, the Relayer will be defined as the usual msg.sender.

Moreover, the ERC2771Context also assures that, regardless of being a Meta Transaction or not, the _msgSender method will always return the intended sender — and it does so by checking the incoming payload.

To prove it, check this out, extracted from the ERC2771Context definition:

Obviously, as we move back to the beginning of the flow we'll get more details on how the data is serialized in order to possibly extract the signed payload — at the time being, just trust me, it works!

So, now, simple as that, our contract already supports Meta Transactions. And then it's time to move back a little bit, and see how the Trusted Forwards works and why it is a core element of this approach.

Stage 2: The Trusted Forwarder

The Trusted Forwarder is a public contract, ideally auditable, that sits in between an UNTRUSTED SOURCE and our recipient contract, playing the duty of defining the standards of the Meta Transaction protocol, assuring the reliability of the communication, and executing the forward of the final transaction.

As one can imagine, these are quite important tasks for a single entity, which is why we are going to discuss each one separately, just to make sure that we're all on the same page. To keep us well informed, we'll use the EIP-2770 as our guideline.

Standard Definition

Meta Transaction is a protocol and, as such, requires some minimal standardization. In this case, since we're dealing with forwarding transactions, we have to follow the ForwardRequest struct — a defined structure data type that contains some vital parameters, which means that when calling the execute method of the Trusted Forward we should ALWAYS provide the same data structure expected by the protocol- otherwise its going tofail, just because it's an unknown request.

This is how the data structure is defined, according to the EIP:

At this point, there's nothing we need to do, since typing the data and signing is part of the first step of the flow. Here we're interested in the standard alone.

Let's move forward.

Signature Verification

An essential part of the Meta Transaction protocol is that it relies on some features to assure reliability and security — The Trusted Forwarder is the piece that holds this reliability, and it does so by implementing a verification method that is executed every time someone makes a new request to it.

The idea of this method is to use a predefined signature method (EIP-712) to authenticate that the payload incoming is indeed signed by the person identified in the payload — if verification fails, the transaction is reverted.

Transaction Forwarding (also known as Command Execution)

Lastly, if the requester sends some properly structured data, signed by the original sender, the last duty is to actually send the transaction to the recipient contract.

The method that wraps the logic of both verification and forwarding is the "execute" method of the Trusted Forward contract. Similar to the definition of the verification method this one also uses the incoming payload to build the final transaction.

Finally, now that you know what, and why the Trusted Forward is a fundamental stone to the Meta Transaction protocol, you can look into some code to have a sense of how those duties are performed. Here’s a good example of a verified contract built using OpenZepelling: https://polygonscan.com/address/0xe7E5c7EE153dB1408E81D647406381b839936777#code — even though I personally love to dig into code I would advice new developers to avoid going into this rabbit holes by yourself and rather spend more time in your dApps. But, regardless of your choice, be aware that most Trusted Forwards are generic implementations, so you can rely on others to build your gasless dApp.

Now, moving to the final part: how to execute Meta Transactions when calling the blockchain.

So, at this point, we already know which changes our contract needs in order to be enabled to accept Meta Transactions and we also know that there’s a generic contract in the middle to validate a certain request if it is sent by following a given standard.

Your question then is? How do I build a structured request that could be validated by the Trusted Forwarder and be successfully executed by my contract?

I’m glad you asked. This is what we’re going to do now. So, fasten your set belts, and let’s understand what the EIP-712 does and how to put that into a working code.

Stage 3: Signing and Sending

We previously discussed that the Trusted Forwarder holds accountability for verifying the authenticity of the request being made, and he does so by validating the signature received alongside the structure input data.

Being so, considering that beyond signature, we should also send the actual transaction to the blockchain, our dApp is responsible for: a) building the forward object (defined by the Forward struct) and providing a proper signature, just like the Trusted Forwarder expects it to be, and; b) send the transaction to the blockchain using an External Owned Account (EOA, for short).

First, about the signing procedure: assembling this piece could be potentially complicated, however, the EIP-2770 uses an already existing EIP to set an expected signature definition. To do so, the EIP-2770 relies on the procedures described in the EIP-712 (pay attention to the EIP numbering, quist similar to the token standard definition, EIP-721).

Not surprisingly, this is the moment when it all come together. The EIP-712 will provide a signature and hashing procedure, once created the forward request it will be shipped to the forwarder, the Trusted Forwarder will use the same procedures to evaluate the request, and then, if all matches, will forward the request to the final contract. The final contract, that trust the Forwarder, will execute the transaction. Voi l’a! Meta transactions!

But… back to signing…

Despite his huge importance on the whole Meta Transaction protocol, we won’t dive deeply into it. The reason is that the EIP-712 is by far the most complex technique of the whole set of tools on the protocol. We should tho, in order to build our applications, know that it introduces a new method to the regular Ethereum RPC nodes, the eth_signTypedData.

You, as a quick learner, might already be able to guess what this method intends to do. It’ll allow for signing a typed data (as the Forward struct). Thus, considering that now we already prepared our payload and signed it, we miss the last piece of the puzzle: sending the transaction to the Ethereum node.

As previously discussed, the whole point of Meta Transactions is to allow for gasless transactions. And, now that we have prepared our transaction according to EIPs definitions, we need to ship it. To execute this, we’ll need to use an EOA, a wallet that belongs to the developer and that has funds to pay for the transaction’s gas cost. Seems simple, but…

The question that usually follows is: since I’ll need to send all Meta Transactions, how do I manage to programmatically send it? This is where it gets a bit out of hand for most developers: you will need to host a backend service to sign on your behalf.

The developer must also be aware that the “relay service” will require direct access to the EOA used to pay for the gas fees. Thus, one should be extremely careful not to disclose the wallet's private key and, in parallel, account for measures to avoid exploitation of the service.

Anyhow, this is what a complete solution would look like if broken into multiple layers:

Layer division of the Meta Transaction procedure

Finally, to wrap up this “signing and sending” section the following code shows how to fully implement a hypothetical call to our contract created at the beginning of this article.

By placing these pieces together and assuring compliance with the EIPs the developer should be able to perform Meta Transactions using what we can call its own “Private Relayer”.

It’s also important to note that by using an EOA the developer should pay special attention to the fact that the wallet is subject to the same constraints applied to all wallets on the network, which means that, for example, miscalculated gas estimations could lead to some unnecessary loss on gas fees or, stuckness on mempool, to say just a couple of potential issues.

Nonetheless, despite risks, pros, and cons, the EIP-2771 is still the king of the realm. And should be for a while, until we came to a more clever and widely validated solution.

As you all can see building an EIP-compliant solution from the ground up can be quite hard, and time-consuming, all of that while still requiring an external service to support the shipping of transactions, let alone the high stakes of building something incorrectly or even mismanaging your keys.

If you want a shortcut to quickly jump into Meta Transactions you should check GasBit. GasBit is an easy-to-use solution made to allow for quick and secure integration of Meta Transactions.

The GasBit product has a set of distinguishing features to make developers' life easier:

  • Nearly a single-line code implementation;
  • Reliable relayers network, assuring the delivery of all transactions within an acceptable time;
  • Pay-as-you-go with Credit Card, forget about native tokens and pay the gas fees from your app directly from your credit card.
  • Set and manage the rules of your transactions, allowing the developer to be in full control of his application transactions.

If you are looking for a solution that will help you save time and money, probably GasBit is made for you.

To learn more and take advantage of the offerings, jump to https://www.gasbit.xyz/ right now and save your team time and energy!

--

--