Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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/every-sides-invent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"thirdweb": patch
---

Automatically trigger SIWE sign in when a wallet is connected
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,11 @@ export type ConnectEmbedProps = {
*/
auth?: SiweAuthOptions;

/**
* @hidden
*/
siweLogin?: () => Promise<void>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still need this?


/**
* Customize the welcome screen. This prop is only applicable when modalSize prop is set to "wide". On "wide" Modal size, a welcome screen is shown on the right side of the modal.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ export function ConnectButton(props: ConnectButtonProps) {
const status = useActiveWalletConnectionStatus();
const connectionManager = useConnectionManager();
const siweAuth = useSiweAuth(wallet, account, props.auth);
useAutoConnect(props);
useAutoConnect({
...props,
siweLogin: siweAuth.doLogin,
});

const fadeAnim = useRef(new Animated.Value(0)); // For background opacity
const slideAnim = useRef(new Animated.Value(screenHeight)); // For bottom sheet position
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,9 @@ export function ConnectButton(props: ConnectButtonProps) {
);
const localeQuery = useConnectLocale(props.locale || "en_US");
const connectionManager = useConnectionManager();
const activeAccount = useActiveAccount();
const activeWallet = useActiveWallet();
const siweAuth = useSiweAuth(activeWallet, activeAccount, props.auth);

usePreloadWalletProviders({
wallets,
Expand Down Expand Up @@ -337,6 +340,7 @@ export function ConnectButton(props: ConnectButtonProps) {
}
accountAbstraction={props.accountAbstraction}
onConnect={props.onConnect}
siweLogin={siweAuth.doLogin}
/>
);

Expand All @@ -362,7 +366,11 @@ export function ConnectButton(props: ConnectButtonProps) {

return (
<WalletUIStatesProvider theme={props.theme} isOpen={false}>
<ConnectButtonInner {...props} connectLocale={localeQuery.data} />
<ConnectButtonInner
{...props}
siweAuth={siweAuth}
connectLocale={localeQuery.data}
/>
<ConnectModal
shouldSetActive={true}
accountAbstraction={props.accountAbstraction}
Expand Down Expand Up @@ -396,11 +404,11 @@ export function ConnectButton(props: ConnectButtonProps) {
function ConnectButtonInner(
props: ConnectButtonProps & {
connectLocale: ConnectLocale;
siweAuth: ReturnType<typeof useSiweAuth>;
},
) {
const activeWallet = useActiveWallet();
const siweAuth = props.siweAuth;
const activeAccount = useActiveAccount();
const siweAuth = useSiweAuth(activeWallet, activeAccount, props.auth);
const [showSignatureModal, setShowSignatureModal] = useState(false);

// if wallet gets disconnected suddently, close the signature modal if it's open
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import {
} from "../../../../core/design-system/CustomThemeProvider.js";
import { radius } from "../../../../core/design-system/index.js";
import {
type SiweAuthOptions,
useSiweAuth,
type SiweAuthOptions,
} from "../../../../core/hooks/auth/useSiweAuth.js";
import type { ConnectEmbedProps } from "../../../../core/hooks/connection/ConnectEmbedProps.js";
import { useActiveAccount } from "../../../../core/hooks/wallets/useActiveAccount.js";
Expand Down Expand Up @@ -247,6 +247,7 @@ export function ConnectEmbed(props: ConnectEmbedProps) {
chain={preferredChain}
appMetadata={props.appMetadata}
client={props.client}
siweLogin={siweAuth.doLogin}
wallets={wallets}
accountAbstraction={props.accountAbstraction}
timeout={
Expand Down Expand Up @@ -326,21 +327,21 @@ const ConnectEmbedContent = (props: {
};
size: "compact" | "wide";
header:
| {
title?: string;
titleIcon?: string;
}
| true
| undefined;
| {
title?: string;
titleIcon?: string;
}
| true
| undefined;
localeId: LocaleId;
onConnect: ((wallet: Wallet) => void) | undefined;
recommendedWallets: Wallet[] | undefined;
showAllWallets: boolean | undefined;
walletConnect:
| {
projectId?: string;
}
| undefined;
| {
projectId?: string;
}
| undefined;
wallets: Wallet[];
welcomeScreen: WelcomeScreen | undefined;
}) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,7 @@ import { sendTransaction } from "../../../../../../../transaction/actions/send-t
import type { WaitForReceiptOptions } from "../../../../../../../transaction/actions/wait-for-tx-receipt.js";
import { waitForReceipt } from "../../../../../../../transaction/actions/wait-for-tx-receipt.js";
import { formatNumber } from "../../../../../../../utils/formatNumber.js";
import { isEcosystemWallet } from "../../../../../../../wallets/ecosystem/is-ecosystem-wallet.js";
import { isInAppWallet } from "../../../../../../../wallets/in-app/core/wallet/index.js";
import type { Wallet } from "../../../../../../../wallets/interfaces/wallet.js";
import { isSmartWallet } from "../../../../../../../wallets/smart/is-smart-wallet.js";
import { isInAppSigner } from "../../../../../../../wallets/in-app/core/wallet/is-in-app-signer.js";
import { spacing } from "../../../../../../core/design-system/index.js";
import { useChainName } from "../../../../../../core/hooks/others/useChainQuery.js";
import { useBuyWithCryptoStatus } from "../../../../../../core/hooks/pay/useBuyWithCryptoStatus.js";
Expand Down Expand Up @@ -779,20 +776,3 @@ function useSwapMutation(props: {
},
});
}

function isInAppSigner(options: {
wallet: Wallet;
connectedWallets: Wallet[];
}) {
const isInAppOrEcosystem = (w: Wallet) =>
isInAppWallet(w) || isEcosystemWallet(w);
const isSmartWalletWithAdmin =
isSmartWallet(options.wallet) &&
options.connectedWallets.some(
(w) =>
isInAppOrEcosystem(w) &&
w.getAccount()?.address?.toLowerCase() ===
options.wallet.getAdminAccount?.()?.address?.toLowerCase(),
);
return isInAppOrEcosystem(options.wallet) || isSmartWalletWithAdmin;
}
24 changes: 19 additions & 5 deletions packages/thirdweb/src/react/web/ui/PayEmbed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import type { AppMetadata } from "../../../wallets/types.js";
import type { WalletId } from "../../../wallets/wallet-types.js";
import { CustomThemeProvider } from "../../core/design-system/CustomThemeProvider.js";
import type { Theme } from "../../core/design-system/index.js";
import type { SiweAuthOptions } from "../../core/hooks/auth/useSiweAuth.js";
import {
useSiweAuth,
type SiweAuthOptions,
} from "../../core/hooks/auth/useSiweAuth.js";
import type {
ConnectButton_connectModalOptions,
PayUIOptions,
Expand All @@ -23,6 +26,9 @@ import { ExecutingTxScreen } from "./TransactionButton/ExecutingScreen.js";
import { DynamicHeight } from "./components/DynamicHeight.js";
import { Spinner } from "./components/Spinner.js";
import type { LocaleId } from "./types.js";
import { useActiveAccount } from "../../core/hooks/wallets/useActiveAccount.js";
import { useActiveWallet } from "../../core/hooks/wallets/useActiveWallet.js";
import { AutoConnect } from "../../web/ui/AutoConnect/AutoConnect.js";

/**
* Props of [`PayEmbed`](https://portal.thirdweb.com/references/typescript/v5/PayEmbed) component
Expand Down Expand Up @@ -300,6 +306,13 @@ export function PayEmbed(props: PayEmbedProps) {
const [screen, setScreen] = useState<"buy" | "execute-tx">("buy");
const theme = props.theme || "dark";
const connectionManager = useConnectionManager();
const activeAccount = useActiveAccount();
const activeWallet = useActiveWallet();
const siweAuth = useSiweAuth(
activeWallet,
activeAccount,
props.connectOptions?.auth,
);

// Add props.chain and props.chains to defined chains store
useEffect(() => {
Expand Down Expand Up @@ -342,6 +355,7 @@ export function PayEmbed(props: PayEmbedProps) {
} else {
content = (
<>
<AutoConnect client={props.client} siweLogin={siweAuth.doLogin} />
{screen === "buy" && (
<BuyScreen
title={metadata?.name || "Buy"}
Expand Down Expand Up @@ -459,10 +473,10 @@ export type PayEmbedConnectOptions = {
* ```
*/
autoConnect?:
| {
timeout: number;
}
| boolean;
| {
timeout: number;
}
| boolean;

/**
* Metadata of the app that will be passed to connected wallet. Setting this is highly recommended.
Expand Down
41 changes: 23 additions & 18 deletions packages/thirdweb/src/wallets/connection/autoConnectCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,32 +82,32 @@ const _autoConnectCore = async ({
getStoredActiveWalletId(storage),
]);

const result = getUrlToken();
const urlToken = getUrlToken();

// If an auth cookie is found and this site supports the wallet, we'll set the auth cookie in the client storage
const wallet = wallets.find((w) => w.id === result?.walletId);
if (result?.authCookie && wallet) {
const wallet = wallets.find((w) => w.id === urlToken?.walletId);
if (urlToken?.authCookie && wallet) {
const clientStorage = new ClientScopedStorage({
storage,
clientId: props.client.clientId,
ecosystem: isEcosystemWallet(wallet)
? {
id: wallet.id,
partnerId: wallet.getConfig()?.partnerId,
}
id: wallet.id,
partnerId: wallet.getConfig()?.partnerId,
}
: undefined,
});
await clientStorage.saveAuthCookie(result.authCookie);
await clientStorage.saveAuthCookie(urlToken.authCookie);
}
if (result?.walletId) {
lastActiveWalletId = result.walletId;
lastConnectedWalletIds = lastConnectedWalletIds?.includes(result.walletId)
if (urlToken?.walletId) {
lastActiveWalletId = urlToken.walletId;
lastConnectedWalletIds = lastConnectedWalletIds?.includes(urlToken.walletId)
? lastConnectedWalletIds
: [result.walletId, ...(lastConnectedWalletIds || [])];
: [urlToken.walletId, ...(lastConnectedWalletIds || [])];
}

if (result?.authProvider) {
await setLastAuthProvider?.(result.authProvider, storage);
if (urlToken?.authProvider) {
await setLastAuthProvider?.(urlToken.authProvider, storage);
}

// if no wallets were last connected or we didn't receive an auth token
Expand All @@ -132,7 +132,7 @@ const _autoConnectCore = async ({
wallet: activeWallet,
client: props.client,
lastConnectedChain,
authResult: result?.authResult,
authResult: urlToken?.authResult,
}),
{
ms: timeout,
Expand All @@ -150,9 +150,9 @@ const _autoConnectCore = async ({
const connectedWallet = await (connectOverride
? connectOverride(activeWallet)
: manager.connect(activeWallet, {
client: props.client,
accountAbstraction: props.accountAbstraction,
}));
client: props.client,
accountAbstraction: props.accountAbstraction,
}));
if (connectedWallet) {
autoConnected = true;
try {
Expand Down Expand Up @@ -183,13 +183,18 @@ const _autoConnectCore = async ({
wallet,
client: props.client,
lastConnectedChain,
authResult: result?.authResult,
authResult: urlToken?.authResult,
});
manager.addConnectedWallet(wallet);
} catch {
// no-op
}
}

// Auto-login with SIWE
if (urlToken && activeWallet && props.siweLogin) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to check if it actually requires auth here too. I think you can pass the require auth function down as well maybe?

await props.siweLogin();
}
manager.isAutoConnecting.setValue(false);
return autoConnected; // useQuery needs a return value
};
Expand Down
5 changes: 5 additions & 0 deletions packages/thirdweb/src/wallets/connection/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,9 @@ export type AutoConnectProps = {
* Callback to be called when the connection is timeout-ed
*/
onTimeout?: () => void;

/**
* @hidden
*/
siweLogin?: () => Promise<void>;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { isEcosystemWallet } from "../../../../wallets/ecosystem/is-ecosystem-wallet.js";
import type { Wallet } from "../../../interfaces/wallet.js";
import { isSmartWallet } from "../../../smart/index.js";
import { isInAppWallet } from "./index.js";

export function isInAppSigner(options: {
wallet: Wallet;
connectedWallets: Wallet[];
}) {
const isInAppOrEcosystem = (w: Wallet) =>
isInAppWallet(w) || isEcosystemWallet(w);
const isSmartWalletWithAdmin =
isSmartWallet(options.wallet) &&
options.connectedWallets.some(
(w) =>
isInAppOrEcosystem(w) &&
w.getAccount()?.address?.toLowerCase() ===
options.wallet.getAdminAccount?.()?.address?.toLowerCase(),
);
return isInAppOrEcosystem(options.wallet) || isSmartWalletWithAdmin;
}
Loading