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
1
// Install Biconomy
2
npm install @biconomy/mexa
3
​
4
// Import Biconomy
5
import {Biconomy} from "@biconomy/mexa";
Copied!
1
// Install Biconomy
2
<script src="https://cdn.jsdelivr.net/npm/@biconomy/[email protected]/dist/mexa.js"></script>
3
​
4
// Import Biconomy
5
let Biconomy = window.Biconomy;
Copied!

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
1
import {Biconomy} from "@biconomy/mexa";
2
const biconomy = new Biconomy(<web3 provider>,{apiKey: <API Key>, debug: true});
3
web3 = new Web3(biconomy);
Copied!
1
let _ethers;
2
import {Biconomy} from "@biconomy/mexa";
3
const biconomy = new Biconomy(window.ethereum,{apiKey: <API Key>, debug: true});
4
_ethers = new ethers.providers.Web3Provider(biconomy);
Copied!
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.
1
let ercForwarderClient;
2
let permitClient;
3
​
4
biconomy.onEvent(biconomy.READY, () => {
5
// Initialize your dapp here like getting user accounts etc
6
ercForwarderClient = biconomy.erc20ForwarderClient;
7
permitClient = biconomy.permitClient;
8
}).onEvent(biconomy.ERROR, (error, message) => {
9
// Handle error while initializing mexa
10
});
Copied!
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. If you do not want to give the approval beforehand by using PermitClient seperately, then you can chain Permit meta transaction with your recipient call and charge users to pay overall gas fee in supported ERC20 tokens of their choice.
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
​Check the repository here for complete example code
1
/* this exmaple is with DAI tokens */
2
​
3
let address = <wallet address>;
4
//getting permit
5
//all parameters are optional
6
const daiPermitOptions = {
7
expiry: Math.floor(Date.now() / 1000 + 3600),
8
allowed: true
9
};
10
11
const tokenAddress = <DAI_address>;
12
// any supported ERC20 token can be used
13
​
14
console.log("getting user's permit to spend dai");
15
await permitClient.daiPermit(daiPermitOptions);
16
​
17
//Create the call data that the recipient contract will receive
18
let callData = contract.methods.setQuote("hello to paying gas in ERC20 tokens").encodeABI();
19
let gasLimit = await contract.methods.addRating(1, 5).estimateGas({from: address});
20
const builtTx = await ercForwarderClient.buildTx({to:config.contract.address,token:tokenAddress,txGas:Number(gasLimit),data:callData});
21
const tx = builtTx.request;
22
//Show the fee to your users!!!
23
const fee = builtTx.cost;
24
// returns a json object with txHash (if transaction is successful), log, message, code and flag
25
const txResponse = await ercForwarderClient.sendTxPersonalSign({req:tx});
26
const txHash = txResponse.txHash;
27
console.log(txHash);
28
​
29
//for EIP712 signature type
30
//const txHash = await ercForwarderClient.sendTxEIP712(tx);
31
​
32
// fetch mined transaction receipt
33
var timer = setInterval(()=> {
34
web3.eth.getTransactionReceipt(txHash, (err, receipt)=> {
35
if(!err && receipt){
36
clearInterval(timer);
37
resolve(receipt);
38
}
39
});
40
}, 2000)
Copied!
​
​Check the repository here for complete example code
1
/* this exmaple is with DAI tokens */
2
​
3
// once biconomy is initalized in your dapp
4
let ethersProvider = new ethers.providers.Web3Provider(biconomy);
5
let signer = ethersProvider.getSigner();
6
​
7
let address = <wallet address>;
8
contract = new ethers.Contract(
9
<CONTRACT_ADDRESS>,
10
<CONTRACT_ABI>,
11
signer.connectUnchecked()
12
);
13
​
14
//all parameters are optional
15
//spender by default would be Biconomy's Fee Proxy contract
16
const daiPermitOptions = {
17
expiry: Math.floor(Date.now() / 1000 + 3600),
18
allowed: true
19
};
20
21
const tokenAddress = <DAI_address>;
22
// any accepted ERC20 token can be used
23
// you can use PermitClient like below for popular tokens like DAI and USDC
24
​
25
//getting permit
26
console.log("getting user's permit to spend tokens");
27
await permitClient.daiPermit(daiPermitOptions);
28
​
29
// Create your target method signature.. here we are calling addRating() method of our contract
30
let functionSignature = contractInterface.encodeFunctionData("setQuote", ["hello meta transactions"]);
31
let gasPrice = await ethersProvider.getGasPrice();
32
let gasLimit = await ethersProvider.estimateGas({
33
to: config.contract.address,
34
from: userAddress,
35
data: data,
36
});
37
console.log(gasLimit.toString());
38
39
const builtTx = await ercForwarderClient.buildTx({to:<CONTRACT_ADDRESS>,token:tokenAddress,txGas:Number(gasLimit),data:functionSignature});
40
const tx = builtTx.request;
41
//Show the fee to your users!!!
42
const fee = builtTx.cost;
43
// number of ERC20 tokens user will pay on behalf of gas for this transaction
44
console.log(fee);
45
​
46
// returns a json object with txHash (if transaction is successful), log, message, code and flag
47
const txResponse = await ercForwarderClient.sendTxEIP712Sign({req:tx});
48
const txHash = txResponse.txHash;
49
console.log(txHash);
50
​
51
//for personal signature type
52
//const txHash = await ercForwarderClient.sendTxPersonalSign(tx);
53
​
54
//event emitter methods
55
ethersProvider.once(txHash, (result) => {
56
// Emitted when the transaction has been mined
57
console.log(result);
58
//doStuff
59
});
Copied!
​
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. 1.
    Initialize the Mexa SDK in the same way as mentioned above
  2. 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 (maximum 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
​Check the repository here for complete example code
1
/* this exmaple is with DAI tokens */
2
// assuming permit is already obtained
3
// to see how to use permit client please refer to SDK front end example code
4
​
5
// Import sigUtil for signing data
6
var sigUtil = require('eth-sig-util')
7
​
8
let address = <wallet public address>;
9
let privateKey = <private key>;
10
let txParams = {
11
"from": address,
12
"gasLimit": web3.utils.toHex(210000),
13
"to": contractAddress,
14
"value": "0x0",
15
"data": contract.methods.setQuote("hello meta transactions").encodeABI()
16
};
17
​
18
// assuming permit is already obtained from users to spend their tokens by ERC fee proxy
19
​
20
const signedTx = await web3.eth.accounts.signTransaction(txParams, `0x${privateKey}`);
21
const data = await biconomy.getForwardRequestAndMessageToSign(signedTx.rawTransaction);
22
// returned data has personal signature format available as well
23
// data.personalSignatureFormat
24
​
25
// forward request that was created to prepare the data to sign
26
// you must pass the same request otherwise it would result in signature mistach
27
const request = data.request;
28
​
29
const cost = data.cost;
30
//fees in number of tokens to be charged
31
console.log(cost);
32
//show the fees to your clients!
33
34
const signature = sigUtil.signTypedMessage(new Buffer.from(privateKey, 'hex'),
35
{data: data.eip712Format}, 'V4');
36
​
37
38
let rawTransaction = signedTx.rawTransaction;
39
​
40
let data = {
41
signature: signature,
42
forwardRequest: request,
43
rawTransaction: rawTransaction,
44
signatureType: biconomy.EIP712_SIGN
45
};
46
​
47
// Use any one of the methods below to check for transaction confirmation
48
// USING PROMISE
49
let receipt = await web3.eth.sendSignedTransaction(data, (error, txHash)=>{
50
if(error) {
51
return console.error(error);
52
}
53
console.log(txHash);
54
});
55
​
56
/********* OR *********/
57
​
58
// Get the transaction Hash using the Event Emitter returned
59
web3.eth.sendSignedTransaction(data)
60
.on('transactionHash', (hash)=> {
61
console.log(`Transaction hash is ${hash}`)
62
})
63
.once('confirmation', (confirmation, receipt)=> {
64
console.log(`Transaction Confirmed.`);
65
console.log(receipt);
66
});
Copied!
​
​Check the repository here for complete example code
1
/* this exmaple is with DAI tokens */
2
// assuming permit is already obtained
3
// to see how to use permit client please refer to SDK front end example code or web3 example
4
​
5
// Import sigUtil for signing data - Optional
6
var sigUtil = require('eth-sig-util');
7
var ethers = require('ethers');
8
​
9
// ethersProvider is initialized with biconomy
10
let ethersProvider = new ethers.providers.Web3Provider(biconomy);
11
let signer = ethersProvider.getSigner();
12
​
13
// Initialize Contracts
14
let contract = new ethers.Contract(<CONTRACT_ADDRESS>,
15
<CONTRACT_ABI>, signer.connectUnchecked());
16
​
17
let contractInterface = new ethers.utils.Interface(<CONTRACT_ABI>);
18
​
19
let address = <wallet public address>;
20
let privateKey = <private key>;
21
let wallet = new ethers.Wallet(privateKey);
22
​
23
// Create your target method signature.. here we are calling addRating() method of our contract
24
let functionSignature = contractInterface.encodeFunctionData("setQuote", ["hello meta transactions"]);
25
let gasPrice = await ethersProvider.getGasPrice();
26
let gasLimit = await ethersProvider.estimateGas({
27
to: config.contract.address,
28
from: userAddress,
29
data: data,
30
});
31
console.log(gasLimit.toString());
32
​
33
let rawTx, signedTx;
34
​
35
rawTx = {
36
to: config.contract.address,
37
data: functionSignature,
38
from: address,
39
value: "0x0",
40
//gasLimit: web3.utils.toHex(gasLimit),
41
};
42
​
43
signedTx = await wallet.signTransaction(rawTx);
44
​
45
// should get user message to sign for EIP712 or personal signature types
46
const forwardRequestData = await biconomy.getForwardRequestAndMessageToSign(
47
signedTx
48
);
49
console.log(forwardRequestData);
50
const signParams = forwardRequestData.eip712Format;
51
// returned data has personal signature format available as well
52
// data.personalSignatureFormat
53
​
54
// forward request that was created to prepare the data to sign
55
// you must pass the same request otherwise it would result in signature mistach
56
const request = forwardRequestData.request;
57
​
58
const cost = forwardRequestData.cost;
59
console.log(cost);
60
//show the fees in number of ERC20 tokens to be spent
61
​
62
//https://github.com/ethers-io/ethers.js/issues/687
63
/** ethers automatically appends EIP712 domain type.
64
* only need to remove for EIP712 format
65
* personal format is available as well by dataToSign.personalSignatureFormat
66
*/
67
delete signParams.types.EIP712Domain;
68
console.log(signParams);
69
const signature = await wallet._signTypedData(signParams.domain, signParams.types, signParams.message);
70
​
71
​
72
// optionally one can sign using sigUtil
73
//sigUtil works perfectly fine
74
/*
75
const signature = sigUtil.signTypedMessage(new Buffer.from(privateKey, 'hex'),
76
{data: dataToSign.eip712Format}, 'V4');
77
*/
78
​
79
let data = {
80
signature: signature,
81
forwardRequest: request,
82
rawTransaction: signedTx,
83
signatureType: biconomy.EIP712_SIGN,
84
};
85
​
86
// send signed transaction with ethers
87
// promise resolves to transaction hash
88
let tx = await ethersProvider.send("eth_sendRawTransaction", [data]);
89
console.log("Transaction hash : ", tx);
90
​
91
//event emitter methods
92
ethersProvider.once(tx, (transaction) => {
93
// Emitted when the transaction has been mined
94
console.log(transaction);
95
//doStuff
96
});
97
​
98
​
Copied!
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
Last modified 8mo ago