diff --git a/.changeset/hip-houses-hear.md b/.changeset/hip-houses-hear.md
new file mode 100644
index 00000000000..f6b5453b9fb
--- /dev/null
+++ b/.changeset/hip-houses-hear.md
@@ -0,0 +1,5 @@
+---
+"thirdweb": minor
+---
+
+Add headless components for Wallets: WalletProvider, WalletIcon and WalletName
diff --git a/apps/portal/src/app/react/v5/components/onchain/page.mdx b/apps/portal/src/app/react/v5/components/onchain/page.mdx
index 17c72de5d27..301490bd514 100644
--- a/apps/portal/src/app/react/v5/components/onchain/page.mdx
+++ b/apps/portal/src/app/react/v5/components/onchain/page.mdx
@@ -136,4 +136,27 @@ Build your own UI and interact with onchain data using headless components.
description="Component to display the name of a chain"
/>
+### Wallets
+
+
+
+
+
+
+
diff --git a/packages/thirdweb/src/exports/react.ts b/packages/thirdweb/src/exports/react.ts
index a3769f9316e..c9a55b1bb38 100644
--- a/packages/thirdweb/src/exports/react.ts
+++ b/packages/thirdweb/src/exports/react.ts
@@ -272,3 +272,17 @@ export {
// Utils
export { getLastAuthProvider } from "../react/web/utils/storage.js";
+
+// Wallet
+export {
+ WalletProvider,
+ type WalletProviderProps,
+} from "../react/web/ui/prebuilt/Wallet/provider.js";
+export {
+ WalletIcon,
+ type WalletIconProps,
+} from "../react/web/ui/prebuilt/Wallet/icon.js";
+export {
+ WalletName,
+ type WalletNameProps,
+} from "../react/web/ui/prebuilt/Wallet/name.js";
diff --git a/packages/thirdweb/src/react/web/ui/prebuilt/Chain/name.tsx b/packages/thirdweb/src/react/web/ui/prebuilt/Chain/name.tsx
index 7cbfa0e6a0f..505f7f25554 100644
--- a/packages/thirdweb/src/react/web/ui/prebuilt/Chain/name.tsx
+++ b/packages/thirdweb/src/react/web/ui/prebuilt/Chain/name.tsx
@@ -48,7 +48,7 @@ export interface ChainNameProps
* name was not fetched succesfully
* @example
* ```tsx
- * Failed to load}
* />
* ```
*/
diff --git a/packages/thirdweb/src/react/web/ui/prebuilt/Chain/provider.tsx b/packages/thirdweb/src/react/web/ui/prebuilt/Chain/provider.tsx
index 97c21ca52d6..a1079668aa6 100644
--- a/packages/thirdweb/src/react/web/ui/prebuilt/Chain/provider.tsx
+++ b/packages/thirdweb/src/react/web/ui/prebuilt/Chain/provider.tsx
@@ -48,6 +48,7 @@ const ChainProviderContext = /* @__PURE__ */ createContext<
* ```
* @component
* @chain
+ * @beta
*/
export function ChainProvider(
props: React.PropsWithChildren,
diff --git a/packages/thirdweb/src/react/web/ui/prebuilt/Wallet/icon.tsx b/packages/thirdweb/src/react/web/ui/prebuilt/Wallet/icon.tsx
new file mode 100644
index 00000000000..4ca48be4848
--- /dev/null
+++ b/packages/thirdweb/src/react/web/ui/prebuilt/Wallet/icon.tsx
@@ -0,0 +1,120 @@
+"use client";
+
+import { type UseQueryOptions, useQuery } from "@tanstack/react-query";
+import type { JSX } from "react";
+import { getWalletInfo } from "../../../../../wallets/__generated__/getWalletInfo.js";
+import type { WalletId } from "../../../../../wallets/wallet-types.js";
+import { useWalletContext } from "./provider.js";
+
+export interface WalletIconProps
+ extends Omit, "src"> {
+ /**
+ * This component will be shown while the icon of the wallet is being fetched
+ * If not passed, the component will return `null`.
+ *
+ * You can/should pass a loading sign or spinner to this prop.
+ * @example
+ * ```tsx
+ * } />
+ * ```
+ */
+ loadingComponent?: JSX.Element;
+ /**
+ * This component will be shown if the icon fails to be retreived
+ * If not passed, the component will return `null`.
+ *
+ * You can/should pass a descriptive text/component to this prop, indicating that the
+ * icon was not fetched succesfully
+ * @example
+ * ```tsx
+ * Failed to load}
+ * />
+ * ```
+ */
+ fallbackComponent?: JSX.Element;
+ /**
+ * Optional `useQuery` params
+ */
+ queryOptions?: Omit, "queryFn" | "queryKey">;
+}
+
+/**
+ * This component tries to resolve the icon of a given wallet, then return an image.
+ * @returns an
with the src of the wallet icon
+ *
+ * @example
+ * ### Basic usage
+ * ```tsx
+ * import { WalletProvider, WalletIcon } from "thirdweb/react";
+ *
+ *
+ *
+ *
+ * ```
+ *
+ * Result: An
component with the src of the icon
+ * ```html
+ *
+ * ```
+ *
+ * ### Show a loading sign while the icon is being loaded
+ * ```tsx
+ * } />
+ * ```
+ *
+ * ### Fallback to a dummy image if the wallet icon fails to resolve
+ * ```tsx
+ * } />
+ * ```
+ *
+ * ### Usage with queryOptions
+ * WalletIcon uses useQuery() from tanstack query internally.
+ * It allows you to pass a custom queryOptions of your choice for more control of the internal fetching logic
+ * ```tsx
+ *
+ * ```
+ *
+ * @component
+ * @wallet
+ * @beta
+ */
+export function WalletIcon({
+ loadingComponent,
+ fallbackComponent,
+ queryOptions,
+ ...restProps
+}: WalletIconProps) {
+ const imageQuery = useWalletIcon({ queryOptions });
+ if (imageQuery.isLoading) {
+ return loadingComponent || null;
+ }
+ if (!imageQuery.data) {
+ return fallbackComponent || null;
+ }
+ return
;
+}
+
+/**
+ * @internal
+ */
+function useWalletIcon(props: {
+ queryOptions?: Omit, "queryFn" | "queryKey">;
+}) {
+ const { id } = useWalletContext();
+ const imageQuery = useQuery({
+ queryKey: ["walletIcon", id],
+ queryFn: async () => fetchWalletImage({ id }),
+ ...props.queryOptions,
+ });
+ return imageQuery;
+}
+
+/**
+ * @internal Exported for tests only
+ */
+async function fetchWalletImage(props: {
+ id: WalletId;
+}) {
+ const image_src = await getWalletInfo(props.id, true);
+ return image_src;
+}
diff --git a/packages/thirdweb/src/react/web/ui/prebuilt/Wallet/name.test.tsx b/packages/thirdweb/src/react/web/ui/prebuilt/Wallet/name.test.tsx
new file mode 100644
index 00000000000..985343e13aa
--- /dev/null
+++ b/packages/thirdweb/src/react/web/ui/prebuilt/Wallet/name.test.tsx
@@ -0,0 +1,13 @@
+import { describe, expect, it, vi } from "vitest";
+import { fetchWalletName } from "./name.js";
+
+vi.mock("./WalletName", () => ({
+ useWalletName: vi.fn(),
+}));
+
+describe.runIf(process.env.TW_SECRET_KEY)("WalletName", () => {
+ it("fetchWalletName: should fetch wallet name from id", async () => {
+ const name = await fetchWalletName({ id: "io.metamask" });
+ expect(name).toBe("MetaMask");
+ });
+});
diff --git a/packages/thirdweb/src/react/web/ui/prebuilt/Wallet/name.tsx b/packages/thirdweb/src/react/web/ui/prebuilt/Wallet/name.tsx
new file mode 100644
index 00000000000..29a0d1387e5
--- /dev/null
+++ b/packages/thirdweb/src/react/web/ui/prebuilt/Wallet/name.tsx
@@ -0,0 +1,144 @@
+"use client";
+
+import { type UseQueryOptions, useQuery } from "@tanstack/react-query";
+import type { JSX } from "react";
+import { getWalletInfo } from "../../../../../wallets/__generated__/getWalletInfo.js";
+import type { WalletId } from "../../../../../wallets/wallet-types.js";
+import { useWalletContext } from "./provider.js";
+
+/**
+ * Props for the WalletName component
+ * @component
+ * @wallet
+ */
+export interface WalletNameProps
+ extends Omit, "children"> {
+ /**
+ * This component will be shown while the name of the wallet name is being fetched
+ * If not passed, the component will return `null`.
+ *
+ * You can/should pass a loading sign or spinner to this prop.
+ * @example
+ * ```tsx
+ * } />
+ * ```
+ */
+ loadingComponent?: JSX.Element;
+ /**
+ * This component will be shown if the name fails to be retreived
+ * If not passed, the component will return `null`.
+ *
+ * You can/should pass a descriptive text/component to this prop, indicating that the
+ * name was not fetched succesfully
+ * @example
+ * ```tsx
+ * Failed to load}
+ * />
+ * ```
+ */
+ fallbackComponent?: JSX.Element;
+ /**
+ * Optional `useQuery` params
+ */
+ queryOptions?: Omit, "queryFn" | "queryKey">;
+ /**
+ * A function to format the name's display value
+ * ```tsx
+ * doSomething()} />
+ * ```
+ */
+ formatFn?: (str: string) => string;
+}
+
+/**
+ * This component fetches then shows the name of a wallet.
+ * It inherits all the attributes of a HTML component, hence you can style it just like how you would style a normal
+ *
+ * @example
+ * ### Basic usage
+ * ```tsx
+ * import { WalletProvider, WalletName } from "thirdweb/react";
+ *
+ *
+ *
+ *
+ * ```
+ * Result:
+ * ```html
+ * MetaMask
+ * ```
+ *
+ * ### Show a loading sign when the name is being fetched
+ * ```tsx
+ * import { WalletProvider, WalletName } from "thirdweb/react";
+ *
+ *
+ * } />
+ *
+ * ```
+ *
+ * ### Fallback to something when the name fails to resolve
+ * ```tsx
+ *
+ * Failed to load} />
+ *
+ * ```
+ *
+ * ### Custom query options for useQuery
+ * This component uses `@tanstack-query`'s useQuery internally.
+ * You can use the `queryOptions` prop for more fine-grained control
+ * ```tsx
+ *
+ * @component
+ * @beta
+ * @wallet
+ */
+export function WalletName({
+ loadingComponent,
+ fallbackComponent,
+ queryOptions,
+ formatFn,
+ ...restProps
+}: WalletNameProps) {
+ const nameQuery = useWalletName({ queryOptions });
+ if (nameQuery.isLoading) {
+ return loadingComponent || null;
+ }
+ if (!nameQuery.data) {
+ return fallbackComponent || null;
+ }
+ if (typeof formatFn === "function") {
+ return {formatFn(nameQuery.data)};
+ }
+ return {nameQuery.data};
+}
+
+/**
+ * @internal
+ */
+function useWalletName(props: {
+ queryOptions?: Omit, "queryFn" | "queryKey">;
+}) {
+ const { id } = useWalletContext();
+ const nameQuery = useQuery({
+ queryKey: ["walletName", id],
+ queryFn: async () => fetchWalletName({ id }),
+ ...props.queryOptions,
+ });
+ return nameQuery;
+}
+
+/**
+ * @internal Exported for tests only
+ */
+export async function fetchWalletName(props: {
+ id: WalletId;
+}) {
+ const info = await getWalletInfo(props.id);
+ return info.name;
+}
diff --git a/packages/thirdweb/src/react/web/ui/prebuilt/Wallet/provider.test.tsx b/packages/thirdweb/src/react/web/ui/prebuilt/Wallet/provider.test.tsx
new file mode 100644
index 00000000000..db97a5ce152
--- /dev/null
+++ b/packages/thirdweb/src/react/web/ui/prebuilt/Wallet/provider.test.tsx
@@ -0,0 +1,61 @@
+import { type FC, useContext } from "react";
+import { describe, expect, it, vi } from "vitest";
+import { render, renderHook, screen } from "~test/react-render.js";
+import {
+ WalletProvider,
+ WalletProviderContext,
+ useWalletContext,
+} from "./provider.js";
+
+describe.runIf(process.env.TW_SECRET_KEY)("WalletProvider", () => {
+ it("useWalletContext should throw an error when used outside of WalletProvider", () => {
+ const consoleErrorSpy = vi
+ .spyOn(console, "error")
+ .mockImplementation(() => {});
+
+ expect(() => {
+ renderHook(() => useWalletContext());
+ }).toThrow(
+ "WalletProviderContext not found. Make sure you are using WalletIcon, WalletName, etc. inside a component",
+ );
+
+ consoleErrorSpy.mockRestore();
+ });
+
+ it("useWalletContext should return the context value when used within WalletProvider", () => {
+ const wrapper: FC = ({ children }: React.PropsWithChildren) => (
+ {children}
+ );
+
+ const { result } = renderHook(() => useWalletContext(), { wrapper });
+
+ expect(result.current.id).toStrictEqual("io.metamask");
+ });
+
+ it("should render children correctly", () => {
+ render(
+
+ Child Component
+ ,
+ );
+
+ expect(screen.getByText("Child Component")).toBeInTheDocument();
+ });
+
+ it("should provide context values to children", () => {
+ function WalletConsumer() {
+ const context = useContext(WalletProviderContext);
+ if (!context) {
+ return No context
;
+ }
+ return {String(context.id)}
;
+ }
+ render(
+
+
+ ,
+ );
+
+ expect(screen.getByText("io.metamask")).toBeInTheDocument();
+ });
+});
diff --git a/packages/thirdweb/src/react/web/ui/prebuilt/Wallet/provider.tsx b/packages/thirdweb/src/react/web/ui/prebuilt/Wallet/provider.tsx
new file mode 100644
index 00000000000..75761cc86ae
--- /dev/null
+++ b/packages/thirdweb/src/react/web/ui/prebuilt/Wallet/provider.tsx
@@ -0,0 +1,65 @@
+"use client";
+
+import type React from "react";
+import { createContext, useContext } from "react";
+import type { WalletId } from "../../../../../wallets/wallet-types.js";
+
+/**
+ * Props for the WalletProvider component
+ * @component
+ * @wallet
+ */
+export type WalletProviderProps = {
+ id: WalletId;
+};
+
+/**
+ * @internal Exported for tests only
+ */
+export const WalletProviderContext = /* @__PURE__ */ createContext<
+ WalletProviderProps | undefined
+>(undefined);
+
+/**
+/**
+ * A React context provider component that supplies Wallet-related data to its child components.
+ *
+ * This component serves as a wrapper around the `WalletProviderContext.Provider` and passes
+ * the provided wallet data down to all of its child components through the context API.
+ *
+ * @example
+ * ### Basic usage
+ * ```tsx
+ * import { WalletProvider, WalletIcon, WaleltName } from "thirdweb/react";
+ *
+ *
+ *
+ *
+ *
+ * ```
+ * @beta
+ * @component
+ * @wallet
+ */
+export function WalletProvider(
+ props: React.PropsWithChildren,
+) {
+ return (
+
+ {props.children}
+
+ );
+}
+
+/**
+ * @internal
+ */
+export function useWalletContext() {
+ const ctx = useContext(WalletProviderContext);
+ if (!ctx) {
+ throw new Error(
+ "WalletProviderContext not found. Make sure you are using WalletIcon, WalletName, etc. inside a component",
+ );
+ }
+ return ctx;
+}