Quickstart: Integrating Particle Connect into Your Web Application
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.
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.
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.
🤩 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
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:
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
.
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
, andappId
— 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
andlanguage
for basic customization of the connection modal UI.- Additional appearance customizations.
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.
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:
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>
);
}
layout.tsx
file located in the app
directory.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
.
"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.
Was this page helpful?