);
}
function Section({ title, children }: { title: string; children: React.ReactNode }) {
- return (
-
-
Status
-
- {tx.status}
-
+ {tx.quote?.fee?.total_fee?.amount &&
|
}
+
+ Status
+ {tx.status}
- {tx.created_at && (
-
|
- )}
+ {tx.created_at &&
|
}
-
{(rail || tx.recipient) && (
{rail && (
@@ -1167,38 +577,15 @@ function TxDetail({
{rail.beneficiary_name &&
}
{rail.account_holder_name &&
}
{rail.address &&
}
- {rail.bank_address &&
}
- {rail.account_holder_address &&
}
)}
-
{tx.recipient && (
{tx.recipient.type &&
}
- {typeof tx.recipient.address === "string" && (
-
- )}
+ {typeof tx.recipient.address === "string" &&
}
{tx.recipient.chain &&
}
- {tx.recipient.account_identifier?.iban && (
-
- )}
+ {tx.recipient.account_identifier?.iban &&
}
{tx.recipient.provider_name &&
}
- {tx.recipient.address && typeof tx.recipient.address === "object" && (
-
- )}
)}
diff --git a/examples/nextjs-iron-ramp/src/components/dynamic/dynamic-button.tsx b/examples/nextjs-iron-ramp/src/components/dynamic/dynamic-button.tsx
index 4c7634c..0418d06 100644
--- a/examples/nextjs-iron-ramp/src/components/dynamic/dynamic-button.tsx
+++ b/examples/nextjs-iron-ramp/src/components/dynamic/dynamic-button.tsx
@@ -1,26 +1,311 @@
"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 GoogleIcon() {
+ return (
+
+ );
+}
+
+function EmailIcon() {
+ return (
+
+ );
+}
+
+function WalletIcon() {
+ return (
+
+ );
+}
+
+function shortenAddress(addr: string): string {
+ if (!addr || addr.length < 10) return addr;
+ return `${addr.slice(0, 6)}...${addr.slice(-4)}`;
+}
+
+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, 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]);
- if (isLoggedIn) {
+ 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" && (
+
+
+
+ {!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 Iron Ramp
+
+
+ {getEvmProviders().length > 0 && (
+ <>
+
+
+ >
+ )}
+ {error &&
{error}
}
+
+ )}
+ {view === "email" && (
+
+ )}
+ {view === "otp" && (
+
+ )}
+ {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-iron-ramp/src/components/footer.tsx b/examples/nextjs-iron-ramp/src/components/footer.tsx
index 7fc87b8..cd84a6b 100644
--- a/examples/nextjs-iron-ramp/src/components/footer.tsx
+++ b/examples/nextjs-iron-ramp/src/components/footer.tsx
@@ -1,43 +1,16 @@
-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/euro-ramp",
- },
- { text: "Docs", url: "https://docs.dynamic.xyz" },
- { text: "Dashboard", url: "https://app.dynamic.xyz" },
- { text: "Support", url: "https://www.dynamic.xyz/join-slack" },
- ],
-}: FooterProps) {
+export default function Footer() {
return (
-