*Zksync * is a layer 2 scaling Ethereum solution that adopts ZK rollups. They help reduce the congested Ethereum network and the transaction fee on Ethereum. We will be looking at how we can integrate Social Login from the Particle network, which users will be able to connect their Dapp with social logins rather than using an external wallet, with our Next js. This allows user to access their wallet with just a simple click. User can lose their private key, and when such happens users won't be able to retrieve such accounts back.
To get started with impelement particle network social logins to leverage social logins with the Zksync blockchain so user can connect to dapp with metamask or any EAO account.
Initialized new nextjs app
npx create-next-app@latest
You can name your app particlezksync or anything of your choice
After you successfully initialize your Nextjs project. You get something like this.
Navigate to your project name directory, where you should have all your file directories such as your package.json, and src which should look like this.
cd particlezksync
or your app name.
Next, you need to install some dependencies. You can use yarn or npm, depending on the package you are using to install your modules.
yarn add @particle-network/auth-core-modal @particle-network/chains ethers @particle-network/aa
Or
npm install @particle-network/auth-core-modal @particle-network/chains ethers @particle-network/aa
You need to created a file name .env in your project root folder,the following keys are required
NEXT_PUBLIC_APP_PROJECT_ID //You can get this from walletconnect
You will get the below keys from the Particle network dashboard.
NEXT_PUBLIC_APP_CLIENT_KEY
NEXT_PUBLIC_APP_APP_ID
Create a folder named Context in your src folder, Inside the context file, create a new file named AuthContext.jsx this is where we will handle all our authentication so we can wrap the authentication context around the application.
In AuthContext.jsx define the following code:
"use client" // directive ensures that this file is treated as a client component in Next.js.
// Importing necessary libraries and hooks from React and Particle Network SDK
import React, { createContext, useContext, useState, useEffect } from "react";
import {
useEthereum, // Hook to interact with Ethereum provider
useConnect, // Hook to manage connection to a wallet
useAuthCore, // Hook to access authentication core methods
} from "@particle-network/auth-core-modal";
import { ZetaChainTestnet } from "@particle-network/chains"; // Importing ZetaChainTestnet from Particle Network SDK
import { AAWrapProvider, SmartAccount, SendTransactionMode } from "@particle-network/aa"; // Tools for account abstraction and transaction management
import { ethers } from "ethers"; // Ethers.js for blockchain interactions
// Creating a context to share state across components
const SocialoginAccount = createContext();
// Main AuthContext component that wraps around child components to provide authentication-related context
export const AuthContext = ({ children }) => {
const chain = ZetaChainTestnet.id; // Getting the chain ID of ZetaChainTestnet
console.log(chain);
const { provider } = useEthereum(); // Accessing Ethereum provider
if (!provider) {
console.error('Ethereum provider is not available.');
return null; // Or handle this case appropriately
}
// Creating a SmartAccount instance for account abstraction and user operations
const smartAccount = new SmartAccount(provider, {
projectId: process.env.NEXT_PUBLIC_APP_PROJECT_ID, // Project ID from environment variables
clientKey: process.env.NEXT_PUBLIC_APP_CLIENT_KEY, // Client key from environment variables from particle network
appId: process.env.NEXT_PUBLIC_APP_APP_ID, // App ID from environment variables from particle network
aaOptions: {
accountContracts: {
SIMPLE: [{ version: "1.0.0", chainIds: [chain] }], // Defining account abstraction options for the ZetaChain
},
},
});
console.log(smartAccount);
// Wrapping SmartAccount with AAWrapProvider to enable transaction handling (e.g., Gasless transactions)
const customProvider = new ethers.providers.Web3Provider(
new AAWrapProvider(smartAccount, SendTransactionMode.Gasless),
"any" // Network is set to "any" for compatibility
);
// State variables to store balance, address, and signer information
const [balance, setBalance] = useState(null);
const [address, setaddress] = useState(null);
const [signer, setSigner] = useState(null);
// Hooks for managing wallet connection and retrieving user info
const { connect, disconnect, connected } = useConnect();
const { userInfo } = useAuthCore();
// Getting the signer from the customProvider
const signerp = customProvider?.getSigner();
console.log("signerp", signerp);
// Effect hook that fetches balance when userInfo, smartAccount, or customProvider changes
useEffect(() => {
if (userInfo) {
fetchBalance();
}
}, [userInfo, smartAccount, customProvider, provider]);
console.log(userInfo);
console.log(balance);
// Function to fetch user's Ethereum balance and signer info
const fetchBalance = async () => {
const addressParticle = await smartAccount.provider.selectedAddress; // Get the selected address from the provider
console.log(addressParticle);
setaddress(addressParticle);
const balanceResponseParticle = await customProvider.getBalance(
addressParticle
); // Fetch balance
setBalance(ethers.utils.formatEther(balanceResponseParticle)); // Format and set balance
const signers = customProvider.getSigner(); // Get the signer
console.log(signers);
setSigner(signers); // Set the signer in state
};
console.log(signer, "signer");
// Function to handle user login via social authentication
const handleLogin = async (authType) => {
if (!userInfo) {
try {
await connect({
socialType: authType, // Specify the social authentication type
chain: ZetaChainTestnet, // Specify the chain to connect to
prompt: "select_account", // Prompt the user to select an account
});
} catch (error) {
console.log(error, "lognError"); // Handle login errors
}
}
};
// Function to execute a user operation, e.g., sending a transaction
const executeUserOp = async () => {
try {
if (!signer) {
console.error("Signer is not available"); // Ensure the signer is available
return;
}
// Define a transaction object
const tx = {
to: "0x9dBa18e9b96b905919cC828C399d313EfD55D800", // Recipient address
value: ethers.utils.parseEther("0.001"), // Transaction value in Ether
};
const txResponse = await signer.sendTransaction(tx); // Send the transaction
const txReceipt = await txResponse.wait(); // Wait for the transaction to be mined
console.log("Transaction receipt:", txReceipt);
} catch (error) {
console.error("Error in executeUserOp:", error); // Handle errors during transaction execution
}
};
// Providing the context values to child components
return (
<SocialoginAccount.Provider
value={{
handleLogin,
userInfo,
balance,
connect,
disconnect,
address,
customProvider,
executeUserOp,
signer,
signerp,
}}
>
{children} {/* Render child components */}
</SocialoginAccount.Provider>
);
};
export default SocialoginAccount;
// Custom hook to consume the context in child components
export const useAuth = () => {
return useContext(SocialoginAccount);
};
We will implement the AuthContext in the layout file in our src/app/layout directory, which should look like this
"use client";
// The "use client" directive indicates that this file is intended to be used as a client component in Next.js.
// Importing the Inter font from Google Fonts using Next.js built-in support.
import { Inter } from "next/font/google";
// Importing global CSS styles.
import "./globals.css";
// Importing chain configurations for ZetaChainTestnet and EthereumSepolia from Particle Network SDK.
import { zkSyncEraSepolia, zkSyncEra } from "@particle-network/chains";
// Importing AuthCoreContextProvider for providing authentication context using Particle Network SDK.
import { AuthCoreContextProvider } from "@particle-network/auth-core-modal";
// Importing a custom AuthContext from a local context file.
import { AuthContext } from "@/context/AuthContext";
// Initializing the Inter font with Latin subset, ensuring consistent typography.
const inter = Inter({ subsets: ["latin"] });
// Defining the RootLayout component, which serves as the main layout for the application.
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
// The root HTML element of the page, setting the language to English.
<html lang="en">
<body className={inter.className}> {/* Applying the Inter font to the entire body */}
{/* Wrapping the application in AuthCoreContextProvider to provide authentication and wallet context */}
<AuthCoreContextProvider
options={{
projectId: process.env.NEXT_PUBLIC_APP_PROJECT_ID as string || "", // Project ID for Particle Network, from environment variables
clientKey: process.env.NEXT_PUBLIC_APP_CLIENT_KEY as string || "", // Client key for Particle Network, from environment variables
appId: process.env.NEXT_PUBLIC_APP_APP_ID as string || "", // App ID for Particle Network, from environment variables
erc4337: {
name: "SIMPLE", // Name of the account abstraction contract
version: "1.0.0", // Version of the account abstraction contract
},
wallet: {
visible: true, // Indicates if the wallet should be visible
customStyle: {
supportChains: [zkSyncEraSepolia, zkSyncEra], // Supported blockchain networks
},
},
}}
>
{/* Wrapping children components with custom AuthContext to manage user authentication */}
<AuthContext>{children}</AuthContext>
</AuthCoreContextProvider>
</body>
</html>
);
}
We create a truncate address components file in a utils folder we created in the root directory of our app src/utils/truncateAddress.js
export const truncateAddress = (address) => {
if(!address) return;
return address.slice(0,8) + "..." + address.slice(address.length - 4, address.lenght)
}
We created a simple component named ConnectAuth.jsx to manage and display the login feature.
"use client";
// The "use client" directive indicates that this component is a client-side component in Next.js.
import React from "react";
// Importing the useConnect hook from the Particle Network SDK for managing wallet connections.
import { useConnect } from "@particle-network/auth-core-modal";
// Importing custom hooks and utilities.
import { useAuth } from "@/context/AuthContext"; // Custom hook to access authentication context.
import { truncateAddress } from "@/utils/truncateAddress"; // Utility function to truncate long Ethereum addresses.
// Define the ConnectAuth component, which handles wallet connection and displays wallet information.
const ConnectAuth = () => {
// Destructuring properties from the useConnect hook to manage connection state and disconnect functionality.
const { connected, disconnect } = useConnect();
// Destructuring properties from the custom useAuth hook to handle login, display balance, address, and logout functionality.
const { handleLogin: login, balance: balanceInfo, address, disconnect: logout, walletAddress } = useAuth();
// Returning JSX that conditionally renders based on the connection state.
return (
<div>
{connected ? (
// If the user is connected, display the wallet information.
<>
<div className="dropdown dropdown-bottom dropdown-end">
{/* Button to view wallet details */}
<div tabIndex={0} role="button" className="btn m-1">
View Wallet
</div>
{/* Dropdown menu with wallet information */}
<ul
tabIndex={0}
className="dropdown-content menu bg-base-100 rounded-box z-[1] w-52 p-2 shadow"
>
<li>
<a>
{/* Display the user's balance */}
<p>{balanceInfo}</p>
</a>
</li>
<li>
<a>
{/* Display the truncated wallet address */}
<p>{truncateAddress(address)}</p>
</a>
</li>
<li>
<a>
{/* Button to disconnect the wallet */}
<button className="" onClick={() => disconnect()}>
Disconnect
</button>
</a>
</li>
</ul>
</div>
</>
) : (
// If the user is not connected, display options to connect with different social accounts.
<>
<div className="dropdown dropdown-bottom dropdown-end">
{/* Button to initiate wallet connection */}
<div tabIndex={0} role="button" className="btn m-1">
Connect Wallet
</div>
{/* Dropdown menu with options to connect via different social platforms */}
<ul
tabIndex={0}
className="dropdown-content menu bg-base-100 rounded-box z-[1] w-52 p-2 shadow"
>
<li>
<a>
{/* Button to connect using Twitter */}
<button className="" onClick={() => login("twitter")}>
Connect with Twitter
</button>
</a>
</li>
<li>
<a>
{/* Button to connect using Google */}
<button className="" onClick={() => login("google")}>
Connect with Google
</button>
</a>
</li>
<li>
<a>
{/* Button to connect using GitHub */}
<button className="" onClick={() => login("github")}>
Connect with Github
</button>
</a>
</li>
</ul>
</div>
</>
)}
</div>
);
};
// Exporting the ConnectAuth component as the default export.
export default ConnectAuth;
We can use the ConnectAuth component in the src/app/page.tsx
import Image from "next/image";
import ConnectAuth from "@/components/ConnectAuth"
export default function Home() {
// const { } = useAuth()
return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<div className="">
Get started by editing
<code className="font-mono font-bold">src/app/page.tsx</code>
</p>
<ConnectAuth />
</div>
<div>
<ConnectAuth />
</div>
</main>
);
}
Top comments (0)