diff --git a/.changeset/tasty-pumpkins-relax.md b/.changeset/tasty-pumpkins-relax.md new file mode 100644 index 00000000000..0c8394d039b --- /dev/null +++ b/.changeset/tasty-pumpkins-relax.md @@ -0,0 +1,5 @@ +--- +"thirdweb": minor +--- + +Add SiweOptions in useConnectModal diff --git a/apps/playground-web/src/app/connect/auth/page.tsx b/apps/playground-web/src/app/connect/auth/page.tsx index f2ab05f7cea..60eba5479fd 100644 --- a/apps/playground-web/src/app/connect/auth/page.tsx +++ b/apps/playground-web/src/app/connect/auth/page.tsx @@ -5,6 +5,7 @@ import { CodeExample } from "@/components/code/code-example"; import ThirdwebProvider from "@/components/thirdweb-provider"; import { metadataBase } from "@/lib/constants"; import type { Metadata } from "next"; +import { BasicAuthHookPreview } from "../../../components/auth/basic-auth-hook"; import { APIHeader } from "../../../components/blocks/APIHeader"; export const metadata: Metadata = { @@ -46,6 +47,12 @@ export default function Page() {
+ +
+ +
+ +
); @@ -101,6 +108,63 @@ export function AuthButton() { ); } +function BasicAuthHook() { + return ( + <> +
+

+ Auth with your own UI +

+

+ Use the `useConnectModal` hook to add authentication to your app with + your own UI. +

+
+ + } + code={`"use client"; + +import { + generatePayload, + isLoggedIn, + login, + logout, +} from "@/app/connect/auth/server/actions/auth"; +import { THIRDWEB_CLIENT } from "@/lib/client"; +import { type SiweAuthOptions, useConnectModal } from "thirdweb/react"; + +const auth: SiweAuthOptions = { + isLoggedIn: (address) => isLoggedIn(address), + doLogin: (params) => login(params), + getLoginPayload: ({ address }) => generatePayload({ address }), + doLogout: () => logout(), +}; + +export function AuthHook() { + const { connect } = useConnectModal(); + const wallet = useActiveWallet(); + const { isLoggedIn } = useSiweAuth(wallet, wallet?.getAccount(), auth); + + const onClick = () => { + if (isLoggedIn) { + auth.doLogout(); + } else { + connect({ + auth, + }); + } + }; + + return ; +} +`} + lang="tsx" + /> + + ); +} + function GatedContent() { return ( <> diff --git a/apps/playground-web/src/components/auth/auth-hook.tsx b/apps/playground-web/src/components/auth/auth-hook.tsx new file mode 100644 index 00000000000..a384adc74ab --- /dev/null +++ b/apps/playground-web/src/components/auth/auth-hook.tsx @@ -0,0 +1,52 @@ +"use client"; + +import { + generatePayload, + isLoggedIn, + login, + logout, +} from "@/app/connect/auth/server/actions/auth"; +import { + type SiweAuthOptions, + useActiveWallet, + useConnectModal, + useSiweAuth, +} from "thirdweb/react"; +import { Button } from "../ui/button"; + +const auth: SiweAuthOptions = { + isLoggedIn: (address) => isLoggedIn(address), + doLogin: (params) => login(params), + getLoginPayload: ({ address }) => + generatePayload({ address, chainId: 84532 }), + doLogout: () => logout(), +}; + +export function AuthHook() { + const { connect } = useConnectModal(); + const wallet = useActiveWallet(); + const { isLoggedIn, doLogout } = useSiweAuth( + wallet, + wallet?.getAccount(), + auth, + ); + + const onClick = async () => { + if (isLoggedIn) { + await doLogout(); + } else { + await connect({ + auth, + onConnect: (wallet) => { + console.log("connected to", wallet); + }, + }); + } + }; + + return ( + + ); +} diff --git a/apps/playground-web/src/components/auth/basic-auth-hook.tsx b/apps/playground-web/src/components/auth/basic-auth-hook.tsx new file mode 100644 index 00000000000..1d2faf78b76 --- /dev/null +++ b/apps/playground-web/src/components/auth/basic-auth-hook.tsx @@ -0,0 +1,36 @@ +import type { RequestCookie } from "next/dist/compiled/@edge-runtime/cookies"; +import { cookies } from "next/headers"; +import { cn } from "../../lib/utils"; +import { AuthHook } from "./auth-hook"; + +export async function BasicAuthHookPreview() { + const jwt = (await cookies()).get("jwt"); + return ( +
+
+ +
+ {jwt && !!jwt.value && ( + + + {Object.keys(jwt).map((key) => ( + + + + + ))} + +
{key} + {jwt[key as keyof RequestCookie]} +
+ )} +
+ ); +} diff --git a/apps/playground-web/src/components/sign-in/modal.tsx b/apps/playground-web/src/components/sign-in/modal.tsx index 96d46b0a4d1..f824a9f9bd5 100644 --- a/apps/playground-web/src/components/sign-in/modal.tsx +++ b/apps/playground-web/src/components/sign-in/modal.tsx @@ -1,6 +1,7 @@ "use client"; import { + type ConnectButtonProps, useActiveAccount, useActiveWallet, useConnectModal, @@ -10,14 +11,49 @@ import { shortenAddress } from "thirdweb/utils"; import { THIRDWEB_CLIENT } from "../../lib/client"; import { Button } from "../ui/button"; -export function ModalPreview() { +const playgroundAuth: ConnectButtonProps["auth"] = { + async doLogin() { + try { + localStorage.setItem("playground-loggedin", "true"); + } catch { + // ignore + } + }, + async doLogout() { + localStorage.removeItem("playground-loggedin"); + }, + async getLoginPayload(params) { + return { + domain: "", + address: params.address, + statement: "", + version: "", + nonce: "", + issued_at: "", + expiration_time: "", + invalid_before: "", + }; + }, + async isLoggedIn() { + try { + return !!localStorage.getItem("playground-loggedin"); + } catch { + return false; + } + }, +}; + +export function ModalPreview({ enableAuth }: { enableAuth?: boolean }) { const account = useActiveAccount(); const wallet = useActiveWallet(); const connectMutation = useConnectModal(); const { disconnect } = useDisconnect(); const connect = async () => { - const wallet = await connectMutation.connect({ client: THIRDWEB_CLIENT }); + const wallet = await connectMutation.connect({ + client: THIRDWEB_CLIENT, + auth: enableAuth ? playgroundAuth : undefined, + }); return wallet; }; diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/Modal/ConnectModalContent.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/Modal/ConnectModalContent.tsx index 9b9a82be125..b54dbfb5ea7 100644 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/Modal/ConnectModalContent.tsx +++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/Modal/ConnectModalContent.tsx @@ -235,6 +235,7 @@ export const ConnectModalContent = (props: { const signatureScreen = ( void) | undefined; + onClose?: (() => void) | undefined; modalSize: "compact" | "wide"; termsOfServiceUrl?: string; privacyPolicyUrl?: string; @@ -42,6 +43,7 @@ export const SignatureScreen: React.FC<{ const { onDone, modalSize, + onClose, termsOfServiceUrl, privacyPolicyUrl, connectLocale, @@ -145,6 +147,7 @@ export const SignatureScreen: React.FC<{ variant="secondary" data-testid="disconnect-button" onClick={() => { + onClose?.(); disconnect(wallet); }} style={{ diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/useConnectModal.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/useConnectModal.tsx index 104b11b97b9..e6a59b2ed1a 100644 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/useConnectModal.tsx +++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/useConnectModal.tsx @@ -6,6 +6,7 @@ import type { Wallet } from "../../../../wallets/interfaces/wallet.js"; import type { SmartWalletOptions } from "../../../../wallets/smart/types.js"; import type { AppMetadata } from "../../../../wallets/types.js"; import type { Theme } from "../../../core/design-system/index.js"; +import type { SiweAuthOptions } from "../../../core/hooks/auth/useSiweAuth.js"; import { SetRootElementContext } from "../../../core/providers/RootElementContext.js"; import { WalletUIStatesProvider } from "../../providers/wallet-ui-states-provider.js"; import { canFitWideModal } from "../../utils/canFitWideModal.js"; @@ -62,6 +63,7 @@ export function useConnectModal() { { + if (props.auth) return; resolve(w); cleanup(); }} @@ -129,8 +131,7 @@ function Modal( onClose={props.onClose} shouldSetActive={props.setActive === undefined ? true : props.setActive} accountAbstraction={props.accountAbstraction} - // TODO: not set up in `useConnectModal` for some reason? - auth={undefined} + auth={props.auth} chain={props.chain} client={props.client} connectLocale={props.connectLocale} @@ -432,6 +433,14 @@ export type UseConnectModalOptions = { * If you want to hide the branding, set this prop to `false` */ showThirdwebBranding?: boolean; + + /** + * Enable SIWE (Sign in with Ethererum) by passing an object of type `SiweAuthOptions` to + * enforce the users to sign a message after connecting their wallet to authenticate themselves. + * + * Refer to the [`SiweAuthOptions`](https://portal.thirdweb.com/references/typescript/v5/SiweAuthOptions) for more details + */ + auth?: SiweAuthOptions; }; // TODO: consilidate Button/Embed/Modal props into one type with extras