Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tasty-pumpkins-relax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"thirdweb": minor
---

Add SiweOptions in useConnectModal
64 changes: 64 additions & 0 deletions apps/playground-web/src/app/connect/auth/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -46,6 +47,12 @@ export default function Page() {
<section className="space-y-8">
<SmartAccountAuth />
</section>

<div className="h-14" />

<section className="space-y-8">
<BasicAuthHook />
</section>
</main>
</ThirdwebProvider>
);
Expand Down Expand Up @@ -101,6 +108,63 @@ export function AuthButton() {
);
}

function BasicAuthHook() {
return (
<>
<div className="space-y-2">
<h2 className="font-semibold text-2xl tracking-tight sm:text-3xl">
Auth with your own UI
</h2>
<p className="max-w-[600px]">
Use the `useConnectModal` hook to add authentication to your app with
your own UI.
</p>
</div>

<CodeExample
preview={<BasicAuthHookPreview />}
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 <Button type="button" onClick={onClick}>{isLoggedIn ? "Sign out" : "Sign in"}</Button>;
}
`}
lang="tsx"
/>
</>
);
}

function GatedContent() {
return (
<>
Expand Down
52 changes: 52 additions & 0 deletions apps/playground-web/src/components/auth/auth-hook.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Button type="button" onClick={onClick}>
{isLoggedIn ? "Sign out" : "Sign in"}
</Button>
);
}
36 changes: 36 additions & 0 deletions apps/playground-web/src/components/auth/basic-auth-hook.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="flex flex-col gap-5">
<div className="mx-auto">
<AuthHook />
</div>
{jwt && !!jwt.value && (
<table className="table-auto border-collapse rounded-lg backdrop-blur">
<tbody>
{Object.keys(jwt).map((key) => (
<tr key={key} className="">
<td className="rounded border p-2">{key}</td>
<td
className={cn(
"max-h-[200px] max-w-[250px] overflow-y-auto whitespace-normal break-words border p-2",
{
"text-xs": key === "value",
},
)}
>
{jwt[key as keyof RequestCookie]}
</td>
</tr>
))}
</tbody>
</table>
)}
</div>
);
}
40 changes: 38 additions & 2 deletions apps/playground-web/src/components/sign-in/modal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client";

import {
type ConnectButtonProps,
useActiveAccount,
useActiveWallet,
useConnectModal,
Expand All @@ -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;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ export const ConnectModalContent = (props: {
const signatureScreen = (
<SignatureScreen
onDone={onClose}
onClose={onClose}
modalSize={props.size}
termsOfServiceUrl={props.meta.termsOfServiceUrl}
privacyPolicyUrl={props.meta.privacyPolicyUrl}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type Status = "signing" | "failed" | "idle";

export const SignatureScreen: React.FC<{
onDone: (() => void) | undefined;
onClose?: (() => void) | undefined;
modalSize: "compact" | "wide";
termsOfServiceUrl?: string;
privacyPolicyUrl?: string;
Expand All @@ -42,6 +43,7 @@ export const SignatureScreen: React.FC<{
const {
onDone,
modalSize,
onClose,
termsOfServiceUrl,
privacyPolicyUrl,
connectLocale,
Expand Down Expand Up @@ -145,6 +147,7 @@ export const SignatureScreen: React.FC<{
variant="secondary"
data-testid="disconnect-button"
onClick={() => {
onClose?.();
disconnect(wallet);
}}
style={{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -62,6 +63,7 @@ export function useConnectModal() {
<Modal
{...props}
onConnect={(w) => {
if (props.auth) return;
resolve(w);
cleanup();
}}
Expand Down Expand Up @@ -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}
Expand Down Expand Up @@ -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
Loading