Stablecoin yield, embedded in your product. No wallet setup required.
Sign in with email or a social account, create a non-custodial MPC wallet in one call - no seed phrase, no extension - then deposit USDC into curated vaults, track positions, and withdraw anytime. Built entirely on Dynamic.
@dynamic-labs-sdk/client@dynamic-labs-sdk/react-hooksCreate the client
DocsOne Dynamic client per app, created at module scope. The EVM extension registers the networks the embedded wallet supports.
lib/dynamic/client.tsimport { createDynamicClient } from "@dynamic-labs-sdk/client"; import { addEvmExtension } from "@dynamic-labs-sdk/evm"; export const client = createDynamicClient({ environmentId: process.env.NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID!, }); addEvmExtension(client);Wrap your app for hooks
DocsThe react-hooks package reads the client from context and uses TanStack Query under the hood, so mount QueryClientProvider outside DynamicProvider once - every hook below works anywhere inside.
app/providers.tsximport { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { DynamicProvider } from "@dynamic-labs-sdk/react-hooks"; import { client } from "@/lib/dynamic/client"; const queryClient = new QueryClient(); export function Providers({ children }: { children: React.ReactNode }) { return ( <QueryClientProvider client={queryClient}> <DynamicProvider client={client}>{children}</DynamicProvider> </QueryClientProvider> ); }Sign in with an email code
DocsThe login card on the left runs this flow: send a one-time passcode, verify it. No password, no seed phrase - the session lives in the client.
components/login.tsximport { useSendEmailOTP, useVerifyOTP } from "@dynamic-labs-sdk/react-hooks"; const { mutate: sendOtp, data: otpVerification } = useSendEmailOTP(); const { mutate: verifyOtp } = useVerifyOTP(); sendOtp({ email }); // User types the 6-digit code from their inbox: verifyOtp({ otp: code, otpVerification });Sign in with a social account
DocsKick off the provider's OAuth flow, then complete it when the user lands back - same session, same embedded wallet.
components/social-login.tsximport { useCompleteSocialRedirect, useSignInWithSocialRedirect, } from "@dynamic-labs-sdk/react-hooks"; const { mutate: signIn } = useSignInWithSocialRedirect(); const { mutate: completeRedirect } = useCompleteSocialRedirect(); signIn({ provider: "google", redirectUrl: window.location.origin }); // ...and when the provider redirects back: completeRedirect({ url: new URL(window.location.href) });Create the embedded wallet
DocsOne call mints a non-custodial embedded (WaaS) wallet for the signed-in user - no extension, no seed phrase; keys never leave Dynamic's MPC. The hook lists every account and re-renders as they're created.
lib/dynamic/wallets.tsimport { createWaasWalletAccounts } from "@dynamic-labs-sdk/client/waas"; import { useGetWalletAccounts } from "@dynamic-labs-sdk/react-hooks"; await createWaasWalletAccounts({ chains: ["EVM"] }); const { data: walletAccounts } = useGetWalletAccounts(); const walletAccount = walletAccounts?.[0];Deposit USDC into a vault
DocsWrap the wallet account in a viem-compatible client and write to the vault contract - the user approves in place; keys never leave Dynamic's MPC.
lib/vaults/deposit.tsimport { createWalletClientForWalletAccount } from "@dynamic-labs-sdk/evm/viem"; import { parseUnits } from "viem"; const walletClient = await createWalletClientForWalletAccount({ walletAccount }); const hash = await walletClient.writeContract({ address: vault.address, abi: vaultAbi, functionName: "deposit", args: [parseUnits(amount, 6), walletAccount.address], });Track balances and positions
DocsRead USDC and vault-share balances with one hook - the portfolio view renders straight from the query and stays fresh.
components/positions.tsximport { useGetTokenBalances } from "@dynamic-labs-sdk/react-hooks"; const { data: balances } = useGetTokenBalances({ walletAccount });