diff --git a/.changeset/breezy-scissors-scream.md b/.changeset/breezy-scissors-scream.md
new file mode 100644
index 00000000000..1c376876372
--- /dev/null
+++ b/.changeset/breezy-scissors-scream.md
@@ -0,0 +1,20 @@
+---
+"thirdweb": minor
+---
+
+Added new `SiteEmbed` React component for embedding thirdweb-supported sites with seamless wallet connection support.
+
+The component allows you to embed other thirdweb-enabled sites while maintaining wallet connection state, supporting both in-app and ecosystem wallets.
+
+Example usage:
+```tsx
+import { SiteEmbed } from "thirdweb/react";
+
+
+```
+
+Note: Embedded sites must include `` and support frame-ancestors in their Content Security Policy.
diff --git a/packages/thirdweb/src/exports/react.ts b/packages/thirdweb/src/exports/react.ts
index b8fc40b4220..69f34dac43a 100644
--- a/packages/thirdweb/src/exports/react.ts
+++ b/packages/thirdweb/src/exports/react.ts
@@ -195,3 +195,6 @@ export type {
FarcasterProfile,
LensProfile,
} from "../social/types.js";
+
+// Site Embed
+export { SiteEmbed } from "../react/web/ui/SiteEmbed.js";
diff --git a/packages/thirdweb/src/react/core/hooks/wallets/useAutoConnect.ts b/packages/thirdweb/src/react/core/hooks/wallets/useAutoConnect.ts
index befcfdb8dce..a44eac7b537 100644
--- a/packages/thirdweb/src/react/core/hooks/wallets/useAutoConnect.ts
+++ b/packages/thirdweb/src/react/core/hooks/wallets/useAutoConnect.ts
@@ -2,6 +2,8 @@
import { useQuery } from "@tanstack/react-query";
import type { AsyncStorage } from "../../../../utils/storage/AsyncStorage.js";
+import { isEcosystemWallet } from "../../../../wallets/ecosystem/is-ecosystem-wallet.js";
+import { ClientScopedStorage } from "../../../../wallets/in-app/core/authentication/client-scoped-storage.js";
import { getUrlToken } from "../../../../wallets/in-app/web/lib/get-url-token.js";
import type { Wallet } from "../../../../wallets/interfaces/wallet.js";
import {
@@ -43,8 +45,25 @@ export function useAutoConnectCore(
getStoredActiveWalletId(storage),
]);
- const { authResult, walletId, authProvider } = getUrlToken();
- if (authResult && walletId) {
+ const { authResult, walletId, authProvider, authCookie } = getUrlToken();
+ const wallet = wallets.find((w) => w.id === walletId);
+
+ // If an auth cookie is found and this site supports the wallet, we'll set the auth cookie in the client storage
+ if (authCookie && wallet) {
+ const clientStorage = new ClientScopedStorage({
+ storage,
+ clientId: props.client.clientId,
+ ecosystem: isEcosystemWallet(wallet)
+ ? {
+ id: wallet.id,
+ partnerId: wallet.getConfig()?.partnerId,
+ }
+ : undefined,
+ });
+ await clientStorage.saveAuthCookie(authCookie);
+ }
+
+ if (walletId) {
lastActiveWalletId = walletId;
lastConnectedWalletIds = lastConnectedWalletIds?.includes(walletId)
? lastConnectedWalletIds
diff --git a/packages/thirdweb/src/react/web/ui/SiteEmbed.test.tsx b/packages/thirdweb/src/react/web/ui/SiteEmbed.test.tsx
new file mode 100644
index 00000000000..fce3460b015
--- /dev/null
+++ b/packages/thirdweb/src/react/web/ui/SiteEmbed.test.tsx
@@ -0,0 +1,39 @@
+import { describe, expect, it } from "vitest";
+import { render, waitFor } from "../../../../test/src/react-render.js";
+import { TEST_CLIENT } from "../../../../test/src/test-clients.js";
+import { SiteEmbed } from "./SiteEmbed.js";
+
+describe("SiteEmbed", () => {
+ it("renders iframe with correct src", () => {
+ const testUrl = "https://example.com/";
+ const { container } = render(
+ ,
+ );
+
+ const iframe = container.querySelector("iframe");
+ expect(iframe).toBeTruthy();
+ expect(iframe?.src).toBe(testUrl);
+ });
+
+ it("throws error if clientId is not provided", () => {
+ const testUrl = "https://example.com/";
+ expect(() =>
+ // biome-ignore lint/suspicious/noExplicitAny: testing invalid input
+ render(),
+ ).toThrow("The SiteEmbed client must have a clientId");
+ });
+
+ it("adds wallet params to url when wallet is connected", async () => {
+ const testUrl = "https://example.com/";
+ const { container } = render(
+ ,
+ {
+ setConnectedWallet: true,
+ },
+ );
+
+ const iframe = container.querySelector("iframe");
+ expect(iframe).toBeTruthy();
+ await waitFor(() => expect(iframe?.src).toContain("walletId="));
+ });
+});
diff --git a/packages/thirdweb/src/react/web/ui/SiteEmbed.tsx b/packages/thirdweb/src/react/web/ui/SiteEmbed.tsx
new file mode 100644
index 00000000000..041dc148d48
--- /dev/null
+++ b/packages/thirdweb/src/react/web/ui/SiteEmbed.tsx
@@ -0,0 +1,90 @@
+"use client";
+import { useQuery } from "@tanstack/react-query";
+import type { ThirdwebClient } from "../../../client/client.js";
+import { getLastAuthProvider } from "../../../react/core/utils/storage.js";
+import { webLocalStorage } from "../../../utils/storage/webStorage.js";
+import { isEcosystemWallet } from "../../../wallets/ecosystem/is-ecosystem-wallet.js";
+import { ClientScopedStorage } from "../../../wallets/in-app/core/authentication/client-scoped-storage.js";
+import type { Ecosystem } from "../../../wallets/in-app/core/wallet/types.js";
+import { useActiveWallet } from "../../core/hooks/wallets/useActiveWallet.js";
+
+/**
+ * Embeds another thirdweb-supported site for seamless in-app and ecosystem wallet connection.
+ *
+ * @note Make sure the embedded site includes and supports frame ancestors, see [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors) for more information.
+ *
+ * @note The embedded site must support the connected wallet (ecosystem or in-app).
+ *
+ * @param {Object} props - The props to pass to the iframe
+ * @param {String} props.src - The URL of the site to embed
+ * @param {ThirdwebClient} props.client - The client to use for the embedded site
+ * @param {Ecosystem} [props.ecosystem] - The ecosystem to use for the embedded site
+ *
+ * @example
+ * ```tsx
+ * import { SiteEmbed } from "thirdweb/react";
+ *
+ *
+ * ```
+ */
+export function SiteEmbed({
+ src,
+ client,
+ ecosystem,
+ ...props
+}: {
+ src: string;
+ client: ThirdwebClient;
+ ecosystem?: Ecosystem;
+} & React.DetailedHTMLProps<
+ React.IframeHTMLAttributes,
+ HTMLIFrameElement
+>) {
+ if (!client.clientId) {
+ throw new Error("The SiteEmbed client must have a clientId");
+ }
+
+ const activeWallet = useActiveWallet();
+ const walletId = activeWallet?.id;
+
+ const {
+ data: { authProvider, authCookie } = {},
+ } = useQuery({
+ queryKey: ["site-embed", walletId, src, client.clientId, ecosystem],
+ enabled:
+ activeWallet && (isEcosystemWallet(activeWallet) || walletId === "inApp"),
+ queryFn: async () => {
+ const storage = new ClientScopedStorage({
+ storage: webLocalStorage,
+ clientId: client.clientId,
+ ecosystem,
+ });
+
+ const authProvider = await getLastAuthProvider(webLocalStorage);
+ const authCookie = await storage.getAuthCookie();
+
+ return { authProvider, authCookie };
+ },
+ });
+
+ const url = new URL(src);
+ if (walletId) {
+ url.searchParams.set("walletId", walletId);
+ }
+ if (authProvider) {
+ url.searchParams.set("authProvider", authProvider);
+ }
+ if (authCookie) {
+ url.searchParams.set("authCookie", authCookie);
+ }
+
+ return (
+
+ );
+}
diff --git a/packages/thirdweb/src/wallets/in-app/web/lib/get-url-token.ts b/packages/thirdweb/src/wallets/in-app/web/lib/get-url-token.ts
index b4e6b4021cf..a0a153a1a95 100644
--- a/packages/thirdweb/src/wallets/in-app/web/lib/get-url-token.ts
+++ b/packages/thirdweb/src/wallets/in-app/web/lib/get-url-token.ts
@@ -9,6 +9,7 @@ export function getUrlToken(): {
walletId?: WalletId;
authResult?: AuthStoredTokenWithCookieReturnType;
authProvider?: AuthOption;
+ authCookie?: string;
} {
if (!window?.location) {
// Not in web
@@ -20,18 +21,24 @@ export function getUrlToken(): {
const authResultString = params.get("authResult");
const walletId = params.get("walletId") as WalletId | undefined;
const authProvider = params.get("authProvider") as AuthOption | undefined;
+ const authCookie = params.get("authCookie") as string | undefined;
- if (authResultString && walletId) {
- const authResult = JSON.parse(authResultString);
- params.delete("authResult");
+ if ((authCookie || authResultString) && walletId) {
+ const authResult = (() => {
+ if (authResultString) {
+ params.delete("authResult");
+ return JSON.parse(decodeURIComponent(authResultString));
+ }
+ })();
params.delete("walletId");
params.delete("authProvider");
+ params.delete("authCookie");
window.history.pushState(
{},
"",
`${window.location.pathname}?${params.toString()}`,
);
- return { walletId, authResult, authProvider };
+ return { walletId, authResult, authProvider, authCookie };
}
return {};
}