EIP-2771

Secure Protocol For Native Meta Transactions

Smart Contract change - Personal & EIP712 Signature

  1. Inherit BaseRelayRecipient contract in your contract.

  2. ​Add BiconomyForwarder address in BaseRelayReceipent while deploying your contract.

  3. Replace msg.sender in your contract with msgSender().

Client Side changes

Now the contracts are updated and deployed, it's time to do the changes in your script.

  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. Here comes the most important part, you need to make 2 providers on your client-side. One normal provider from your connected wallet e.g. window.ethereum for Metamask, to take the User Signature on the wallet pop-up window. Second provider object to be used with Biconomy to send the transaction. This second provider should be a normal provider initialised with network RPC URL. That's it.

  3. So the flow will be, to take the user signature you need to use the normal provider, once you get the user signature. pass that signature in the

This does not work with those EIP712 Signatures where chainId is used in DomainData. Metamask will throw an error if the ChainId specified does not match that of the current RPC it is connected to.

SDK Based Integration

In basic terms, you need one provider object for using Biconomy (Mexa), another for signing meta transactions.

1. Importing Mexa

Via npm
Standalone JS File
Via npm
npm install @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 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
Web3 + EIP2771/Forward
import {Biconomy} from "@biconomy/mexa";
import { Web3 } from "web3";
​
// We're creating a 2nd Ethers provider linked to your L2 network of choice
let biconomy = new Biconomy(new Web3.providers.HttpProvider("YOUR RPC URL HERE"),
{apiKey: <API Key>, debug: true});
let networkWeb3 = new Web3(biconomy);
​
/*
This provider linked to your wallet.
If needed, substitute your wallet solution in place of window.ethereum
*/
let walletWeb3 = new Web3(window.ethereum);
Ethers + EIP2771/Forward
import {Biconomy} from "@biconomy/mexa";
import { ethers } from "ethers";
​
// We're creating a 2nd Ethers provider linked to your L2 network of choice
let biconomy = new Biconomy(new Web3.providers.HttpProvider("YOUR RPC URL HERE")
,{apiKey: <API Key>, debug: true});
let networkProvider = new ethers.providers.Web3Provider(biconomy);
​
/*
This provider linked to your wallet.
If needed, substitute your wallet solution in place of window.ethereum
*/
let walletProvider = new ethers.providers.Web3Provider(window.ethereum);

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.

EIP2771
EIP2771
biconomy.onEvent(biconomy.READY, () => {
// Initialize stuff that needs to interact with your node endpoint
// ie. view calls to contracts
}).onEvent(biconomy.ERROR, (error, message) => {
// Handle error while initializing mexa
});

4. Sign & Send Meta Transactions

Example code below is being updated. Please wait until we update the code below.

EIP2771 + Ethers
EIP2771 + Web3
EIP2771 + Ethers
let walletSigner = walletProvider.getSigner();
let networkSigner = networkProvider.getSigner();
​
// Initialize Contracts
let contract = new ethers.Contract(<CONTRACT_ADDRESS>,
<CONTRACT_ABI>, networkSigner.connectUnchecked());
​
let contractInterface = new ethers.utils.Interface(<CONTRACT_ABI>);
​
let address = <wallet public address>; //user address
​
let functionSignature = contractInterface.encodeFunctionData("addRating", [1,5]);
let gasPrice = await ethersProvider.getGasPrice();
let gasLimit = await ethersProvider.estimateGas({
to: <CONTRACT_ADDRESS>,
from: address,
data: functionSignature
});
console.log(gasLimit.toString())
​
let rawTx = {
to: <CONTRACT_ADDRESS>,
data: functionSignature,
from: address
};
const metaTxData = await biconomy.getForwardRequestAndMessageToSign(rawTx);
console.log(dataToSign);
const hashToSign = metaTxData.personalSignatureFormat;
const signature = await walletSigner.signMessage(hashToSign);
​
let data = {
signature: signature,
rawTransaction: signedTx,
signatureType: biconomy.PERSONAL_SIGN,
};
​
// send signed transaction with ethers
// promise resolves to transaction hash
let tx = networkProvider.send("eth_sendRawTransaction", [data]);
console.log("Transaction hash : ", tx);
//event emitter for ethers
networkProvider.once(tx, (transaction) => {
// Emitted when the transaction has been mined
console.log(transaction);
//doStuff
});
EIP2771 + Web3
// 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.addRating(1, 5).encodeABI()
};
​
const dataToSign = await biconomy.getForwardRequestMessageToSign(txParams);
// dataToSign has personalFormat available as well
const signature = sigUtil.signTypedMessage(new Buffer.from(privateKey, 'hex'),
{data: dataToSign.eip712Format}, 'V4');
let rawTransaction = signedTx.rawTransaction;
​
let data = {
signature: signature,
rawTransaction: rawTransaction,
signatureType: biconomy.EIP712_SIGN
};
​
// Use any one of the methods below to check for transaction confirmation
// USING PROMISE
let receipt = await networkWeb3.eth.sendSignedTransaction(data, (error, txHash)=>{
if(error) {
return console.error(error);
}
console.log(txHash);
});
​
/********* OR *********/
​
// Get the transaction Hash using the Event Emitter returned
networkWeb3.eth.sendSignedTransaction(data)
.on('transactionHash', (hash)=> {
console.log(`Transaction hash is ${hash}`)
})
.once('confirmation', (confirmation, receipt)=> {
console.log(`Transaction Confirmed.`);
console.log(receipt);
});