Skip to content

Particle Network Wallet Abstraction

Introduction

Particle Network offers Wallet Abstraction services with an Account Abstraction stack, providing a suite of SDKs focused on reducing user onboarding friction.

By embedding customizable Externally Owned Account (EOA) and Account Abstraction (AA) components, Particle allows quick 2-click onboarding via social logins like Google, email, and phone, as well as traditional Web3 methods. This approach removes the need for users to manage a conventional wallet, delivering a streamlined, application-specific experience for Web3 interactions.

Particle Network supports Moonbeam, Moonriver, and the Moonbase Alpha TestNet with both standard EOA interactions and native ERC-4337 SimpleAccount implementations, providing full-stack account abstraction.

Key components of Particle Network's Moonbeam integration include:

  • Particle Connect: Particle's flagship Wallet-as-a-Service solution, offering embedded wallets powered by MPC-TSS for smooth, Web2-like onboarding and interactions, with Account Abstraction support integrated within a single SDK
  • Particle Network Modular AA Stack: Beyond the default EOA-based interactions, Particle also offers a modular AA stack for ERC-4337 account abstraction on Moonbeam, allowing flexibility in the smart account, bundler, and paymaster configurations to suit AA-enabled applications

Particle Network Smart WaaS map

In this guide, you'll go through a step-by-step example of using Particle Connect on Moonbeam.

Create an Application

To use Particle Connect on Moonbeam, you'll need to create an account on the Particle Network dashboard and spin up an application

  1. Navigate to the Particle Network dashboard, then sign up or log in

    Dashboard login

  2. Once logged in, click Add New Project to create a new project

    Project creation

  3. Enter the project name and click Save

    Application creation

  4. From the project's dashboard, scroll down to the Your Apps section and create a new app by selecting iOS, Android, or Web and providing the requested information

    Application creation

  5. Finally, copy the Project ID, Client Key, and App ID

    Application dashboard

Install Dependencies

To integrate Particle Connect into your Moonbeam application, you'll need only a few dependencies. Particle Connect offers built-in Account Abstraction (AA) support; however, in this example, we'll install the Particle AA SDK to utilize EIP-1193 providers, such as ethers.

yarn add @particle-network/connectkit viem@^2 @particle-network/aa ethers

Note that this tutorial is based on a Next.js app with TypeScript and Tailwind CSS.

Configure Particle Connect

We’ll configure and initialize Particle Connect (Particle's flagship authentication SDK). Begin by creating a new file called ConnectKit.tsx in your project’s root directory, where we’ll set up the ParticleConnectKit component as the primary interface for configuration.

Before proceeding, head back to the Particle dashboard and retrieve the following API keys:

  • projectId – your project’s unique ID
  • clientKey – your client-specific key
  • appId – your application ID

These keys are essential as they connect your Particle Connect instance with the Particle dashboard, enabling features like no-code customization, user activity tracking, and API request authentication.

Place the API keys in a .env file in the following format:

NEXT_PUBLIC_PROJECT_ID='INSERT_PROJECT_ID'
NEXT_PUBLIC_CLIENT_KEY='INSERT_CLIENT_KEY'
NEXT_PUBLIC_APP_ID='INSERT_APP_ID'

This setup ensures that your API keys are securely accessible to the Next.js application while protecting them from unauthorized access.

Here’s the code to add to your ConnectKit.tsx file:

"use client";

import React from "react";
import { ConnectKitProvider, createConfig } from "@particle-network/connectkit";
import { authWalletConnectors } from "@particle-network/connectkit/auth";
import { moonbeam } from "@particle-network/connectkit/chains";
import { wallet, EntryPosition } from "@particle-network/connectkit/wallet";
import { aa } from "@particle-network/connectkit/aa";

const config = createConfig({
  projectId: process.env.NEXT_PUBLIC_PROJECT_ID!,
  clientKey: process.env.NEXT_PUBLIC_CLIENT_KEY!,
  appId: process.env.NEXT_PUBLIC_APP_ID!,

  walletConnectors: [authWalletConnectors({})],

  plugins: [
    wallet({
      entryPosition: EntryPosition.BR, // Positions the modal button at the bottom right on login
      visible: true, // Determines if the wallet modal is displayed
    }),
    aa({
      name: "SIMPLE",
      version: "2.0.0",
    }),
  ],
  chains: [moonbeam],
});

export const ParticleConnectkit = ({ children }: React.PropsWithChildren) => {
  return <ConnectKitProvider config={config}>{children}</ConnectKitProvider>;
};

This setup initializes ParticleConnectKit, a wrapper for the configured ConnectKitProvider instance, using your project keys. It also defines essential SDK settings, such as supported chains (e.g., Moonbeam), wallet positioning and visibility options, and a SIMPLE smart account instance.

For further customization options, refer to the Particle Connect documentation.

At this point, you've signed up and created an application, installed all required dependencies, and configured ParticleConnectKit and SmartAccount, if applicable.

Integrate the ParticleConnectKit Component in Your App

After completing the configuration, wrap your application with the ParticleConnectKit component to enable global access to the Particle Connect SDK. Update your layout.tsx file in src as shown below:

import { ParticleConnectkit } from "@/ConnectKit";
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "Particle Connectkit App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <ParticleConnectkit>{children}</ParticleConnectkit>
      </body>
    </html>
  );
}

Wrapping your application in ParticleConnectKit provides global access to the SDK, making features like social logins and wallet generation available throughout your app. This setup in layout.tsx ensures all components can access Particle Connect’s capabilities.

Connecting Wallet

With the configured layout.tsx file, the next step is to add a central Connect Wallet button for user connectivity. You can achieve this by importing ConnectButton from @particle-network/connectkit. Once the user logs in, the ConnectButton transforms into an embedded widget.

"use client";
import { ConnectButton, useAccount } from "@particle-network/connectkit";

const HomePage = () => {
  const { address, isConnected, chainId } = useAccount();

  return (
    <div className="flex justify-center items-center h-screen">
      <div className="text-center">
        <ConnectButton />
        {isConnected && (
          <>
            <h2>Address: {address}</h2>
            <h2>Chain ID: {chainId}</h2>
          </>
        )}
      </div>
    </div>
  );
};

export default HomePage;

Sending transactions with an EIP-1193 provider

Using Particle Connect alongside the Particle AA SDK enables you to work with an EIP-1193 provider like ethers. This approach is beneficial because you're likely already familiar with these providers or if you integrate Particle Connect into an existing application.

To set this up, wrap the smart account provided by Particle Connect with an instance of ethers to create a customProvider. You can use ethers as usual from there, with the smart account as the underlying transaction signer.

import {useSmartAccount } from "@particle-network/connectkit";
import { AAWrapProvider, SendTransactionMode } from "@particle-network/aa";

const smartAccount = useSmartAccount();

// Init custom provider with gasless transaction mode
const customProvider = smartAccount
? new ethers.BrowserProvider(
    new AAWrapProvider(
        smartAccount,
        SendTransactionMode.Gasless
    ) as Eip1193Provider,
    "any"
    )
: null;

/**
 * Sends a transaction using the ethers.js library.
 * This transaction is gasless since the customProvider is initialized as gasless
*/
const executeTxEthers = async () => {
    if (!customProvider) return;

    const signer = await customProvider.getSigner();
    const tx = {
      to: recipientAddress,
      value: parseEther("0.01").toString(),
    };

    const txResponse = await signer.sendTransaction(tx);
    const txReceipt = await txResponse.wait();
    console.log(txReceipt?.hash)
  };

Example of Utilization

With those above established, Particle Connect can be used similarly, as shown in the example application below.

Specifically, this application creates a smart account on Moonbeam MainNet through social login, then uses it to send a gasless transaction of 0.001 GLMR with the ethers provider.

"use client";
import React, { useEffect, useState } from "react";

// Particle imports
import {
  ConnectButton,
  useAccount,
  usePublicClient,
  useSmartAccount,
} from "@particle-network/connectkit";

// Eip1193 and AA Provider
import { AAWrapProvider, SendTransactionMode } from "@particle-network/aa"; // Only needed with Eip1193 provider
import { ethers, type Eip1193Provider } from "ethers";
import { formatEther, parseEther } from "viem";

export default function Home() {
  const { isConnected, chain } = useAccount();
  const publicClient = usePublicClient();
  const smartAccount = useSmartAccount();

  const [userAddress, setUserAddress] = useState<string>("");
  const [balance, setBalance] = useState<string | null>(null);
  const [recipientAddress, setRecipientAddress] = useState<string>("");
  const [transactionHash, setTransactionHash] = useState<string | null>(null);

  // Init custom provider with gasless transaction mode
  const customProvider = smartAccount
    ? new ethers.BrowserProvider(
        new AAWrapProvider(
          smartAccount,
          SendTransactionMode.Gasless
        ) as Eip1193Provider,
        "any"
      )
    : null;

  /**
   * Fetches the balance of a given address.
   * @param {string} address - The address to fetch the balance for.
   */
  const fetchBalance = async (address: string) => {
    try {
      const balanceResponse = await publicClient?.getBalance({
        address: address as `0x${string}`,
      });
      if (balanceResponse) {
        const balanceInEther = formatEther(balanceResponse).toString();
        setBalance(balanceInEther);
      } else {
        setBalance("0.0");
      }
    } catch (error) {
      console.error("Error fetching balance:", error);
      setBalance("0.0");
    }
  };

  /**
   * Loads the user's account data, including address and balance.
   */
  useEffect(() => {
    const loadAccountData = async () => {
      if (isConnected && smartAccount) {
        try {
          const address = await smartAccount.getAddress();
          setUserAddress(address);
          await fetchBalance(address);
        } catch (error) {
          console.error("Error loading account data:", error);
        }
      }
    };
    loadAccountData();
  }, [isConnected, smartAccount]);

  /**
   * Sends a transaction using the ethers.js library.
   * This transaction is gasless since the customProvider is initialized as gasless
   */
  const executeTxEthers = async () => {
    if (!customProvider) return;

    const signer = await customProvider.getSigner();
    try {
      const tx = {
        to: recipientAddress,
        value: parseEther("0.01").toString(),
      };

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

      setTransactionHash(txReceipt?.hash || null);
    } catch (error) {
      console.error("Failed to send transaction using ethers.js:", error);
    }
  };

  return (
    <div className="container min-h-screen flex flex-col justify-center items-center mx-auto gap-4 px-4 md:px-8">
      <div className="w-full flex justify-center mt-4">
        <ConnectButton label="Click to login" />
      </div>
      {isConnected && (
        <>
          <div className="border border-purple-500 p-6 rounded-lg w-full">
            <h2 className="text-lg font-semibold mb-2 text-white">
              Address: <code>{userAddress || "Loading..."}</code>
            </h2>
            <h2 className="text-lg font-semibold mb-2 text-white">
              Balance: {balance || "Loading..."} {chain?.nativeCurrency.symbol}
            </h2>
            <input
              type="text"
              placeholder="Recipient Address"
              value={recipientAddress}
              onChange={(e) => setRecipientAddress(e.target.value)}
              className="mt-4 p-3 w-full rounded border border-gray-700 bg-gray-900 text-white focus:outline-none"
            />
            <button
              className="bg-purple-600 hover:bg-purple-700 text-white font-bold py-2 px-4 rounded mt-4"
              onClick={executeTxEthers}
              disabled={!recipientAddress}
            >
              Send 0.001 {chain?.nativeCurrency.name}
            </button>
            {transactionHash && (
              <p className="text-green-500 mt-4">
                Transaction Hash: {transactionHash}
              </p>
            )}
          </div>
        </>
      )}
    </div>
  );
}

That concludes the brief introduction to Particle's Smart Wallet-as-a-Service stack and how to get started with Particle on Moonbeam. For more information, you can check out Particle Network's documentation.

Find the repository with the complete code implementation on the Particle Network GitHub.

The information presented herein has been provided by third parties and is made available solely for general information purposes. Moonbeam does not endorse any project listed and described on the Moonbeam Doc Website (https://docs.moonbeam.network/). Moonbeam Foundation does not warrant the accuracy, completeness or usefulness of this information. Any reliance you place on such information is strictly at your own risk. Moonbeam Foundation disclaims all liability and responsibility arising from any reliance placed on this information by you or by anyone who may be informed of any of its contents. All statements and/or opinions expressed in these materials are solely the responsibility of the person or entity providing those materials and do not necessarily represent the opinion of Moonbeam Foundation. The information should not be construed as professional or financial advice of any kind. Advice from a suitably qualified professional should always be sought in relation to any particular matter or circumstance. The information herein may link to or integrate with other websites operated or content provided by third parties, and such other websites may link to this website. Moonbeam Foundation has no control over any such other websites or their content and will have no liability arising out of or related to such websites or their content. The existence of any such link does not constitute an endorsement of such websites, the content of the websites, or the operators of the websites. These links are being provided to you only as a convenience and you release and hold Moonbeam Foundation harmless from any and all liability arising from your use of this information or the information provided by any third-party website or service.
Last update: November 22, 2024
| Created: November 7, 2023