Documentation Index
Fetch the complete documentation index at: https://docs.dynamic.xyz/docs/llms.txt
Use this file to discover all available pages before exploring further.
What We’re Building
A React (Next.js) app that connects Dynamic’s embedded wallets to Deframe Pods yield strategies, allowing users to:
- Deposit stablecoins into yield strategies
- Withdraw from positions
- Track yields and positions across multiple protocols
If you want to take a quick look at the final code, check out the GitHub repository.
Building the Application
Project setup
Follow the JS SDK Quickstart to initialize a Next.js app with Dynamic. Scaffold with create-next-app, then continue with the dependencies below.
Dashboard: Under Chains & Networks, enable Ethereum and any other EVM networks you use. Under Sign-in Methods and Wallets, enable what your flow needs (including Embedded wallets). Under Security → Allowed Origins, add the origin where the app runs (for example http://localhost:3000).
Install Dependencies
npm install @dynamic-labs-sdk/client @dynamic-labs-sdk/evm viem
Create a .env.local file with your Dynamic environment ID and Deframe Pods API key:
NEXT_PUBLIC_DYNAMIC_ENV_ID=your-environment-id-here
NEXT_PUBLIC_PODS_API_KEY=your-pods-api-key-here
NEXT_PUBLIC_PODS_API_URL=https://api.deframe.io
You can find your Environment ID in the Dynamic dashboard under Developer Settings → SDK & API Keys.
For the Deframe Pods API key, visit Deframe to get your API key.
Initialize Dynamic
Create src/lib/dynamic.ts:
import { createDynamicClient } from "@dynamic-labs-sdk/client";
import { addEvmExtension } from "@dynamic-labs-sdk/evm";
export const dynamicClient = createDynamicClient({
environmentId: process.env.NEXT_PUBLIC_DYNAMIC_ENV_ID!,
metadata: { name: "Pods Yield" },
});
addEvmExtension();
Create src/lib/providers.tsx with a custom wallet context:
"use client";
import { createContext, useContext, useState, useEffect, useCallback, type ReactNode } from "react";
import { getWalletAccounts, onEvent, isSignedIn, logout, detectOAuthRedirect, completeSocialAuthentication } from "@dynamic-labs-sdk/client";
import { createWaasWalletAccounts } from "@dynamic-labs-sdk/client/waas";
import { isEvmWalletAccount, type EvmWalletAccount } from "@dynamic-labs-sdk/evm";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { dynamicClient } from "./dynamic";
interface WalletContextValue {
evmAccount: EvmWalletAccount | null;
loggedIn: boolean;
ensureEvmWallet: () => Promise<void>;
disconnect: () => Promise<void>;
}
const WalletContext = createContext<WalletContextValue>({
evmAccount: null,
loggedIn: false,
ensureEvmWallet: async () => {},
disconnect: async () => {},
});
export function useWallet() { return useContext(WalletContext); }
const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 1000 * 60 * 5, refetchOnWindowFocus: false } } });
export default function Providers({ children }: { children: ReactNode }) {
const [evmAccount, setEvmAccount] = useState<EvmWalletAccount | null>(null);
const [loggedIn, setLoggedIn] = useState(false);
const refresh = useCallback(() => {
const accounts = getWalletAccounts(dynamicClient);
setEvmAccount(accounts.find(isEvmWalletAccount) ?? null);
setLoggedIn(isSignedIn(dynamicClient));
}, []);
const disconnect = useCallback(async () => {
await logout(dynamicClient);
setEvmAccount(null);
setLoggedIn(false);
}, []);
const ensureEvmWallet = useCallback(async () => {
try {
const accounts = getWalletAccounts(dynamicClient);
if (!accounts.some(isEvmWalletAccount) && isSignedIn(dynamicClient)) {
await createWaasWalletAccounts({ chains: ["EVM"] }, dynamicClient);
}
} catch {}
refresh();
}, [refresh]);
useEffect(() => {
const handleOAuthRedirect = async () => {
if (typeof window === "undefined") return;
try {
const url = new URL(window.location.href);
const isOAuth = await detectOAuthRedirect({ url }, dynamicClient);
if (isOAuth) {
await completeSocialAuthentication({ url }, dynamicClient);
await ensureEvmWallet();
window.history.replaceState({}, "", window.location.pathname);
return;
}
} catch {}
refresh();
};
handleOAuthRedirect();
const unsubWallets = onEvent({ event: "walletAccountsChanged", listener: () => ensureEvmWallet() }, dynamicClient);
const unsubLogout = onEvent({ event: "logout", listener: () => { setEvmAccount(null); setLoggedIn(false); } }, dynamicClient);
return () => { unsubWallets(); unsubLogout(); };
}, [refresh, ensureEvmWallet]);
return (
<WalletContext.Provider value={{ evmAccount, loggedIn, ensureEvmWallet, disconnect }}>
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
</WalletContext.Provider>
);
}
Create Pods Client
Create src/lib/pods.ts for the Deframe Pods API client. This file contains all the API communication logic for interacting with the Deframe Pods service.
All type definitions are available in src/lib/pods-types.ts in the GitHub repository.
const PODS_API_BASE = process.env.NEXT_PUBLIC_PODS_API_URL || "https://api.deframe.io";
const PODS_API_KEY = process.env.NEXT_PUBLIC_PODS_API_KEY;
async function fetchFromPodsAPI<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
const response = await fetch(`${PODS_API_BASE}${endpoint}`, {
...options,
headers: {
"Content-Type": "application/json",
"x-api-key": PODS_API_KEY!,
...options.headers,
},
});
if (!response.ok) {
const error = await response.text();
throw new Error(`Pods API error: ${error}`);
}
return response.json();
}
// getStrategies, getStrategy, getDepositBytecode, getWithdrawBytecode, getWalletPositions...
export const client = {
getStrategies,
getStrategy,
getDepositBytecode,
getWithdrawBytecode,
getWalletPositions,
};
You can get detailed information about the Deframe Pods API here.
Create Transaction Operations Hook
Create src/lib/useTransactionOperations.ts for handling deposits and withdrawals using createWalletClientForWalletAccount from the JS SDK:
src/lib/useTransactionOperations.ts
import { useState, useCallback } from "react";
import { createWalletClientForWalletAccount } from "@dynamic-labs-sdk/evm/viem";
import { base, mainnet, polygon } from "viem/chains";
import { client as podsClient } from "./pods";
import type { Strategy } from "./pods-types";
import { useWallet } from "@/lib/providers";
function getViemChain(chainId: number) {
switch (chainId) {
case mainnet.id: return mainnet;
case polygon.id: return polygon;
default: return base;
}
}
export function useTransactionOperations(_walletClient: unknown, selectedChainId: number) {
const { evmAccount } = useWallet();
const [isOperating, setIsOperating] = useState(false);
const [operationError, setOperationError] = useState<Error | null>(null);
const executeDeposit = useCallback(async (strategy: Strategy, amount: string) => {
const walletAddress = evmAccount?.address;
if (!walletAddress || !evmAccount) throw new Error("Wallet not connected");
setIsOperating(true);
try {
const amountInSmallestUnit = BigInt(
Math.floor(parseFloat(amount) * 10 ** strategy.assetDecimals)
).toString();
const { bytecode } = await podsClient.getDepositBytecode({
strategyId: strategy.id,
chainId: selectedChainId,
amount: amountInSmallestUnit,
asset: strategy.assetName,
wallet: walletAddress,
});
const chain = getViemChain(selectedChainId);
const walletClient = createWalletClientForWalletAccount({ walletAccount: evmAccount, chain });
let lastHash: string | undefined;
for (const tx of bytecode) {
const hash = await walletClient.sendTransaction({
chain,
account: evmAccount.address as `0x${string}`,
to: tx.to as `0x${string}`,
value: BigInt(tx.value),
data: tx.data as `0x${string}`,
});
lastHash = hash;
}
return lastHash!;
} catch (error) {
const err = error instanceof Error ? error : new Error(String(error));
setOperationError(err);
throw err;
} finally {
setIsOperating(false);
}
}, [evmAccount, selectedChainId]);
// executeWithdraw follows the same pattern...
return { isOperating, operationError, executeDeposit /*, executeWithdraw */ };
}
Build Yield Interface Component
Create src/components/YieldInterface.tsx for the main UI. It reads evmAccount from useWallet() instead of useDynamicContext():
src/components/YieldInterface.tsx
"use client";
import { useEffect, useState, useCallback } from "react";
import { base } from "viem/chains";
import { useTransactionOperations } from "../lib/useTransactionOperations";
import { client as podsClient } from "../lib/pods";
import { useWallet } from "@/lib/providers";
import type { Strategy, WalletPositions } from "../lib/pods-types";
export function YieldInterface() {
const { evmAccount } = useWallet();
const [selectedChainId, setSelectedChainId] = useState(base.id);
const [strategies, setStrategies] = useState<Strategy[]>([]);
const [positions, setPositions] = useState<WalletPositions | null>(null);
const { isOperating, executeDeposit, executeWithdraw } =
useTransactionOperations(null, selectedChainId);
const fetchStrategies = useCallback(async () => {
const response = await podsClient.getStrategies(selectedChainId, 100);
setStrategies(response.data.filter((s) => s.isActive !== false));
}, [selectedChainId]);
useEffect(() => { fetchStrategies(); }, [fetchStrategies]);
useEffect(() => {
if (!evmAccount?.address) { setPositions(null); return; }
podsClient.getWalletPositions(evmAccount.address).then(setPositions).catch(() => setPositions(null));
}, [evmAccount?.address]);
// render strategies and positions...
}
Run the Application
Start the development server:
The application will be available at http://localhost:3000.
Add your local development URL to the CORS origins in your Dynamic dashboard under Developer Settings > CORS Origins.
Full source code
GitHub repository →
Additional Resources