From d3a7fca8c438cc8e3f2e6c685081de920740c6a9 Mon Sep 17 00:00:00 2001 From: Avneesh Agarwal Date: Mon, 4 May 2026 21:34:14 +1000 Subject: [PATCH 1/8] feat(swaps): migrate mayan and lifi examples to JS SDK Converts nextjs-bridge-mayan and nextjs-bridge-swaps-lifi from the React SDK (@dynamic-labs/*) to the JS SDK (@dynamic-labs-sdk/*), applying the standard visual theme and custom DynamicButton auth flow. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- examples/nextjs-bridge-mayan/.env.example | 1 + examples/nextjs-bridge-mayan/.gitignore | 1 + examples/nextjs-bridge-mayan/package.json | 6 +- .../nextjs-bridge-mayan/src/app/layout.tsx | 13 +- examples/nextjs-bridge-mayan/src/app/page.tsx | 8 +- .../src/components/ActionButtons.tsx | 10 +- .../src/components/MultiChainSwap.tsx | 147 ++---- .../src/components/dynamic/dynamic-button.tsx | 416 +++++++++++++++++ .../src/components/dynamic/logo.tsx | 59 +++ .../src/components/footer.tsx | 47 ++ .../src/components/nav.tsx | 111 +---- .../nextjs-bridge-mayan/src/lib/dynamic.ts | 11 +- .../nextjs-bridge-mayan/src/lib/providers.tsx | 133 +++++- .../nextjs-bridge-swaps-lifi/.env.example | 2 + examples/nextjs-bridge-swaps-lifi/.gitignore | 1 + .../nextjs-bridge-swaps-lifi/package.json | 26 +- .../src/app/globals.css | 96 +--- .../src/app/layout.tsx | 13 +- .../nextjs-bridge-swaps-lifi/src/app/page.tsx | 7 +- .../src/components/ActionButtons.tsx | 21 +- .../src/components/MultiChainSwap.tsx | 250 +++++----- .../src/components/SwapForm.tsx | 156 ++----- .../src/components/dynamic/dynamic-button.tsx | 426 +++++++++++++++++- .../src/components/dynamic/logo.tsx | 2 +- .../src/components/footer.tsx | 10 +- .../src/components/hamburger-menu.tsx | 93 ---- .../src/components/header.tsx | 30 +- .../src/components/mode-toggle.tsx | 40 -- .../src/components/theme-provider.tsx | 11 - .../src/lib/dynamic.ts | 19 +- .../src/lib/lifi-provider.tsx | 49 +- .../nextjs-bridge-swaps-lifi/src/lib/lifi.ts | 22 +- .../src/lib/providers.tsx | 172 +++++-- .../nextjs-bridge-swaps-lifi/src/lib/wagmi.ts | 31 -- 34 files changed, 1510 insertions(+), 930 deletions(-) create mode 100644 examples/nextjs-bridge-mayan/.env.example create mode 100644 examples/nextjs-bridge-mayan/src/components/dynamic/dynamic-button.tsx create mode 100644 examples/nextjs-bridge-mayan/src/components/dynamic/logo.tsx create mode 100644 examples/nextjs-bridge-mayan/src/components/footer.tsx create mode 100644 examples/nextjs-bridge-swaps-lifi/.env.example delete mode 100644 examples/nextjs-bridge-swaps-lifi/src/components/hamburger-menu.tsx delete mode 100644 examples/nextjs-bridge-swaps-lifi/src/components/mode-toggle.tsx delete mode 100644 examples/nextjs-bridge-swaps-lifi/src/components/theme-provider.tsx delete mode 100644 examples/nextjs-bridge-swaps-lifi/src/lib/wagmi.ts diff --git a/examples/nextjs-bridge-mayan/.env.example b/examples/nextjs-bridge-mayan/.env.example new file mode 100644 index 0000000..d6dda17 --- /dev/null +++ b/examples/nextjs-bridge-mayan/.env.example @@ -0,0 +1 @@ +NEXT_PUBLIC_DYNAMIC_ENV_ID=your-environment-id-here diff --git a/examples/nextjs-bridge-mayan/.gitignore b/examples/nextjs-bridge-mayan/.gitignore index 5ef6a52..7b8da95 100644 --- a/examples/nextjs-bridge-mayan/.gitignore +++ b/examples/nextjs-bridge-mayan/.gitignore @@ -32,6 +32,7 @@ yarn-error.log* # env files (can opt-in for committing if needed) .env* +!.env.example # vercel .vercel diff --git a/examples/nextjs-bridge-mayan/package.json b/examples/nextjs-bridge-mayan/package.json index 3619e4f..17c0de7 100644 --- a/examples/nextjs-bridge-mayan/package.json +++ b/examples/nextjs-bridge-mayan/package.json @@ -9,10 +9,10 @@ "lint": "next lint" }, "dependencies": { - "@dynamic-labs/ethereum": "4.48.2", - "@dynamic-labs/ethers-v6": "4.48.2", - "@dynamic-labs/sdk-react-core": "4.48.2", + "@dynamic-labs-sdk/client": "^0.24.1", + "@dynamic-labs-sdk/evm": "^0.24.1", "@mayanfinance/swap-sdk": "10.9.3", + "lucide-react": "0.542.0", "@tanstack/react-query": "5.85.3", "next": "15.4.10", "react": "19.1.2", diff --git a/examples/nextjs-bridge-mayan/src/app/layout.tsx b/examples/nextjs-bridge-mayan/src/app/layout.tsx index e20c5b6..ccefb4a 100644 --- a/examples/nextjs-bridge-mayan/src/app/layout.tsx +++ b/examples/nextjs-bridge-mayan/src/app/layout.tsx @@ -1,14 +1,17 @@ import "./globals.css"; import type { Metadata } from "next"; -import { Inter } from "next/font/google"; +import { Roboto } from "next/font/google"; import Providers from "@/lib/providers"; -const inter = Inter({ subsets: ["latin"] }); +const roboto = Roboto({ + subsets: ["latin"], + weight: ["300", "400", "500", "700"], +}); export const metadata: Metadata = { - title: "Dynamic - Cross-Chain Swaps", + title: "Dynamic - Cross-Chain Swaps with Mayan", description: - "Fast and secure cross-chain bridging and swapping powered by Dynamic", + "Fast and secure cross-chain bridging and swapping powered by Dynamic and Mayan Finance", }; export default function RootLayout({ @@ -18,7 +21,7 @@ export default function RootLayout({ }) { return ( - + {children} diff --git a/examples/nextjs-bridge-mayan/src/app/page.tsx b/examples/nextjs-bridge-mayan/src/app/page.tsx index ca6e1a6..c37927f 100644 --- a/examples/nextjs-bridge-mayan/src/app/page.tsx +++ b/examples/nextjs-bridge-mayan/src/app/page.tsx @@ -2,12 +2,16 @@ import MultiChainSwap from "@/components/MultiChainSwap"; import Nav from "@/components/nav"; +import Footer from "@/components/footer"; export default function Main() { return ( -
+
); } diff --git a/examples/nextjs-bridge-mayan/src/components/ActionButtons.tsx b/examples/nextjs-bridge-mayan/src/components/ActionButtons.tsx index 17682ac..663fedb 100644 --- a/examples/nextjs-bridge-mayan/src/components/ActionButtons.tsx +++ b/examples/nextjs-bridge-mayan/src/components/ActionButtons.tsx @@ -1,7 +1,5 @@ "use client"; -import { DynamicConnectButton } from "@dynamic-labs/sdk-react-core"; - interface ActionButtonsProps { isLoading: boolean; isExecuting: boolean; @@ -23,9 +21,9 @@ export default function ActionButtons({ return (
- - Connect Wallet to Get Started - +

+ Please sign in to get started +

); @@ -40,7 +38,7 @@ export default function ActionButtons({ className={`px-8 py-3 rounded-lg font-medium transition-all duration-200 shadow-md hover:shadow-lg ${ isLoading ? "bg-gray-300 text-gray-500 cursor-not-allowed" - : "bg-gradient-to-r from-blue-500 to-purple-600 text-white hover:from-blue-600 hover:to-purple-700" + : "bg-[#4779FF] text-white hover:bg-[#3366ee]" }`} > {isLoading ? ( diff --git a/examples/nextjs-bridge-mayan/src/components/MultiChainSwap.tsx b/examples/nextjs-bridge-mayan/src/components/MultiChainSwap.tsx index e7f9650..307a9d4 100644 --- a/examples/nextjs-bridge-mayan/src/components/MultiChainSwap.tsx +++ b/examples/nextjs-bridge-mayan/src/components/MultiChainSwap.tsx @@ -1,19 +1,18 @@ "use client"; -import { useDynamicContext } from "@dynamic-labs/sdk-react-core"; -import { getSigner } from "@dynamic-labs/ethers-v6"; import { useEffect, useState } from "react"; import { parseUnits } from "viem"; +import { createWalletClientForWalletAccount } from "@dynamic-labs-sdk/evm/viem"; import { ALL_CHAINS, type ChainKey, isEVMChain } from "@/constants/chains"; import { fetchTokensForChain, type TokenData } from "@/lib/mayan-api"; +import { useWallet } from "@/lib/providers"; import ActionButtons from "./ActionButtons"; import RouteDisplay from "./RouteDisplay"; import StatusMessages from "./StatusMessages"; import SwapForm from "./SwapForm"; import { fetchQuote, swapFromEvm } from "@mayanfinance/swap-sdk"; import type { Quote, Token } from "@mayanfinance/swap-sdk"; -import { isEthereumWallet } from "@dynamic-labs/ethereum"; interface SimpleChain { id: number | string; @@ -35,10 +34,10 @@ interface SwapState { } export default function MultiChainSwap() { - const { primaryWallet, sdkHasLoaded } = useDynamicContext(); + const { evmAccount, loggedIn } = useWallet(); - const isConnected = !!primaryWallet; - const address = primaryWallet?.address; + const isConnected = loggedIn && !!evmAccount; + const address = evmAccount?.address; const [swapState, setSwapState] = useState({ fromChain: ALL_CHAINS[0], @@ -56,7 +55,6 @@ export default function MultiChainSwap() { const [toTokens, setToTokens] = useState([]); const [isLoadingTokens, setIsLoadingTokens] = useState(false); - // Convert TokenData to Token (Mayan SDK format) const convertTokenDataToToken = (tokenData: TokenData): Token => { return { contract: tokenData.contract, @@ -65,29 +63,26 @@ export default function MultiChainSwap() { decimals: tokenData.decimals, logoURI: tokenData.logoURI || "", chainId: tokenData.chainId, - mint: tokenData.contract, // Use contract address as mint for EVM - coingeckoId: "", // Not available in TokenData - supportsPermit: false, // Default to false - verified: true, // Assume verified if we're fetching from API - standard: "erc20", // Use lowercase for TokenStandard + mint: tokenData.contract, + coingeckoId: "", + supportsPermit: false, + verified: true, + standard: "erc20", } as Token; }; - // Load tokens when chains change useEffect(() => { const loadTokens = async () => { if (!swapState.fromChain?.id || !swapState.toChain?.id) return; setIsLoadingTokens(true); try { - // Only load tokens for EVM chains (those with numeric IDs) if (isEVMChain(swapState.fromChain) && isEVMChain(swapState.toChain)) { const [fromTokensResponse, toTokensResponse] = await Promise.all([ fetchTokensForChain(swapState.fromChain.id as number), fetchTokensForChain(swapState.toChain.id as number), ]); - // Convert TokenData to Token and sort by popularity const sortedFromTokens = sortTokensByPopularity( fromTokensResponse.map(convertTokenDataToToken) ); @@ -98,21 +93,17 @@ export default function MultiChainSwap() { setFromTokens(sortedFromTokens); setToTokens(sortedToTokens); } else { - // For non-EVM chains, set empty token arrays for now setFromTokens([]); setToTokens([]); } - // Clear token selections when chains change setSwapState((prev) => ({ ...prev, fromToken: null, toToken: null, quote: null, })); - } catch (error) { - console.error("Error loading tokens:", error); - // Set empty arrays on error to prevent stale data + } catch { setFromTokens([]); setToTokens([]); } finally { @@ -123,12 +114,10 @@ export default function MultiChainSwap() { loadTokens(); }, [swapState.fromChain?.id, swapState.toChain?.id]); - // Enhanced token loading function that can be called manually const loadTokensForChain = async ( chainId: number | string, isFromChain: boolean ) => { - // Only load tokens for EVM chains (those with numeric IDs) if (typeof chainId !== "number") { if (isFromChain) { setFromTokens([]); @@ -141,29 +130,18 @@ export default function MultiChainSwap() { setIsLoadingTokens(true); try { const tokens = await fetchTokensForChain(chainId); - - // Convert TokenData to Token and sort by popularity const sortedTokens = sortTokensByPopularity( tokens.map(convertTokenDataToToken) ); if (isFromChain) { setFromTokens(sortedTokens); - setSwapState((prev) => ({ - ...prev, - fromToken: null, - quote: null, - })); + setSwapState((prev) => ({ ...prev, fromToken: null, quote: null })); } else { setToTokens(sortedTokens); - setSwapState((prev) => ({ - ...prev, - toToken: null, - quote: null, - })); + setSwapState((prev) => ({ ...prev, toToken: null, quote: null })); } - } catch (error) { - console.error(`Error loading tokens for chain ${chainId}:`, error); + } catch { if (isFromChain) { setFromTokens([]); } else { @@ -174,19 +152,9 @@ export default function MultiChainSwap() { } }; - // Sort tokens by popularity (common tokens first) const sortTokensByPopularity = (tokens: Token[]): Token[] => { const popularSymbols = [ - "USDC", - "USDT", - "ETH", - "WETH", - "WBTC", - "DAI", - "MATIC", - "BNB", - "AVAX", - "ARB", + "USDC", "USDT", "ETH", "WETH", "WBTC", "DAI", "MATIC", "BNB", "AVAX", "ARB", ]; return tokens.sort((a, b) => { @@ -197,48 +165,36 @@ export default function MultiChainSwap() { b.symbol.toUpperCase().includes(symbol.toUpperCase()) ); - // If both are popular, sort by popularity index - if (aIndex !== -1 && bIndex !== -1) { - return aIndex - bIndex; - } - - // If only one is popular, popular one goes first + if (aIndex !== -1 && bIndex !== -1) return aIndex - bIndex; if (aIndex !== -1) return -1; if (bIndex !== -1) return 1; - - // If neither is popular, sort alphabetically by symbol return a.symbol.localeCompare(b.symbol); }); }; const executeSwapQuote = async (quote: Quote) => { - if (!sdkHasLoaded || !isConnected || !address || !quote) { + if (!isConnected || !address || !evmAccount || !quote) { throw new Error("Not ready"); } - if (primaryWallet && isEthereumWallet(primaryWallet)) { - try { - // Get the ethers signer from Dynamic - const signer = await getSigner(primaryWallet); - - // Execute the swap using the Mayan SDK with ethers signer - const result = await swapFromEvm( - quote, - address, // swapperAddress - address, // destinationAddress - null, // referrerAddresses - signer, // signer (ethers) - null, // permit - null, // overrides - null, // payload - {} // options - ); - - return result; - } catch (error) { - throw error; - } - } + // Use the JS SDK viem wallet client for signing + const walletClient = createWalletClientForWalletAccount({ + walletAccount: evmAccount, + }); + + const result = await swapFromEvm( + quote, + address, + address, + null, + walletClient as Parameters[3], + null, + null, + null, + {} + ); + + return result; }; const handleGetQuote = async () => { @@ -270,16 +226,6 @@ export default function MultiChainSwap() { swapState.fromToken.decimals ); - if ( - !swapState.fromChain || - !swapState.toChain || - !swapState.fromToken || - !swapState.toToken - ) { - throw new Error("Invalid chain or token selection"); - } - - // The chain objects are already SimpleChain objects with id property const fromChain = swapState.fromChain; const toChain = swapState.toChain; const fromToken = swapState.fromToken; @@ -306,14 +252,14 @@ export default function MultiChainSwap() { setSwapState((prev) => ({ ...prev, - quote: quote, + quote, isLoading: false, error: null, })); } catch (error) { setSwapState((prev) => ({ ...prev, - error: (error as unknown as Error).message || "Failed to get quote", + error: (error as Error).message || "Failed to get quote", isLoading: false, })); } @@ -347,8 +293,7 @@ export default function MultiChainSwap() { } catch (error) { setSwapState((prev) => ({ ...prev, - error: - error instanceof Error ? error.message : "Failed to execute swap", + error: error instanceof Error ? error.message : "Failed to execute swap", isLoading: false, isExecuting: false, })); @@ -364,7 +309,7 @@ export default function MultiChainSwap() { }; return ( -
+

Mayan Cross-Chain Swap @@ -385,23 +330,13 @@ export default function MultiChainSwap() { toTokens={toTokens} isLoadingTokens={isLoadingTokens} onFromChainChange={(chain) => { - setSwapState((prev) => ({ - ...prev, - fromChain: chain, - fromToken: null, - })); - // Auto-load tokens for the selected chain + setSwapState((prev) => ({ ...prev, fromChain: chain, fromToken: null })); if (chain) { loadTokensForChain(chain.id, true); } }} onToChainChange={(chain) => { - setSwapState((prev) => ({ - ...prev, - toChain: chain, - toToken: null, - })); - // Auto-load tokens for the selected chain + setSwapState((prev) => ({ ...prev, toChain: chain, toToken: null })); if (chain) { loadTokensForChain(chain.id, false); } diff --git a/examples/nextjs-bridge-mayan/src/components/dynamic/dynamic-button.tsx b/examples/nextjs-bridge-mayan/src/components/dynamic/dynamic-button.tsx new file mode 100644 index 0000000..9732b12 --- /dev/null +++ b/examples/nextjs-bridge-mayan/src/components/dynamic/dynamic-button.tsx @@ -0,0 +1,416 @@ +"use client"; + +import { useState, useCallback, useRef, useEffect } from "react"; +import { + getAvailableWalletProvidersData, + connectAndVerifyWithWalletProvider, + sendEmailOTP, + verifyOTP, + authenticateWithSocial, + type WalletProviderData, + type OTPVerification, +} from "@dynamic-labs-sdk/client"; +import { exportWaasPrivateKey } from "@dynamic-labs-sdk/client/waas"; +import { useWallet } from "@/lib/providers"; +import { dynamicClient } from "@/lib/dynamic"; +import { KeyRound } from "lucide-react"; + +function shortenAddress(address: string): string { + return `${address.slice(0, 6)}...${address.slice(-4)}`; +} + +function GoogleIcon() { + return ( + + + + + + + ); +} + +function EmailIcon() { + return ( + + + + + ); +} + +function WalletIcon() { + return ( + + + + + + ); +} + +const primaryBtn = + "w-full flex items-center gap-3 px-4 py-2.5 rounded-lg text-sm font-medium transition-colors bg-[#4779FF] text-white hover:bg-[#3366ee] disabled:opacity-50 disabled:cursor-not-allowed"; +const outlineBtn = + "w-full flex items-center gap-3 px-4 py-2.5 rounded-lg text-sm font-medium transition-colors bg-white border border-[#DADADA] text-[#030303] hover:bg-[#F9F9F9] disabled:opacity-50 disabled:cursor-not-allowed"; + +export default function DynamicButton() { + const { evmAccount, loggedIn, disconnect, ensureEvmWallet } = useWallet(); + const [open, setOpen] = useState(false); + const [view, setView] = useState<"menu" | "email" | "otp" | "wallet" | "export">("menu"); + const [email, setEmail] = useState(""); + const [otp, setOtp] = useState(""); + const [otpVerification, setOtpVerification] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const panelRef = useRef(null); + const exportContainerRef = useRef(null); + const [exportPassword, setExportPassword] = useState(""); + const [exportRevealed, setExportRevealed] = useState(false); + + useEffect(() => { + if (!open) return; + function handler(e: MouseEvent) { + if (panelRef.current && !panelRef.current.contains(e.target as Node)) { + setOpen(false); + setView("menu"); + setError(null); + } + } + document.addEventListener("mousedown", handler); + return () => document.removeEventListener("mousedown", handler); + }, [open]); + + const reset = () => { + setView("menu"); + setEmail(""); + setOtp(""); + setOtpVerification(null); + setError(null); + setLoading(false); + setExportPassword(""); + setExportRevealed(false); + }; + + const handleExportKey = useCallback(async () => { + if (!exportContainerRef.current || !evmAccount) return; + setLoading(true); + setError(null); + try { + await exportWaasPrivateKey( + { displayContainer: exportContainerRef.current, password: exportPassword, walletAccount: evmAccount }, + dynamicClient + ); + setExportRevealed(true); + } catch (err) { + setError(err instanceof Error ? err.message : "Export failed"); + } finally { + setLoading(false); + } + }, [exportPassword, evmAccount]); + + const handleSendOTP = useCallback(async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + setError(null); + try { + const verification = await sendEmailOTP({ email }, dynamicClient); + setOtpVerification(verification); + setView("otp"); + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to send OTP"); + } finally { + setLoading(false); + } + }, [email]); + + const handleVerifyOTP = useCallback(async (e: React.FormEvent) => { + e.preventDefault(); + if (!otpVerification) return; + setLoading(true); + setError(null); + try { + await verifyOTP({ otpVerification, verificationToken: otp }, dynamicClient); + await ensureEvmWallet(); + setOpen(false); + reset(); + } catch (err) { + const msg = err instanceof Error ? err.message : "Invalid code"; + if (msg.toLowerCase().includes("unauthorized")) { + setError("Verification failed. Please request a new code and try again."); + } else { + setError(msg); + } + } finally { + setLoading(false); + } + }, [otpVerification, otp, ensureEvmWallet]); + + const handleGoogle = useCallback(async () => { + setLoading(true); + setError(null); + try { + await authenticateWithSocial( + { + provider: "google", + redirectUrl: typeof window !== "undefined" ? window.location.href : "", + }, + dynamicClient + ); + } catch (err) { + setError(err instanceof Error ? err.message : "Google sign-in failed"); + setLoading(false); + } + }, []); + + const getEvmProviders = (): WalletProviderData[] => + getAvailableWalletProvidersData(dynamicClient).filter((p) => p.chain === "EVM"); + + const handleConnectWallet = useCallback(async (providerKey: string) => { + setLoading(true); + setError(null); + try { + await connectAndVerifyWithWalletProvider({ walletProviderKey: providerKey }, dynamicClient); + await ensureEvmWallet(); + setOpen(false); + reset(); + } catch (err) { + setError(err instanceof Error ? err.message : "Connection failed"); + } finally { + setLoading(false); + } + }, [ensureEvmWallet]); + + if (loggedIn && evmAccount) { + return ( +
+ + + {open && ( +
+ {view !== "export" && ( + <> +
+

Connected

+

+ {evmAccount.address} +

+
+
+ +
+ + + )} + + {view === "export" && ( +
+ +
+ +

Export Private Key

+
+ {!exportRevealed && ( + <> + setExportPassword(e.target.value)} + placeholder="Enter your password" + className="w-full text-sm rounded-lg px-3 py-2 outline-none focus:ring-2 focus:ring-[#4779FF]" + style={{ border: "1px solid #DADADA", background: "#F9F9F9" }} + /> + {error &&

{error}

} + + + )} +
+
+ )} +
+ )} +
+ ); + } + + return ( +
+ + + {open && ( +
+ {view === "menu" && ( +
+

Sign in to Mayan Bridge

+ + + + + + {getEvmProviders().length > 0 && ( + <> +
+
+ or +
+
+ + + )} + + {error &&

{error}

} +
+ )} + + {view === "email" && ( +
+ +

Enter your email

+ setEmail(e.target.value)} + placeholder="you@example.com" + required + className="w-full px-3 py-2 text-sm border border-[#DADADA] rounded-lg outline-none focus:border-[#4779FF] focus:ring-1 focus:ring-[#4779FF]/30 text-[#030303]" + /> + {error &&

{error}

} + +
+ )} + + {view === "otp" && ( +
+ +

Enter the code

+

Sent to {email}

+ setOtp(e.target.value)} + placeholder="6-digit code" + required + maxLength={6} + className="w-full px-3 py-2 text-sm border border-[#DADADA] rounded-lg outline-none focus:border-[#4779FF] focus:ring-1 focus:ring-[#4779FF]/30 text-[#030303] tracking-widest text-center" + /> + {error &&

{error}

} + +
+ )} + + {view === "wallet" && ( +
+ +

Choose an EVM wallet

+ {getEvmProviders().length === 0 ? ( +

No EVM wallets detected. Install MetaMask or another EVM wallet.

+ ) : ( + getEvmProviders().map((provider) => ( + + )) + )} + {error &&

{error}

} +
+ )} +
+ )} +
+ ); +} diff --git a/examples/nextjs-bridge-mayan/src/components/dynamic/logo.tsx b/examples/nextjs-bridge-mayan/src/components/dynamic/logo.tsx new file mode 100644 index 0000000..e03fe3a --- /dev/null +++ b/examples/nextjs-bridge-mayan/src/components/dynamic/logo.tsx @@ -0,0 +1,59 @@ +interface DynamicLogoProps { + width?: number; + height?: number; + className?: string; +} + +export default function DynamicLogo({ + width = 150, + height = 30, + className = "text-[#141839]", +}: DynamicLogoProps) { + return ( + + + + + + + + + + + + ); +} diff --git a/examples/nextjs-bridge-mayan/src/components/footer.tsx b/examples/nextjs-bridge-mayan/src/components/footer.tsx new file mode 100644 index 0000000..8dc4705 --- /dev/null +++ b/examples/nextjs-bridge-mayan/src/components/footer.tsx @@ -0,0 +1,47 @@ +import DynamicLogo from "./dynamic/logo"; + +interface FooterProps { + bottomLinks?: { + text: string; + url: string; + }[]; +} + +export default function Footer({ + bottomLinks = [ + { + text: "GitHub", + url: "https://github.com/dynamic-labs/examples/tree/main/examples/nextjs-bridge-mayan", + }, + { text: "Docs", url: "https://docs.dynamic.xyz" }, + { text: "Dashboard", url: "https://app.dynamic.xyz" }, + { text: "Support", url: "https://www.dynamic.xyz/join-slack" }, + ], +}: FooterProps) { + return ( +
+
+
+
+ Powered by + +
+ +
+
+
+ ); +} diff --git a/examples/nextjs-bridge-mayan/src/components/nav.tsx b/examples/nextjs-bridge-mayan/src/components/nav.tsx index dc71408..e244e9b 100644 --- a/examples/nextjs-bridge-mayan/src/components/nav.tsx +++ b/examples/nextjs-bridge-mayan/src/components/nav.tsx @@ -1,100 +1,25 @@ -import { DynamicWidget } from "@/lib/dynamic"; -import Image from "next/image"; -import { openExternalLink } from "@/lib/utils"; -import { useState } from "react"; +"use client"; -export default function Nav() { - const [isMenuOpen, setIsMenuOpen] = useState(false); +import Link from "next/link"; +import DynamicLogo from "./dynamic/logo"; +import DynamicButton from "./dynamic/dynamic-button"; +export default function Nav() { return ( -
- dynamic +
+ + + - {/* Desktop Navigation */} -
- - - +
+
- - {/* Mobile Navigation */} -
- -
- - {/* Mobile Menu Dropdown */} - {isMenuOpen && ( -
-
-
- -
-
- - -
-
-
- )} -
+
); } diff --git a/examples/nextjs-bridge-mayan/src/lib/dynamic.ts b/examples/nextjs-bridge-mayan/src/lib/dynamic.ts index cf0f50f..4a9e635 100644 --- a/examples/nextjs-bridge-mayan/src/lib/dynamic.ts +++ b/examples/nextjs-bridge-mayan/src/lib/dynamic.ts @@ -1,2 +1,9 @@ -export * from "@dynamic-labs/sdk-react-core"; -export * from "@dynamic-labs/ethereum"; +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: "Mayan Bridge" }, +}); + +addEvmExtension(); diff --git a/examples/nextjs-bridge-mayan/src/lib/providers.tsx b/examples/nextjs-bridge-mayan/src/lib/providers.tsx index 797cf56..f0078c4 100644 --- a/examples/nextjs-bridge-mayan/src/lib/providers.tsx +++ b/examples/nextjs-bridge-mayan/src/lib/providers.tsx @@ -1,24 +1,127 @@ "use client"; -import { EthereumWalletConnectors } from "@dynamic-labs/ethereum"; -import { DynamicContextProvider } from "@dynamic-labs/sdk-react-core"; +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"; -export default function Providers({ children }: { children: React.ReactNode }) { - const queryClient = new QueryClient(); +interface WalletContextValue { + evmAccount: EvmWalletAccount | null; + loggedIn: boolean; + ensureEvmWallet: () => Promise; + disconnect: () => Promise; +} + +const WalletContext = createContext({ + 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(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 { + // wallet may already exist — ignore + } + refresh(); + }, [refresh]); + + useEffect(() => { + const handleOAuthRedirect = async () => { + if (typeof window === "undefined") return; + try { + const url = new URL(window.location.href); + if (await detectOAuthRedirect({ url }, dynamicClient)) { + await completeSocialAuthentication({ url }, dynamicClient); + await ensureEvmWallet(); + window.history.replaceState({}, "", window.location.pathname); + return; + } + } catch { + // not an OAuth redirect — continue normally + } + refresh(); + }; + + handleOAuthRedirect(); + + const unsub1 = onEvent( + { event: "walletAccountsChanged", listener: () => ensureEvmWallet() }, + dynamicClient + ); + const unsub2 = onEvent( + { + event: "logout", + listener: () => { + setEvmAccount(null); + setLoggedIn(false); + }, + }, + dynamicClient + ); + + return () => { + unsub1(); + unsub2(); + }; + }, [refresh, ensureEvmWallet]); return ( - + {children} - + ); } diff --git a/examples/nextjs-bridge-swaps-lifi/.env.example b/examples/nextjs-bridge-swaps-lifi/.env.example new file mode 100644 index 0000000..2432594 --- /dev/null +++ b/examples/nextjs-bridge-swaps-lifi/.env.example @@ -0,0 +1,2 @@ +NEXT_PUBLIC_DYNAMIC_ENV_ID=your-environment-id-here +NEXT_PUBLIC_LIFI_API_KEY=your-lifi-api-key-here diff --git a/examples/nextjs-bridge-swaps-lifi/.gitignore b/examples/nextjs-bridge-swaps-lifi/.gitignore index 5ef6a52..7b8da95 100644 --- a/examples/nextjs-bridge-swaps-lifi/.gitignore +++ b/examples/nextjs-bridge-swaps-lifi/.gitignore @@ -32,6 +32,7 @@ yarn-error.log* # env files (can opt-in for committing if needed) .env* +!.env.example # vercel .vercel diff --git a/examples/nextjs-bridge-swaps-lifi/package.json b/examples/nextjs-bridge-swaps-lifi/package.json index 725c947..dccab92 100644 --- a/examples/nextjs-bridge-swaps-lifi/package.json +++ b/examples/nextjs-bridge-swaps-lifi/package.json @@ -9,35 +9,26 @@ "lint": "next lint", "lint:fix": "next lint --fix", "type-check": "tsc --noEmit", - "clean": "rm -rf .next && rm -rf node_modules/.cache", - "build:analyze": "ANALYZE=true next build" + "clean": "rm -rf .next && rm -rf node_modules/.cache" }, "dependencies": { - "@dynamic-labs/ethereum": "4.48.2", - "@dynamic-labs/ethereum-aa": "4.48.2", - "@dynamic-labs/sdk-react-core": "4.48.2", - "@dynamic-labs/solana": "4.48.2", - "@dynamic-labs/wagmi-connector": "4.48.2", + "@dynamic-labs-sdk/client": "^0.24.1", + "@dynamic-labs-sdk/evm": "^0.24.1", + "@dynamic-labs-sdk/solana": "^0.24.1", "@lifi/sdk": "3.8.11", "@lifi/wallet-management": "3.14.1", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "2.1.16", "@radix-ui/react-slot": "1.2.3", "@tanstack/react-query": "5.84.1", - "@wagmi/core": "^3.0.0", "class-variance-authority": "0.7.1", "clsx": "2.1.1", - "crypto-browserify": "3.12.1", "lucide-react": "0.542.0", "next": "15.4.10", - "next-themes": "0.4.6", - "process": "0.11.10", "react": "19.1.2", "react-dom": "19.1.2", - "stream-browserify": "3.0.0", "tailwind-merge": "3.3.1", - "viem": "2.38.0", - "wagmi": "2.16.1" + "viem": "2.38.0" }, "devDependencies": { "@eslint/eslintrc": "3.3.1", @@ -51,12 +42,5 @@ "postcss": "8.5.6", "tailwindcss": "4.1.12", "typescript": "5.9.2" - }, - "browser": { - "crypto": false - }, - "overrides": { - "rpc-websockets": "7.10.0", - "@solana/web3.js": "1.91.6" } } diff --git a/examples/nextjs-bridge-swaps-lifi/src/app/globals.css b/examples/nextjs-bridge-swaps-lifi/src/app/globals.css index 7332218..868cf35 100644 --- a/examples/nextjs-bridge-swaps-lifi/src/app/globals.css +++ b/examples/nextjs-bridge-swaps-lifi/src/app/globals.css @@ -5,21 +5,6 @@ @theme inline { --color-background: var(--background); --color-foreground: var(--foreground); - --font-sans: var(--font-geist-sans); - --font-mono: var(--font-geist-mono); - --color-sidebar-ring: var(--sidebar-ring); - --color-sidebar-border: var(--sidebar-border); - --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); - --color-sidebar-accent: var(--sidebar-accent); - --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); - --color-sidebar-primary: var(--sidebar-primary); - --color-sidebar-foreground: var(--sidebar-foreground); - --color-sidebar: var(--sidebar); - --color-chart-5: var(--chart-5); - --color-chart-4: var(--chart-4); - --color-chart-3: var(--chart-3); - --color-chart-2: var(--chart-2); - --color-chart-1: var(--chart-1); --color-ring: var(--ring); --color-input: var(--input); --color-border: var(--border); @@ -44,71 +29,24 @@ :root { --radius: 0.625rem; - --background: oklch(1 0 0); - --foreground: oklch(0.129 0.042 264.695); - --card: oklch(1 0 0); - --card-foreground: oklch(0.129 0.042 264.695); - --popover: oklch(1 0 0); - --popover-foreground: oklch(0.129 0.042 264.695); - --primary: oklch(0.208 0.042 265.755); - --primary-foreground: oklch(0.984 0.003 247.858); - --secondary: oklch(0.968 0.007 247.896); - --secondary-foreground: oklch(0.208 0.042 265.755); - --muted: oklch(0.968 0.007 247.896); - --muted-foreground: oklch(0.554 0.046 257.417); - --accent: oklch(0.968 0.007 247.896); - --accent-foreground: oklch(0.208 0.042 265.755); + --background: #f9f9f9; + --foreground: #030303; + --card: #ffffff; + --card-foreground: #030303; + --popover: #ffffff; + --popover-foreground: #030303; + --primary: #4779ff; + --primary-foreground: #ffffff; + --secondary: #f9f9f9; + --secondary-foreground: #030303; + --muted: #f9f9f9; + --muted-foreground: #606060; + --accent: #f9f9f9; + --accent-foreground: #030303; --destructive: oklch(0.577 0.245 27.325); - --border: oklch(0.929 0.013 255.508); - --input: oklch(0.929 0.013 255.508); - --ring: oklch(0.704 0.04 256.788); - --chart-1: oklch(0.646 0.222 41.116); - --chart-2: oklch(0.6 0.118 184.704); - --chart-3: oklch(0.398 0.07 227.392); - --chart-4: oklch(0.828 0.189 84.429); - --chart-5: oklch(0.769 0.188 70.08); - --sidebar: oklch(0.984 0.003 247.858); - --sidebar-foreground: oklch(0.129 0.042 264.695); - --sidebar-primary: oklch(0.208 0.042 265.755); - --sidebar-primary-foreground: oklch(0.984 0.003 247.858); - --sidebar-accent: oklch(0.968 0.007 247.896); - --sidebar-accent-foreground: oklch(0.208 0.042 265.755); - --sidebar-border: oklch(0.929 0.013 255.508); - --sidebar-ring: oklch(0.704 0.04 256.788); -} - -.dark { - --background: oklch(0.129 0.042 264.695); - --foreground: oklch(0.984 0.003 247.858); - --card: oklch(0.208 0.042 265.755); - --card-foreground: oklch(0.984 0.003 247.858); - --popover: oklch(0.208 0.042 265.755); - --popover-foreground: oklch(0.984 0.003 247.858); - --primary: oklch(0.929 0.013 255.508); - --primary-foreground: oklch(0.208 0.042 265.755); - --secondary: oklch(0.279 0.041 260.031); - --secondary-foreground: oklch(0.984 0.003 247.858); - --muted: oklch(0.279 0.041 260.031); - --muted-foreground: oklch(0.704 0.04 256.788); - --accent: oklch(0.279 0.041 260.031); - --accent-foreground: oklch(0.984 0.003 247.858); - --destructive: oklch(0.704 0.191 22.216); - --border: oklch(1 0 0 / 10%); - --input: oklch(1 0 0 / 15%); - --ring: oklch(0.551 0.027 264.364); - --chart-1: oklch(0.488 0.243 264.376); - --chart-2: oklch(0.696 0.17 162.48); - --chart-3: oklch(0.769 0.188 70.08); - --chart-4: oklch(0.627 0.265 303.9); - --chart-5: oklch(0.645 0.246 16.439); - --sidebar: oklch(0.208 0.042 265.755); - --sidebar-foreground: oklch(0.984 0.003 247.858); - --sidebar-primary: oklch(0.488 0.243 264.376); - --sidebar-primary-foreground: oklch(0.984 0.003 247.858); - --sidebar-accent: oklch(0.279 0.041 260.031); - --sidebar-accent-foreground: oklch(0.984 0.003 247.858); - --sidebar-border: oklch(1 0 0 / 10%); - --sidebar-ring: oklch(0.551 0.027 264.364); + --border: #dadada; + --input: #dadada; + --ring: #4779ff; } @layer base { diff --git a/examples/nextjs-bridge-swaps-lifi/src/app/layout.tsx b/examples/nextjs-bridge-swaps-lifi/src/app/layout.tsx index 4d78c45..57d45bc 100644 --- a/examples/nextjs-bridge-swaps-lifi/src/app/layout.tsx +++ b/examples/nextjs-bridge-swaps-lifi/src/app/layout.tsx @@ -1,14 +1,17 @@ import "./globals.css"; import type { Metadata } from "next"; -import { Inter } from "next/font/google"; +import { Roboto } from "next/font/google"; import Providers from "@/lib/providers"; import Header from "@/components/header"; import Footer from "@/components/footer"; -const inter = Inter({ subsets: ["latin"] }); +const roboto = Roboto({ + subsets: ["latin"], + weight: ["300", "400", "500", "700"], +}); export const metadata: Metadata = { - title: "Bridges with Dynamic", + title: "LiFi Cross-Chain Swaps with Dynamic", description: "Bridge tokens across multiple chains seamlessly with Dynamic's MPC wallets and LiFi. Access the best routes and execute cross-chain transfers with confidence.", }; @@ -19,8 +22,8 @@ export default function RootLayout({ children: React.ReactNode; }) { return ( - - + +
{children} diff --git a/examples/nextjs-bridge-swaps-lifi/src/app/page.tsx b/examples/nextjs-bridge-swaps-lifi/src/app/page.tsx index aa8142a..46aa2ec 100644 --- a/examples/nextjs-bridge-swaps-lifi/src/app/page.tsx +++ b/examples/nextjs-bridge-swaps-lifi/src/app/page.tsx @@ -1,12 +1,7 @@ "use client"; -import { PageLayout } from "@/components/ui/page-layout"; import MultiChainSwap from "@/components/MultiChainSwap"; export default function Main() { - return ( - - - - ); + return ; } diff --git a/examples/nextjs-bridge-swaps-lifi/src/components/ActionButtons.tsx b/examples/nextjs-bridge-swaps-lifi/src/components/ActionButtons.tsx index ef3f70a..3c5509f 100644 --- a/examples/nextjs-bridge-swaps-lifi/src/components/ActionButtons.tsx +++ b/examples/nextjs-bridge-swaps-lifi/src/components/ActionButtons.tsx @@ -1,7 +1,6 @@ "use client"; import { Button } from "@/components/ui/button"; -import { useDynamicContext } from "@dynamic-labs/sdk-react-core"; import { Loader2, ArrowLeft } from "lucide-react"; interface ActionButtonsProps { @@ -11,6 +10,7 @@ interface ActionButtonsProps { hasSelectedRoute: boolean; showRouteDisplay: boolean; hasActiveRoute: boolean; + isConnected: boolean; onGetRoutes: () => void; onExecuteSwap: () => void; onClear: () => void; @@ -25,16 +25,14 @@ export default function ActionButtons({ hasSelectedRoute, showRouteDisplay, hasActiveRoute, + isConnected, onGetRoutes, onExecuteSwap, onClear, onBackToForm, onShowExecutionDisplay, }: ActionButtonsProps) { - const { primaryWallet, setShowAuthFlow } = useDynamicContext(); - if (showRouteDisplay) { - // Route display view - show Execute Swap, View Execution (if active), and Back buttons return (
@@ -89,19 +87,12 @@ export default function ActionButtons({ ); } - // Form view - show Get Routes, View Execution (if active), and Clear buttons return (
-
onAmountChange(e.target.value)} placeholder="0.00" - className="w-full text-2xl font-semibold bg-transparent border-none outline-none" + className="w-full text-2xl font-semibold bg-transparent border-none outline-none text-[#030303]" /> - -
- -
- - {fromToken && ( -
- Balance:{" "} - {isLoadingFromBalances - ? "Loading..." - : `${fromTokenBalance} ${fromToken.symbol}`} -
- )}
{/* Swap Direction Button */}
{/* To Section */} -
+
- To + To
-
0.00
- -
- -
- - {toToken && ( -
- Balance:{" "} - {isLoadingToBalances - ? "Loading..." - : `${toTokenBalance} ${toToken.symbol}`} -
- )} +
0.00
{/* Token Selection Modal */} - + Select a token
-

+

Available chains

@@ -324,8 +222,8 @@ export default function SwapForm({ className={cn( "flex-shrink-0 w-10 h-10 rounded-full flex items-center justify-center text-lg border cursor-pointer", currentChain?.id === chain.id - ? "border-primary bg-primary/10" - : "border-border bg-background" + ? "border-[#4779FF] bg-[#4779FF]/10" + : "border-[#DADADA] bg-white" )} > setSearchQuery(e.target.value)} - className="w-full pl-4 pr-4 py-3 border border-input rounded-xl focus:ring-2 focus:ring-ring focus:border-ring bg-background" + className="w-full pl-4 pr-4 py-3 border border-[#DADADA] rounded-xl focus:ring-2 focus:ring-[#4779FF] focus:border-[#4779FF] bg-white text-[#030303] outline-none" />
@@ -355,9 +253,9 @@ export default function SwapForm({ key={token.address} onClick={() => handleTokenSelect(token)} className={cn( - "w-full flex items-center space-x-3 p-3 rounded-xl transition-colors hover:bg-accent hover:text-accent-foreground border border-transparent cursor-pointer", + "w-full flex items-center space-x-3 p-3 rounded-xl transition-colors hover:bg-[#F9F9F9] border border-transparent cursor-pointer", currentToken?.address === token.address && - "bg-primary/10 border-primary" + "bg-[#4779FF]/10 border-[#4779FF]" )} > {token.logoURI ? ( @@ -373,13 +271,13 @@ export default function SwapForm({ 🪙 )}
-
{token.name}
-
+
{token.name}
+
{token.symbol}
{currentToken?.address === token.address && ( -
+
)} ))} diff --git a/examples/nextjs-bridge-swaps-lifi/src/components/dynamic/dynamic-button.tsx b/examples/nextjs-bridge-swaps-lifi/src/components/dynamic/dynamic-button.tsx index 97a5f74..32497f6 100644 --- a/examples/nextjs-bridge-swaps-lifi/src/components/dynamic/dynamic-button.tsx +++ b/examples/nextjs-bridge-swaps-lifi/src/components/dynamic/dynamic-button.tsx @@ -1,29 +1,421 @@ "use client"; +import { useState, useCallback, useRef, useEffect } from "react"; import { - DynamicConnectButton, - useDynamicContext, - useIsLoggedIn, -} from "@dynamic-labs/sdk-react-core"; -import { Button } from "../ui/button"; + getAvailableWalletProvidersData, + connectAndVerifyWithWalletProvider, + sendEmailOTP, + verifyOTP, + authenticateWithSocial, + type WalletProviderData, + type OTPVerification, +} from "@dynamic-labs-sdk/client"; +import { exportWaasPrivateKey } from "@dynamic-labs-sdk/client/waas"; +import { useWallet } from "@/lib/providers"; +import { dynamicClient } from "@/lib/dynamic"; +import { KeyRound } from "lucide-react"; + +function shortenAddress(address: string): string { + return `${address.slice(0, 6)}...${address.slice(-4)}`; +} + +function GoogleIcon() { + return ( + + + + + + + ); +} + +function EmailIcon() { + return ( + + + + + ); +} + +function WalletIcon() { + return ( + + + + + + ); +} + +const primaryBtn = + "w-full flex items-center gap-3 px-4 py-2.5 rounded-lg text-sm font-medium transition-colors bg-[#4779FF] text-white hover:bg-[#3366ee] disabled:opacity-50 disabled:cursor-not-allowed"; +const outlineBtn = + "w-full flex items-center gap-3 px-4 py-2.5 rounded-lg text-sm font-medium transition-colors bg-white border border-[#DADADA] text-[#030303] hover:bg-[#F9F9F9] disabled:opacity-50 disabled:cursor-not-allowed"; export default function DynamicButton() { - const isLoggedIn = useIsLoggedIn(); - const { setShowDynamicUserProfile } = useDynamicContext(); + const { evmAccount, loggedIn, disconnect, ensureWallets } = useWallet(); + const [open, setOpen] = useState(false); + const [view, setView] = useState<"menu" | "email" | "otp" | "wallet" | "export">("menu"); + const [email, setEmail] = useState(""); + const [otp, setOtp] = useState(""); + const [otpVerification, setOtpVerification] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const panelRef = useRef(null); + const exportContainerRef = useRef(null); + const [exportPassword, setExportPassword] = useState(""); + const [exportRevealed, setExportRevealed] = useState(false); + + useEffect(() => { + if (!open) return; + function handler(e: MouseEvent) { + if (panelRef.current && !panelRef.current.contains(e.target as Node)) { + setOpen(false); + setView("menu"); + setError(null); + } + } + document.addEventListener("mousedown", handler); + return () => document.removeEventListener("mousedown", handler); + }, [open]); + + const reset = () => { + setView("menu"); + setEmail(""); + setOtp(""); + setOtpVerification(null); + setError(null); + setLoading(false); + setExportPassword(""); + setExportRevealed(false); + }; + + const handleExportKey = useCallback(async () => { + if (!exportContainerRef.current || !evmAccount) return; + setLoading(true); + setError(null); + try { + await exportWaasPrivateKey( + { displayContainer: exportContainerRef.current, password: exportPassword, walletAccount: evmAccount }, + dynamicClient + ); + setExportRevealed(true); + } catch (err) { + setError(err instanceof Error ? err.message : "Export failed"); + } finally { + setLoading(false); + } + }, [exportPassword, evmAccount]); + + const handleSendOTP = useCallback(async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + setError(null); + try { + const verification = await sendEmailOTP({ email }, dynamicClient); + setOtpVerification(verification); + setView("otp"); + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to send OTP"); + } finally { + setLoading(false); + } + }, [email]); - if (isLoggedIn) { + const handleVerifyOTP = useCallback(async (e: React.FormEvent) => { + e.preventDefault(); + if (!otpVerification) return; + setLoading(true); + setError(null); + try { + await verifyOTP({ otpVerification, verificationToken: otp }, dynamicClient); + await ensureWallets(); + setOpen(false); + reset(); + } catch (err) { + const msg = err instanceof Error ? err.message : "Invalid code"; + if (msg.toLowerCase().includes("unauthorized")) { + setError("Verification failed. Please request a new code and try again."); + } else { + setError(msg); + } + } finally { + setLoading(false); + } + }, [otpVerification, otp, ensureWallets]); + + const handleGoogle = useCallback(async () => { + setLoading(true); + setError(null); + try { + await authenticateWithSocial( + { + provider: "google", + redirectUrl: typeof window !== "undefined" ? window.location.href : "", + }, + dynamicClient + ); + } catch (err) { + setError(err instanceof Error ? err.message : "Google sign-in failed"); + setLoading(false); + } + }, []); + + const getAllProviders = (): WalletProviderData[] => + getAvailableWalletProvidersData(dynamicClient).filter( + (p) => p.chain === "EVM" || p.chain === "SOL" + ); + + const handleConnectWallet = useCallback(async (providerKey: string) => { + setLoading(true); + setError(null); + try { + await connectAndVerifyWithWalletProvider({ walletProviderKey: providerKey }, dynamicClient); + await ensureWallets(); + setOpen(false); + reset(); + } catch (err) { + setError(err instanceof Error ? err.message : "Connection failed"); + } finally { + setLoading(false); + } + }, [ensureWallets]); + + const displayAccount = evmAccount; + + if (loggedIn && displayAccount) { return ( - +
+ + + {open && ( +
+ {view !== "export" && ( + <> +
+

Connected

+

+ {displayAccount.address} +

+
+
+ +
+ + + )} + + {view === "export" && ( +
+ +
+ +

Export Private Key

+
+ {!exportRevealed && ( + <> + setExportPassword(e.target.value)} + placeholder="Enter your password" + className="w-full text-sm rounded-lg px-3 py-2 outline-none focus:ring-2 focus:ring-[#4779FF]" + style={{ border: "1px solid #DADADA", background: "#F9F9F9" }} + /> + {error &&

{error}

} + + + )} +
+
+ )} +
+ )} +
); } + return ( - - Log in or sign up - +
+ + + {open && ( +
+ {view === "menu" && ( +
+

Sign in to LiFi Swaps

+ + + + + + {getAllProviders().length > 0 && ( + <> +
+
+ or +
+
+ + + )} + + {error &&

{error}

} +
+ )} + + {view === "email" && ( +
+ +

Enter your email

+ setEmail(e.target.value)} + placeholder="you@example.com" + required + className="w-full px-3 py-2 text-sm border border-[#DADADA] rounded-lg outline-none focus:border-[#4779FF] focus:ring-1 focus:ring-[#4779FF]/30 text-[#030303]" + /> + {error &&

{error}

} + +
+ )} + + {view === "otp" && ( +
+ +

Enter the code

+

Sent to {email}

+ setOtp(e.target.value)} + placeholder="6-digit code" + required + maxLength={6} + className="w-full px-3 py-2 text-sm border border-[#DADADA] rounded-lg outline-none focus:border-[#4779FF] focus:ring-1 focus:ring-[#4779FF]/30 text-[#030303] tracking-widest text-center" + /> + {error &&

{error}

} + +
+ )} + + {view === "wallet" && ( +
+ +

Choose a wallet

+ {getAllProviders().length === 0 ? ( +

No wallets detected. Install MetaMask, Phantom, or another wallet.

+ ) : ( + getAllProviders().map((provider) => ( + + )) + )} + {error &&

{error}

} +
+ )} +
+ )} +
); } diff --git a/examples/nextjs-bridge-swaps-lifi/src/components/dynamic/logo.tsx b/examples/nextjs-bridge-swaps-lifi/src/components/dynamic/logo.tsx index 4f45d15..e03fe3a 100644 --- a/examples/nextjs-bridge-swaps-lifi/src/components/dynamic/logo.tsx +++ b/examples/nextjs-bridge-swaps-lifi/src/components/dynamic/logo.tsx @@ -7,7 +7,7 @@ interface DynamicLogoProps { export default function DynamicLogo({ width = 150, height = 30, - className = "text-[#141839] dark:text-white", + className = "text-[#141839]", }: DynamicLogoProps) { return ( +
-
+
- powered by - + Powered by +
); } diff --git a/examples/nextjs-bridge-swaps-lifi/src/components/mode-toggle.tsx b/examples/nextjs-bridge-swaps-lifi/src/components/mode-toggle.tsx deleted file mode 100644 index 2d4bd08..0000000 --- a/examples/nextjs-bridge-swaps-lifi/src/components/mode-toggle.tsx +++ /dev/null @@ -1,40 +0,0 @@ -"use client"; - -import * as React from "react"; -import { Moon, Sun } from "lucide-react"; -import { useTheme } from "next-themes"; - -import { Button } from "@/components/ui/button"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; - -export function ModeToggle() { - const { setTheme } = useTheme(); - - return ( - - - - - - setTheme("light")}> - Light - - setTheme("dark")}> - Dark - - setTheme("system")}> - System - - - - ); -} diff --git a/examples/nextjs-bridge-swaps-lifi/src/components/theme-provider.tsx b/examples/nextjs-bridge-swaps-lifi/src/components/theme-provider.tsx deleted file mode 100644 index 189a2b1..0000000 --- a/examples/nextjs-bridge-swaps-lifi/src/components/theme-provider.tsx +++ /dev/null @@ -1,11 +0,0 @@ -"use client"; - -import * as React from "react"; -import { ThemeProvider as NextThemesProvider } from "next-themes"; - -export function ThemeProvider({ - children, - ...props -}: React.ComponentProps) { - return {children}; -} diff --git a/examples/nextjs-bridge-swaps-lifi/src/lib/dynamic.ts b/examples/nextjs-bridge-swaps-lifi/src/lib/dynamic.ts index fa58ecd..3bf4c54 100644 --- a/examples/nextjs-bridge-swaps-lifi/src/lib/dynamic.ts +++ b/examples/nextjs-bridge-swaps-lifi/src/lib/dynamic.ts @@ -1,8 +1,11 @@ -export * from "@dynamic-labs/sdk-react-core"; -export * from "@dynamic-labs/ethereum"; -export * from "@dynamic-labs/solana"; -export { - ZeroDevSmartWalletConnectors, - isZeroDevConnector, -} from "@dynamic-labs/ethereum-aa"; -export { DynamicWagmiConnector } from "@dynamic-labs/wagmi-connector"; +import { createDynamicClient } from "@dynamic-labs-sdk/client"; +import { addEvmExtension } from "@dynamic-labs-sdk/evm"; +import { addSolanaExtension } from "@dynamic-labs-sdk/solana"; + +export const dynamicClient = createDynamicClient({ + environmentId: process.env.NEXT_PUBLIC_DYNAMIC_ENV_ID!, + metadata: { name: "LiFi Cross-Chain Swaps" }, +}); + +addEvmExtension(); +addSolanaExtension(); diff --git a/examples/nextjs-bridge-swaps-lifi/src/lib/lifi-provider.tsx b/examples/nextjs-bridge-swaps-lifi/src/lib/lifi-provider.tsx index ced0e60..8a72450 100644 --- a/examples/nextjs-bridge-swaps-lifi/src/lib/lifi-provider.tsx +++ b/examples/nextjs-bridge-swaps-lifi/src/lib/lifi-provider.tsx @@ -1,27 +1,13 @@ "use client"; import { config as lifiConfig } from "@lifi/sdk"; -import { useSyncWagmiConfig } from "@lifi/wallet-management"; import { useQuery } from "@tanstack/react-query"; import { type FC, type PropsWithChildren, useEffect, useState } from "react"; -import type { Config, CreateConnectorFn } from "wagmi"; import { initializeLiFiConfig, loadLiFiChains } from "./lifi"; -import { useDynamicContext } from "@dynamic-labs/sdk-react-core"; +import { useWallet } from "./providers"; -type LiFiConfigParam = Parameters[0]; -type SyncConfigParam = Parameters[0]; - -interface LiFiProviderProps extends PropsWithChildren { - wagmiConfig: Config; - connectors: CreateConnectorFn[]; -} - -export const LiFiProvider: FC = ({ - children, - wagmiConfig, - connectors, -}) => { - const { sdkHasLoaded } = useDynamicContext(); +export const LiFiProvider: FC = ({ children }) => { + const { evmAccount, loggedIn } = useWallet(); const [isInitialized, setIsInitialized] = useState(false); const { @@ -32,7 +18,6 @@ export const LiFiProvider: FC = ({ queryKey: ["lifi-chains"] as const, queryFn: async () => { const chains = await loadLiFiChains(); - if (chains.length > 0) { lifiConfig.setChains(chains); } @@ -42,30 +27,35 @@ export const LiFiProvider: FC = ({ gcTime: 10 * 60 * 1000, retry: 3, retryDelay: 1000, - enabled: sdkHasLoaded, + enabled: loggedIn, }); useEffect(() => { - if (sdkHasLoaded && !isInitialized) { + if (loggedIn && !isInitialized) { try { - initializeLiFiConfig(wagmiConfig as unknown as LiFiConfigParam); + initializeLiFiConfig(() => evmAccount); setIsInitialized(true); } catch { setIsInitialized(false); } } - }, [sdkHasLoaded, wagmiConfig, isInitialized]); + }, [loggedIn, evmAccount, isInitialized]); - useSyncWagmiConfig( - wagmiConfig as unknown as SyncConfigParam, - connectors, - chains - ); + // Re-initialize when the account changes so the wallet client getter is fresh + useEffect(() => { + if (isInitialized && evmAccount) { + try { + initializeLiFiConfig(() => evmAccount); + } catch { + // ignore + } + } + }, [evmAccount, isInitialized]); - if (chainsLoading || !sdkHasLoaded || !isInitialized) { + if (chainsLoading || !loggedIn || !isInitialized) { return (
- {!sdkHasLoaded + {!loggedIn ? "Loading Dynamic SDK..." : chainsLoading ? "Loading LiFi chains..." @@ -82,5 +72,6 @@ export const LiFiProvider: FC = ({ ); } + void chains; return <>{children}; }; diff --git a/examples/nextjs-bridge-swaps-lifi/src/lib/lifi.ts b/examples/nextjs-bridge-swaps-lifi/src/lib/lifi.ts index 5a4bc71..b688a7c 100644 --- a/examples/nextjs-bridge-swaps-lifi/src/lib/lifi.ts +++ b/examples/nextjs-bridge-swaps-lifi/src/lib/lifi.ts @@ -1,15 +1,25 @@ import { ChainType, EVM, createConfig, getChains } from "@lifi/sdk"; -import { getWalletClient, switchChain, type Config } from "@wagmi/core"; +import type { EvmWalletAccount } from "@dynamic-labs-sdk/evm"; +import { createWalletClientForWalletAccount } from "@dynamic-labs-sdk/evm/viem"; -export const initializeLiFiConfig = (wagmiConfig: Config) => { +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type AnyWalletClient = any; + +export const initializeLiFiConfig = (getEvmAccount: () => EvmWalletAccount | null) => { return createConfig({ integrator: "Dynamic", providers: [ EVM({ - getWalletClient: () => getWalletClient(wagmiConfig), - switchChain: async (chainId: number) => { - const chain = await switchChain(wagmiConfig, { chainId }); - return getWalletClient(wagmiConfig, { chainId: chain.id }); + getWalletClient: async (): Promise => { + const account = getEvmAccount(); + if (!account) throw new Error("No EVM wallet connected"); + return createWalletClientForWalletAccount({ walletAccount: account }); + }, + switchChain: async (_chainId: number): Promise => { + // Chain switching is handled per-transaction by the embedded wallet + const account = getEvmAccount(); + if (!account) throw new Error("No EVM wallet connected"); + return createWalletClientForWalletAccount({ walletAccount: account }); }, }), ], diff --git a/examples/nextjs-bridge-swaps-lifi/src/lib/providers.tsx b/examples/nextjs-bridge-swaps-lifi/src/lib/providers.tsx index 5a4fb78..a6d8077 100644 --- a/examples/nextjs-bridge-swaps-lifi/src/lib/providers.tsx +++ b/examples/nextjs-bridge-swaps-lifi/src/lib/providers.tsx @@ -1,53 +1,139 @@ "use client"; -import { LiFiProvider } from "@/lib/lifi-provider"; -import { config } from "@/lib/wagmi"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { WagmiProvider } from "wagmi"; -import type { CreateConnectorFn } from "wagmi"; -import { ThemeProvider } from "@/components/theme-provider"; import { - DynamicContextProvider, - EthereumWalletConnectors, - ZeroDevSmartWalletConnectors, - DynamicWagmiConnector, -} from "@/lib/dynamic"; + 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 { + isSolanaWalletAccount, + type SolanaWalletAccount, +} from "@dynamic-labs-sdk/solana"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { dynamicClient } from "./dynamic"; + +interface WalletContextValue { + evmAccount: EvmWalletAccount | null; + solanaAccount: SolanaWalletAccount | null; + loggedIn: boolean; + ensureWallets: () => Promise; + disconnect: () => Promise; +} + +const WalletContext = createContext({ + evmAccount: null, + solanaAccount: null, + loggedIn: false, + ensureWallets: 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(null); + const [solanaAccount, setSolanaAccount] = useState(null); + const [loggedIn, setLoggedIn] = useState(false); + + const refresh = useCallback(() => { + const accounts = getWalletAccounts(dynamicClient); + setEvmAccount(accounts.find(isEvmWalletAccount) ?? null); + setSolanaAccount(accounts.find(isSolanaWalletAccount) ?? null); + setLoggedIn(isSignedIn(dynamicClient)); + }, []); + + const disconnect = useCallback(async () => { + await logout(dynamicClient); + setEvmAccount(null); + setSolanaAccount(null); + setLoggedIn(false); + }, []); + + const ensureWallets = useCallback(async () => { + try { + const accounts = getWalletAccounts(dynamicClient); + if (!accounts.some(isEvmWalletAccount) && isSignedIn(dynamicClient)) { + await createWaasWalletAccounts({ chains: ["EVM", "SOL"] }, dynamicClient); + } + } catch { + // wallet may already exist — ignore + } + refresh(); + }, [refresh]); + + useEffect(() => { + const handleOAuthRedirect = async () => { + if (typeof window === "undefined") return; + try { + const url = new URL(window.location.href); + if (await detectOAuthRedirect({ url }, dynamicClient)) { + await completeSocialAuthentication({ url }, dynamicClient); + await ensureWallets(); + window.history.replaceState({}, "", window.location.pathname); + return; + } + } catch { + // not an OAuth redirect — continue normally + } + refresh(); + }; + + handleOAuthRedirect(); -export default function Providers({ children }: { children: React.ReactNode }) { - const queryClient = new QueryClient(); + const unsub1 = onEvent( + { event: "walletAccountsChanged", listener: () => ensureWallets() }, + dynamicClient + ); + const unsub2 = onEvent( + { + event: "logout", + listener: () => { + setEvmAccount(null); + setSolanaAccount(null); + setLoggedIn(false); + }, + }, + dynamicClient + ); - const connectors: CreateConnectorFn[] = []; + return () => { + unsub1(); + unsub2(); + }; + }, [refresh, ensureWallets]); return ( - - - - - - - {children} - - - - - - + {children} + ); } diff --git a/examples/nextjs-bridge-swaps-lifi/src/lib/wagmi.ts b/examples/nextjs-bridge-swaps-lifi/src/lib/wagmi.ts deleted file mode 100644 index c425296..0000000 --- a/examples/nextjs-bridge-swaps-lifi/src/lib/wagmi.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { createConfig, http } from "wagmi"; -import { - arbitrum, - avalanche, - base, - mainnet, - optimism, - polygon, -} from "wagmi/chains"; - -const chains = [mainnet, polygon, arbitrum, optimism, base, avalanche] as const; - -export const config = createConfig({ - chains, - multiInjectedProviderDiscovery: false, - ssr: true, - transports: { - [mainnet.id]: http(), - [polygon.id]: http(), - [arbitrum.id]: http(), - [optimism.id]: http(), - [base.id]: http(), - [avalanche.id]: http(), - }, -}); - -declare module "wagmi" { - interface Register { - config: typeof config; - } -} From f18fbba9ab65882230f25b173858b119c8c5f969 Mon Sep 17 00:00:00 2001 From: Avneesh Agarwal Date: Mon, 4 May 2026 21:36:40 +1000 Subject: [PATCH 2/8] feat(yield): migrate aave, morpho, pods examples to JS SDK Converts nextjs-stablecoin-yield-aave, nextjs-defi-lending-morpho, and nextjs-stablecoin-yield-pods from React SDK (@dynamic-labs/sdk-react-core, @dynamic-labs/wagmi-connector) to the JS SDK (@dynamic-labs-sdk/client ^0.24.1 + @dynamic-labs-sdk/evm ^0.24.1). - Removes DynamicContextProvider, DynamicWagmiConnector, WagmiProvider, ThemeProvider and all wagmi hooks (useAccount, useChainId, useWriteContract, useSwitchChain, useReadContract, useReadContracts) - Adds createDynamicClient + addEvmExtension pattern in src/lib/dynamic.ts - Adds custom WalletContext with useWallet() hook in src/lib/providers.tsx - Replaces wagmi contract hooks with direct viem publicClient reads and createWalletClientForWalletAccount for writes - Applies standard visual theme (Roboto font, #4779FF palette, custom DynamicButton with Google/Email/Wallet auth flows) - Removes theme-provider.tsx, mode-toggle.tsx, hamburger-menu.tsx, wagmi.ts Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .../nextjs-defi-lending-morpho/package.json | 10 +- .../src/app/earn/page.tsx | 6 +- .../src/app/globals.css | 2 - .../src/app/layout.tsx | 18 +- .../src/components/HamburgerMenu.tsx | 64 +-- .../src/components/MarketCard.tsx | 6 +- .../src/components/Navigation.tsx | 6 +- .../src/components/PositionCard.tsx | 64 ++- .../src/components/VaultCard.tsx | 17 +- .../src/components/dynamic/DynamicButton.tsx | 297 +++++++++++- .../src/components/footer.tsx | 54 +-- .../src/components/mode-toggle.tsx | 40 -- .../src/components/theme-provider.tsx | 11 - .../src/lib/dynamic.ts | 9 + .../src/lib/hooks/useMarketOperations.ts | 231 +++++----- .../src/lib/hooks/useMarketsData.ts | 12 +- .../src/lib/hooks/useMarketsList.ts | 4 +- .../src/lib/hooks/useMarketsOperations.ts | 231 ++++++---- .../src/lib/hooks/useNetwork.ts | 19 +- .../src/lib/hooks/useRewardsOperations.ts | 39 +- .../src/lib/hooks/useVaultDetail.ts | 4 +- .../src/lib/hooks/useVaultOperations.ts | 270 ++++++----- .../src/lib/hooks/useVaultPositions.ts | 124 +++-- .../src/lib/hooks/useVaultsList.ts | 4 +- .../src/lib/networks.ts | 37 +- .../src/lib/providers.tsx | 225 ++++----- .../src/lib/wagmi.ts | 22 - .../nextjs-stablecoin-yield-aave/package.json | 8 +- .../src/app/globals.css | 2 - .../src/app/layout.tsx | 18 +- .../src/components/MarketsInterface.tsx | 71 ++- .../src/components/dynamic/dynamic-button.tsx | 354 +++++++++++++- .../src/components/footer.tsx | 54 +-- .../src/components/hamburger-menu.tsx | 93 ---- .../src/components/header.tsx | 18 +- .../src/components/mode-toggle.tsx | 40 -- .../src/components/theme-provider.tsx | 11 - .../src/lib/dynamic.ts | 9 + .../src/lib/providers.tsx | 171 ++++--- .../src/lib/wagmi.ts | 19 - .../nextjs-stablecoin-yield-pods/package.json | 15 +- .../src/app/globals.css | 15 +- .../src/app/layout.tsx | 20 +- .../src/app/page.tsx | 6 +- .../src/components/YieldInterface.tsx | 430 ++++++++---------- .../src/components/dynamic/dynamic-button.tsx | 297 +++++++++++- .../src/components/footer.tsx | 54 +-- .../src/components/hamburger-menu.tsx | 93 ---- .../src/components/header.tsx | 33 +- .../src/components/mode-toggle.tsx | 40 -- .../src/components/theme-provider.tsx | 11 - .../src/lib/dynamic.ts | 9 + .../src/lib/providers.tsx | 140 ++++-- .../src/lib/useTransactionOperations.ts | 101 ++-- .../src/lib/wagmi.ts | 20 - 55 files changed, 2217 insertions(+), 1761 deletions(-) delete mode 100644 examples/nextjs-defi-lending-morpho/src/components/mode-toggle.tsx delete mode 100644 examples/nextjs-defi-lending-morpho/src/components/theme-provider.tsx create mode 100644 examples/nextjs-defi-lending-morpho/src/lib/dynamic.ts delete mode 100644 examples/nextjs-defi-lending-morpho/src/lib/wagmi.ts delete mode 100644 examples/nextjs-stablecoin-yield-aave/src/components/hamburger-menu.tsx delete mode 100644 examples/nextjs-stablecoin-yield-aave/src/components/mode-toggle.tsx delete mode 100644 examples/nextjs-stablecoin-yield-aave/src/components/theme-provider.tsx create mode 100644 examples/nextjs-stablecoin-yield-aave/src/lib/dynamic.ts delete mode 100644 examples/nextjs-stablecoin-yield-aave/src/lib/wagmi.ts delete mode 100644 examples/nextjs-stablecoin-yield-pods/src/components/hamburger-menu.tsx delete mode 100644 examples/nextjs-stablecoin-yield-pods/src/components/mode-toggle.tsx delete mode 100644 examples/nextjs-stablecoin-yield-pods/src/components/theme-provider.tsx create mode 100644 examples/nextjs-stablecoin-yield-pods/src/lib/dynamic.ts delete mode 100644 examples/nextjs-stablecoin-yield-pods/src/lib/wagmi.ts diff --git a/examples/nextjs-defi-lending-morpho/package.json b/examples/nextjs-defi-lending-morpho/package.json index 2ee4909..20b8aef 100644 --- a/examples/nextjs-defi-lending-morpho/package.json +++ b/examples/nextjs-defi-lending-morpho/package.json @@ -10,10 +10,8 @@ }, "dependencies": { "@coinbase/onchainkit": "0.38.17", - "@dynamic-labs/ethereum": "4.48.2", - "@dynamic-labs/global-wallet": "4.48.2", - "@dynamic-labs/sdk-react-core": "4.48.2", - "@dynamic-labs/wagmi-connector": "4.48.2", + "@dynamic-labs-sdk/client": "^0.24.1", + "@dynamic-labs-sdk/evm": "^0.24.1", "@radix-ui/react-dialog": "1.1.15", "@radix-ui/react-dropdown-menu": "2.1.16", "@radix-ui/react-slot": "1.2.3", @@ -23,15 +21,13 @@ "crypto-browserify": "3.12.1", "lucide-react": "0.542.0", "next": "15.4.10", - "next-themes": "0.4.6", "postcss": "8.5.6", "process": "0.11.10", "react": "19.1.2", "react-dom": "19.1.2", "stream-browserify": "3.0.0", "tailwind-merge": "3.3.1", - "viem": "2.38.0", - "wagmi": "2.16.0" + "viem": "2.38.0" }, "devDependencies": { "@eslint/eslintrc": "3.3.1", diff --git a/examples/nextjs-defi-lending-morpho/src/app/earn/page.tsx b/examples/nextjs-defi-lending-morpho/src/app/earn/page.tsx index df12e39..02f664f 100644 --- a/examples/nextjs-defi-lending-morpho/src/app/earn/page.tsx +++ b/examples/nextjs-defi-lending-morpho/src/app/earn/page.tsx @@ -2,16 +2,18 @@ import { useState } from "react"; import { ChevronLeft, ChevronRight } from "lucide-react"; -import { useAccount } from "wagmi"; import { useVaultsList, useVaultPositions } from "../../lib/hooks"; import { VaultCard } from "@/components/VaultCard"; import { PositionCard } from "@/components/PositionCard"; +import { useWallet } from "@/lib/providers"; const VAULTS_PER_PAGE = 6; export default function EarnPage() { const [page, setPage] = useState(0); - const { address, isConnected } = useAccount(); + const { evmAccount, loggedIn } = useWallet(); + const address = evmAccount?.address; + const isConnected = loggedIn && !!evmAccount; const { vaults, loading, error } = useVaultsList("tvl-desc"); const { positions, loading: positionsLoading } = useVaultPositions(address, vaults); diff --git a/examples/nextjs-defi-lending-morpho/src/app/globals.css b/examples/nextjs-defi-lending-morpho/src/app/globals.css index 4477549..bbf2630 100644 --- a/examples/nextjs-defi-lending-morpho/src/app/globals.css +++ b/examples/nextjs-defi-lending-morpho/src/app/globals.css @@ -1,7 +1,5 @@ @import "tailwindcss"; -@custom-variant dark (&:is(.dark *)); - @theme inline { /* Earn Dashboard color tokens */ --color-earn-primary: #4779FF; diff --git a/examples/nextjs-defi-lending-morpho/src/app/layout.tsx b/examples/nextjs-defi-lending-morpho/src/app/layout.tsx index f3527b1..296c068 100644 --- a/examples/nextjs-defi-lending-morpho/src/app/layout.tsx +++ b/examples/nextjs-defi-lending-morpho/src/app/layout.tsx @@ -6,7 +6,11 @@ import Footer from "@/components/footer"; import "./globals.css"; -const roboto = Roboto({ subsets: ["latin"], weight: ["400", "500", "700"], variable: "--font-roboto" }); +const roboto = Roboto({ + subsets: ["latin"], + weight: ["300", "400", "500", "700"], + variable: "--font-roboto", +}); export const metadata: Metadata = { title: "DeFi Lending & Borrowing with Dynamic", @@ -19,12 +23,14 @@ export default function RootLayout({ children: React.ReactNode; }) { return ( - - + + - -
{children}
-