This guide will walk you through how to use Biconomy’s smart accounts with Dynamic. You can find more info on how Biconomy works here: https://docs.biconomy.io/account. We will use Ethereum Sepolia, so make sure your paymaster etc. is set up for that, and also make sure your user is on this network once logged in.

This guide and example is for V4 of the Biconomy packages and V3 of Dynamic.

Install Dynamic and Biconomy

In your app’s repository, install @dynamic-labs/sdk-react-core, @dynamic-labs/ethereum and @biconomy/{account, bundler}:

yarn add @dynamic-labs/sdk-react-core @biconomy/account @biconomy/bundler

Initialize & configure Dynamic

Add the DynamicContextProvider

All you need to get started is the DynamicContextProvider - you will want to wrap your app with this component at the highest possible level, like so:

import { DynamicContextProvider } from '@dynamic-labs/sdk-react-core'
import { EthereumWalletConnectors } from '@dynamic-labs/ethereum'

<DynamicContextProvider
  settings={{
    // Find your environment id at https://app.dynamic.xyz/dashboard/developer/api
    environmentId: 'REPLACE-WITH-YOUR-ENVIRONMENT-ID',
    walletConnectors: [EthereumWalletConnectors]
  }}
>
  {/* Your app's components */}
</DynamicContextProvider>

To enable embedded wallets for your users, toggle it on along with email/social auth in the dashboard.

Configure your Biconomy bundler and paymaster

Go to the Biconomy Dashboard and configure a Paymaster and a Bundler for your app. Make sure these correspond to the desired network for your user’s smart accounts.

Make sure to set the paymaster URL as the value for REACT_APP_BICONOMY_PAYMASTER_URL in your env file, and do the same with your bundler URL for REACT_APP_BICONOMY_BUNDLER_URL.

Create our Biconomy constants

Let’s introduce a new biconomy.js file to house our Biconomy methods. Here we’ll initialize the paymaster, the bundler, the validation module, and the method to create a smart account using createSmartAccountClient.

The validation module allows the user to authorize actions from their Biconomy smart account by signing messages with their Dynamic wallet.

biconomy.js
import { Bundler } from "@biconomy/bundler";
import { Paymaster, createSmartAccountClient, DEFAULT_ENTRYPOINT_ADDRESS, ECDSAOwnershipValidationModule, DEFAULT_ECDSA_OWNERSHIP_MODULE } from "@biconomy/account";
import { sepolia } from "viem/chains";

const bundler = new Bundler({
  bundlerUrl: process.env.REACT_APP_BICONOMY_BUNDLER_URL,
  chainId: sepolia.id, // Replace this with your desired network
  entryPointAddress: DEFAULT_ENTRYPOINT_ADDRESS, // This is a Biconomy constant
});

const paymaster = new Paymaster({
  paymasterUrl: process.env.REACT_APP_BICONOMY_PAYMASTER_URL,
});

const createValidationModule = async (signer) => {
  return await ECDSAOwnershipValidationModule.create({
    signer: signer,
    moduleAddress: DEFAULT_ECDSA_OWNERSHIP_MODULE, // This is a Biconomy constant
  });
};

export const createSmartAccount = async (walletClient) => {
  const validationModule = await createValidationModule(walletClient);
  console.log('creating')

  return await createSmartAccountClient({
    signer: walletClient,
    chainId: sepolia.id, // Replace this with your target network
    bundler: bundler, // Use the `bundler` we initialized above
    paymaster: paymaster, // Use the `paymaster` we initialized above
    entryPointAddress: DEFAULT_ENTRYPOINT_ADDRESS, // This is a Biconomy constant
    defaultValidationModule: validationModule, // Use the `validationModule` we initialized above
    activeValidationModule: validationModule, // Use the `validationModule` we initialized above
  });
};

Create our custom hook

We’ll create a custom hook to fetch the wallet client from Dynamic, and then initialize the Biconomy smart account using the methods we created above.

useBiconomyAccount.js
import { useState, useEffect, useCallback } from 'react';
import { useDynamicContext } from '@dynamic-labs/sdk-react-core';
import { createSmartAccount } from "./biconomy.js";

export function useBiconomyAccount() {
  const { primaryWallet } = useDynamicContext();
  const [smartAccount, setSmartAccount] = useState(null);

  const createAndSetSmartAccount = useCallback(async () => {
    if (!primaryWallet) {
      setSmartAccount(null);
      return;
    }

    if(!primaryWallet.connector.isEmbeddedWallet) {
      alert('No embedded wallet selected');
      return;
    }

    try {
      const walletClient = await primaryWallet.getWalletClient();
      if (walletClient && !smartAccount) {
        const newSmartAccount = await createSmartAccount(walletClient);
        setSmartAccount(newSmartAccount);
      }
    } catch (error) {
      console.error('Error fetching wallet clients or creating smart account:', error);
    }
  }, [primaryWallet, smartAccount]);

  useEffect(() => {
    createAndSetSmartAccount();
  }, [createAndSetSmartAccount]);

  return { smartAccount };
}

Create Main component

Since we’re calling dynamic hooks, we need to do this inside the component tree, above where the DynamicContextProvider wrapper lives. For our purposes, let’s create a Main.js component, and this is also where we can place or signup and login UI (DynamicWidget):

Main.js

import { useEffect } from "react";
import { useBiconomyAccount } from "./useBiconomyAccount.js";

const Main = () => {
  const { smartAccount } = useBiconomyAccount();
  
  useEffect(() => {
    console.log('My Biconomy smart account', smartAccount)
  }, [smartAccount])

  return <></>;
};

export default Main;

As you can see, we’re looking the now initialized smart account in Main, and you can then do whatever you want with it!

End to end flow

You can find a complete example of the integration here: https://github.com/dynamic-labs/dynamic-biconomy-example