Skip to main content

Account Abstraction on Base using Particle Network

Particle Network is a Smart Wallet-as-a-Service provider on Base, providing a modular Account Abstraction stack, allowing developers to use a variety of Paymasters, Bundlers, or smart accounts along with social logins.

This document will guide you through the process of using Particle Network within your Base application, building a simple React project using create-react-app, Particle Auth Core, and Particle's AA SDK.


Objectives

By the end of this guide, you should be able to:

  • Use Particle Auth Core to generate an Externally-Owned-Account (EOA) via a social login
  • Assign a chosen smart account to the EOA generated by Particle Auth Core
  • Set up a Bundler and Paymaster
  • Construct and execute a gasless transaction

Prerequisites

Wallet funds

This guide requires you to have ETH on Base Sepolia, which will be used to showcase the execution of a gasless burn transaction.

  • To fund your wallet with ETH on Base Sepolia, visit one of the faucets listed on the Base Faucets page.

Familiarity with modern, frontend web development

In this example, you'll be building a React-based application using create-react-app. It's recommended that you have some level of familiarity with the basics of working with React.


Understanding Particle Network

Wallet-as-a-Service

Particle Network provides a large suite of SDKs centered around the reduction of account-based friction.

In this case, "account-based friction" refers to barriers-to-entry that some Web3 users may face as they onboard into an application and begin managing a wallet.

This friction, in the context of this guide, can be placed within two distinct categories:

  1. The login process. Often, decentralized applications that tend to be more consumer-facing prefer login flows that aren't dependent upon a user downloading and managing a traditional wallet, as this can be a pain point for some.
  2. The rigidity of standard accounts. Externally Owned Accounts, or EOAs, are often quite rigid in how they operate. They're secured by one key and limited to a strict range of functions, thus developers (and therefore users) are confined to relatively low-level interaction with applications.

Wallet-as-a-Service (WaaS) aims to solve the first of these two points, the login process. WaaS solutions provide an alternative to standard wallets, typically allowing users to use applications through accounts generated by social logins (such as Google, email, or phone). The interfaces for interacting with these accounts are also often embedded within the applications, resulting in a consistent, application-specific experience.

WaaS providers other than Particle Network include Web3Auth, Privy, and Magic, among others.

Account Abstraction

Particle Network also aims to tackle the second friction point described above: account flexibility.

Account Abstraction refers to a transition away from standard account structures, EOAs, to smart accounts. Smart accounts are contracts that act as a wallet, providing users with an account that feels equivalent to an EOA but is intrinsically programmable (due to it being a smart contract) and thus more flexible.

The most popular modern implementation of Account Abstraction is ERC-4337, which enables Account Abstraction without any consensus-layer protocol changes. It does this through numerous components of supporting infrastructure, including a Bundler and Paymaster.

Particle Network describes its Account Abstraction stack as modular, referring to cross-compatibility with any provider of Bundlers, Paymasters, or smart accounts). Particle Network's Account Abstraction SDK runs and uses its own Bundler and Paymaster, with built-in support for Biconomy's Paymaster. However, Particle has made it simple to plug into external infrastructure and components, such as Paymasters or Bundlers from providers like Stackup or Pimlico.

Wallet-as-a-Service + Account Abstraction

Leveraging Account Abstraction directly with Wallet-as-a-Service allows users to onboard through social logins into embedded wallets that use smart accounts, not EOAs, allowing for a greater degree of flexibility.

Particle Network does this by allowing developers to use its Account Abstraction SDK alongside its Wallet-as-a-Service SDK (Particle Auth) to facilitate the intersection between both technologies, as we'll cover in this guide.

info

To learn more about Account Abstraction and the concepts outlined above, see ERC-4337, or Base’s Introduction to Account Abstraction guide.


Setting up the frontend

This guide will go through the process of creating a React-based application through the create-react-app template, as shown below.

Creating a React project

To begin, we'll need to initialize a standard create-react-app project.

Within your IDE of choice, run the following command, replacing {name} with the name of your project.

npx create-react-app {name}

This will create a React project within a directory under the name you set within {name}. This directory should contain the following structure by default:

{name}/
├── node_modules/
├── public/
├── src/
├── .gitignore
├── package.json
├── package-lock.json
└── README.md

Throughout this guide, we'll primarily be working with the src folder and the files within it. src will contain the following by default:

src/
├── App.css
├── App.js
├── App.test.js
├── index.css
├── index.js
├── logo.svg
└── reportWebVitals.js

This structure won't function properly out of the box as our example will be using JSX, a syntax extension that requires a special file type. Thus, you'll need to change the file extension of your App.js and index.js files to either .jsx (JavaScript) or .tsx (TypeScript).

We will use these two files, App.tsx and index.tsx, within this guide.

Configuring & Initializing Particle Network

Installing the dependencies

Before jumping into the application itself, it's important to walk through a few vital configurations required for Particle Network's SDKs to function properly.

Within this example, we'll be using three core libraries from Particle, these include:

  • @particle-network/auth-core-modal, to directly initiate a social login and drive the usage of an embedded wallet.
  • @particle-network/aa, for configuring, assigning, and deploying a smart account.
  • @particle-network/chain, to allow Base to be used within this example.

To add these as dependencies within your project, run one of the two following commands at the root of your project.

yarn add @particle-network/auth-core-modal @particle-network/aa @particle-network/chains

# OR

npm install @particle-network/auth-core-modal @particle-network/aa @particle-network/chains

In addition to the above libraries from @particle-network, we'll be using Ethers for core functions, such as retrieving the user's balance, sending a gasless transaction, and so on.

For the sake of simplicity, we'll be using Ethers v5.7.2, the last release before the Ethers v6 upgrade. Although, if you'd like to use a newer version (v6), comments noting the new syntax of v6 will be left on the code snippets throughout this guide.

Thus, you'll need to install Ethers through either of the following commands:

yarn add [email protected]

# OR

npm install [email protected]

Setting up the Particle Network dashboard

As you'll find in a moment, every library from Particle Network requires three key values for authentication. These are:

  1. Your projectId, assigned to a project created through the Particle dashboard.
  2. Your clientKey, similarly assigned to a project created through the dashboard, but serving a different purpose.
  3. Your appId, retrieved through the creation of an application (within a project) on the dashboard.

Configuring both @particle-network/auth-core-modal and @particle-network/aa will require the retrieval and utilization of these three values.

To create a project and an application through the Particle dashboard:

  1. Navigate to https://dashboard.particle.network.
  2. Create a new account (with your email).
  3. Click "Add New Project" and enter the name of your project.
  4. Copy and save the "Project ID" and "Client Key."
  5. Create a new application. For this example, we’ll select "Web."
  6. Once again, enter the name of your project, alongside the domain where you intend to host this application. If you don't have a domain in mind, feel free to use any filler domain (such as 'base.org'), as it won't affect any underlying functionalities.
  7. Copy the "App ID" shown after creating an application.

With these values retrieved, it's recommended that you assign them to environment variables within your application, such as REACT_APP_PROJECT_ID, REACT_APP_CLIENT_KEY, and REACT_APP_APP_ID.

Configuring Particle Auth Core

Now that you've installed @particle-network/auth-core-modal (among other dependencies) and retrieved your project ID, client key, and app ID, you're ready to configure and therefore initialize the Particle Auth Core SDK.

As mentioned, we'll be working out of two files within this guide:

  1. App.jsx/tsx, containing our core application logic (such as initiating social login and executing the transaction).
  2. index.jsx/tsx, used in this example for configuring AuthCoreContextProvider from @particle-network/auth-core-modal, the core configuration object for Particle Auth Core.

AuthCoreContextProvider is a React component used to define these three aforementioned values, customize the embedded wallet modal and enable account abstraction within it. This will wrap our primary application component (App from App.jsx/tsx), therefore allowing Particle Auth Core to be used through various hooks within App.

To achieve this, AuthCoreContextProvider will take the following parameters:

  • projectId, clientKey, and appId. These are the required values previously retrieved from the Particle dashboard.
  • erc4337, used to define the type of smart account you intend to use, ensuring it's displayed and reflected within the embedded wallet interface.
  • wallet, for customizing the embedded wallet interface through the restriction of supported chains, color options, etc.

With this in mind, an example of what your index.jsx/tsx file may look like given the usage of AuthCoreContextProvider has been included below.

import React from 'react';
import ReactDOM from 'react-dom/client';

import { AuthType } from '@particle-network/auth-core';
import { BaseSepolia } from '@particle-network/chains';
import { AuthCoreContextProvider } from '@particle-network/auth-core-modal';

import App from './App';

// Optional, needed for some environments
import('buffer').then(({ Buffer }) => {
window.Buffer = Buffer;
});
// -----

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<AuthCoreContextProvider
options={{
projectId: process.env.REACT_APP_PROJECT_ID,
clientKey: process.env.REACT_APP_CLIENT_KEY,
appId: process.env.REACT_APP_APP_ID,
erc4337: {
// The name of the smart account you'd like to use
// SIMPLE, BICONOMY, LIGHT, or CYBERCONNECT
name: 'SIMPLE',
// The version of the smart account you're using
// 1.0.0 for everything except Biconomy, which can be either 1.0.0 or 2.0.0
version: '1.0.0',
},
wallet: {
// Set to false to remove the embedded wallet modal
visible: true,
customStyle: {
// Locks the chain selector to Base Sepolia
supportChains: [BaseSepolia],
},
},
}}
>
<App />
</AuthCoreContextProvider>
</React.StrictMode>,
);

Setting Up Your Application

Importing and configuring hooks

The second core component of this application is App.jsx/tsx, which will contain logic achieving the following:

  1. Configuration and assignment of a smart account (SimpleAccount in this example).
  2. Construction of a custom Ethers provider, using a custom AA-enabled EIP-1193 provider object.
  3. Initiation of social login.
  4. Execution of a gasless transaction.

To achieve this, we'll be using a combination of hooks (from @particle-network/auth-core-modal) and base functions on Ethers, which will be automatically powered by AA through the custom EIP-1193 provider object.

These hooks include useEthereum for the retrieval of the standard EOA-based provider object, useConnect for managing social logins, and useAuthCore to retrieve the user’s information (after social login).

To begin building App.jsx/tsx, you'll need to define the relevant functions from these hooks through a process similar to the example below:

import React, { useState, useEffect } from 'react';
import { useEthereum, useConnect, useAuthCore } from '@particle-network/auth-core-modal';
import { BaseSepolia } from '@particle-network/chains';
import { AAWrapProvider, SendTransactionMode, SmartAccount } from '@particle-network/aa';
import { ethers } from 'ethers';

import './App.css';

const App = () => {
// Standard, EOA-based 1193 provider
const { provider } = useEthereum();
// Used for initiating social login and disconnecting users (post-login)
const { connect, disconnect } = useConnect();
// Automatically loaded with relevant user information after logging in
const { userInfo } = useAuthCore();
};

Configuring the Smart Account

Choosing and defining a smart account

The EOA generated through the social login process will be used as the Signer for the smart account specified within this configuration, this will then be reflected through the embedded wallet modal (through its former selection within AuthCoreContextProvider).

As mentioned, Particle Network supports a variety of smart accounts directly through its AA SDK, these include:

  • Light Account (by Alchemy).
  • Biconomy V1 and V2.
  • SimpleAccount (eth-infinitism).
  • CyberConnect.

We'll be using SimpleAccount in this case. Although, note that you can change which smart account you use at any time through one line of code. This line of code exists on the SmartAccount object (imported from @particle-network/aa). SmartAccount acts as the central point for initializing a smart account.

To configure SmartAccount, you'll be using the following parameters:

  • projectId, clientKey, and appId. These were used within AuthCoreContextProvider and can be retrieved from the Particle dashboard through the same procedure.
  • aaOptions, which contains accountContracts. Within accountContracts, you'll need to define a property corresponding with the smart account you'd like to use, i.e. BICONOMY, LIGHT, CYBERCONNECT, or SIMPLE.
    • This property contains chainIds and version.

See the snippet below for an example of a constructed SmartAccount object:

import React, { useState, useEffect } from 'react';
import { useEthereum, useConnect, useAuthCore } from '@particle-network/auth-core-modal';
import { BaseSepolia } from '@particle-network/chains';
import { AAWrapProvider, SendTransactionMode, SmartAccount } from '@particle-network/aa';
import { ethers } from 'ethers';

import './App.css';

const App = () => {
...

const smartAccount = new SmartAccount(provider, {
projectId: process.env.REACT_APP_PROJECT_ID,
clientKey: process.env.REACT_APP_CLIENT_KEY,
appId: process.env.REACT_APP_APP_ID,
aaOptions: {
accountContracts: {
SIMPLE: [{ chainIds: [BaseSepolia.id], version: '1.0.0' }]
// BICONOMY: [{ chainIds: [BaseSepolia.id], version: '1.0.0' }]
// BICONOMY: [{ chainIds: [BaseSepolia.id], version: '2.0.0' }]
// LIGHT: [{ chainIds: [BaseSepolia.id], version: '1.0.0' }]
// CYBERCONNECT: [{ chainIds: [BaseSepolia.id], version: '1.0.0' }]
}
}
});
};

Constructing a custom Ethers object

There are two primary mechanisms to interact with the smart account defined previously. These are:

  1. Directly through the constructed SmartAccount object, such as with {your object}.sendTransaction.
  2. Through a Web3 library, such as Ethers or Web3.js. These libraries can facilitate interaction through a standardized provider object, allowing for a consistent syntax and setup through any wallet or account structure.

As mentioned, we'll be using Ethers within this guide. You'll be able to use any typical Ethers method, such as sendTransaction or getBalance through the AA-enabled provider we construct with AAWrapProvider (imported from @particle-network/aa).

Essentially, we'll construct an instance of AAWrapProvider, passing in the previously defined SmartAccount object alongside an object representing the method selected to pay gas fees. This will allow Ethers to directly load the smart account and drive transactions/signatures through Particle's embedded wallet.

You can achieve this in one line of code, e.g.:

// For Ethers V6, use ethers.BrowserProvider instead; the syntax below won't work.
const customProvider = new ethers.providers.Web3Provider(
new AAWrapProvider(smartAccount, SendTransactionMode.Gasless),
'any',
);

SendTransactionMode has three options. They are:

  1. SendTransactionMode.Gasless, which will request gas sponsorship on every transaction sent through Ethers. By default, this will be through Particle's Omnichain Paymaster. If you don't have enough USDT deposited in the Paymaster to cover the gas fees, or if the transaction fails to meet your sponsorship conditions (set on the Particle dashboard), the user will pay the gas fees themselves.
  2. SendTransactionMode.UserPaidNative, the default method used if SendTransactionMode is missing from AAWrapProvider. This forces the user to pay gas fees themselves.
  3. SendTransactionMode.UserSelect, which allows a user to select which gas fee payment mechanism they use (ERC-20 token, native token, or request sponsorship).

In this example, we're using SendTransactionMode.Gasless. Because this example uses Base Sepolia, all transactions will automatically be sponsored.

Initiating Social Login

Calling the connect function

At this point, you've configured Particle Auth Core, initialized a smart account of choice, and constructed a custom Ethers provider to drive on-chain interaction.

As covered earlier, one of the core benefits of Wallet-as-a-Service is the ability to onboard users through their social accounts. In this case, users’ social login will directly create or log them into a smart account (if they have already created one through Particle Auth), allowing for the immediate usage of account abstraction.

To initiate the social login process programmatically, you'll need to use the connect function, defined from the useConnect hook imported earlier (from @particle-network/auth-core-modal).

Upon calling connect, a user will be brought through the social login process, after which an EOA will be generated (through MPC-TSS) and used as a signer for the smart account.

connect takes the following parameters:

  • socialType, the specific social login mechanism you'd like users to go through. If left as an empty string, a generalized social login modal will be shown. Otherwise, use strings such as 'google', 'twitter', 'email', etc.
  • chain, an object (imported from @particle-network/chains) corresponding with the chain you'd like to use. In this example, it'll be BaseSepolia.

Ideally, some logic should be set in place to ensure connect isn't called if a user has already logged in. This can be done by only calling connect on the condition that userInfo (from useAuthCore) is undefined, indicating that the user isn't logged in.

Below is an example of calling connect (within the conditional described above).

const handleLogin = async (authType) => {
if (!userInfo) {
await connect({
socialType: authType,
chain: BaseSepolia,
});
}
};

In most applications, connect (or handleLogin in this example), will be bound to a "Login" or "Connect" button, as will be done here.

Executing a Gasless Transaction

Constructing a transaction

Because we're using Ethers in this guide, constructing and executing a gasless transaction (intrinsically gasless through the previously defined SendTransactionMode) is identical to the flow you're likely already familiar with. However, it's important to note that transactions sent through ERC-4337 account abstraction do not follow standard transaction structures, these are called UserOperations.

Typically, UserOperations follow lower-level, alternative structures. Although, through the usage of AAWrapProvider, the conversion between a simple transaction object (with to, value, data, etc.) to a complete UserOperation is handled automatically, allowing you to send transactions as you would normally.

Thus, we'll be constructing a simple transaction (tx) adhering to the following structure:

  • to, the recipient address. For this example, we can burn a small amount of ETH on Base Sepolia, which means the recipient will be 0x000000000000000000000000000000000000dEaD.
  • value, the value being sent in wei. Because of the default denomination in wei, this will be set as ethers.utils.parseEther("0.001").

If you intend on interacting with a contract, data can also be filled out (or within Ethers, a Contract object can be built).

Therefore, your tx object should look like this:

const executeUserOp = async () => {
...

const tx = {
to: "0x000000000000000000000000000000000000dEaD",
value: ethers.utils.parseEther("0.001"),
};

...
};

Executing a transaction

Now that you've defined a standard transaction object, you'll need to execute it. Once again, due to the usage of Ethers, this is quite straightforward.

We'll be using a signer object retrieved from {your provider}.getSigner() to call the sendTransaction method, which simply takes the tx object we constructed a moment ago.

Upon calling signer.sendTransaction(tx), the user will be prompted to confirm the transaction (sign a UserOperation hash) through an application-embedded popup. After doing so, the transaction will immediately be sent on Base Sepolia.

To reflect the transaction hash after its confirmation on-chain, you can call the wait method on the variable you saved signer.sendTransaction(tx) to. The resulting object will contain a transactionHash value.

See the example below for a visualization of this process:

const executeUserOp = async () => {
const signer = customProvider.getSigner();

const tx = {
to: '0x000000000000000000000000000000000000dEaD',
value: ethers.utils.parseEther('0.001'),
};

const txResponse = await signer.sendTransaction(tx);
const txReceipt = await txResponse.wait();

return txReceipt.transactionHash;
};

Mapping to JSX

You've now initiated social login (through handleLogin), assigned a smart account (through SmartAccount), and executed a gasless transaction (through executeUserOp).

To present all of this to the user and allow them to interact with these functions for themselves, you'll need to map handleLogin and executeUserOp to the JSX of your App component. This will format the frontend that a user interacts with to test this application.

Essentially, this displays either "Sign in with Google" or "Sign in with Twitter" through custom buttons that are only shown if the user hasn't logged in (determined through the state of userInfo). Upon logging in, the user can either call executeUserOp or disconnect (which was defined from useConnect).

Below is an example of what your App.jsx/tsx file may look at this point. At the bottom of this snippet you'll find the JSX:

import React, { useState, useEffect } from 'react';

import { useEthereum, useConnect, useAuthCore } from '@particle-network/auth-core-modal';
import { BaseSepolia } from '@particle-network/chains';
import { AAWrapProvider, SmartAccount, SendTransactionMode } from '@particle-network/aa';

import { ethers } from 'ethers';

import './App.css';

const App = () => {
const { provider } = useEthereum();
const { connect, disconnect } = useConnect();
const { userInfo } = useAuthCore();

// Initializes and assigns a smart account to the EOA resulting from social login
const smartAccount = new SmartAccount(provider, {
projectId: process.env.REACT_APP_PROJECT_ID,
clientKey: process.env.REACT_APP_CLIENT_KEY,
appId: process.env.REACT_APP_APP_ID,
aaOptions: {
accountContracts: {
SIMPLE: [{ chainIds: [BaseSepolia.id], version: '1.0.0' }],
},
},
});

// Enables interaction with the smart account through Ethers
const customProvider = new ethers.providers.Web3Provider(
new AAWrapProvider(smartAccount, SendTransactionMode.Gasless),
'any',
);

// Initiates social login according to authType
const handleLogin = async (authType) => {
if (!userInfo) {
await connect({
socialType: authType,
chain: BaseSepolia,
});
}
};

// Executes a gasless burn of 0.001 ETH
const executeUserOp = async () => {
const signer = customProvider.getSigner();

const tx = {
to: '0x000000000000000000000000000000000000dEaD',
value: ethers.utils.parseEther('0.001'),
};

const txResponse = await signer.sendTransaction(tx);
const txReceipt = await txResponse.wait();

return txReceipt.transactionHash;
};

// The JSX
return (
<div className="App">
<div className="logo-section">
<img src="https://i.imgur.com/EerK7MS.png" alt="Logo 1" className="logo logo-big" />
<img src="https://i.imgur.com/1RV3pMV.png" alt="Logo 2" className="logo" />
</div>
{!userInfo ? (
<div className="login-section">
<button className="sign-button" onClick={() => handleLogin('google')}>
Sign in with Google
</button>
<button className="sign-button" onClick={() => handleLogin('twitter')}>
Sign in with Twitter
</button>
</div>
) : (
<div className="profile-card">
<h2>{userInfo.name}</h2>
<div className="button-section">
<button className="sign-message-button" onClick={executeUserOp}>
Execute User Operation
</button>
<button className="disconnect-button" onClick={() => disconnect()}>
Logout
</button>
</div>
</div>
)}
</div>
);
};

export default App;

With everything complete, you're ready to run your application to test it. To do so, go through the following process:

  1. Navigate to the root of your project.
  2. Run either npm run start or yarn start.
  3. Once running, log in with either your Google or Twitter.
  4. Fund the address displayed on the wallet modal in the bottom right.
  5. Click "Execute User Operation".

Conclusion

Congratulations! You've just built an application from scratch, onboarding users into smart accounts through social logins using Particle Network.

To learn more about using Particle Network on Base, take a look at the following resources:


We use cookies and similar technologies on our websites to enhance and tailor your experience, analyze our traffic, and for security and marketing. You can choose not to allow some type of cookies by clicking . For more information see our Cookie Policy.