Skip to content

Commit a19c0c9

Browse files
[SDK] Feature: Add SiweOptions for useConnectModal (#6317)
Co-authored-by: Joaquim Verges <joaquim.verges@gmail.com>
1 parent cf202c0 commit a19c0c9

File tree

8 files changed

+210
-4
lines changed

8 files changed

+210
-4
lines changed

.changeset/tasty-pumpkins-relax.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"thirdweb": minor
3+
---
4+
5+
Add SiweOptions in useConnectModal

apps/playground-web/src/app/connect/auth/page.tsx

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { CodeExample } from "@/components/code/code-example";
55
import ThirdwebProvider from "@/components/thirdweb-provider";
66
import { metadataBase } from "@/lib/constants";
77
import type { Metadata } from "next";
8+
import { BasicAuthHookPreview } from "../../../components/auth/basic-auth-hook";
89
import { APIHeader } from "../../../components/blocks/APIHeader";
910

1011
export const metadata: Metadata = {
@@ -46,6 +47,12 @@ export default function Page() {
4647
<section className="space-y-8">
4748
<SmartAccountAuth />
4849
</section>
50+
51+
<div className="h-14" />
52+
53+
<section className="space-y-8">
54+
<BasicAuthHook />
55+
</section>
4956
</main>
5057
</ThirdwebProvider>
5158
);
@@ -101,6 +108,63 @@ export function AuthButton() {
101108
);
102109
}
103110

111+
function BasicAuthHook() {
112+
return (
113+
<>
114+
<div className="space-y-2">
115+
<h2 className="font-semibold text-2xl tracking-tight sm:text-3xl">
116+
Auth with your own UI
117+
</h2>
118+
<p className="max-w-[600px]">
119+
Use the `useConnectModal` hook to add authentication to your app with
120+
your own UI.
121+
</p>
122+
</div>
123+
124+
<CodeExample
125+
preview={<BasicAuthHookPreview />}
126+
code={`"use client";
127+
128+
import {
129+
generatePayload,
130+
isLoggedIn,
131+
login,
132+
logout,
133+
} from "@/app/connect/auth/server/actions/auth";
134+
import { THIRDWEB_CLIENT } from "@/lib/client";
135+
import { type SiweAuthOptions, useConnectModal } from "thirdweb/react";
136+
137+
const auth: SiweAuthOptions = {
138+
isLoggedIn: (address) => isLoggedIn(address),
139+
doLogin: (params) => login(params),
140+
getLoginPayload: ({ address }) => generatePayload({ address }),
141+
doLogout: () => logout(),
142+
};
143+
144+
export function AuthHook() {
145+
const { connect } = useConnectModal();
146+
const wallet = useActiveWallet();
147+
const { isLoggedIn } = useSiweAuth(wallet, wallet?.getAccount(), auth);
148+
149+
const onClick = () => {
150+
if (isLoggedIn) {
151+
auth.doLogout();
152+
} else {
153+
connect({
154+
auth,
155+
});
156+
}
157+
};
158+
159+
return <Button type="button" onClick={onClick}>{isLoggedIn ? "Sign out" : "Sign in"}</Button>;
160+
}
161+
`}
162+
lang="tsx"
163+
/>
164+
</>
165+
);
166+
}
167+
104168
function GatedContent() {
105169
return (
106170
<>
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
"use client";
2+
3+
import {
4+
generatePayload,
5+
isLoggedIn,
6+
login,
7+
logout,
8+
} from "@/app/connect/auth/server/actions/auth";
9+
import {
10+
type SiweAuthOptions,
11+
useActiveWallet,
12+
useConnectModal,
13+
useSiweAuth,
14+
} from "thirdweb/react";
15+
import { Button } from "../ui/button";
16+
17+
const auth: SiweAuthOptions = {
18+
isLoggedIn: (address) => isLoggedIn(address),
19+
doLogin: (params) => login(params),
20+
getLoginPayload: ({ address }) =>
21+
generatePayload({ address, chainId: 84532 }),
22+
doLogout: () => logout(),
23+
};
24+
25+
export function AuthHook() {
26+
const { connect } = useConnectModal();
27+
const wallet = useActiveWallet();
28+
const { isLoggedIn, doLogout } = useSiweAuth(
29+
wallet,
30+
wallet?.getAccount(),
31+
auth,
32+
);
33+
34+
const onClick = async () => {
35+
if (isLoggedIn) {
36+
await doLogout();
37+
} else {
38+
await connect({
39+
auth,
40+
onConnect: (wallet) => {
41+
console.log("connected to", wallet);
42+
},
43+
});
44+
}
45+
};
46+
47+
return (
48+
<Button type="button" onClick={onClick}>
49+
{isLoggedIn ? "Sign out" : "Sign in"}
50+
</Button>
51+
);
52+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import type { RequestCookie } from "next/dist/compiled/@edge-runtime/cookies";
2+
import { cookies } from "next/headers";
3+
import { cn } from "../../lib/utils";
4+
import { AuthHook } from "./auth-hook";
5+
6+
export async function BasicAuthHookPreview() {
7+
const jwt = (await cookies()).get("jwt");
8+
return (
9+
<div className="flex flex-col gap-5">
10+
<div className="mx-auto">
11+
<AuthHook />
12+
</div>
13+
{jwt && !!jwt.value && (
14+
<table className="table-auto border-collapse rounded-lg backdrop-blur">
15+
<tbody>
16+
{Object.keys(jwt).map((key) => (
17+
<tr key={key} className="">
18+
<td className="rounded border p-2">{key}</td>
19+
<td
20+
className={cn(
21+
"max-h-[200px] max-w-[250px] overflow-y-auto whitespace-normal break-words border p-2",
22+
{
23+
"text-xs": key === "value",
24+
},
25+
)}
26+
>
27+
{jwt[key as keyof RequestCookie]}
28+
</td>
29+
</tr>
30+
))}
31+
</tbody>
32+
</table>
33+
)}
34+
</div>
35+
);
36+
}

apps/playground-web/src/components/sign-in/modal.tsx

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"use client";
22

33
import {
4+
type ConnectButtonProps,
45
useActiveAccount,
56
useActiveWallet,
67
useConnectModal,
@@ -10,14 +11,49 @@ import { shortenAddress } from "thirdweb/utils";
1011
import { THIRDWEB_CLIENT } from "../../lib/client";
1112
import { Button } from "../ui/button";
1213

13-
export function ModalPreview() {
14+
const playgroundAuth: ConnectButtonProps["auth"] = {
15+
async doLogin() {
16+
try {
17+
localStorage.setItem("playground-loggedin", "true");
18+
} catch {
19+
// ignore
20+
}
21+
},
22+
async doLogout() {
23+
localStorage.removeItem("playground-loggedin");
24+
},
25+
async getLoginPayload(params) {
26+
return {
27+
domain: "",
28+
address: params.address,
29+
statement: "",
30+
version: "",
31+
nonce: "",
32+
issued_at: "",
33+
expiration_time: "",
34+
invalid_before: "",
35+
};
36+
},
37+
async isLoggedIn() {
38+
try {
39+
return !!localStorage.getItem("playground-loggedin");
40+
} catch {
41+
return false;
42+
}
43+
},
44+
};
45+
46+
export function ModalPreview({ enableAuth }: { enableAuth?: boolean }) {
1447
const account = useActiveAccount();
1548
const wallet = useActiveWallet();
1649
const connectMutation = useConnectModal();
1750
const { disconnect } = useDisconnect();
1851

1952
const connect = async () => {
20-
const wallet = await connectMutation.connect({ client: THIRDWEB_CLIENT });
53+
const wallet = await connectMutation.connect({
54+
client: THIRDWEB_CLIENT,
55+
auth: enableAuth ? playgroundAuth : undefined,
56+
});
2157
return wallet;
2258
};
2359

packages/thirdweb/src/react/web/ui/ConnectWallet/Modal/ConnectModalContent.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ export const ConnectModalContent = (props: {
235235
const signatureScreen = (
236236
<SignatureScreen
237237
onDone={onClose}
238+
onClose={onClose}
238239
modalSize={props.size}
239240
termsOfServiceUrl={props.meta.termsOfServiceUrl}
240241
privacyPolicyUrl={props.meta.privacyPolicyUrl}

packages/thirdweb/src/react/web/ui/ConnectWallet/screens/SignatureScreen.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ type Status = "signing" | "failed" | "idle";
3232

3333
export const SignatureScreen: React.FC<{
3434
onDone: (() => void) | undefined;
35+
onClose?: (() => void) | undefined;
3536
modalSize: "compact" | "wide";
3637
termsOfServiceUrl?: string;
3738
privacyPolicyUrl?: string;
@@ -42,6 +43,7 @@ export const SignatureScreen: React.FC<{
4243
const {
4344
onDone,
4445
modalSize,
46+
onClose,
4547
termsOfServiceUrl,
4648
privacyPolicyUrl,
4749
connectLocale,
@@ -145,6 +147,7 @@ export const SignatureScreen: React.FC<{
145147
variant="secondary"
146148
data-testid="disconnect-button"
147149
onClick={() => {
150+
onClose?.();
148151
disconnect(wallet);
149152
}}
150153
style={{

packages/thirdweb/src/react/web/ui/ConnectWallet/useConnectModal.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { Wallet } from "../../../../wallets/interfaces/wallet.js";
66
import type { SmartWalletOptions } from "../../../../wallets/smart/types.js";
77
import type { AppMetadata } from "../../../../wallets/types.js";
88
import type { Theme } from "../../../core/design-system/index.js";
9+
import type { SiweAuthOptions } from "../../../core/hooks/auth/useSiweAuth.js";
910
import { SetRootElementContext } from "../../../core/providers/RootElementContext.js";
1011
import { WalletUIStatesProvider } from "../../providers/wallet-ui-states-provider.js";
1112
import { canFitWideModal } from "../../utils/canFitWideModal.js";
@@ -62,6 +63,7 @@ export function useConnectModal() {
6263
<Modal
6364
{...props}
6465
onConnect={(w) => {
66+
if (props.auth) return;
6567
resolve(w);
6668
cleanup();
6769
}}
@@ -129,8 +131,7 @@ function Modal(
129131
onClose={props.onClose}
130132
shouldSetActive={props.setActive === undefined ? true : props.setActive}
131133
accountAbstraction={props.accountAbstraction}
132-
// TODO: not set up in `useConnectModal` for some reason?
133-
auth={undefined}
134+
auth={props.auth}
134135
chain={props.chain}
135136
client={props.client}
136137
connectLocale={props.connectLocale}
@@ -432,6 +433,14 @@ export type UseConnectModalOptions = {
432433
* If you want to hide the branding, set this prop to `false`
433434
*/
434435
showThirdwebBranding?: boolean;
436+
437+
/**
438+
* Enable SIWE (Sign in with Ethererum) by passing an object of type `SiweAuthOptions` to
439+
* enforce the users to sign a message after connecting their wallet to authenticate themselves.
440+
*
441+
* Refer to the [`SiweAuthOptions`](https://portal.thirdweb.com/references/typescript/v5/SiweAuthOptions) for more details
442+
*/
443+
auth?: SiweAuthOptions;
435444
};
436445

437446
// TODO: consilidate Button/Embed/Modal props into one type with extras

0 commit comments

Comments
 (0)