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
# .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:
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.