SDK

How to integrate the Forward SDK to enable your users to pay gas using ERC20 tokens

Biconomy's SDK (Mexa) is a javascript based implementation that can be easily integrated into your DApp. The SDK can be used on client side code running in a browser or a javascript based DApp running on a backend server.

Mexa now comes with PermitClient and ERC20ForwarderClient to smoothly enable paying gas in ERC20 tokens with just few extra lines of code in your DApp.

ERC20ForwarderClient handles the following :

  • Fetching transaction cost estimates in ERC20 token terms

  • Building meta transaction messages for users' wallets to sign

  • Connecting with user's wallets to sign transactions

The following will be unchanged in your dApp :

  • Making calls to view methods relating to your dApp

  • Building the calldata passed by our Trusted Forwarder to your recipient contract

  • All transactions which don't require Forward​

This page exclusively covers using Mexa to forward meta transactions that charge users in ERC20 tokens

If you want mix and match with other Biconomy approaches. We recommend using the Mexa web3 provider in conjunction with ERC20ForwarderClient.

SDK Frontend Integration

1. Installing and importing the SDK

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

Via NPM
Standalone JS File
Via NPM
// Install Biconomy
npm install @biconomy/mexa
​
// Import Biconomy
import {Biconomy} from "@biconomy/mexa";
Standalone JS File
// Install Biconomy
<script src="https://cdn.jsdelivr.net/npm/@biconomy/mexa@latest/dist/mexa.js"></script>
​
// Import Biconomy
let Biconomy = window.Biconomy.default;

2. Initializing the SDK

You can use Mexa either with Web3.js or Ethers.js library. It works with both libraries.

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

web3
Ethers
web3
import {Biconomy} from "@biconomy/mexa";
const biconomy = new Biconomy(<web3 provider>,{apiKey: <API Key>, debug: true});
web3 = new Web3(biconomy);
Ethers
let _ethers;
import {Biconomy} from "@biconomy/mexa";
const biconomy = new Biconomy(window.ethereum,{apiKey: <API Key>, debug: true});
_ethers = new ethers.providers.Web3Provider(biconomy);

When Initializing SDK for this approach, some additional optional parameters can be passed. Learn more about them in Configuration

3. Initialize your DApp after Mexa initialisation

Since Mexa fetches data from Biconomy's server in order to serve better, it's better to initialize your DApp or perform any action after the biconomy.READY event.

Or if there is some error while initializing Mexa, it's better to catch and log biconomy.ERROR event for better debugging.

let ercForwarderClient;
let permitClient;
​
biconomy.onEvent(biconomy.READY, () => {
// Initialize your dapp here like getting user accounts etc
ercForwarderClient = biconomy.erc20ForwarderClient;
permitClient = biconomy.permitClient;
}).onEvent(biconomy.ERROR, (error, message) => {
// Handle error while initializing mexa
});

You have now enabled meta transactions in your DApp.

Please note the initialisation of the Permit Client and ERC20 Forwarder Client. Let's learn more about what this means and the steps involved...

  • DApp developers need their users to permit (or approve where permit is not supported) the ERC20FeeProxy contract to spend tokens on their behalf. Use PermitClient to make this easier

  • Once the allowance is verified you can go ahead and build the meta transaction [ using Mexa's ERCForwarderClient ] which will be processed using Biconomy's smart contracts and relayers under the hood. In this step, you can also calculate the cost in the amount of ERC20 token the transaction will cost your users.

  • Finally, you can send this built transaction [ using Mexa's ERCForwarderClient ] that will deduct ERC20 tokens from user's wallet as gas fees and provides you with a transaction hash.

Permit is a meta transaction method for approvals, implemented in a handful of tokens (including Dai, USDC, and Uniswap LP tokens). When tokens do not support this method, you must prompt users do an approve() transaction that they must pay the gas for.

USDC and Uniswap LP tokens follow the EIP 2612 standard for their permit function. Dai uses a non standard method. Biconomy's PermitClient supports both.

For final step above, you have an option to choose the signature type you'd like your users to sign the message with. Mexa will take care of internally building forward request for given ERC20 token, getting the signature and handle the transaction - rather than sending a signed transaction directly to the blockchain from the user's wallet.

Let's see this in action in below sample code for SDK integration for this approach.

Example Code

web3
ethers
web3

​Check the repository here for complete example code

/* this exmaple is with DAI tokens */
​
let address = <wallet address>;
//getting permit
//all parameters are optional
const daiPermitOptions = {
expiry: Math.floor(Date.now() / 1000 + 3600),
allowed: true
};
const tokenAddress = <DAI_address>;
// any supported ERC20 token can be used
​
console.log("getting user's permit to spend dai");
await permitClient.daiPermit(daiPermitOptions);
​
//Create the call data that the recipient contract will receive
let callData = contract.methods.setQuote("hello to paying gas in ERC20 tokens").encodeABI();
let gasLimit = await contract.methods.addRating(1, 5).estimateGas({from: address});
const builtTx = await ercForwarderClient.buildTx({to:config.contract.address,token:tokenAddress,txGas:Number(gasLimit),data:callData});
const tx = builtTx.request;
//Show the fee to your users!!!
const fee = builtTx.cost;
// returns a json object with txHash (if transaction is successful), log, message, code and flag
const txResponse = await ercForwarderClient.sendTxPersonalSign({req:tx});
const txHash = txResponse.txHash;
console.log(txHash);
​
//for EIP712 signature type
//const txHash = await ercForwarderClient.sendTxEIP712(tx);
​
// fetch mined transaction receipt
var timer = setInterval(()=> {
web3.eth.getTransactionReceipt(txHash, (err, receipt)=> {
if(!err && receipt){
clearInterval(timer);
resolve(receipt);
}
});
}, 2000)

​

ethers

​Check the repository here for complete example code

/* this exmaple is with DAI tokens */
​
// once biconomy is initalized in your dapp
let ethersProvider = new ethers.providers.Web3Provider(biconomy);
let signer = ethersProvider.getSigner();
​
let address = <wallet address>;
contract = new ethers.Contract(
<CONTRACT_ADDRESS>,
<CONTRACT_ABI>,
signer.connectUnchecked()
);
​
//all parameters are optional
//spender by default would be Biconomy's Fee Proxy contract
const daiPermitOptions = {
expiry: Math.floor(Date.now() / 1000 + 3600),
allowed: true
};
const tokenAddress = <DAI_address>;
// any accepted ERC20 token can be used
// you can use PermitClient like below for popular tokens like DAI and USDC
​
//getting permit
console.log("getting user's permit to spend tokens");
await permitClient.daiPermit(daiPermitOptions);
​
// Create your target method signature.. here we are calling addRating() method of our contract
let functionSignature = contractInterface.encodeFunctionData("setQuote", ["hello meta transactions"]);
let gasPrice = await ethersProvider.getGasPrice();
let gasLimit = await ethersProvider.estimateGas({
to: config.contract.address,
from: userAddress,
data: data,
});
console.log(gasLimit.toString());
const builtTx = await ercForwarderClient.buildTx({to:<CONTRACT_ADDRESS>,token:tokenAddress,txGas:Number(gasLimit),data:functionSignature});
const tx = builtTx.request;
//Show the fee to your users!!!
const fee = builtTx.cost;
// number of ERC20 tokens user will pay on behalf of gas for this transaction
console.log(fee);
​
// returns a json object with txHash (if transaction is successful), log, message, code and flag
const txResponse = await ercForwarderClient.sendTxEIP712Sign({req:tx});
const txHash = txResponse.txHash;
console.log(txHash);
​
//for personal signature type
//const txHash = await ercForwarderClient.sendTxPersonalSign(tx);
​
//event emitter methods
ethersProvider.once(txHash, (result) => {
// Emitted when the transaction has been mined
console.log(result);
//doStuff
});

​

Congratulations πŸ‘

SDK Backend Integration

Here, we provide more freedom to the developer in case of sendSignedTransaction RPC method where a raw transaction is signed by the user's private key in the backend.

  1. Initialize the Mexa SDK in the same way as mentioned above

  2. There is no change in the way you call the web3.js sendSignedTransaction method, except adding the extra information about the signature type, forwarded request and making sure that the user has given approval to spend the tokens.

The developer can choose to take the user’s signatures with any signature type they wish to.

Passing extra information using web3

web3.eth.sendSignedTransaction(data, callback) will be used here but with parameters mentioned below

Parameters

data A JSON object containing the user's signature, forward request, the raw transaction and signature type. Data to be signed can be generated using the methodgetForwardRequestAndMessageToSign(rawTransaction, tokenAddress)

callback Optional callback, returns an error object as first parameter and the result as second.

Returns

PromiEvent A promise combined event emitter. Will be resolved when the transaction receipt is available

Passing extra information using ethers

provider.send("eth_sendRawTransaction",[data], callback) will be used here but with parameters mentioned below

Parameters

data A JSON object containing the user's signature, forward request, the raw transaction and signature type. Data to be signed can be generated using the methodgetForwardRequestAndMessageToSign(rawTransaction, tokenAddress)

callback Optional callback, returns an error object as first parameter and the result as second.

Returns

Promise A promise which will not resolve until transaction hash is mined. Promise resolves with transaction hash. One can use event emitter method of ethers provider to get mined transaction receipt.

getForwardRequestAndMessageToSign helper parameters : rawTransaction, tokenAddress(optional if not spending DAI) returns : eip712Format (data to sign in EIP712 format for EIP712 signature) personalSignatureFormat (data to sign for personal signature) request (forward request prepared to pass as extra parameters) cost (charge in number of ERC20 tokens)

​

Based on your Meta Transaction Type on the recipient contract (registered as ERC20 Forwarder) Mexa will prepare the data to sign in the EIP712 format as well as personal signature format (default)

Example Code

web3
ethers
web3

​Check the repository here for complete example code

/* this exmaple is with DAI tokens */
// assuming permit is already obtained
// to see how to use permit client please refer to SDK front end example code
​
// Import sigUtil for signing data
var sigUtil = require('eth-sig-util')
​
let address = <wallet public address>;
let privateKey = <private key>;
let txParams = {
"from": address,
"gasLimit": web3.utils.toHex(210000),
"to": contractAddress,
"value": "0x0",
"data": contract.methods.setQuote("hello meta transactions").encodeABI()
};
​
// assuming permit is already obtained from users to spend their tokens by ERC fee proxy
​
const signedTx = await web3.eth.accounts.signTransaction(txParams, `0x${privateKey}`);
const data = await biconomy.getForwardRequestAndMessageToSign(signedTx.rawTransaction);
// returned data has personal signature format available as well
// data.personalSignatureFormat
​
// forward request that was created to prepare the data to sign
// you must pass the same request otherwise it would result in signature mistach
const request = data.request;
​
const cost = data.cost;
//fees in number of tokens to be charged
console.log(cost);
//show the fees to your clients!
const signature = sigUtil.signTypedMessage(new Buffer.from(privateKey, 'hex'),
{data: data.eip712Format}, 'V4');
​
let rawTransaction = signedTx.rawTransaction;
​
let data = {
signature: signature,
forwardRequest: request,
rawTransaction: rawTransaction,
signatureType: biconomy.EIP712_SIGN
};
​
// Use any one of the methods below to check for transaction confirmation
// USING PROMISE
let receipt = await web3.eth.sendSignedTransaction(data, (error, txHash)=>{
if(error) {
return console.error(error);
}
console.log(txHash);
});
​
/********* OR *********/
​
// Get the transaction Hash using the Event Emitter returned
web3.eth.sendSignedTransaction(data)
.on('transactionHash', (hash)=> {
console.log(`Transaction hash is ${hash}`)
})
.once('confirmation', (confirmation, receipt)=> {
console.log(`Transaction Confirmed.`);
console.log(receipt);
});

​

ethers

​Check the repository here for complete example code

/* this exmaple is with DAI tokens */
// assuming permit is already obtained
// to see how to use permit client please refer to SDK front end example code or web3 example
​
// Import sigUtil for signing data - Optional
var sigUtil = require('eth-sig-util');
var ethers = require('ethers');
​
// ethersProvider is initialized with biconomy
let ethersProvider = new ethers.providers.Web3Provider(biconomy);
let signer = ethersProvider.getSigner();
​
// Initialize Contracts
let contract = new ethers.Contract(<CONTRACT_ADDRESS>,
<CONTRACT_ABI>, signer.connectUnchecked());
​
let contractInterface = new ethers.utils.Interface(<CONTRACT_ABI>);
​
let address = <wallet public address>;
let privateKey = <private key>;
let wallet = new ethers.Wallet(privateKey);
​
// Create your target method signature.. here we are calling addRating() method of our contract
let functionSignature = contractInterface.encodeFunctionData("setQuote", ["hello meta transactions"]);
let gasPrice = await ethersProvider.getGasPrice();
let gasLimit = await ethersProvider.estimateGas({
to: config.contract.address,
from: userAddress,
data: data,
});
console.log(gasLimit.toString());
​
let rawTx, signedTx;
​
rawTx = {
to: config.contract.address,
data: functionSignature,
from: address,
value: "0x0",
//gasLimit: web3.utils.toHex(gasLimit),
};
​
signedTx = await wallet.signTransaction(rawTx);
​
// should get user message to sign for EIP712 or personal signature types
const forwardRequestData = await biconomy.getForwardRequestAndMessageToSign(
signedTx
);
console.log(forwardRequestData);
const signParams = forwardRequestData.eip712Format;
// returned data has personal signature format available as well
// data.personalSignatureFormat
​
// forward request that was created to prepare the data to sign
// you must pass the same request otherwise it would result in signature mistach
const request = forwardRequestData.request;
​
const cost = forwardRequestData.cost;
console.log(cost);
//show the fees in number of ERC20 tokens to be spent
​
//https://github.com/ethers-io/ethers.js/issues/687
/** ethers automatically appends EIP712 domain type.
* only need to remove for EIP712 format
* personal format is available as well by dataToSign.personalSignatureFormat
*/
delete signParams.types.EIP712Domain;
console.log(signParams);
const signature = await wallet._signTypedData(signParams.domain, signParams.types, signParams.message);
​
​
// optionally one can sign using sigUtil
//sigUtil works perfectly fine
/*
const signature = sigUtil.signTypedMessage(new Buffer.from(privateKey, 'hex'),
{data: dataToSign.eip712Format}, 'V4');
*/
​
let data = {
signature: signature,
forwardRequest: request,
rawTransaction: signedTx,
signatureType: biconomy.EIP712_SIGN,
};
​
// send signed transaction with ethers
// promise resolves to transaction hash
let tx = await ethersProvider.send("eth_sendRawTransaction", [data]);
console.log("Transaction hash : ", tx);
​
//event emitter methods
ethersProvider.once(tx, (transaction) => {
// Emitted when the transaction has been mined
console.log(transaction);
//doStuff
});
​
​

If you don't wish to use use the SDK, transactions can be directly sent using Biconomy's APIs also. Check the next section for implementation steps