"use client";
import styled from "@emotion/styled";
import {
ChevronRightIcon,
ExitIcon,
PaperPlaneIcon,
PinBottomIcon,
PlusIcon,
TextAlignJustifyIcon,
} from "@radix-ui/react-icons";
import { useQuery } from "@tanstack/react-query";
import {
type Dispatch,
type JSX,
type SetStateAction,
useCallback,
useContext,
useEffect,
useState,
} from "react";
import { trackPayEvent } from "../../../../analytics/track/pay.js";
import type { Chain } from "../../../../chains/types.js";
import { getCachedChain } from "../../../../chains/utils.js";
import type { ThirdwebClient } from "../../../../client/client.js";
import { getContract } from "../../../../contract/contract.js";
import type { SupportedFiatCurrency } from "../../../../pay/convert/type.js";
import { getLastAuthProvider } from "../../../../react/core/utils/storage.js";
import { shortenAddress } from "../../../../utils/address.js";
import { isContractDeployed } from "../../../../utils/bytecode/is-contract-deployed.js";
import { webLocalStorage } from "../../../../utils/storage/webStorage.js";
import { isEcosystemWallet } from "../../../../wallets/ecosystem/is-ecosystem-wallet.js";
import type { Ecosystem } from "../../../../wallets/in-app/core/wallet/types.js";
import type { Account, Wallet } from "../../../../wallets/interfaces/wallet.js";
import { isSmartWallet } from "../../../../wallets/smart/is-smart-wallet.js";
import type { SmartWalletOptions } from "../../../../wallets/smart/types.js";
import {
type AppMetadata,
type SocialAuthOption,
socialAuthOptions,
} from "../../../../wallets/types.js";
import type {
EcosystemWalletId,
WalletId,
} from "../../../../wallets/wallet-types.js";
import { AccountProvider } from "../../../core/account/provider.js";
import {
CustomThemeProvider,
parseTheme,
useCustomTheme,
} from "../../../core/design-system/CustomThemeProvider.js";
import {
fontSize,
iconSize,
radius,
spacing,
type Theme,
} from "../../../core/design-system/index.js";
import type {
ConnectButton_connectModalOptions,
ConnectButton_detailsButtonOptions,
ConnectButton_detailsModalOptions,
ConnectButtonProps,
PayUIOptions,
} from "../../../core/hooks/connection/ConnectButtonProps.js";
import {
useChainFaucets,
useChainMetadata,
} from "../../../core/hooks/others/useChainQuery.js";
import { useActiveAccount } from "../../../core/hooks/wallets/useActiveAccount.js";
import { useActiveWallet } from "../../../core/hooks/wallets/useActiveWallet.js";
import { useActiveWalletChain } from "../../../core/hooks/wallets/useActiveWalletChain.js";
import { useAdminWallet } from "../../../core/hooks/wallets/useAdminWallet.js";
import { useDisconnect } from "../../../core/hooks/wallets/useDisconnect.js";
import { useSwitchActiveWalletChain } from "../../../core/hooks/wallets/useSwitchActiveWalletChain.js";
import { SetRootElementContext } from "../../../core/providers/RootElementContext.js";
import {
type AccountBalanceInfo,
formatAccountFiatBalance,
formatAccountTokenBalance,
} from "../../../core/utils/account.js";
import type {
SupportedNFTs,
SupportedTokens,
} from "../../../core/utils/defaultTokens.js";
import { useWalletInfo } from "../../../core/utils/wallet.js";
import { WalletUIStatesProvider } from "../../providers/wallet-ui-states-provider.js";
import { BuyWidget } from "../Bridge/BuyWidget.js";
import { Container, Line } from "../components/basic.js";
import { Button, IconButton } from "../components/buttons.js";
import { ChainActiveDot } from "../components/ChainActiveDot.js";
import { CopyIcon } from "../components/CopyIcon.js";
import { fallbackChainIcon } from "../components/fallbackChainIcon.js";
import { IconContainer } from "../components/IconContainer.js";
import { Modal } from "../components/Modal.js";
import { Skeleton } from "../components/Skeleton.js";
import { Spacer } from "../components/Spacer.js";
import { Spinner } from "../components/Spinner.js";
import { ToolTip } from "../components/Tooltip.js";
import { Link, Text } from "../components/text.js";
import { WalletImage } from "../components/WalletImage.js";
import { fadeInAnimation } from "../design-system/animations.js";
import { StyledButton } from "../design-system/elements.js";
import { AccountAddress } from "../prebuilt/Account/address.js";
import { AccountAvatar } from "../prebuilt/Account/avatar.js";
import { AccountBalance } from "../prebuilt/Account/balance.js";
import { AccountBlobbie } from "../prebuilt/Account/blobbie.js";
import { AccountName } from "../prebuilt/Account/name.js";
import { ChainIcon } from "../prebuilt/Chain/icon.js";
import { ChainName } from "../prebuilt/Chain/name.js";
import { ChainProvider } from "../prebuilt/Chain/provider.js";
import type { LocaleId } from "../types.js";
import { onModalUnmount } from "./constants.js";
import { CoinsIcon } from "./icons/CoinsIcon.js";
import { FundsIcon } from "./icons/FundsIcon.js";
import { OutlineWalletIcon } from "./icons/OutlineWalletIcon.js";
import { getConnectLocale } from "./locale/getConnectLocale.js";
import type { ConnectLocale } from "./locale/types.js";
import { MenuButton, MenuLink } from "./MenuButton.js";
import { ScreenSetupContext, useSetupScreen } from "./Modal/screen.js";
import {
NetworkSelectorContent,
type NetworkSelectorProps,
} from "./NetworkSelector.js";
import { WalletManagerScreen } from "./screens/Details/WalletManagerScreen.js";
import { LinkedProfilesScreen } from "./screens/LinkedProfilesScreen.js";
import { LinkProfileScreen } from "./screens/LinkProfileScreen.js";
import { ManageWalletScreen } from "./screens/ManageWalletScreen.js";
import { PrivateKey } from "./screens/PrivateKey.js";
import { ReceiveFunds } from "./screens/ReceiveFunds.js";
import { SendFunds } from "./screens/SendFunds.js";
import type { WalletDetailsModalScreen } from "./screens/types.js";
import { type AssetTabs, ViewAssets } from "./screens/ViewAssets.js";
import { ViewNFTs } from "./screens/ViewNFTs.js";
import { ViewTokens } from "./screens/ViewTokens.js";
import { WalletConnectReceiverScreen } from "./screens/WalletConnectReceiverScreen.js";
import { TransactionsScreen } from "./TransactionsScreen.js";
const TW_CONNECTED_WALLET = "tw-connected-wallet";
const LocalhostChainId = 1337;
/**
* @internal
*/
export const ConnectedWalletDetails: React.FC<{
onDisconnect: (info: { wallet: Wallet; account: Account }) => void;
detailsButton?: ConnectButton_detailsButtonOptions;
detailsModal?: ConnectButton_detailsModalOptions;
theme: "light" | "dark" | Theme;
supportedTokens?: SupportedTokens;
supportedNFTs?: SupportedNFTs;
chains: Chain[];
chain?: Chain;
switchButton: ConnectButtonProps["switchButton"];
connectLocale: ConnectLocale;
client: ThirdwebClient;
connectOptions: DetailsModalConnectOptions | undefined;
}> = (props) => {
const { connectLocale: locale, client } = props;
const setRootEl = useContext(SetRootElementContext);
const walletChain = useActiveWalletChain();
function closeModal() {
setRootEl(null);
}
function openModal() {
setRootEl(
,
);
}
const isNetworkMismatch =
props.chain && walletChain && walletChain.id !== props.chain.id;
if (props.detailsButton?.render) {
return (
// biome-ignore lint/a11y/useKeyWithClickEvents: ok
// biome-ignore lint/a11y/noStaticElementInteractions: TODO
);
}
if (props.chain && isNetworkMismatch) {
return (
);
}
const combinedClassName = `${TW_CONNECTED_WALLET} ${props.detailsButton?.className || ""}`;
const tokenAddress =
props.detailsButton?.displayBalanceToken?.[Number(walletChain?.id)];
return (
{props.detailsButton?.connectedAccountAvatarUrl ? (
) : (
}
loadingComponent={
}
queryOptions={{
refetchOnMount: false,
refetchOnWindowFocus: false,
}}
style={{
height: "100%",
objectFit: "cover",
width: "100%",
}}
/>
)}
{/* Address */}
{props.detailsButton?.connectedAccountName ? (
{props.detailsButton.connectedAccountName}
) : (
}
loadingComponent={}
/>
)}
{props.detailsButton?.showBalanceInFiat ? (
<>
}
loadingComponent={
}
tokenAddress={tokenAddress}
/>
}
showBalanceInFiat="USD"
tokenAddress={tokenAddress}
/>
>
) : (
}
formatFn={detailsBtn_formatTokenBalanceForButton}
loadingComponent={}
tokenAddress={tokenAddress}
/>
)}
);
};
/**
* @internal Exported for tests
*/
export function detailsBtn_formatFiatBalanceForButton(
props: AccountBalanceInfo,
) {
return ` (${formatAccountFiatBalance({ ...props, decimals: 0 })})`;
}
/**
* @internal Exported for test
*/
export function detailsBtn_formatTokenBalanceForButton(
props: AccountBalanceInfo,
) {
return `${formatAccountTokenBalance({ ...props, decimals: props.balance < 1 ? 5 : 4 })}`;
}
/**
* @internal Exported for tests only
*/
export function DetailsModal(props: {
client: ThirdwebClient;
locale: ConnectLocale;
detailsModal?: ConnectButton_detailsModalOptions;
theme: "light" | "dark" | Theme;
supportedTokens?: SupportedTokens;
supportedNFTs?: SupportedNFTs;
closeModal: () => void;
onDisconnect: (info: { wallet: Wallet; account: Account }) => void;
chains: Chain[];
displayBalanceToken?: Record;
connectOptions: DetailsModalConnectOptions | undefined;
assetTabs?: AssetTabs[];
showBalanceInFiat?: SupportedFiatCurrency;
initialScreen?: WalletDetailsModalScreen;
}) {
const [screen, setScreen] = useState(
props.initialScreen || "main",
);
const { disconnect } = useDisconnect();
const [isOpen, setIsOpen] = useState(true);
const { client, locale } = props;
const walletChain = useActiveWalletChain();
const activeAccount = useActiveAccount();
const theme = parseTheme(props.theme);
const activeWallet = useActiveWallet();
const chainFaucetsQuery = useChainFaucets(walletChain);
const chainMetadataQuery = useChainMetadata(walletChain);
const disableSwitchChain = !activeWallet?.switchChain;
const screenSetup = useSetupScreen({
size: "compact",
wallets: activeWallet ? [activeWallet] : [],
welcomeScreen: undefined,
});
const closeModal = useCallback(() => {
setIsOpen(false);
onModalUnmount(() => {
props.closeModal();
});
}, [props.closeModal]);
function handleDisconnect(info: { wallet: Wallet; account: Account }) {
setIsOpen(false);
props.closeModal();
props.onDisconnect(info);
}
useEffect(() => {
if (!activeAccount) {
closeModal();
}
}, [activeAccount, closeModal]);
const { hideSendFunds, hideReceiveFunds, hideBuyFunds } =
props.detailsModal || {};
const hideAllButtons = hideSendFunds && hideReceiveFunds && hideBuyFunds;
const avatarContent = (
{props.detailsModal?.connectedAccountAvatarUrl ? (
) : (
activeAccount && (
}
loadingComponent={}
style={{
height: "100%",
objectFit: "cover",
width: "100%",
}}
/>
)
)}
{!props.detailsModal?.hideSwitchWallet ? (
{activeWallet && (
)}
) : null}
);
let content = (
{props.detailsModal?.hideSwitchWallet ? (
avatarContent
) : (
{/** biome-ignore lint/a11y/noStaticElementInteractions: TODO */}
{
setScreen("wallet-manager");
}}
onKeyDown={(e) => {
if (e.key === "w") {
setScreen("wallet-manager");
}
}}
style={{
cursor: "pointer",
}}
>
{avatarContent}
)}
{props.detailsModal?.connectedAccountName ? (
{props.detailsModal.connectedAccountName}
) : (
}
loadingComponent={
}
/>
)}
{!hideAllButtons && (
<>
{/* Send, Receive, Swap */}
{!hideSendFunds && (
)}
{!hideReceiveFunds && (
)}
{!hideBuyFunds &&
chainMetadataQuery.data &&
!chainMetadataQuery.data.testnet && (
)}
>
)}
{/* Network Switcher */}
setScreen("network-switcher")}
showBalanceInFiat={props.detailsModal?.showBalanceInFiat}
/>
{/* Transactions */}
{
setScreen("transactions");
}}
style={{
fontSize: fontSize.sm,
}}
>
{locale.transactions}
{/* View Funds */}
{/* Hide the View Funds button if the assetTabs props is set to an empty array */}
{(props.assetTabs === undefined || props.assetTabs.length > 0) && (
{
setScreen("view-assets");
}}
style={{
fontSize: fontSize.sm,
}}
>
{locale.viewFunds.viewAssets}
)}
{/* Manage Wallet */}
{
setScreen("manage-wallet");
}}
style={{
fontSize: fontSize.sm,
}}
>
{props.locale.manageWallet.title}
{/* Request Testnet funds */}
{(props.detailsModal?.showTestnetFaucet ?? false) &&
(chainFaucetsQuery.faucets.length > 0 ||
walletChain?.id === LocalhostChainId) && (
{locale.requestTestnetFunds}
)}
{props.detailsModal?.footer && (
)}
{props.detailsModal?.hideDisconnect !== true && (
{
if (activeWallet && activeAccount) {
disconnect(activeWallet);
handleDisconnect({
account: activeAccount,
wallet: activeWallet,
});
}
}}
type="button"
>
{locale.disconnectWallet}
)}
);
if (screen === "transactions") {
content = (
setScreen("main")}
setScreen={setScreen}
title={locale.buy}
/>
);
}
if (
screen === "wallet-manager" &&
activeAccount &&
walletChain &&
activeWallet
) {
content = (
setScreen("main")}
recommendedWallets={props.connectOptions?.recommendedWallets}
showAllWallets={!!props.connectOptions?.showAllWallets}
walletConnect={props.connectOptions?.walletConnect}
wallets={props.connectOptions?.wallets}
/>
);
}
if (screen === "network-switcher") {
content = (
c.id === walletChain.id) === undefined
? [walletChain, ...props.chains]
: props.chains
}
client={client}
closeModal={closeModal}
connectLocale={locale}
networkSelector={props.detailsModal?.networkSelector}
onBack={() => {
setScreen("main");
}}
/>
);
} else if (screen === "view-assets") {
content = (
{
setScreen("main");
}}
setScreen={setScreen}
supportedNFTs={props.supportedNFTs}
supportedTokens={props.supportedTokens}
theme={props.theme}
/>
);
} else if (screen === "view-nfts") {
content = (
{
setScreen("main");
}}
supportedNFTs={props.supportedNFTs}
theme={props.theme}
/>
);
} else if (screen === "view-tokens") {
content = (
{
setScreen("main");
}}
supportedTokens={props.supportedTokens}
/>
);
} else if (screen === "private-key" || screen === "export") {
content = (
{
setScreen("manage-wallet");
}}
theme={props.theme}
wallet={activeWallet}
/>
);
} else if (screen === "manage-wallet") {
content = (
{
setScreen("main");
}}
manageWallet={props.detailsModal?.manageWallet}
setScreen={setScreen}
/>
);
} else if (screen === "wallet-connect-receiver") {
content = (
{
setScreen("manage-wallet");
}}
/>
);
} else if (screen === "linked-profiles") {
content = (
setScreen("manage-wallet")}
setScreen={setScreen}
/>
);
} else if (screen === "link-profile") {
content = (
{
setScreen("linked-profiles");
}}
walletConnect={props.connectOptions?.walletConnect}
/>
);
}
// send funds
else if (screen === "send") {
content = (
{
setScreen("main");
}}
supportedTokens={props.supportedTokens}
/>
);
}
// receive funds
else if (screen === "receive") {
content = (
{
setScreen("main");
}}
walletId={activeWallet?.id}
/>
);
}
// thirdweb pay
else if (screen === "buy") {
const requestedChainId =
props.detailsModal?.payOptions?.prefillBuy?.chain?.id ||
walletChain?.id ||
props.chains[0]?.id ||
1;
content = (
setScreen("main")}
onSuccess={() => setScreen("main")}
supportedTokens={props.supportedTokens}
theme={props.theme}
style={{
border: "none",
borderRadius: radius.lg,
}}
tokenAddress={
props.displayBalanceToken?.[Number(requestedChainId)] as
| `0x${string}`
| undefined
}
/>
);
}
return (
{
if (!_open) {
closeModal();
if (props.detailsModal?.onClose) {
props.detailsModal?.onClose(screen);
}
}
}}
size="compact"
>
{activeAccount?.address && (
{content}
)}
);
}
/**
* When this button is clicked, it will switch to the screen where users
* can select a chain to switch to.
* @internal
*/
export function NetworkSwitcherButton(props: {
setScreen: Dispatch>;
disableSwitchChain: boolean;
displayBalanceToken: Record | undefined;
client: ThirdwebClient;
showBalanceInFiat?: SupportedFiatCurrency;
}) {
const { disableSwitchChain, setScreen, showBalanceInFiat, client } = props;
const walletChain = useActiveWalletChain();
if (!walletChain) {
return null;
}
return (
{
setScreen("network-switcher");
}}
type="button"
>
}
loadingComponent={
}
style={{
height: `${iconSize.md}px`,
width: `${iconSize.md}px`,
}}
/>
Unknown chain #{walletChain?.id}}
loadingComponent={}
/>
{showBalanceInFiat ? (
<>
}
formatFn={(props: AccountBalanceInfo) =>
formatAccountTokenBalance({ ...props, decimals: 7 })
}
loadingComponent={}
tokenAddress={
props.displayBalanceToken?.[Number(walletChain?.id)]
}
/>{" "}
` (${formatAccountFiatBalance({ ...props, decimals: 3 })})`
}
loadingComponent={}
showBalanceInFiat="USD"
tokenAddress={
props.displayBalanceToken?.[Number(walletChain?.id)]
}
/>
>
) : (
}
formatFn={(props: AccountBalanceInfo) =>
formatAccountTokenBalance({ ...props, decimals: 7 })
}
loadingComponent={}
tokenAddress={
props.displayBalanceToken?.[Number(walletChain?.id)]
}
/>
)}
);
}
const WalletInfoButton = /* @__PURE__ */ StyledButton((_) => {
const theme = useCustomTheme();
return {
all: "unset",
"&:hover": {
background: theme.colors.connectedButtonBgHover,
transition: "background 250ms ease",
},
alignItems: "center",
animation: `${fadeInAnimation} 300ms ease`,
background: theme.colors.connectedButtonBg,
border: `1px solid ${theme.colors.borderColor}`,
borderRadius: radius.md,
boxSizing: "border-box",
cursor: "pointer",
display: "inline-flex",
gap: spacing.xs,
height: "50px",
lineHeight: "normal",
minWidth: "165px",
overflow: "hidden",
padding: spacing.xs,
WebkitTapHighlightColor: "transparent",
};
});
/**
* @internal Export for tests
*/
export const StyledChevronRightIcon = /* @__PURE__ */ styled(
/* @__PURE__ */ ChevronRightIcon,
)(() => {
const theme = useCustomTheme();
return {
color: theme.colors.secondaryText,
};
});
/**
* @internal Exported for test
*/
export function ConnectedToSmartWallet(props: {
client: ThirdwebClient;
connectLocale: ConnectLocale;
}) {
const activeAccount = useActiveAccount();
const activeWallet = useActiveWallet();
const isSW = isSmartWallet(activeWallet);
const chain = useActiveWalletChain();
const { client, connectLocale: locale } = props;
const [isSmartWalletDeployed, setIsSmartWalletDeployed] = useState(false);
useEffect(() => {
if (activeAccount && isSW && activeAccount.address && chain) {
const contract = getContract({
address: activeAccount.address,
chain,
client,
});
isContractDeployed(contract).then((isDeployed) => {
setIsSmartWalletDeployed(isDeployed);
});
} else {
setIsSmartWalletDeployed(false);
}
}, [activeAccount, chain, client, isSW]);
const content = (
{locale.connectedToSmartWallet}
);
if (chain && activeAccount && isSW) {
return (
<>
{isSmartWalletDeployed ? (
{content}
) : (
{content}
)}
>
);
}
return null;
}
/**
* @internal Exported for tests
*/
export function InAppWalletUserInfo(props: {
client: ThirdwebClient;
locale: ConnectLocale;
}) {
const { client, locale } = props;
const account = useActiveAccount();
const activeWallet = useActiveWallet();
const adminWallet = useAdminWallet();
const { data: walletInfo } = useWalletInfo(activeWallet?.id);
const isSW = isSmartWallet(activeWallet);
const { data: walletName } = useQuery({
enabled: !!activeWallet?.id && !!walletInfo,
queryFn: async () => {
const lastAuthProvider = await getLastAuthProvider(webLocalStorage);
if (lastAuthProvider === "guest") {
return "Guest";
}
if (
lastAuthProvider &&
(activeWallet?.id === "inApp" || activeWallet?.id === "smart") &&
socialAuthOptions.includes(lastAuthProvider as SocialAuthOption)
) {
return (
lastAuthProvider.slice(0, 1).toUpperCase() + lastAuthProvider.slice(1)
);
}
return walletInfo?.name;
},
queryKey: [
"wallet-name",
{ walletAddress: account?.address, walletId: activeWallet?.id },
],
});
const userInfoQuery = useQuery({
enabled: !!adminWallet,
queryFn: async () => {
const isInAppWallet =
adminWallet &&
(adminWallet.id === "inApp" || adminWallet.id.startsWith("ecosystem."));
if (!isInAppWallet) {
return null;
}
let ecosystem: Ecosystem | undefined;
if (isEcosystemWallet(adminWallet)) {
const ecosystemWallet = adminWallet as Wallet;
const partnerId = ecosystemWallet.getConfig()?.partnerId;
ecosystem = {
id: ecosystemWallet.id,
partnerId,
};
}
const { getUserEmail, getUserPhoneNumber } = await import(
"../../../../wallets/in-app/web/lib/auth/index.js"
);
const [email, phone] = await Promise.all([
getUserEmail({
client: client,
ecosystem,
}),
getUserPhoneNumber({
client: client,
ecosystem,
}),
]);
return email || phone || null;
},
queryKey: ["in-app-wallet-user", client, account?.address],
});
if (!userInfoQuery.data && isSW) {
return ;
}
if (userInfoQuery.data || walletName) {
return (
{userInfoQuery.data || walletName}
);
}
return (
);
}
/**
* @internal Exported for tests
*/
export function SwitchNetworkButton(props: {
style?: React.CSSProperties;
className?: string;
switchNetworkBtnTitle?: string;
targetChain: Chain;
connectLocale: ConnectLocale;
}) {
const switchChain = useSwitchActiveWalletChain();
const [switching, setSwitching] = useState(false);
const locale = props.connectLocale;
const switchNetworkBtnTitle =
props.switchNetworkBtnTitle ?? locale.switchNetwork;
return (
);
}
type DetailsModalConnectOptions = {
connectModal?: ConnectButton_connectModalOptions;
walletConnect?: {
projectId?: string;
};
accountAbstraction?: SmartWalletOptions;
wallets?: Wallet[];
appMetadata?: AppMetadata;
chain?: Chain;
chains?: Chain[];
recommendedWallets?: Wallet[];
hiddenWallets?: WalletId[];
showAllWallets?: boolean;
};
export type UseWalletDetailsModalOptions = {
/**
* A client is the entry point to the thirdweb SDK.
* It is required for all other actions.
* You can create a client using the `createThirdwebClient` function. Refer to the [Creating a Client](https://portal.thirdweb.com/typescript/v5/client) documentation for more information.
*
* You must provide a `clientId` or `secretKey` in order to initialize a client. Pass `clientId` if you want for client-side usage and `secretKey` for server-side usage.
*
* ```tsx
* import { createThirdwebClient } from "thirdweb";
*
* const client = createThirdwebClient({
* clientId: "",
* })
* ```
*/
client: ThirdwebClient;
/**
* Set the theme for the Wallet Details Modal. By default it is set to `"dark"`
*
* theme can be set to either `"dark"`, `"light"` or a custom theme object.
* You can also import [`lightTheme`](https://portal.thirdweb.com/references/typescript/v5/lightTheme)
* or [`darkTheme`](https://portal.thirdweb.com/references/typescript/v5/darkTheme)
* functions from `thirdweb/react` to use the default themes as base and overrides parts of it.
* @example
* ```ts
* import { lightTheme } from "thirdweb/react";
*
* const customTheme = lightTheme({
* colors: {
* modalBg: 'red'
* }
* })
*
* ```
*/
theme?: "light" | "dark" | Theme;
/**
* Customize the tokens shown in the "Send Funds" screen in Details Modal for various networks.
*
* By default, The "Send Funds" screen shows a few popular tokens for default chains and the native token. For other chains it only shows the native token.
* @example
*
* supportedTokens prop allows you to customize this list as shown below which shows "Dai Stablecoin" when users wallet is connected to the "Base" mainnet.
*
* ```tsx
* import { useWalletDetailsModal } from 'thirdweb/react';
*
* function Example() {
* const detailsModal = useWalletDetailsModal();
*
* function handleClick() {
* detailsModal.open({
* client,
* supportedTokens:{
* 84532: [
* {
* address: '0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb', // token contract address
* name: 'Dai Stablecoin',
* symbol: 'DAI',
* icon: 'https://assets.coingecko.com/coins/images/9956/small/Badge_Dai.png?1687143508',
* },
* ],
* }
* });
* }
*
* return (
*
* );
* }
* ```
*/
supportedTokens?: SupportedTokens;
/**
* Customize the NFTs shown in the "View Funds" screen in Details Modal for various networks.
*
* By default, The "View Funds" screen shows a few popular tokens for default chains and the native token. For other chains it only shows the native token.
* @example
*
* supportedTokens prop allows you to customize this list as shown below which shows "Pudgy Penguins" when a users wallet is connected to Ethereum mainnet.
*
* ```tsx
* import { ConnectButton } from 'thirdweb/react';
*
* function Example() {
* return (
*
* );
* }
* ```
*/
supportedNFTs?: SupportedNFTs;
/**
* By default - Details Modal UI uses the `en-US` locale for english language users.
*
* You can customize the language used in the Details Modal UI by setting the `locale` prop.
*
* Refer to the [`LocaleId`](https://portal.thirdweb.com/references/typescript/v5/LocaleId) type for supported locales.
*/
locale?: LocaleId;
/**
* Array of chains that your app supports. They will be displayed in the network selector in the screen.
*
* This is only relevant if your app is a multi-chain app and works across multiple blockchains.
* If your app only works on a single blockchain, you should only specify the `chain` prop.
*
* You can create a `Chain` object using the [`defineChain`](https://portal.thirdweb.com/references/typescript/v5/defineChain) function.
* At minimum, you need to pass the `id` of the blockchain to `defineChain` function to create a `Chain` object.
*
* ```tsx
* import { defineChain } from "thirdweb/react";
*
* const polygon = defineChain({
* id: 137,
* });
* ```
*/
chains?: Chain[];
/**
* Show a "Request Testnet funds" link in Wallet Details Modal when user is connected to a testnet.
*
* By default it is `false`, If you want to show the "Request Testnet funds" link when user is connected to a testnet, set this prop to `true`
*/
showTestnetFaucet?: boolean;
/**
* customize the Network selector shown in the Wallet Details Modal
*/
networkSelector?: NetworkSelectorProps;
/**
* Hide the "Disconnect Wallet" button in the Wallet Details Modal.
*
* By default it is `false`
*/
hideDisconnect?: boolean;
/**
* Hide the "Switch Wallet" button in the Wallet Details Modal.
*
* By default it is `false`
*/
hideSwitchWallet?: boolean;
/**
* Callback to be called when a wallet is disconnected by clicking the "Disconnect Wallet" button in the Wallet Details Modal.
*
* ```tsx
* import { useWalletDetailsModal } from 'thirdweb/react';
*
* function Example() {
* const detailsModal = useWalletDetailsModal();
*
* function handleClick() {
* detailsModal.open({
* client,
* onDisconnect: ({ wallet, account }) => {
* console.log('disconnected', wallet, account);
* }
* });
* }
*
* return (
*
* );
* }
* ```
*/
onDisconnect?: (info: { wallet: Wallet; account: Account }) => void;
/**
* Render custom UI at the bottom of the Details Modal
* @param props - props passed to the footer component which includes a function to close the modal
* @example
* ```tsx
* function Example() {
* const detailsModal = useWalletDetailsModal();
*
* return (
*
* )
* }
*
* function CustomFooter(props: { close: () => void }) {
* return ...
* }
* ```
*/
footer?: (props: { close: () => void }) => JSX.Element;
/**
* Configure options for thirdweb Pay.
*
* thirdweb Pay allows users to buy tokens using crypto or fiat currency.
*/
payOptions?: Extract;
/**
* Display the balance of a token instead of the native token
* @example
* ```tsx
* const displayBalanceToken = {
* // show USDC balance when connected to Ethereum mainnet or Polygon
* [ethereum.id]: "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
* [polygon.id]: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
* }
* ```
*/
displayBalanceToken?: Record;
/**
* Options to configure the Connect UI shown when user clicks the "Connect Wallet" button in the Wallet Switcher screen.
*/
connectOptions?: DetailsModalConnectOptions;
/**
* Render custom UI for the connected wallet name in the `ConnectButton` Details Modal, overriding ENS name or wallet address.
*/
connectedAccountName?: React.ReactNode;
/**
* Use custom avatar URL for the connected wallet image in the `ConnectButton` Details Modal, overriding ENS avatar or Blobbie icon.
*/
connectedAccountAvatarUrl?: string;
/**
* Hide the "Send Funds" button in the Details Modal.
*
* By default the "Send Funds" button is shown.
*/
hideSendFunds?: boolean;
/**
* Hide the "Receive Funds" button in the Details Modal.
*
* By default the "Receive Funds" button is shown.
*/
hideReceiveFunds?: boolean;
/**
* Hide the "Buy Funds" button in the Details Modal.
*
* By default the "Buy Funds" button is shown.
*/
hideBuyFunds?: boolean;
/**
* All wallet IDs included in this array will be hidden from wallet selection when connected.
*/
hiddenWallets?: WalletId[];
/**
* When you click on "View Assets", by default the "Tokens" tab is shown first.
* If you want to show the "NFTs" tab first, change the order of the asset tabs to: ["nft", "token"]
* Note: If an empty array is passed, the [View Funds] button will be hidden
*/
assetTabs?: AssetTabs[];
/**
* Show the token balance's value in fiat.
* Note: Not all tokens are resolvable to a fiat value. In that case, nothing will be shown.
*/
showBalanceInFiat?: SupportedFiatCurrency;
/**
* Configure options for managing the connected wallet.
*/
manageWallet?: {
/**
* Allow linking other profiles to the connected wallet.
*
* By default it is `true`.
*/
allowLinkingProfiles?: boolean;
};
/**
* The callback function for when the modal is closed
* @param screen The name of the screen that was being shown when user closed the modal
*/
onClose?: (screen: string) => void;
/**
* The initial screen to show when the modal opens.
*
* @defaultValue "main"
*
* @example
* ```tsx
* // Open directly to the Export Private Key screen
* detailsModal.open({
* client,
* screen: "export"
* });
* ```
*/
screen?: WalletDetailsModalScreen;
};
/**
* Hook to open the Wallet Details Modal that shows various information about the connected wallet and allows users to perform various actions like sending funds, receiving funds, switching networks, Buying tokens, etc.
*
* @example
* ### Basic usage
* ```tsx
* import { createThirdwebClient } from "thirdweb";
* import { useWalletDetailsModal } from "thirdweb/react";
*
* const client = createThirdwebClient({
* clientId: "",
* });
*
* function Example() {
* const detailsModal = useWalletDetailsModal();
*
* function handleClick() {
* detailsModal.open({ client, theme: 'light' });
* }
*
* return
* }
* ```
*
* ### Open directly to Export Private Key screen
* ```tsx
* detailsModal.open({
* client,
* screen: "export"
* });
* ```
*
* ### Callback for when the modal is closed
* ```tsx
* detailsModal.open({
* client,
* onClose: (screen: string) => console.log({ screen })
* });
* ```
*
* @wallet
*/
export function useWalletDetailsModal() {
const account = useActiveAccount();
const setRootEl = useContext(SetRootElementContext);
function closeModal() {
setRootEl(null);
}
function openModal(props: UseWalletDetailsModalOptions) {
if (!account) {
throw new Error("Wallet is not connected.");
}
getConnectLocale(props.locale || "en_US")
.then((locale) => {
setRootEl(
{
props.onDisconnect?.(info);
closeModal();
}}
supportedNFTs={props.supportedNFTs}
supportedTokens={props.supportedTokens}
theme={props.theme || "dark"}
/>,
);
})
.catch(() => {
closeModal();
});
}
return {
open: openModal,
};
}