Skip to main content
Set up EIP-4337 smart account clients to enable UserOperations, gas sponsorship, and batched transactions.

Prerequisites

Para Setup Required

Provider Setup

Installation

npm install @account-kit/smart-contracts @account-kit/infra @aa-sdk/core viem

Configuration

useSmartAccountClient.ts
import { useState, useEffect, useRef } from "react";
import { useViemAccount } from "@getpara/react-sdk/evm";
import { createModularAccountV2Client, type ModularAccountV2Client } from "@account-kit/smart-contracts";
import { alchemy, sepolia } from "@account-kit/infra";
import { WalletClientSigner } from "@aa-sdk/core";
import { createWalletClient, http, type Address } from "viem";

const ALCHEMY_API_KEY = process.env.NEXT_PUBLIC_ALCHEMY_API_KEY!;
const GAS_POLICY_ID = process.env.NEXT_PUBLIC_ALCHEMY_GAS_POLICY_ID;
const CHAIN = sepolia;

export function useSmartAccountClient() {
  const { viemAccount, isLoading: isViemLoading } = useViemAccount();
  const [client, setClient] = useState<ModularAccountV2Client | null>(null);
  const [address, setAddress] = useState<Address | null>(null);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<Error | null>(null);
  const initializedForAddress = useRef<string | null>(null);

  useEffect(() => {
    if (!viemAccount || isViemLoading || !ALCHEMY_API_KEY) return;
    if (initializedForAddress.current === viemAccount.address) return;

    const initializeClient = async () => {
      setIsLoading(true);
      setError(null);

      try {
        const walletClient = createWalletClient({
          account: viemAccount,
          chain: CHAIN,
          transport: http(),
        });

        const signer = new WalletClientSigner(walletClient, "para");

        const accountClient = await createModularAccountV2Client({
          transport: alchemy({ apiKey: ALCHEMY_API_KEY }),
          chain: sepolia,
          signer,
          policyId: GAS_POLICY_ID || undefined,
        });

        setClient(accountClient);
        setAddress(accountClient.account.address);
        initializedForAddress.current = viemAccount.address;
      } catch (err) {
        setError(err instanceof Error ? err : new Error("Failed to initialize"));
      } finally {
        setIsLoading(false);
      }
    };

    initializeClient();
  }, [viemAccount, isViemLoading]);

  return { client, address, isLoading: isLoading || isViemLoading, error };
}

Next Steps