Skip to main content
This guide is currently React only.

Prerequisites

  • Dynamic env ID
  • Alchemy API key
  • Alchemy RPC URL configured in your Dynamic Dashboard for the target network

Step By Step

1. Install dependencies

npm i -s @dynamic-labs/sdk-react-core @dynamic-labs/ethereum @account-kit/smart-contracts @account-kit/infra @aa-sdk/core

2. Configure your Alchemy environment

# .env
VITE_ALCHEMY_API_KEY=your-alchemy-api-key

3. Add Dynamic to your application

In order to use Dynamic, you should wrap your app with DynamicContextProvider at the highest possible level i.e.
import { DynamicContextProvider } from '@dynamic-labs/sdk-react-core' // provides Dynamic context to your app
import { EthereumWalletConnectors } from '@dynamic-labs/ethereum' // enables EVM wallet connectors
import Home from './Home' // your app root/page

// Your Dynamic environment ID (find in dashboard: https://app.dynamic.xyz/dashboard/developer/api)
const DYNAMIC_ENVIRONMENT_ID = 'XXXXX'

const App = () => {
  return (
    <div className="app">
      <DynamicContextProvider
        settings={{
          environmentId: DYNAMIC_ENVIRONMENT_ID, // link this app to your Dynamic env
          walletConnectors: [EthereumWalletConnectors], // keep bundle light with only Ethereum connectors
        }}
      >
        {/* Your application lives inside the provider */}
        <Home />
      </DynamicContextProvider>
    </div>
  )
}

export default App

4. Create the Alchemy Light Account hook

Create a reusable hook that:
  • Validates the wallet is Ethereum compatible
  • Uses the Alchemy RPC URL configured in your Dynamic Dashboard for the selected chain
  • Defines the chain with defineAlchemyChain using the resolved RPC URL
  • Builds a Dynamic-based Smart Account signer
  • Initializes createLightAccountAlchemyClient
useAlchemyLightAccount.ts
import { createLightAccountAlchemyClient } from "@account-kit/smart-contracts"; // builds an Alchemy Light Account client
import {  alchemy, defineAlchemyChain } from "@account-kit/infra"; // transport factory + helper to define the chain
import { WalletClientSigner, type SmartAccountSigner } from "@aa-sdk/core"; // signer implementation for WalletClient
import { type Wallet, useSwitchNetwork } from "@dynamic-labs/sdk-react-core"; // wallet types + network switch hook
import { isEthereumWallet } from "@dynamic-labs/ethereum"; // type guard for EVM wallets
import { useEffect, useMemo, useRef, useState } from "react"; // React primitives

// Hook to initialize an Alchemy Light Account client from a Dynamic wallet
const useAlchemyLightAccount = (primaryWallet: Wallet | null | undefined) => {
  const [lightAccountClient, setLightAccountClient] = useState<Awaited<ReturnType<typeof createLightAccountAlchemyClient>> | null>(null); // client state
  const [error, setError] = useState<string | null>(null); // error state for UI
  const [loading, setLoading] = useState(false); // loading state for UI

  const apiKey = useMemo(() => import.meta.env.VITE_ALCHEMY_API_KEY as string | undefined, []); // Alchemy API key
  const mountedRef = useRef(true); // track mounted state to avoid state updates on unmounted component

  // keep mounted ref in sync
  useEffect(() => {
    mountedRef.current = true;
    return () => {
      mountedRef.current = false;
    };
  }, []);

  // initialize the Light Account client whenever dependencies change
  useEffect(() => {
    let cancelled = false; // local cancellation flag
    async function init() {
      setError(null);
      setLoading(true);
      setLightAccountClient(null);
      try {
        if (!primaryWallet) {
          return; // nothing to do without a connected wallet
        }
        if (!isEthereumWallet(primaryWallet)) {
          throw new Error("This wallet is not an Ethereum wallet");
        }
        if (!apiKey) {
          throw new Error("Missing VITE_ALCHEMY_API_KEY");
        }
        const publicClient = await primaryWallet.getPublicClient(); // Viem Public Client from Dynamic

        const isRpcBaseUrlAnAlchemyUrl = publicClient.transport.url.includes('alchemy'); // validate RPC provider

        if(!isRpcBaseUrlAnAlchemyUrl) {
          throw new Error("Your RPC URL is not an Alchemy URL, please add it for this network in the Dynamic Dashboard");
        }

        const dynamicProvider = await primaryWallet.getWalletClient(); // Dynamic's WalletClient (Viem)
 
        const chain = defineAlchemyChain({ chain: dynamicProvider.chain, rpcBaseUrl: publicClient.transport.url }); // derive chain using the wallet's RPC URL

        const dynamicSigner: SmartAccountSigner = new WalletClientSigner(dynamicProvider, "dynamic"); // wrap WalletClient as SmartAccount signer

        const client = await createLightAccountAlchemyClient({
          transport: alchemy({ apiKey }), // Alchemy transport with your API key
          chain, // chain configured using the Alchemy RPC URL from Dynamic Dashboard
          signer: dynamicSigner, // owner signer for the smart account
        });
        if (!cancelled && mountedRef.current) {
          setLightAccountClient(client); // publish client to consumers
        }
      } catch (e) {
        if (!cancelled && mountedRef.current) {
          setError(e instanceof Error ? e.message : String(e)); // surface error to UI
        }
      } finally {
        if (!cancelled && mountedRef.current) {
          setLoading(false); // clear loading state
        }
      }
    }

    init(); // kick off initialization
    return () => {
      cancelled = true; // ensure no state updates after unmount
    };
  }, [primaryWallet, apiKey]);

  return { client: lightAccountClient, loading, error } as const; // stable return shape
};

export default useAlchemyLightAccount; // export the hook for use in your app

5. Use the hook in your app

Use the hook anywhere inside the DynamicContextProvider to get a ready Light Account client:
Home.tsx
import { useDynamicContext } from '@dynamic-labs/sdk-react-core' // access Dynamic context (wallets, user, etc.)
import useAlchemyLightAccount from './useAlchemyLightAccount' // the hook we built above

export default function Home() {
  const { primaryWallet } = useDynamicContext() // currently connected wallet
  const { client, loading, error } = useAlchemyLightAccount(primaryWallet) // init and consume Light Account client

  if (loading) return <p>Setting up smart account…</p> // show a loading indicator
  if (error) return <p>Error: {error}</p> // surface any initialization error

  return (
    <div>
      <button
        disabled={!client} // disable until client is ready
        onClick={async () => {
          if (!client) return // guard when not ready
          console.log('Light account client ready', client) // your app logic here (send UO, etc.)
        }}
      >
        Use Light Account
      </button>
    </div>
  )
}

Troubleshooting

  • Missing API key: Ensure VITE_ALCHEMY_API_KEY is set in your env.
  • Non-Ethereum wallet: Switch to an Ethereum wallet in Dynamic.
  • RPC provider mismatch: Ensure the RPC URL configured in your Dynamic Dashboard for the selected chain is an Alchemy RPC URL.
I