Recommended: JavaScript SDK with React Hooks For new React apps, we recommend the JavaScript SDK with React Hooks (@dynamic-labs-sdk/react-hooks) instead of the legacy React SDK documented here. The JS SDK comes with many benefits such as a much smaller bundle size and other optimizations. Use the React quickstart (JavaScript SDK) to get started.
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.