Skip to main content

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 SecurityAllowed 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

Configure Dynamic Environment

Create a .env.local file with your Dynamic environment ID and Deframe Pods API key:
.env.local
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:
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();

Configure Providers

Create src/lib/providers.tsx with a custom wallet context:
src/lib/providers.tsx
"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.
src/lib/pods.ts
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:
npm run dev
The application will be available at http://localhost:3000.

Configure CORS

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