Complete Guide to Meta Transactions

By akohad Jan6,2023

[ad_1]

Allow users to send transactions without paying any gas

As hard as it is to keep up with new Web3 trends, ‘meta transactions’ is clearly one of the hottest new terms floating around at the moment. It’s not hard to see why, given the promise and value they offer. For one, offering customers gas-less transactions provides a level of convenience that will make them more likely to interact with dApps they would otherwise think twice about. Spending $5 on a transaction can cause some to pause and think, “is this really worth it?” while having no gas fee leaves that question completely unasked. This is such a valuable benefit that entire companies, like Gelato, have been built to offer this as a service. But what are meta transactions, how do they work, and how can you implement them from scratch? These questions will be answered below, with a complete GitHub repo to help you offer gas-less transactions to your dApp users today.

A meta transaction is a blockchain interaction in which the initiator does not pay a gas fee. While they are the initial source of the transaction, the transaction itself is not sent from their wallet. It is instead sent from another wallet (in conjunction with a relay smart contract) on behalf of the initiator. For the sake of clarity, let’s look at a concrete example.

Say you visit an NFT mint page and want to mint a free NFT. You click ‘Mint’ and your MetaMask wallet pops up. But it doesn’t ask you to approve a transaction, it asks you to sign a message — which costs no gas. The message contains the details about your desired transaction. Those details are then used to send that transaction from a separate wallet and the NFT is delivered to your wallet. Boom! You just minted an NFT without paying any gas fees. But how exactly did it happen?

New to trading? Try crypto trading bots or copy trading on best crypto exchanges

A meta transaction is initiated first when the user signs a message with their wallet, as described above. While a wallet can sign any message, ‘hello world’ for example, a meta transaction requires the wallet to sign a message which contains the details about the desired transaction. A signature is a hash that can be reversed to acquire its original contents and verify its source. For example, if you sign the message ‘hello world’ with your wallet, the generated hash could be reversed to recover the message (hello world) and to know with certainty who signed that message. This is important, because the signature is generated with the wallet’s private key. Since no one has access to your private key except you (ideally) no one can sign a message on your behalf, making signatures a sure way to know who sent what message.

What were we talking about again? Oh right, meta transactions.

Once the user signs a message containing the details of her desired transaction (from address, to address, value, data, etc), that signature can be used to send that transaction on behalf of the original user. A separate wallet does this by passing that original signature to a ‘relay’ or ‘forwarder’ smart contract. The forwarder smart contract verifies that the signature was generated by the same wallet as the ‘from’ address to ensure we are not sending a transaction on behalf of someone who didn’t ask for it. Once that is proven, the forwarder sends the transaction to the destination smart contract and the desired interaction is carried out.

A couple of notes: the destination smart contract must be written in such a way that it accepts forwarded transactions and the wallet which sends the transaction (after the user signs the message) must have ETH in it to pay gas fees on behalf of the user.

Let’s get down to the nitty gritty and see what code is required to make this happen.

Before we get into it, some will want to skip reading this altogether and go straight to the GitHub repo. I know this because I’m the same way. For those folks, here’s the link to the GitHub repo.

Also note that prior knowledge of Solidity and Javascript is necessary to understand the following code.

If you’re still with me, let’s dive in.

Smart Contract Implementation

The first thing we need is a smart contract that can receive relayed transactions. The function below is a key piece of enabling a smart contract to receive relayed transactions.

This function ensures the received transaction is valid by checking that its data is a valid length and that the sender is a trusted forwarder. Here the ‘signer’ is the forwarding smart contract. When we deploy our destination smart contract, we should set the address of the trusted forwarding contract in the constructor. We do not want this smart contract to receive relayed transactions from just anyone.

Once those checks are passed, msgSender() returns the original source of the transaction — the signer on whose behalf we are sending the transaction.

We can call this function within another function to access this original signer’s address and perform the desired functionality. For example, see below.

Here we are accessing the original signer’s address using the function described above — msgSender();

Once we have that address, we can initiate the transfer of an ERC20 token from that user’s wallet to our smart contract. The functionality here could be swapped out for whatever your use case may be.

Now let’s take a look at the forwarding smart contract. It should first be noted that this smart contract requires us to import a few OpenZeppelin smart contracts. Namely, those are ECDSA.sol, EIP712.sol, and Ownable.sol.

There are two main functions that make our forwarding contract work. Let’s take a look at one of them below.

This function takes in a struct containing data about the desired transaction. These data are as follows.

  1. from — the address of the original signer.
  2. to — the address of the destination smart contract
  3. value — the amount of ETH to be sent with the transaction
  4. nonce — the index of the transaction, ensures transactions are handled sequentially
  5. data — a hash of the function to be called in the destination contract along with parameters

After those data are hashed, that hash is used along with the signature to make sure the ‘from’ member of the struct matches the originator of the signature. If they match, the function returns true. Otherwise, it returns false. This is used in another function in our forwarder smart contract, called execute() as we will see below.

As you can see, this function makes sure the transaction details and signature pass the verify function. If so, it sends the transaction to the destination smart contract.

Frontend Implementation

Now that our smart contracts are in place, we can construct frontend code to get a valid signature from our dApp user.

We will be working with JavaScript and the only dependency we’ll need is Ethers. Our goal is to get two things from our user: the transaction details, and their signature.

We can get the transaction data using the code above. Our ‘data’ variable is a hash of the input parameters we want to send to the destination smart contract. These may look different depending on your use case. Once we have that we remove the ‘0x’ prefix using data.slice. We do this so we can append this hash to the end of the hash of our function name (see Req.data). Then in our struct we fill in the rest of the data.

  1. userAddress — the currently connected wallet address
  2. recipient — the destination contract address
  3. nonce — will increment each time the user submits a successful transaction
  4. data — the hash of the function name we are trying to call (in our destination smart contract this function is ‘pay’) with the hash of our input parameters appended to the end

Once we have all those details, the code above requests a signature from the user. This is the signature we will send to the forwarding smart contract. I did this by sending the Req struct and flatSignature to a cloud function.

Let’s take a look at the cloud function that sends this transaction.

Backend Implementation

Here is the cloud function that takes in the Req struct and signature and sends this transaction on behalf of the user.

It’s pretty simple code. All we’re doing is creating a wallet and contract object with Ethers and calling the ‘execute’ function from our forwarding contract. Then we are sending the result back to the frontend.

If you’d like to see this in action, here’s the link to the frontend where you can interact with this architecture on the Goerli testnet.

Feel free to fork the repo and make this work for your specific use case.

[ad_2]

Source link

By akohad

Related Post

Leave a Reply

Your email address will not be published. Required fields are marked *