Integrating Particle Connect into your web application can be done in under five minutes.

This guide provides a concise overview of how to install, configure, and enable social logins in a Next.js application using create @particle-network/connectkit as your starting point.

For a detailed explanation of each feature and more advanced configurations, refer to the Particle Connect SDK reference.


1

Boilerplate: Initializing Particle Connect

The easiest way to get started with Particle Connect is through its built-in starter/boilerplate; this includes the core file setup and code structure needed to configure Particle Connect.

To initialize this boilerplate, copy and execute one of the following commands.

Terminal
npm init @particle-network/connectkit@latest
# or
pnpm create @particle-network/connectkit@latest
# or
yarn create @particle-network/connectkit

After running one of the aforementioned commands, the CLI will guide you through a setup process; it will prompt you to enter a project name, choose your preferred framework (Next.js or React), and select additional options.

Terminal
🤩 Welcome to Particle Network!

✔ What is the name of your project? … connectkit-aa-usage

✔ What is the template of your project? › create-next-app
✔ Which chains does your app support?​ › EVM
✔ Which ERC-4337 Contract does your app support?​ › BICONOMY-2.0.0
✔ Does it support an embedded wallet?​ … yes
This guide will proceed with a Next.js project setup, although React (create-react-app) is also available.
2

Configuring Particle Connect

With a starter project initialized, you’re ready to configure Particle Connect through its core component, ConnectKitProvider.

Although before diving in too deep here, you’ll need some keys from the Particle dashboard.

Particle Connect requires three key values from the dashboard to be initiated: projectId, clientKey, and appId.

To retrieve these values for configuration within your application, follow these steps:

For a quick overview of the Particle dashboard, watch this video or check out the Dashboard Quickstart Guide.

After obtaining your projectId, clientKey, and appId, you’ll need to use these to configure the aforementioned ConnectKitProvider component from @particle-network/connectkit.

The boilerplate already includes the basic variable setup —just add your API keys to the .env.sample file and rename it to .env.

At this point, you’re ready to move onto either running and testing the application or beginning development through app/page.tsx.

Although, below we’ll continue and go over some of the granular controls given to you through ConnectKitProvider.

3

Configuring ConnectKitProvider

When working with ConnectKitProvider, it’s recommended to use a dedicated Connectkit.tsx file in the src directory where you can configure and export the component. Then, you’ll use this export to wrap your main App component (the location of your Particle Connect implementation through ConnectButton).

Here’s how you can configure ConnectKitProvider:

Required Configurations:

  • projectId, clientKey, and appId — Get these from the Particle dashboard, as covered prior.
  • chains — Specify the supported chains for your dApp (this is an array of Viem-originating chain objects imported from @particle-network/connectkit/chains).
  • walletConnectors — Define the wallets you want to support.

Optional Configurations:

  • theme and language for basic customization of the connection modal UI.
  • Additional appearance customizations.
In the boilerplate application, you’ll find a pre-configured Connectkit.tsx file located in the src directory.

Below is an example of a configured instance of ConnectKitProvider. For more details around each available property, head over to the Particle Connect Web SDK reference.

For more detailed information, visit the full Particle Connect SDK documentation.
4

Wrap your application with ConnectKitProvider

Wrap your primary application component (wherever you place and use ConnectButton alongside the various hooks from Particle Connect) with the ParticleConnectKit component (exported from ConnectKitProvider).

Here’s an example of what this looks like for a layout.tsx file:

layout.tsx
import { ParticleConnectkit } from '@/connectkit'; // Export of a configured ConnectKitProvider instance
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,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <ParticleConnectkit>{children}</ParticleConnectkit>
      </body>
    </html>
  );
}

In the boilerplate application, you’ll find a pre-configured layout.tsx file located in the app directory.
5

Facilitating logins and chain interactions

With Particle Connect now configured, you can proceed to enable social logins within your application through the aforementioned ConnectButton component.

Additionally, for driving application-level interaction (after initial onboarding), @particle-network/connectkit provides a variety of hooks. You can explore all available hooks in the Particle Connect SDK documentation.

The boilerplate application includes a basic example featuring only a Connect button (ConnectButton).

After logging in (connecting), users can access the embedded wallet modal provided by Particle Connect via the button in the bottom right corner, unless customized through the wallet configuration within ConnectKitProvider.

To explore additional features like fetching and displaying user information, balances, on-ramp services, and sending transactions using either the native Particle Provider (Viem-based WalletClient) or ethers.js through an EIP-1193 provider, paste the following code into app/page.tsx.

page.tsx
"use client";

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

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

// Connectkit uses Viem, so Viem's features can be utilized
import { formatEther, parseEther } from "viem";

// Optional: Import ethers provider for EIP-1193 compatibility
import { ethers, type Eip1193Provider } from "ethers";

export default function Home() {
  // Initialize account-related states from Particle's useAccount hook
  const { address, isConnected, isConnecting, isDisconnected, chainId } =
    useAccount();
  const { disconnect, disconnectAsync } = useDisconnect();
  const { getUserInfo } = useParticleAuth();

  // Optional: Initialize public client for RPC calls using Viem
  const publicClient = usePublicClient();

  // Retrieve the primary wallet from the Particle Wallets
  const [primaryWallet] = useWallets();

  // Define state variables
  const [account, setAccount] = useState(null); // Store account information
  const [balance, setBalance] = useState<string>(""); // Store user's balance
  const [userAddress, setUserAddress] = useState<string>(""); // Store user's address
  const [userInfo, setUserInfo] = useState<any>(null); // Store user's information
  const [isLoadingUserInfo, setIsLoadingUserInfo] = useState<boolean>(false); // Loading state for fetching user info
  const [userInfoError, setUserInfoError] = useState<string | null>(null); // Error state for fetching user info
  const [recipientAddress, setRecipientAddress] = useState<string>(""); // Store recipient's address for transactions
  const [transactionHash, setTransactionHash] = useState<string | null>(null); // Store transaction hash after sending
  const [isSending, setIsSending] = useState<boolean>(false); // State for showing sending status

  // Connection status message based on the account's connection state
  const connectionStatus = isConnecting
    ? "Connecting..."
    : isConnected
    ? "Connected"
    : isDisconnected
    ? "Disconnected"
    : "Unknown";

  // Load account details and fetch balance when address or chainId changes
  useEffect(() => {
    async function loadAccount() {
      if (address) {
        setAccount(account);
        setUserAddress(address);
        await fetchBalance();
      }
    }
    loadAccount();
  }, [chainId, address]);

  // Fetch and set user information when connected
  useEffect(() => {
    const fetchUserInfo = async () => {
      setIsLoadingUserInfo(true);
      setUserInfoError(null);

      try {
        const userInfo = await getUserInfo();
        console.log(userInfo);
        setUserInfo(userInfo);
      } catch (error) {
        setUserInfoError(
          "Error fetching user info: The current wallet is not a particle wallet."
        );
        console.error("Error fetching user info:", error);
      } finally {
        setIsLoadingUserInfo(false);
      }
    };

    if (isConnected) {
      fetchUserInfo();
    }
  }, [isConnected, getUserInfo]);

  // Fetch user's balance and format it for display
  const fetchBalance = async () => {
    try {
      if (!address) return;
      const balanceResponse = await publicClient?.getBalance({ address });
      const balanceInEther = formatEther(balanceResponse!);
      console.log(balanceResponse);
      setBalance(parseFloat(balanceInEther).toFixed(4)); // Display balance with 4 decimal places
    } catch (error) {
      console.error("Error fetching balance:", error);
    }
  };

  // Handle user disconnect action
  const handleDisconnect = async () => {
    try {
      await disconnectAsync();
    } catch (error) {
      console.error("Error disconnecting:", error);
    }
  };

  // Option 1: Send transaction using ethers.js with a custom EIP-1193 provider
  const executeTxEthers = async () => {
    const tx = {
      to: recipientAddress,
      value: parseEther("0.01"), // Set value to 0.01 Ether
      data: "0x", // No data, as there is no contract interaction
    };

    setIsSending(true);

    try {
      const EOAprovider = await primaryWallet.connector.getProvider();
      const customProvider = new ethers.BrowserProvider(
        EOAprovider as Eip1193Provider,
        "any"
      );

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

      if (txReceipt) {
        setTransactionHash(txReceipt.hash);
      } else {
        console.error("Transaction receipt is null");
      }
    } catch (error) {
      console.error("Error executing EVM transaction:", error);
    } finally {
      setIsSending(false);
    }
  };

  // Option 2: Send transaction using the native Particle provider
  const executeTxNative = async () => {
    try {
      const tx = {
        to: recipientAddress,
        value: parseEther("0.01"), // Set value to 0.01 Ether
        data: "0x", // No data, as there is no contract interaction
        chainId: primaryWallet.chainId, // Current chainId
        account: primaryWallet.accounts[0], // Primary account
      };

      setIsSending(true);

      const walletClient = primaryWallet.getWalletClient();
      const transactionResponse = await walletClient.sendTransaction(tx);

      setTransactionHash(transactionResponse);
      console.log("Transaction sent:", transactionResponse);
    } catch (error) {
      console.error("Failed to send transaction:", error);
    } finally {
      setIsSending(false);
    }
  };

  // Parameters for the on-ramp URL
  const fiatCoin = "USD";
  const cryptoCoin = "ETH";
  const network = "Ethereum";
  const theme = "dark";
  const language = "en";

  // Function to handle the on-ramp button click
  const handleOnRamp = () => {
    const onRampUrl = `https://ramp.particle.network/?fiatCoin=${fiatCoin}&cryptoCoin=${cryptoCoin}&network=${network}&theme=${theme}&language=${language}`;
    window.open(onRampUrl, "_blank");
  };

  // Function to copy text to clipboard
  const copyToClipboard = (text: string) => {
    navigator.clipboard.writeText(text).then(() => {
      alert("Copied to clipboard!");
    });
  };

  // Function to truncate Ethereum address
  const truncateAddress = (address: string) => {
    return address.slice(0, 6) + "..." + address.slice(-4);
  };

  return (
    <div className=" flex flex-col items-center justify-between p-8 bg-black text-white">
      {/* Header */}
      <header className="mb-8 flex flex-col items-center">
        <img
          src="https://camo.githubusercontent.com/8f19e01ee2f062917c087e75d1a504315a04fbf8d42cbcd61fc4b8a20f11118e/68747470733a2f2f692e696d6775722e636f6d2f786d647a5855342e706e67"
          alt="Particle Network Logo"
          className="mb-4 w-96"
        />
        <h1 className="text-4xl font-bold">Particle Connect 2.0</h1>
        <h2 className="mt-4 text-xl font-bold">
          Particle Connect Quickstart— Fetch user data and send trasnactions
        </h2>
      </header>

      <main className="flex-grow flex flex-col items-center justify-center w-full max-w-6xl mx-auto">
        {/* Display connection status */}
        <div className="bg-gray-800 p-4 rounded-lg shadow-lg max-w-sm mx-auto mb-4">
          <h2 className="text-md font-semibold text-white">
            Status: {connectionStatus}
          </h2>
        </div>

        {isConnected ? (
          <>
            <div className="flex justify-center w-full">
              <div className="grid grid-cols-1 md:grid-cols-2 gap-8 w-full max-w-4xl">
                <div className="border border-purple-500 p-6 rounded-lg">
                  {isLoadingUserInfo ? (
                    <div>Loading user info...</div>
                  ) : userInfoError ? (
                    <div className="text-red-500">{userInfoError}</div>
                  ) : (
                    userInfo && ( // Conditionally render user info
                      <div className="flex items-center">
                        <h2 className="text-lg font-semibold text-white mr-2">
                          Name: {userInfo.name || "N/A"}
                        </h2>
                        {userInfo.avatar && (
                          <img
                            src={userInfo.avatar}
                            alt="User Avatar"
                            className="w-10 h-10 rounded-full"
                          />
                        )}
                      </div>
                    )
                  )}
                  <h2 className="text-lg font-semibold mb-2 text-white flex items-center">
                    Address: <code>{truncateAddress(userAddress)}</code>
                    <button
                      className="bg-purple-600 hover:bg-purple-700 text-white font-bold py-1 px-2 ml-2 rounded transition duration-300 ease-in-out transform hover:scale-105 shadow-lg flex items-center"
                      onClick={() => copyToClipboard(userAddress)}
                    >
                      📋
                    </button>
                  </h2>
                  <h2 className="text-lg font-semibold mb-2 text-white">
                    Chain ID: <code>{chainId}</code>
                  </h2>
                  <h2 className="text-lg font-semibold mb-2 text-white flex items-center">
                    Balance: {balance !== "" ? balance : "Loading..."}
                    <button
                      className="bg-purple-600 hover:bg-purple-700 text-white font-bold py-1 px-2 ml-2 rounded transition duration-300 ease-in-out transform hover:scale-105 shadow-lg flex items-center"
                      onClick={fetchBalance}
                    >
                      🔄
                    </button>
                  </h2>
                  <button
                    className="mt-4 bg-purple-600 hover:bg-purple-700 text-white font-bold py-2 px-4 rounded transition duration-300 ease-in-out transform hover:scale-105 shadow-lg"
                    onClick={handleOnRamp}
                  >
                    Buy Crypto with Fiat
                  </button>
                  <div>
                    <button
                      className="mt-4 bg-red-600 hover:bg-red-700 text-white font-bold py-2 px-4 rounded transition duration-300 ease-in-out transform hover:scale-105 shadow-lg"
                      onClick={handleDisconnect}
                    >
                      Disconnect
                    </button>
                  </div>
                </div>

                <div className="border border-purple-500 p-6 rounded-lg">
                  <h2 className="text-2xl font-bold mb-2 text-white">
                    Send a transaction
                  </h2>
                  <h2 className="text-lg">Send 0.01</h2>
                  <input
                    type="text"
                    placeholder="Recipient Address"
                    value={recipientAddress}
                    onChange={(e) => setRecipientAddress(e.target.value)}
                    className="mt-4 p-2 w-full rounded border border-gray-700 bg-gray-900 text-white focus:outline-none focus:ring-2 focus:ring-purple-400"
                  />
                  <button
                    className="mt-4 bg-purple-600 hover:bg-purple-700 text-white font-bold py-2 px-4 rounded transition duration-300 ease-in-out transform hover:scale-105 shadow-lg"
                    onClick={executeTxNative}
                    disabled={!recipientAddress || isSending}
                  >
                    {isSending ? "Sending..." : `Send 0.01 Particle provider`}
                  </button>
                  <button
                    className="mt-4 bg-purple-600 hover:bg-purple-700 text-white font-bold py-2 px-4 rounded transition duration-300 ease-in-out transform hover:scale-105 shadow-lg"
                    onClick={executeTxEthers}
                    disabled={!recipientAddress || isSending}
                  >
                    {isSending ? "Sending..." : `Send 0.01 with ethers`}
                  </button>
                  {/* Display transaction notification with the hash */}
                  {transactionHash && (
                    <div className="mt-4 p-2 bg-gray-800 rounded-lg text-white">
                      Transaction Hash: {transactionHash}
                    </div>
                  )}
                </div>
              </div>
            </div>
          </>
        ) : (
          <ConnectButton />
        )}
      </main>
    </div>
  );
}

A Next.js demo repository containing the above code can be found here.