DEV Community

Muhdsodiq Bolarinwa
Muhdsodiq Bolarinwa

Posted on

Integrating Zksync With Particle Network

*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
Enter fullscreen mode Exit fullscreen mode

You can name your app particlezksync or anything of your choice
After you successfully initialize your Nextjs project. You get something like this.

zksync particle network

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.

zksync particle network account abstraction

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 
Enter fullscreen mode Exit fullscreen mode

Or

npm install @particle-network/auth-core-modal @particle-network/chains ethers @particle-network/aa 
Enter fullscreen mode Exit fullscreen mode

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);
};
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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&nbsp;
          <code className="font-mono font-bold">src/app/page.tsx</code>
        </p>
          <ConnectAuth />

      </div>


      <div>
        <ConnectAuth />

      </div>
    </main>
  );
}

Enter fullscreen mode Exit fullscreen mode

Top comments (0)