Biconomy: Simplified Onboarding Using ERC-4337-Compatible Smart Accounts

Introduction:

Before analyzing Biconomy's ERC-4337-Compatible Smart Accounts implementation, let's first understand the types of accounts compatible with the Ethereum Virtual Machine (EVM):

Externally Owned Accounts (EOAs)

Externally Owned Accounts (EOAs) are Ethereum accounts controlled by private keys. They can send transactions, interact with smart contracts, and hold Ether and other tokens. Unlike smart contract accounts, EOAs do not have associated code and cannot execute complex logic.

EOA Flow:



Contract Accounts (CAs)

Contract Accounts (CAs) are Ethereum accounts that contain smart contract code. They are controlled by the code they contain, rather than by a private key like EOAs. These accounts can hold Ether and tokens, execute complex logic, and interact with other smart contracts on the blockchain.


CA Flow:



EIP-4337-Compatible Smart Accounts

EIP-4337-Compatible Smart Accounts combine the ability of EOA and CA, it brings smart contract functionality to wallets in a single account. Account abstraction enhances user experience across DeFi, GameFi, DAOs, and beyond by integrating smart contract functionality into single-account EIP-4337-Compatible Wallets.



Biconomy's ERC-4337: Account Abstraction Via Entry Point Contract

The ERC logic replicates the functionality of the current transaction mempool in a higher-level mempool which takes in smart contract wallet calls. There are four main components to ERC 4337: UserOperation, EntryPoint, Sender Contract, Paymaster, and Bundler.




UserOperations: Transaction objects created by Dapps to execute user transactions. These are confirmed by the Sender Contract and generated by the Dapp.

Bundlers: Collect UserOperations from the mempool, bundle them, and send to the EntryPoint for execution. They play a crucial role in relaying operations.

Sender Contract: Represents user wallet accounts in smart contract form. It verifies operation signatures, pays fees, and manages execution.

Paymaster: Optional contracts covering gas costs for user operations. Allows users to pay fees with tokens. Must stake ETH with the EntryPoint for payment.

There are two main loops:

Verification Loop: The contract verifies user operation signatures, compensates Bundlers, and ensures valid operations.

Execution Loop: Sends callData from UserOperations to the Sender Contract, executes specified transactions, refunds the Sender Contract with leftover gas.

How Biconomy's ERC-4337 Enhances User Experience and Boosts Adoption:

Easy User Onboarding: Social login, account creation & recovery to seamlessly onboard web2 users.

Fiat On Ramp: Let your users easily & reliably buy/sell crypto within your dApp.

Unified account management: Combining features of EOAs and CAs in a single account, using EIP-4337 to simplify wallet management.

Gasless transactions: Allowing users to pay transaction fees using any supported tokens, making it more user-friendly.

Chain agnostic: Facilitating seamless interaction to leverage cross-chain messaging effectively.

Custom Transaction Bundling: Allow developers to build methods to do transaction batching which enables users to execute multiple actions, even across multiple chains, in a single transaction. Eg. Approve and Deposit can be done in the same transaction without changing anything in dApp smart contracts.How to get started with Biconomy SDK?

Biconomy: Getting started

The Biconomy dashboard is your gateway to accessing our services such as Paymaster, Bundlers, and allows you to retrieve your API keys for usage with our SDK and API. Additionally this is where you can top up and configure your Paymasters for sponsoring transactions.

Step 1:

Accessing Dashboard - Login to the dashboard - https://dashboard.biconomy.io/onboarding

Step 2: Fill all the fields like Name, Position, Project name, Team size and Project type. Click To the dashboard button.

Step 3:

Setup Paymaster - Add your first Paymaster to sponsor gas fees for your users or combine both functionalities within a single Paymaster. With Paymaster, you have the option to set limits on gas usage and conveniently monitor gas allocation through transaction analytics.

Step 4: Click Add your first paymaster button.

Step 5: Fill all the fields like Paymaster Name, Blockchain Network, and Paymaster version. Click Register button.

Step 6:

Retrieve API Keys and Paymaster URL - Select Overview tab option to view API Key and Paymaster URL

Copy your API Key and Paymaster URL to make best use of Biconomy SDK.

Step 7: Select Policies tab option to register your smart contracts and whitelist the methods that will be executed in your dApp to sponsor the transaction using Paymaster. Click Add your first contract

Step 8: Add your Name and Smart contract address

πŸ“˜

For the detailed docs to setting up your biconomy dashboard, refer https://docs.biconomy.io/docs/category/biconomy-dashboard

Biconomy: Quick start

This quick start guide will help you to get started with a basic Node.js project with Typescript to create a Smart Account Package from the Biconomy SDK.

Environment Setup:

Step 1: Clone the github repository.

git clone [email protected]:bcnmy/quickstart.git
git clone https://github.com/bcnmy/quickstart.git

Step 2: Go to the project folder (i.e cd ~/Desktop)

Step 3: Run the following commands to install all the package and dependencies.

yarn install
yarn dev

Step 4: Great! You should be able to see the message "Hello World" in the terminal. You're good with the initial setup.

Step 5: Create a new .env file to the root of your project and add your EOA private key.

PRIVATE_KEY = "YOUR_PRIVATE_KEY"

Step 6: Navigate to src folder

cd ~/Desktop/quickstart/src and open index.ts file.

console.log("Hello World!")

Step 7: Add import statement to access PRIVATE_KEY created from .env file.

import { config } from "dotenv"

config()

Step 8: Add import statement for Bundler, Account, Module package as below:

import { IBundler, Bundler } from '@biconomy/bundler'
import { DEFAULT_ENTRYPOINT_ADDRESS } from "@biconomy/account"
import { ethers } from 'ethers'
import { ChainId } from "@biconomy/core-types"
import {  BiconomySmartAccountV2, DEFAULT_ENTRYPOINT_ADDRESS } from "@biconomy/account"
import { ECDSAOwnershipValidationModule, DEFAULT_ECDSA_OWNERSHIP_MODULE } from "@biconomy/modules";

Step 9: Create an instance for the Bundler

const bundler: IBundler = new Bundler({
    bundlerUrl: 'https://bundler.biconomy.io/api/v2/80001/nJPK7B3ru.dd7f7861-190d-41bd-af80-6877f74b8f44',     
    chainId: ChainId.POLYGON_MUMBAI,
    entryPointAddress: DEFAULT_ENTRYPOINT_ADDRESS,
  })

Code Explanation:

  • bundlerUrl - Used to retrieve Bundler URL from the Biconomy Dashboard
  • chainId- Your actual chain id of a particular network. In this sample, we are using Polygon testnet network.
  • entryPointAddress- Used to retrieve default entry point address from the Biconomy Dashboard.

Step 10: Create an instance for the provider and wallet

const provider = new ethers.providers.JsonRpcProvider("https://rpc.ankr.com/polygon_mumbai")
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY || "", provider);

Code Explanation:

  • provider - Create a provider using a public RPC provider endpoint from ankr. Please feel free to use your own service provider like Infura or Alchemy as per your requirement.
  • wallet- Create a wallet instance accessing your PRIVATE_KEY from .env file.

Step 11: Create an instance for Module and Smart Contract Account

async function createAccount() {

  const module = await ECDSAOwnershipValidationModule.create({
    signer: wallet,
    moduleAddress: DEFAULT_ECDSA_OWNERSHIP_MODULE
  })

  let biconomySmartAccount = await BiconomySmartAccountV2.create({
  signer: wallet,
  chainId: ChainId.POLYGON_MUMBAI,
  bundler: bundler, 
  entryPointAddress: DEFAULT_ENTRYPOINT_ADDRESS,
  defaultValidationModule: module,
  activeValidationModule: module
})
  console.log("address: ", await biconomySmartAccount.getAccountAddress())
  return biconomySmartAccount;
}

Step 12: Create your first userOp

async function createTransaction() {
  console.log("creating account")

  const smartAccount = await createAccount();

  const transaction = {
    to: '0x322Af0da66D00be980C7aa006377FCaaEee3BDFD',
    data: '0x',
    value: ethers.utils.parseEther('0.1'),
  }

  const userOp = await smartAccount.buildUserOp([transaction])
  userOp.paymasterAndData = "0x"

  const userOpResponse = await smartAccount.sendUserOp(userOp)

  const transactionDetail = await userOpResponse.wait()

  console.log("transaction detail below")
  console.log(transactionDetail)
}

Hurray! you're done with all the basic setup of Biconomy SDK. Please check the complete code sample below:

import { config } from "dotenv"
import { IBundler, Bundler } from '@biconomy/bundler'
import { ChainId } from "@biconomy/core-types"
import { BiconomySmartAccountV2, DEFAULT_ENTRYPOINT_ADDRESS } from "@biconomy/account"
import { ECDSAOwnershipValidationModule, DEFAULT_ECDSA_OWNERSHIP_MODULE } from "@biconomy/modules";
import { ethers } from 'ethers';

config()

const provider = new ethers.providers.JsonRpcProvider("https://rpc.ankr.com/polygon_mumbai")
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY || "", provider);

const bundler: IBundler = new Bundler({
  bundlerUrl: 'https://bundler.biconomy.io/api/v2/80001/nJPK7B3ru.dd7f7861-190d-41bd-af80-6877f74b8f44',     
  chainId: ChainId.POLYGON_MUMBAI,
  entryPointAddress: DEFAULT_ENTRYPOINT_ADDRESS,
})

const module = await ECDSAOwnershipValidationModule.create({
  signer: wallet,
  moduleAddress: DEFAULT_ECDSA_OWNERSHIP_MODULE
})

  async function createAccount() {
    let biconomyAccount = await BiconomySmartAccountV2.create({
      signer: wallet,
      chainId: ChainId.POLYGON_MUMBAI,
      bundler: bundler, 
      entryPointAddress: DEFAULT_ENTRYPOINT_ADDRESS,
      defaultValidationModule: module,
      activeValidationModule: module
    })
    console.log("address", biconomyAccount.accountAddress)
    return biconomyAccount
  }

  async function createTransaction() {
    const smartAccount = await createAccount();
    try {
      const transaction = {
        to: '0x322Af0da66D00be980C7aa006377FCaaEee3BDFD',
        data: '0x',
        value: ethers.utils.parseEther('0.1'),
      }
    
      const userOp = await smartAccount.buildUserOp([transaction])
      userOp.paymasterAndData = "0x"
    
      const userOpResponse = await smartAccount.sendUserOp(userOp)
    
      const transactionDetail = await userOpResponse.wait()
    
      console.log("transaction detail below")
      console.log(`https://mumbai.polygonscan.com/tx/${transactionDetail.receipt.transactionHash}`)
    } catch (error) {
      console.log(error)
    }
  }

  createTransaction()

πŸ“˜

For the detailed quickstart guide, refer https://docs.biconomy.io/docs/quickstart