EIP-2771 Approach

Secure Protocol For Native Meta Transactions

Smart Contract change - Personal & EIP712 Signature

  1. 1.
    Inherit BaseRelayRecipient contract in your contract.
  2. 2.
    Set trustedForwarder to Biconomy Forwarder's address while deploying your contract.
  3. 3.
    Replace msg.sender in your contract with _msgSender().
More details about Standard EIP-2771 Approach can be found here.
import "@opengsn/gsn/contracts/BaseRelayRecipient.sol";
contract MyContract is BaseRelayRecipient {
* Set the trustedForwarder address either in constructor or
* in other init function in your contract
constructor(address _trustedForwarder) public {
trustedForwarder = _trustedForwarder;
* You should add one setTrustedForwarder(address _trustedForwarder)
* method with onlyOwner modifier so you can change the trusted
* forwarder address to switch to some other meta transaction protocol
* if any better protocol comes tomorrow or current one is upgraded.
* Override this function.
* This version is to keep track of BaseRelayRecipient you are using
* in your contract.
function versionRecipient() external view override returns (string memory) {
return "1";

Client Side changes

Now the contracts are updated and deployed, it's time to do the changes in your script.
  1. 1.
    After Registering Your dApp, SmartContact & Contract method on Desired network on Biconomy Dashboard, copy the <api-key>, you will need it on the client-side code.
  2. 2.
    Here comes the most important part, you need to pass the normal provider from your connected wallet e.g. window.ethereum for Metamask in Biconomy options. Using this provider Biconomy will take care of getting the User Signature. The main provider object to be passed in Biconomy should be JSON RPC provider initialized with network RPC URL on which your Dapp is deployed. That's it.
  3. 3.
    So the flow will be, Mexa will take the user signature using the connected wallet and will relay the transaction on your network via Biconomy servers.

SDK Based Integration

In basic terms, you need one provider object for using Biconomy (Mexa), another for signing meta transactions (to be passed in Biconomy options)

1. Importing Mexa

Via npm
Standalone JS File
npm install @biconomy/mexa
// Install Biconomy
<script src="[email protected]/dist/mexa.js"></script>
// Import Biconomy
let Biconomy = window.Biconomy;

2. Initializing SDK

You can use Mexa either with Web3.js or Ethers.js. We'll be making two provider objects, one linked to your dApp's network RPC, and the other to your user's wallet.
Web3 + EIP2771/Forward
Ethers + EIP2771/Forward
import {Biconomy} from "@biconomy/mexa";
import { Web3 } from "web3";
// Pass connected wallet provider under walletProvider field
let biconomy = new Biconomy(new Web3.providers.HttpProvider("YOUR RPC URL HERE"),{
walletProvider: <Wallet Provider>,
apiKey: <API Key>,
debug: true
let networkWeb3 = new Web3(biconomy);
import {Biconomy} from "@biconomy/mexa";
import { ethers } from "ethers";
// Pass connected wallet provider under walletProvider field
let biconomy = new Biconomy(new ethers.providers.JsonRpcProvider("YOUR RPC URL HERE"),
walletProvider: <Wallet Provider>,
apiKey: <API Key>,
debug: true
let networkProvider = new ethers.providers.Web3Provider(biconomy);

3. Initialize your dApp after Mexa initialization

Mexa fetches data from Biconomy's servers. Because of this, it's best to initialize your dApp or perform any action after the biconomy.READY event occurs.
If there is an error while initializing Mexa, it's good to catch and log a biconomy.ERROR event for better debugging.
In this scenario, you may need to initialise both instances of Mexa.
biconomy.onEvent(biconomy.READY, () => {
// Initialize your contracts here using biconomy's provider instance
// Initialize dapp here like getting user accounts etc
}).onEvent(biconomy.ERROR, (error, message) => {
// Handle error while initializing mexa

4. Sign & Send Meta Transactions

EIP2771 + Ethers
EIP2771 + Web3
Check the repository here for complete example code
// Initialize Constants
let contract = new ethers.Contract(<CONTRACT_ADDRESS>,
<CONTRACT_ABI>, biconomy.getSignerByAddress(userAddress));
let contractInterface = new ethers.utils.Interface(<CONTRACT_ABI>);
let userAddress = <Selected Address>;
// Create your target method signature.. here we are calling setQuote() method of our contract
let { data } = await contract.populateTransaction.setQuote(newQuote);
let provider = biconomy.getEthersProvider();
// you can also use networkProvider created above
let gasLimit = await provider.estimateGas({
from: userAddress,
data: data
console.log("Gas limit : ", gasLimit);
let txParams = {
data: data,
from: userAddress,
gasLimit: gasLimit, // optional
signatureType: "EIP712_SIGN"
// as ethers does not allow providing custom options while sending transaction
// you can also use networkProvider created above
// signature will be taken by mexa using normal provider (metamask wallet etc) that you passed in Biconomy options
let tx = await provider.send("eth_sendTransaction", [txParams]);
console.log("Transaction hash : ", tx);
//event emitter methods
provider.once(tx, (transaction) => {
// Emitted when the transaction has been mined
//show success message
//do something with transaction hash
Check the repository here for complete example code
// Mexa takes care of getting signatures as explained above so you only need to use networkWeb3
// Initialize constants
let contract = new networkWeb3.eth.Contract(
<Your Contract ABI>,
<Your Contract Address>
let userAddress = <selected address>;
//Call your target method (must be registered on the dashboard).. here we are calling setQuote() method of our contract
let tx = contract.methods.setQuote(newQuote).send({
from: userAddress,
signatureType: biconomy.EIP712_SIGN,
//optionally you can add other options like gasLimit
tx.on("transactionHash", function (hash) {
console.log(`Transaction hash is ${hash}`);
showInfoMessage(`Transaction sent. Waiting for confirmation ..`);
}).once("confirmation", function (confirmationNumber, receipt) {
//do something with transaction hash