@@ -112,12 +111,12 @@ export default async function Page(props: {
-
+
);
}
return (
-
+
-
- );
-}
-
-function Providers({
- children,
- theme,
-}: {
- children: React.ReactNode;
- theme: string;
-}) {
- if (!NEXT_PUBLIC_CHECKOUT_IFRAME_CLIENT_ID) {
- throw new Error("NEXT_PUBLIC_CHECKOUT_IFRAME_CLIENT_ID is not set");
- }
- return (
-
- {children}
-
+
);
}
diff --git a/apps/dashboard/src/app/bridge/swap-widget/SwapWidgetEmbed.client.tsx b/apps/dashboard/src/app/bridge/swap-widget/SwapWidgetEmbed.client.tsx
new file mode 100644
index 00000000000..a8836d29550
--- /dev/null
+++ b/apps/dashboard/src/app/bridge/swap-widget/SwapWidgetEmbed.client.tsx
@@ -0,0 +1,104 @@
+"use client";
+
+import { useMemo } from "react";
+import type { Address } from "thirdweb";
+import { type SupportedFiatCurrency, SwapWidget } from "thirdweb/react";
+import { NEXT_PUBLIC_SWAP_IFRAME_CLIENT_ID } from "@/constants/public-envs";
+import { getConfiguredThirdwebClient } from "@/constants/thirdweb.server";
+
+export function SwapWidgetEmbed({
+ buyChainId,
+ buyTokenAddress,
+ buyAmount,
+ sellChainId,
+ sellTokenAddress,
+ sellAmount,
+ showThirdwebBranding,
+ theme,
+ currency,
+}: {
+ buyChainId?: number;
+ buyTokenAddress?: Address;
+ buyAmount?: string;
+ sellChainId?: number;
+ sellTokenAddress?: Address;
+ sellAmount?: string;
+ showThirdwebBranding?: boolean;
+ theme: "light" | "dark";
+ currency?: SupportedFiatCurrency;
+}) {
+ const client = useMemo(
+ () =>
+ getConfiguredThirdwebClient({
+ clientId: NEXT_PUBLIC_SWAP_IFRAME_CLIENT_ID,
+ secretKey: undefined,
+ teamId: undefined,
+ }),
+ [],
+ );
+
+ const prefill = useMemo(() => {
+ const result: {
+ buyToken?: { chainId: number; tokenAddress?: string; amount?: string };
+ sellToken?: { chainId: number; tokenAddress?: string; amount?: string };
+ } = {};
+
+ if (buyChainId) {
+ result.buyToken = {
+ chainId: buyChainId,
+ tokenAddress: buyTokenAddress,
+ amount: buyAmount,
+ };
+ }
+
+ if (sellChainId) {
+ result.sellToken = {
+ chainId: sellChainId,
+ tokenAddress: sellTokenAddress,
+ amount: sellAmount,
+ };
+ }
+
+ return Object.keys(result).length > 0 ? result : undefined;
+ }, [
+ buyChainId,
+ buyTokenAddress,
+ buyAmount,
+ sellChainId,
+ sellTokenAddress,
+ sellAmount,
+ ]);
+
+ return (
+
{
+ sendMessageToParent({
+ source: "swap-widget",
+ type: "success",
+ });
+ }}
+ onError={(error) => {
+ sendMessageToParent({
+ source: "swap-widget",
+ type: "error",
+ message: error.message,
+ });
+ }}
+ />
+ );
+}
+
+function sendMessageToParent(content: object) {
+ try {
+ window.parent.postMessage(content, "*");
+ } catch (error) {
+ console.error("Failed to send post message to parent window");
+ console.error(error);
+ }
+}
diff --git a/apps/dashboard/src/app/bridge/swap-widget/layout.tsx b/apps/dashboard/src/app/bridge/swap-widget/layout.tsx
new file mode 100644
index 00000000000..2993635a556
--- /dev/null
+++ b/apps/dashboard/src/app/bridge/swap-widget/layout.tsx
@@ -0,0 +1,27 @@
+import { Inter } from "next/font/google";
+import { cn } from "@/lib/utils";
+
+const fontSans = Inter({
+ display: "swap",
+ subsets: ["latin"],
+ variable: "--font-sans",
+});
+
+export default function SwapWidgetLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return (
+
+
+ {children}
+
+
+ );
+}
diff --git a/apps/dashboard/src/app/bridge/swap-widget/page.tsx b/apps/dashboard/src/app/bridge/swap-widget/page.tsx
new file mode 100644
index 00000000000..622f57f8d02
--- /dev/null
+++ b/apps/dashboard/src/app/bridge/swap-widget/page.tsx
@@ -0,0 +1,83 @@
+import type { Metadata } from "next";
+import "@workspace/ui/global.css";
+import type { SupportedFiatCurrency } from "thirdweb/react";
+import { isValidCurrency } from "../_common/isValidCurrency";
+import {
+ onlyAddress,
+ onlyNumber,
+ parseQueryParams,
+} from "../_common/parseQueryParams";
+import { BridgeProvidersLite } from "../(general)/components/client/Providers.client";
+import { SwapWidgetEmbed } from "./SwapWidgetEmbed.client";
+
+const title = "thirdweb Swap: Cross-Chain Token Swaps";
+const description =
+ "Swap tokens across any chain with the best rates. Cross-chain swaps made simple with thirdweb.";
+
+export const metadata: Metadata = {
+ description,
+ openGraph: {
+ description,
+ title,
+ },
+ title,
+};
+
+type SearchParams = {
+ [key: string]: string | string[] | undefined;
+};
+
+export default async function Page(props: {
+ searchParams: Promise;
+}) {
+ const searchParams = await props.searchParams;
+
+ // Buy token params
+ const buyChainId = parseQueryParams(searchParams.buyChain, onlyNumber);
+ const buyTokenAddress = parseQueryParams(
+ searchParams.buyTokenAddress,
+ onlyAddress,
+ );
+ const buyAmount = parseQueryParams(searchParams.buyAmount, (v) => v);
+
+ // Sell token params
+ const sellChainId = parseQueryParams(searchParams.sellChain, onlyNumber);
+ const sellTokenAddress = parseQueryParams(
+ searchParams.sellTokenAddress,
+ onlyAddress,
+ );
+ const sellAmount = parseQueryParams(searchParams.sellAmount, (v) => v);
+
+ // Optional params
+ const showThirdwebBranding = parseQueryParams(
+ searchParams.showThirdwebBranding,
+ (v) => v !== "false",
+ );
+
+ const theme =
+ parseQueryParams(searchParams.theme, (v) =>
+ v === "light" ? "light" : "dark",
+ ) || "dark";
+
+ const currency = parseQueryParams(searchParams.currency, (v) =>
+ isValidCurrency(v) ? (v as SupportedFiatCurrency) : undefined,
+ );
+
+ return (
+
+
+
+
+
+ );
+}