"use client"; import styled from "@emotion/styled"; import { useEffect, useMemo, useState } from "react"; import { getDefaultWallets } from "../../../../wallets/defaultWallets.js"; import { AccountProvider } from "../../../core/account/provider.js"; import { iconSize } from "../../../core/design-system/index.js"; import { useSiweAuth } from "../../../core/hooks/auth/useSiweAuth.js"; import type { ConnectButtonProps } from "../../../core/hooks/connection/ConnectButtonProps.js"; import { useActiveAccount } from "../../../core/hooks/wallets/useActiveAccount.js"; import { useActiveWallet } from "../../../core/hooks/wallets/useActiveWallet.js"; import { useActiveWalletConnectionStatus } from "../../../core/hooks/wallets/useActiveWalletConnectionStatus.js"; import { useConnectionManager } from "../../../core/providers/connection-manager.js"; import { defaultTokens } from "../../../core/utils/defaultTokens.js"; import { useSetIsWalletModalOpen, WalletUIStatesProvider, } from "../../providers/wallet-ui-states-provider.js"; import { canFitWideModal } from "../../utils/canFitWideModal.js"; import { usePreloadWalletProviders } from "../../utils/usePreloadWalletProviders.js"; import { AutoConnect } from "../AutoConnect/AutoConnect.js"; import { Container } from "../components/basic.js"; import { Button } from "../components/buttons.js"; import { Modal } from "../components/Modal.js"; import { Spinner } from "../components/Spinner.js"; import { fadeInAnimation } from "../design-system/animations.js"; import { ConnectedWalletDetails } from "./Details.js"; import { LockIcon } from "./icons/LockIcon.js"; import { useConnectLocale } from "./locale/getConnectLocale.js"; import type { ConnectLocale } from "./locale/types.js"; import ConnectModal from "./Modal/ConnectModal.js"; import { SignatureScreen } from "./screens/SignatureScreen.js"; const TW_CONNECT_WALLET = "tw-connect-wallet"; /** * A fully featured wallet connection component that allows to: * * - Connect to 500+ external wallets * - Connect with email, phone, passkey or socials * - Convert any wallet to a ERC4337 smart wallet for gasless transactions * - Sign in with ethereum (Auth) * * Once connected, the component allows to: * * - Resolve ENS names and avatars * - Manage multiple connected wallets * - Send and receive native tokens and ERC20 tokens * - View ERC20 tokens and NFTs * - Onramp, bridge and swap tokens * - Switch chains * - Connect to another app with WalletConnect * * @example * * ## Default setup * * ```tsx * import { createThirdwebClient } from "thirdweb"; * import { ConnectButton } from "thirdweb/react"; * * const client = createThirdwebClient({ clientId: "YOUR_CLIENT_ID" }); * * * ``` * * [View all available config options](https://portal.thirdweb.com/references/typescript/v5/ConnectButtonProps) * * ## Customization options * * ### Customizing wallet options * * ```tsx * * ``` * * [View all available wallets](https://portal.thirdweb.com/typescript/v5/supported-wallets) * * ### Customizing the default chain to connect to * * ```tsx * import { sepolia } from "thirdweb/chains"; * * * ``` * * ### Enabling Account Abstraction * * By passing the `accountAbstraction` prop, ALL connected wallets will be converted to smart accounts. * And by setting `sponsorGas` to `true`, all transactions done with those smart accounts will be sponsored. * * ```tsx * ; * ``` * * Note that this prop doesn't affect ecosystem wallets. Ecosystem wallets will only be converted to smart accounts if the ecosystem owner has enabled account abstraction. * * ### Enabling sign in with ethereum (Auth) * * ```tsx * { * console.log("checking if logged in!", { address }); * return await isLoggedIn(); * }, * doLogin: async (params) => { * console.log("logging in!"); * await login(params); * }, * getLoginPayload: async ({ address }) => * generatePayload({ address }), * doLogout: async () => { * console.log("logging out!"); * await logout(); * }, * }} * />; * ``` * * ### Customizing the theme * * ```tsx * * ``` * * For more granular control, you can also pass a custom theme object: * * ```tsx * * ``` * * [View all available themes properties](https://portal.thirdweb.com/references/typescript/v5/Theme) * * ### Overriding styles using class names * * Some elements in this component have classes with a `tw-` prefix. * You can target these classes in your own CSS stylesheet to override their styles. * * In some cases, you may need to use the `!important` flag for the override to take effect. Do not use on auto-generated class names, as they may change between builds. * * ```css * .tw-back-button { * background-color: red !important; * } * ``` * * ### Changing the display language * * ```tsx * * ``` * * [View all available locales](https://portal.thirdweb.com/references/typescript/v5/LocaleId) * * ### Customizing the connect button UI * * ```tsx * * ``` * * ### Customizing the modal UI * * ```tsx * * ``` * * ### Customizing details button UI (after connecting) * * ```tsx * * ``` * * [View all available auth helper functions](https://portal.thirdweb.com/references/typescript/v5/createAuth) * * ### Customizing the Auth sign in button (after connecting, but before authenticating) * * ```tsx * ; * ``` * * ### Customizing supported Tokens and NFTs * * These tokens and NFTs will be shown in the modal when the user clicks "View Assets", as well as the send token screen. * * ```tsx * * ``` * * ### Customizing the orders of the tabs in the [View Funds] screen * 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 * * ```tsx * * ``` * * ### Callback for when the details modal is closed * ```tsx * { * console.log({ screen }); * } * }} * /> * ``` * * @param props * Props for the `ConnectButton` component * * Refer to [ConnectButtonProps](https://portal.thirdweb.com/references/typescript/v5/ConnectButtonProps) to see the available props. * * @returns A JSX element that renders the component. * * @component * @walletConnection */ export function ConnectButton(props: ConnectButtonProps) { const wallets = useMemo( () => props.wallets || getDefaultWallets({ appMetadata: props.appMetadata, chains: props.chains, executionMode: props.accountAbstraction ? { mode: "EIP4337", smartAccount: props.accountAbstraction, } : undefined, }), [props.wallets, props.appMetadata, props.chains, props.accountAbstraction], ); const localeQuery = useConnectLocale(props.locale || "en_US"); const connectionManager = useConnectionManager(); const activeAccount = useActiveAccount(); const activeWallet = useActiveWallet(); const siweAuth = useSiweAuth(activeWallet, activeAccount, props.auth); const hiddenWallets = props.hiddenWallets || props.detailsModal?.hiddenWallets; usePreloadWalletProviders({ wallets, }); // Add props.chain and props.chains to defined chains store useEffect(() => { if (props.chain) { connectionManager.defineChains([props.chain]); } }, [props.chain, connectionManager]); useEffect(() => { if (props.chains) { connectionManager.defineChains(props.chains); } }, [props.chains, connectionManager]); const size = useMemo(() => { return !canFitWideModal() || wallets.length === 1 ? "compact" : props.connectModal?.size || "compact"; }, [wallets.length, props.connectModal?.size]); const preferredChain = props.accountAbstraction?.chain || props.chain || props.chains?.[0]; const autoConnectComp = props.autoConnect !== false && ( ); if (!localeQuery.data) { const combinedClassName = `${props.connectButton?.className || ""} ${TW_CONNECT_WALLET}`; return ( {autoConnectComp} ); } return ( {autoConnectComp} ); } function ConnectButtonInner( props: ConnectButtonProps & { connectLocale: ConnectLocale; siweAuth: ReturnType; }, ) { const siweAuth = props.siweAuth; const activeAccount = useActiveAccount(); const [showSignatureModal, setShowSignatureModal] = useState(false); const hiddenWallets = props.hiddenWallets || props.detailsModal?.hiddenWallets; // if wallet gets disconnected suddently, close the signature modal if it's open useEffect(() => { if (!activeAccount) { setShowSignatureModal(false); } }, [activeAccount]); const theme = props.theme || "dark"; const connectionStatus = useActiveWalletConnectionStatus(); const locale = props.connectLocale; const isLoading = connectionStatus === "connecting"; const connectButtonLabel = props.connectButton?.label || locale.defaultButtonTitle; const setIsWalletModalOpen = useSetIsWalletModalOpen(); const supportedTokens = useMemo(() => { if (!props.supportedTokens) { return undefined; } const tokens = { ...defaultTokens }; for (const k in props.supportedTokens) { const key = Number(k); const tokenList = props.supportedTokens[key]; if (tokenList) { tokens[key] = tokenList; } } return tokens; }, [props.supportedTokens]); if (!activeAccount) { // Connect Wallet button const combinedClassName = `${props.connectButton?.className || ""} ${TW_CONNECT_WALLET}`; return ( { setIsWalletModalOpen(true); }} style={{ fontSize: "16px", height: "50px", minWidth: "165px", ...props.connectButton?.style, }} type="button" variant="primary" > {isLoading ? ( ) : ( connectButtonLabel )} ); } if (siweAuth.requiresAuth) { // loading state if loading // TODO: figure out a way to consolidate the loading states with the ones from locale loading if (siweAuth.isPending || siweAuth.isLoggingIn || siweAuth.isLoggingOut) { const combinedClassName = `${props.connectButton?.className || ""} ${TW_CONNECT_WALLET}`; return ( ); } // sign in button + modal if *not* loading and *not* logged in if (!siweAuth.isLoggedIn) { return ( <> setShowSignatureModal(false)} privacyPolicyUrl={props.connectModal?.privacyPolicyUrl} termsOfServiceUrl={props.connectModal?.termsOfServiceUrl} /> ); } // otherwise, show the details button } return ( { // logout on explicit disconnect! if (siweAuth.requiresAuth) { siweAuth.doLogout(); } props.onDisconnect?.(info); }} supportedNFTs={props.supportedNFTs} supportedTokens={supportedTokens} switchButton={props.switchButton} theme={theme} /> ); } const AnimatedButton = /* @__PURE__ */ styled(Button)({ animation: `${fadeInAnimation} 300ms ease`, });