How to Integrate Native Meta Transactions in your DeFi DApp
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.
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
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;
}
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
- 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