Gas Price is a necessary evil in the world of Web3. Although it plays a vital role in terms of rewarding the “Miners” and making smart contract execution more secure, it creates an awful user experience. Even from a purely business perspective, it makes much more sense in having a fixed billing system for the users, while the execution costs are covered by the service provider. With the traditional approach, it’s impossible to build such a system without introducing a centralized entity into the system.
💡Biconomy - A ray of hope
Among other features, Biconomy makes it possible for developers to provide “Gasless Transactions” with the least amount of effort. This is a fully decentralized solution powered by smart contracts and no compromise is made in terms of security and stability.
Biconomy is based on the simple concept that the service provider pays the gas on behalf of the users. The users are only required to sign a particular message while interacting with the smart contract. This signature is then validated by Biconomy and if sufficient funds are available to sponsor the transaction, the gas is paid, and the transaction details are forwarded to the Dapp smart contract.
The important point to note is that there is nothing like a transaction without gas fees, however, Biconomy just enables us to maintain a special account where we deposit enough funds to pay for the gas price for all users. The gas price gets paid from this “special” account and it’s up to us or the project admin to ensure that sufficient funds are always available for sponsoring transactions. To make this even simpler, Biconomy provides an intuitive Dashboard for managing everything.
🎯Scope of this article
This article will act as a beginner-friendly tutorial for getting started with Biconomy. For the sake of simplicity, we will be focusing only on Gasless Transactions, specifically Gasless Transactions using EIP 2771-based approach. This article won’t discuss the architecture used by Biconomy nor the various other jaw-dropping features provided. To learn about them I recommend giving their official documentation a read.
In this tutorial we will:
- Write a simple Smart Contract using
EIP 2771
to make it compatible with Biconomy. - Deploy and test our smart contract on the Polygon Testnet (Mumbai).
- Build a very simple front end using React to interact with our smart contract.
I follow a “code-first-explanation-next” approach. Please let me know if this is helpful or if I should try any different approach in the comment section.
⚠️Disclaimer
At the point of writing this article, Biconomy is transitioning to a newer SDK-based approach that brings yet more to the table, however here we will be using the older approach which is much simpler and sufficient for our needs. Documentation of the latest SDK can be found here. The new SDK is still WIP and not polished enough for developers to use. Once ready, I plan to cover it in my future articles.
🎒Pre-requisites
I know you all are excited to get started, but before we dive in, make sure you got the following pre-requisites sorted:
- Remix IDE: For developing smart contracts
- Metamask: Metamask is a popular non-custodial wallet used for interacting with the blockchain.
- Connected with Testnet: Although you can use any network of your choice, I will be using Polygon’s Mumbai Testnet. If you want to follow along, make sure you have added Mumbai Testnet in Metamask following this article, and then fund your account using this faucet.
💻Writing the Smart Contract
Let’s get started with the crux of our project, i.e., the smart contract. In your Remix IDE create a new file named Greetings.sol
and paste the following code.
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;
contract Greetings {
mapping (address => string) public greetings;
function setGreeting(string memory newGreeting) public {
greetings[msg.sender] = newGreeting;
}
}
This is a very simple smart contract that stores a string each corresponding to each address that called the setGreeting
function. The string to be stored is passed as an argument to the function. The important point to note here is msg.sender
is used for finding the account address that invoked the setGreeting
function.
Let’s try to understand the challenge of using the above contract with Biconomy. Refer to the following diagram to understand how Biconomy forwards transactions:
If you look closely, you will understand that a “TrustedForwarder” contract is used to invoke our smart contract. In this scenario, regardless of which account initiates the call, the msg.sender
will always return the address of the caller, i.e., the TrustedForwarder’s address. To solve this issue, we have to make the following changes to our smart contract:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;
import "@openzeppelin/contracts/metatx/ERC2771Context.sol";
contract Greetings is ERC2771Context {
constructor(address trustedForwarderAddress) ERC2771Context(trustedForwarderAddress) {}
mapping (address => string) public greetings;
function setGreeting(string memory newGreeting) public {
greetings[_msgSender()] = newGreeting;
}
}
Here we are doing the following:
- We are using the
ERC2771Context
contract from OpenZeppelin contracts. It provides us with the_msgSender()
function that will be very important to us. -
The
trustedForwarderAddress
is the contract that we expect to call our smart contract. This forwarder is responsible for signature verification and protection against replay attacks. Before forwarding the transaction, the signer (our user’s address) is appended to the calldata and passed to the smart contact. The_msgSender()
function returns this address.You can find the list of Biconomy’s TrustedForwarder’s addresses here.
Inside our code we replace
msg.sender
with_msgSender()
to reflect the correct user.
And with that our smart contract is ready for gasless transactions!!
Deployment and Biconomy Setup
Now that our smart contract is ready, it’s time to deploy our smart contract. Let’s deploy it to Mumbai Testnet (You can choose any other network of your choice).
- In the deploy section of Remix, select
Injected Provider - MetaMask
as the Environment. Also, make sure your Metamask is connected with your desired chain. - For your chosen chain, find the respective TrustedForwarder address here. Since I am using Mumbai Testnet, mine is 0x69015912AA33720b842dCD6aC059Ed623F28d9f7. You must pass this address as a constructor argument while deploying the contract.
- Finally, click on
transact
and confirm the MetaMask popup to deploy the smart contract. Now to set up Biconomy, sign in to your Biconomy Dashboard. Next, you have to register a new Dapp and mention the chain in which your smart contract is deployed. Once registered, click on the card corresponding to your Dapp and this will open the respective Dapp’s dashboard. Here you can see the various details associated with your Dapp. For a freshly created Dapp, the Gas Balance would be 0. Before you can let users enjoy gasless transactions, you have to load crypto (native token) into this Dapp to pay for the gas on behalf of the users. Click on theFill Gas
button, enter the amount of token you want to deposit, and clickFill Me Up
. A MetaMask popup will ask you to send the required amount of tokens and confirm it to deposit the funds. It should be reflected on your Dapp promptly underGAS BALANCE
.
A single Dapp can be associated with multiple smart contracts. Head to the Smart Contract
section in your dashboard. Enter the Name, address, and ABI of your smart contract. All these details can be found in Remix. Make sure that Meta Transaction Type
is set to Trusted Forwarder
. Click on Add
to add the smart contract to your Dapp.
Now the final step is to create API Endpoints for the function we want to invoke. Head to the Dapp APIs
section.
- Click on the white
Add
Button - Enter a suitable name for your API.
- Choose the corresponding Smart Contract and Method to be invoked.
- Click on the orange
Add
button to create a new API endpoint. Hurray!! Finally, we are ready for gasless transactions 👏👏👏.
🌐Building a basic website
Now that we are ready for gasless transactions, it’s time to build a simple website for interacting with our smart contract. In this tutorial, we are going to use React for building our demo website.
Use the following command to create a new react project:
npx create-react-app biconomy_tutorial
I have named my project biconomy_tutorial
, however, feel free to choose any name of your liking. Once the project is initialized, move inside the newly created project folder. Next, we will be using the biconomy/mexa
package for interacting with our smart contract. Use the following command for installing it:
npm install @biconomy/mexa
🚫Known Issue
In case you have used any other libraries like web3.js
, you must be already familiar with the Webpack Error. For those who are new, whenever you try to import the biconomy/mexa
package in your React code, you would get an error message similar to the following:
To resolve this we will need react-app-rewired
. First, install the required dependencies using the following command:
npm install react-app-rewired crypto-browserify stream-browserify assert stream-http https-browserify os-browserify url path-browserify browserify-zlib process buffer util
Now create a new file called config-overrides.js
and paste in the following code:
const webpack = require('webpack');
module.exports = function override(config) {
config.ignoreWarnings = [/Failed to parse source map/];
const fallback = config.resolve.fallback || {};
Object.assign(fallback, {
crypto: require.resolve("crypto-browserify"),
stream: require.resolve("stream-browserify"),
assert: require.resolve("assert"),
http: require.resolve("stream-http"),
https: require.resolve("https-browserify"),
os: require.resolve("os-browserify"),
url: require.resolve("url"),
path: require.resolve("path-browserify"),
zlib: require.resolve("browserify-zlib"),
process: require.resolve("process/browser"),
buffer: require.resolve("buffer"),
util: require.resolve("util"),
fs: false,
tls: false,
net: false,
});
config.resolve.fallback = fallback;
config.plugins = (config.plugins || []).concat([
new webpack.ProvidePlugin({
process: 'process/browser',
Buffer: ['buffer', 'Buffer'],
})
])
return config;
}
Finally in the package.json
file change the start
task to react-app-wired start
. Finally, close and restart the react app. Now this problem should be resolved.
Building the website
Open the App.js
file in our react project in any code editor of your choice and make the following modifications:
import './App.css';
import { Biconomy } from "@biconomy/mexa";
import { ethers } from 'ethers';
function App() {
const setMessage = async (message) => {
}
return (
<div className="App">
<header className="App-header">
<button onClick={() => setMessage("Hello World")}>Set Message</button>
</header>
</div>
);
}
export default App;
Here we added a simple button that calls the setMessage
function. This function takes a string as input and passes it to the smart contract function. For simplicity, here we are hardcoding the string to be “Hello World”. To simplify our project even further, we will implement the complete logic of using Biconomy inside the setMessage
function, however, an experienced React developer would prefer to use proper state management.
Before using Biconomy, we have to first initialize it. The code for initializing Biconomy is as follows:
const setMessage = async (message) => {
const biconomy = new Biconomy(window.ethereum, {
apiKey: "5hUl....1a87",
debug: true,
contractAddresses: [
"0x160d3a01bf31cabbb6fcb60b5573bbad859e46c7"
]
})
await biconomy.init();
}
In the above code snippet note that:
- As the Ethereum provider, we are using
window.ethereum
, i.e., the Ethereum object injected by the Metamask wallet. In case you want to use a different wallet, you have to modify it respectively. (Let me know in the comments if you want an example of using a different wallet). - Make sure you enter your own API Key for Biconomy that you will find in the Biconomy Dashboard. Needless to say, instead of hardcoding it, it’s recommended to use environment variables.
- We also have to pass a list of contract addresses that we want to interact with. Ideally, this list will contain only those contract addresses that we want to interact with, and not all the contract addresses added to our dashboard.
The next step is to create a contract instance. The beauty of Biconomy is from here on, the process of interacting with the smart contract is similar to the traditional approach. I am using ethers
, however, in the official documentation, you will also find how to use web3.js
.
const setMessage = async(message) => {
...
const contractInstance = new ethers.Contract(
CONTRACT_ADDRESS,
CONTRACT_ABI,
biconomy.ethersProvider
)
const ethersProvider = new ethers.providers.Web3Provider(window.ethereum);
const connectedAddress = (await ethersProvider.listAccounts())[0];
const tx = await contractInstance.populateTransaction.setGreeting(message, {
gasLimit: 1000000,
gasPrice: 1000000000
})
const txDetails = {
from: contractAddress,
to: CONTRACT_ADDRESS,
data: tx.data,
signatureType: "PERSONAL_SIGN"
}
const provider = await biconomy.provider
await provider.send("eth_sendTransaction", [txDetails])
}
Here we are performing the following tasks:
- Creating an instance of the smart contract.
- We get and store the current connected Metamask account in the
connectedAddress
variable. - Next, we define the transaction and store it in
tx
variable. Note that here we are not sending the transaction to the blockchain. Rather we are using thepopulateTransaction
method because we want to generate the calldata that would be sent to the blockchain. - Any function invocation in the blockchain is also recorded in form of a transaction. Without getting into the complexities, you can assume that what differentiates a transfer of native tokens (also called transaction) and calling a function is that in the latter, a signed payload containing the function signature and the arguments are passed which is also termed as the
calldata
. We generate these transaction details and store them in thetxDetails
variable. - Next, we obtain the
provider
from the definedBiconomy
object. - Finally, we send the transaction for verification to the blockchain. Here we use the
eth_sendTransaction
function. This is where the users receive a Metamask popup asking them to sign a specific payload. Behind the hood, this transaction payload is actually passed by the Biconomy Relay to the TrustedForwarder contract, and it also pays for the required gas.
With this, you are now ready to make Gasless transactions 😎. Go ahead and give your newly created website a try. I have a small task for you:
👉Try to make a gasless transaction. Verify whether the message was set properly and
whether the user had to pay any gas.
👉Let me know your findings in the comment section
In case you run into errors or feel I was not able to explain the topic properly, please let me know and I will try my best to clear every doubt.
👨💻About Me
Hi, I am Bhaskar Dutta, a Software Developer here at Xenabler where we try to bring technology and people together, one commit at a time.
If you have read so far, I feel we are going to be partners in an otherwise long journey of exploring the ever-changing world of technology. I would really want to connect with my readers and know them a bit more and if you want something similar, do send a connect request on LinkedIn.
Before saying goodbye, remember to take care of yourself and let the special people in your life know you Love Them. We will soon meet again in a different tutorial, till then Take Care of Yourself, let the special people in your life know you Love Them, and Keep Building.
Top comments (0)