How to Integrate Native Meta Transactions in your DeFi DApp

Anubhav Girdhar
Biconomy
Published in
7 min readMay 18, 2020

This is the third article in a three part series to get you started with building DApps on Ethereum

Part 1 : How to build a DApp

-get started with building your first DApp on Ethereum

Part 2 : How to build a DeFi App

- build a decentralized finance DApp on Ethereum

Github Repo and Working Demo of this article

High Level Overview of Meta transaction

A transaction on Ethereum requires users to pay fees (gas) on every transaction in the form of ether. This leads to a complicated UX as users have to understand the inner workings of public blockchains and market dynamics for transaction fees.

Anatomy of a DApp without Meta transactions

Enter Meta Transactions

Instead of sending the transaction directly to the blockchain, the user signs the transaction and the off-chain relayer sends the transaction to the blockchain while paying gas fees for the user.

Signing a message/transaction using Public-key cryptography, or asymmetric cryptography does not cost any fees for the user

Anatomy of a DApp with Meta Transactions

Contract Setup with Signature Verification on chain

What are Signatures?

Ethereum uses the Elliptic Curve Digital Signature Algorithm (ECDSA) to validate the origin and integrity of messages.

Elliptic Curve Cryptography (ECC) and ECDSA are a specific flavour of asymmetric cryptography. They are widely used in blockchain technology because of three reasons:

  • Their computational performance is economical compared to a lot of other algorithms
  • The keys that are generated are relatively short
  • Bitcoin started it, so most new blockchain projects have copied it

Solidity provides a globally available method ecrecover that returns an address given the three parameters. If the returned address is the same as the signer’s address, then the signature is valid.

  • The keys that are generated are relatively short
  • Bitcoin started it, so most new blockchain projects have copied it

How to implement EIP712

This new standard introduces several concepts which developers must be familiar with, so this section will zoom in on what you need to know to implement it in dApps.

Take for instance that you are building a decentralised auction dApp in which bidders sign bids off-chain, and a smart contract verifies these signed bids on-chain.

Contract Setup with Signature Verification on chain

Solidity provides a globally available method ecrecover that returns an address given the three parameters. If the returned address is the same as the signer’s address, then the signature is valid.

Before a wallet provider signs EIP712-typed data, it first formats and hashes it. As such, your contract needs to be able to do the same in order to use ecrecover to determine which address signed it, and you have to replicate this formatting/hash function in your Solidity contract code. This is perhaps the trickiest step in the process, so be precise and careful here.

1. Design your domain separator

Domain Separator is a mandatory field

‌This helps to prevent a signature meant for one dApp from working in another. As EIP712 explains:

  • It is possible that two DApps come up with an identical structure like Transfer(address from,address to,uint256 amount) that should not be compatible. By introducing a domain separator the dApp developers are guaranteed that there can be no signature collision.
struct EIP712Domain {
string name;
string version;
uint256 chainId;
address verifyingContract;
}
  • It is possible that two DApps come up with an identical structure like Transfer(address from,address to,uint256 amount) that should not be compatible. By introducing a domain separator the dApp developers are guaranteed that there can be no signature collision.

name: the dApp name, e.g. “Compound-DApp”

version: The current version number of your dApp or platform. It prevents signatures from one dApp version from working with those of others.

chainId: Network Id e.g. Kovan Testnet = 42; Main Network = 1

verifyingContract: The contract address that will verify the resulting signature.

1. Design your data structures

this is the structure you intend users to sign

//This struct verifies transaction data
struct MetaTransaction {
address holder;
uint256 nonce;
}
Glimpse of the metamask popup for signature

Add nonce for replay protection

mapping(address => uint256) public nonces;

Next, define the type hashes to fit your data structures.

Note that there are no spaces after commas and brackets.

bytes32 internal constant EIP712_DOMAIN_TYPEHASH =   keccak256(bytes("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"));bytes32 internal constant META_TRANSACTION_TYPEHASH = keccak256(bytes("MetaTransaction(address holder,uint256 nonce)"));

3. Modify the respective function

Note : Kindly refer to the previous article for continuity of this smart contract

Make a function to withdraw cEth from the contract

function withdraw(address userAddress, uint256 nonce, bytes32 r, bytes32 s, uint8 v) external  {}

r,s,v are part of the signature formed off-chain which can be used to verify signatures on-chain

Define the hash to verify signature on chain

bytes32 digest = keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR,
keccak256(abi.encode(META_TRANSACTION_TYPEHASH, userAddress, nonce))));

Verify signatures on chain

require(userAddress != address(0), "invalid-address-0");
require(userAddress == ecrecover(digest, v, r, s), "invalid-signatures");

Check for cEth balance in the contract and transfer to the user

uint amount = cEth.balanceOf(address(this));
cEth.transfer(userAddress, amount);

Increment the nonce after successful transaction

nonces[userAddress]++;

That’s it for the smart contract code!

Deploy the contract on Kovan Network and copy the contract address and abi

Warning : This is not a safe or secure smart contract, everything has been simplified to not deviate from learning about meta transaction integration. It is advised to not reuse the smart contract code.

How to Set Up the frontend to take user’s signatures

1. Write signing code for your dApp

Your JavaScript dApp needs to be able to ask MetaMask to sign your data. d

Define data types as defined in the smart contract

const domainType = [
{ name: "name", type: "string" },
{ name: "version", type: "string" },
{ name: "chainId", type: "uint256" },
{ name: "verifyingContract", type: "address" }
];
const metaTransactionType = [
{ name: "holder", type: "address" },
{ name: "nonce", type: "uint256" }
];

Our domain separator would look like this:

Note : Use your deployed contract address in the verifyingContract field

let domainData = {
name: "Compound-DApp",
version: "1",
chainId : "42", // Kovan
verifyingContract: "0x23a950252a89e46c5aB63e8F7906974F5d3d0330"
};

Fetch nonce from the contract

const nonce = await contract.methods.nonces(window.ethereum.selectedAddress).call();

Assign values to the JSON signing message

let message = {
holder: window.ethereum.selectedAddress,
nonce: parseInt(nonce),
};

Define data to Sign

const dataToSign = JSON.stringify({
types: {
EIP712Domain: domainType,
MetaTransaction: metaTransactionType
},
domain: domainData,
primaryType: "MetaTransaction",
message: message
});

Next, make the eth_signTypedData_v4 signing call to web3:

Signatures produced by web3.js are the concatenation of r, s, and v, so a necessary first step is splitting those parameters back out.

web3.currentProvider.sendAsync(
{jsonrpc: "2.0",
id: 999999999999,
method: "eth_signTypedData_v4",
params: [window.ethereum.selectedAddress, dataToSign],
from: signer
},
function(err, result) {
if (err) {
return console.error(err);
}
const signature = result.result.substring(2);
const r = "0x" + signature.substring(0, 64);
const s = "0x" + signature.substring(64, 128);
const v = parseInt(signature.substring(128, 130), 16);
}
);

Call the respective meta transaction enabled function

(async function withdraw() {
const result = await contract.methods.withdraw( window.ethereum.selectedAddress, nonce, r, s, v)
.send({ from: window.ethereum.selectedAddress })
console.log(result)})();

Sweet! We just integrated meta transaction in our DeFi DApp.

The last step is the integration of a relayer who would relay the signed messages by the user and send it to the blockchain.

Introducing Biconomy

Biconomy is a relayer infrastructure network and transaction platform enabling you to build applications easily and reduce the friction between decentralized applications built on the blockchain and end-users.

Integrating Biconomy is a two step process

  1. Register your DApp on Biconomy Dashboard
  • Register your DApp on Biconomy Dashboard
  • Upload your Smart Contracts
  • Select method to enable Meta Transactions

Head over to Biconomy Docs for step-by-step tutorial to register your dapp

2) Integrate Mexa SDK

Mexa can be installed either via npm repository or using standalone javascript file using html <script/> tag

Install Mexa via npm

npm install @biconomy/mexa

Import and Initialize Mexa

import Biconomy from "@biconomy/mexa";
const biconomy = new Biconomy(window.ethereum,{apiKey: "q9oEztJM8.e8ed08a7-5b38-48e3-b4c0-f66e6b66f407"});
web3 = new Web3(biconomy);

Initialize DApp after Mexa initialization

biconomy.onEvent(biconomy.READY, () => {
// Initialize your dapp here like getting user accounts etc
await window.ethereum.enable();
contract = new web3.eth.Contract(config.contract.abi, config.contract.address);
startApp();
}).onEvent(biconomy.ERROR, (error, message) => {
// Handle error while initializing mexa
console.log(error)
});

Congratulations 👏

You have now enabled meta transactions in your DApp. Interact with web3 the way you have been doing it.

Now whenever there is a write transaction action(registered in mexa dashboard also) initiated from the user , Mexa will ask for user’s signature in an EIP-712 format and handle the transaction rather than sending signed transaction directly to blockchain from user’s wallet.

Check out Github Repo and Working Demo of this article here.

About Biconomy

At Biconomy, our mission is to make next-generation technologies simple to use. We are building a smart relayer infrastructure network for blockchain transactions that reduces the friction between applications built on the blockchain and the end-users. Biconomy’s relayer network comprises a suite of SDKs and APIs that simplify the DApp development process for developers as well as abstracting many of the blockchain complexities, vastly enhancing the end-user experience for DApps and wallets.

Telegram Channel: https://t.me/biconomy

Website: https://biconomy.io

Twitter: https://twitter.com/biconomy

Email: support@biconomy.io

Discord : https://discord.gg/dUDTab

--

--