From e3edd1ba501b40aa08954f5165c68516ea033478 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Thu, 13 Nov 2025 08:49:57 +1300 Subject: [PATCH] [SDK] Add all connected wallets in onConnect callbacks --- .changeset/fair-mails-divide.md | 5 +++ .../components/in-app-wallet/ecosystem.tsx | 14 +++++- packages/thirdweb/src/exports/react.native.ts | 7 +++ packages/thirdweb/src/exports/react.ts | 1 + .../hooks/connection/ConnectButtonProps.ts | 8 ++-- .../hooks/connection/ConnectEmbedProps.ts | 8 ++-- .../src/react/core/hooks/connection/types.ts | 6 +++ .../ui/ConnectWallet/Modal/ConnectEmbed.tsx | 3 +- .../ui/ConnectWallet/Modal/ConnectModal.tsx | 3 +- .../Modal/ConnectModalContent.tsx | 5 ++- .../web/ui/ConnectWallet/useConnectModal.tsx | 3 +- .../src/wallets/connection/autoConnect.ts | 5 ++- .../connection/autoConnectCore.test.ts | 45 +++++++++++-------- .../src/wallets/connection/autoConnectCore.ts | 26 ++++++----- .../thirdweb/src/wallets/connection/types.ts | 7 +-- .../manager/connection-manager.test.ts | 2 +- .../thirdweb/src/wallets/manager/index.ts | 7 +-- 17 files changed, 104 insertions(+), 51 deletions(-) create mode 100644 .changeset/fair-mails-divide.md create mode 100644 packages/thirdweb/src/react/core/hooks/connection/types.ts diff --git a/.changeset/fair-mails-divide.md b/.changeset/fair-mails-divide.md new file mode 100644 index 00000000000..3958d093bdb --- /dev/null +++ b/.changeset/fair-mails-divide.md @@ -0,0 +1,5 @@ +--- +"thirdweb": minor +--- + +Add all connected wallets in all onConnect callbacks diff --git a/apps/playground-web/src/components/in-app-wallet/ecosystem.tsx b/apps/playground-web/src/components/in-app-wallet/ecosystem.tsx index 9324c5d0a56..8f621a5d9cf 100644 --- a/apps/playground-web/src/components/in-app-wallet/ecosystem.tsx +++ b/apps/playground-web/src/components/in-app-wallet/ecosystem.tsx @@ -19,5 +19,17 @@ const getEcosystemWallet = () => { export function EcosystemConnectEmbed( props?: Omit, ) { - return ; + return ( + { + console.log("active wallet", activeWallet.id); + console.log( + "all connected wallets", + allConnectedWallets.map((wallet) => wallet.id), + ); + }} + /> + ); } diff --git a/packages/thirdweb/src/exports/react.native.ts b/packages/thirdweb/src/exports/react.native.ts index 3a59ddc4902..eeba1b18f07 100644 --- a/packages/thirdweb/src/exports/react.native.ts +++ b/packages/thirdweb/src/exports/react.native.ts @@ -17,7 +17,14 @@ export type { ConnectButton_detailsButtonOptions, ConnectButton_detailsModalOptions, ConnectButtonProps, + DirectPaymentOptions, + FundWalletOptions, + PaymentInfo, + PayUIOptions, + TransactionOptions, } from "../react/core/hooks/connection/ConnectButtonProps.js"; +export type { ConnectEmbedProps } from "../react/core/hooks/connection/ConnectEmbedProps.js"; +export type { OnConnectCallback } from "../react/core/hooks/connection/types.js"; export { useContractEvents } from "../react/core/hooks/contract/useContractEvents.js"; // contract export { useReadContract } from "../react/core/hooks/contract/useReadContract.js"; diff --git a/packages/thirdweb/src/exports/react.ts b/packages/thirdweb/src/exports/react.ts index c7b12cf5b9d..48e07836492 100644 --- a/packages/thirdweb/src/exports/react.ts +++ b/packages/thirdweb/src/exports/react.ts @@ -23,6 +23,7 @@ export type { TransactionOptions, } from "../react/core/hooks/connection/ConnectButtonProps.js"; export type { ConnectEmbedProps } from "../react/core/hooks/connection/ConnectEmbedProps.js"; +export type { OnConnectCallback } from "../react/core/hooks/connection/types.js"; export { useContractEvents } from "../react/core/hooks/contract/useContractEvents.js"; // contract export { useReadContract } from "../react/core/hooks/contract/useReadContract.js"; diff --git a/packages/thirdweb/src/react/core/hooks/connection/ConnectButtonProps.ts b/packages/thirdweb/src/react/core/hooks/connection/ConnectButtonProps.ts index eb9e99fcf61..8f0b7cd821c 100644 --- a/packages/thirdweb/src/react/core/hooks/connection/ConnectButtonProps.ts +++ b/packages/thirdweb/src/react/core/hooks/connection/ConnectButtonProps.ts @@ -25,6 +25,7 @@ import type { TokenInfo, } from "../../utils/defaultTokens.js"; import type { SiweAuthOptions } from "../auth/useSiweAuth.js"; +import type { OnConnectCallback } from "./types.js"; export type PaymentInfo = Prettify< { @@ -937,13 +938,14 @@ export type ConnectButtonProps = { * * ```tsx * { - * console.log("connected to", wallet) + * onConnect={(activeWallet, allConnectedWallets) => { + * console.log("connected to", activeWallet) + * console.log("all connected wallets", allConnectedWallets) * }} * /> * ``` */ - onConnect?: (wallet: Wallet) => void; + onConnect?: OnConnectCallback; /** * Called when the user disconnects the wallet by clicking on the "Disconnect Wallet" button in the `ConnectButton`'s Details Modal. diff --git a/packages/thirdweb/src/react/core/hooks/connection/ConnectEmbedProps.ts b/packages/thirdweb/src/react/core/hooks/connection/ConnectEmbedProps.ts index 7c2427db14e..cf9a9fe2065 100644 --- a/packages/thirdweb/src/react/core/hooks/connection/ConnectEmbedProps.ts +++ b/packages/thirdweb/src/react/core/hooks/connection/ConnectEmbedProps.ts @@ -8,6 +8,7 @@ import type { WelcomeScreen } from "../../../web/ui/ConnectWallet/screens/types. import type { LocaleId } from "../../../web/ui/types.js"; import type { Theme } from "../../design-system/index.js"; import type { SiweAuthOptions } from "../auth/useSiweAuth.js"; +import type { OnConnectCallback } from "./types.js"; export type ConnectEmbedProps = { /** @@ -213,14 +214,15 @@ export type ConnectEmbedProps = { * * ```tsx * { - * console.log("connected to", wallet) + * onConnect={(activeWallet, allConnectedWallets) => { + * console.log("connected to", activeWallet) + * console.log("all connected wallets", allConnectedWallets) * }} * /> * ``` * ``` */ - onConnect?: (wallet: Wallet) => void; + onConnect?: OnConnectCallback; /** * By default, A "Powered by Thirdweb" branding is shown at the bottom of the embed. diff --git a/packages/thirdweb/src/react/core/hooks/connection/types.ts b/packages/thirdweb/src/react/core/hooks/connection/types.ts new file mode 100644 index 00000000000..6cd88116c45 --- /dev/null +++ b/packages/thirdweb/src/react/core/hooks/connection/types.ts @@ -0,0 +1,6 @@ +import type { Wallet } from "../../../../wallets/interfaces/wallet.js"; + +export type OnConnectCallback = ( + activeWallet: Wallet, + allConnectedWallets: Wallet[], +) => void; diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/Modal/ConnectEmbed.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/Modal/ConnectEmbed.tsx index 140ebcfcfda..1363085ec6d 100644 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/Modal/ConnectEmbed.tsx +++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/Modal/ConnectEmbed.tsx @@ -16,6 +16,7 @@ import { useSiweAuth, } from "../../../../core/hooks/auth/useSiweAuth.js"; import type { ConnectEmbedProps } from "../../../../core/hooks/connection/ConnectEmbedProps.js"; +import type { OnConnectCallback } from "../../../../core/hooks/connection/types.js"; import { useActiveAccount } from "../../../../core/hooks/wallets/useActiveAccount.js"; import { useActiveWallet } from "../../../../core/hooks/wallets/useActiveWallet.js"; import { useIsAutoConnecting } from "../../../../core/hooks/wallets/useIsAutoConnecting.js"; @@ -354,7 +355,7 @@ const ConnectEmbedContent = (props: { | true | undefined; localeId: LocaleId; - onConnect: ((wallet: Wallet) => void) | undefined; + onConnect: OnConnectCallback | undefined; recommendedWallets: Wallet[] | undefined; showAllWallets: boolean | undefined; hiddenWallets: WalletId[] | undefined; diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/Modal/ConnectModal.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/Modal/ConnectModal.tsx index d6175f1c24b..77f1b1d1d03 100644 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/Modal/ConnectModal.tsx +++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/Modal/ConnectModal.tsx @@ -6,6 +6,7 @@ import type { Wallet } from "../../../../../wallets/interfaces/wallet.js"; import type { SmartWalletOptions } from "../../../../../wallets/smart/types.js"; import type { WalletId } from "../../../../../wallets/wallet-types.js"; import type { SiweAuthOptions } from "../../../../core/hooks/auth/useSiweAuth.js"; +import type { OnConnectCallback } from "../../../../core/hooks/connection/types.js"; import { useActiveAccount } from "../../../../core/hooks/wallets/useActiveAccount.js"; import { useIsWalletModalOpen, @@ -26,7 +27,7 @@ type ConnectModalOptions = { wallets: Wallet[]; accountAbstraction: SmartWalletOptions | undefined; auth: SiweAuthOptions | undefined; - onConnect: ((wallet: Wallet) => void) | undefined; + onConnect: OnConnectCallback | undefined; size: "compact" | "wide"; welcomeScreen: WelcomeScreen | undefined; meta: { 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 0346060c65a..445a060d792 100644 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/Modal/ConnectModalContent.tsx +++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/Modal/ConnectModalContent.tsx @@ -9,6 +9,7 @@ import { type SiweAuthOptions, useSiweAuth, } from "../../../../core/hooks/auth/useSiweAuth.js"; +import type { OnConnectCallback } from "../../../../core/hooks/connection/types.js"; import { useActiveAccount } from "../../../../core/hooks/wallets/useActiveAccount.js"; import { useActiveWallet } from "../../../../core/hooks/wallets/useActiveWallet.js"; import { useSetActiveWallet } from "../../../../core/hooks/wallets/useSetActiveWallet.js"; @@ -43,7 +44,7 @@ export const ConnectModalContent = (props: { wallets: Wallet[]; accountAbstraction: SmartWalletOptions | undefined; auth: SiweAuthOptions | undefined; - onConnect: ((wallet: Wallet) => void) | undefined; + onConnect: OnConnectCallback | undefined; size: "compact" | "wide"; meta: { title?: string; @@ -93,7 +94,7 @@ export const ConnectModalContent = (props: { } if (props.onConnect) { - props.onConnect(wallet); + props.onConnect(wallet, connectionManager.connectedWallets.getValue()); } onModalUnmount(() => { diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/useConnectModal.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/useConnectModal.tsx index c7258d0f29f..a1048c27657 100644 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/useConnectModal.tsx +++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/useConnectModal.tsx @@ -8,6 +8,7 @@ import type { AppMetadata } from "../../../../wallets/types.js"; import type { WalletId } from "../../../../wallets/wallet-types.js"; import type { Theme } from "../../../core/design-system/index.js"; import type { SiweAuthOptions } from "../../../core/hooks/auth/useSiweAuth.js"; +import type { OnConnectCallback } from "../../../core/hooks/connection/types.js"; import { SetRootElementContext } from "../../../core/providers/RootElementContext.js"; import { WalletUIStatesProvider } from "../../providers/wallet-ui-states-provider.js"; import { canFitWideModal } from "../../utils/canFitWideModal.js"; @@ -90,7 +91,7 @@ export function useConnectModal() { function Modal( props: UseConnectModalOptions & { - onConnect: (wallet: Wallet) => void; + onConnect: OnConnectCallback; onClose: () => void; connectLocale: ConnectLocale; }, diff --git a/packages/thirdweb/src/wallets/connection/autoConnect.ts b/packages/thirdweb/src/wallets/connection/autoConnect.ts index 23055e5ed79..1dfcd0c511a 100644 --- a/packages/thirdweb/src/wallets/connection/autoConnect.ts +++ b/packages/thirdweb/src/wallets/connection/autoConnect.ts @@ -17,8 +17,9 @@ import type { AutoConnectProps } from "./types.js"; * * const autoConnected = await autoConnect({ * client, - * onConnect: (wallet) => { - * console.log("wallet", wallet); + * onConnect: (activeWallet, allConnectedWallets) => { + * console.log("active wallet", activeWallet); + * console.log("all connected wallets", allConnectedWallets); * }, * }); * ``` diff --git a/packages/thirdweb/src/wallets/connection/autoConnectCore.test.ts b/packages/thirdweb/src/wallets/connection/autoConnectCore.test.ts index 2a04deb6864..b56ff1665ae 100644 --- a/packages/thirdweb/src/wallets/connection/autoConnectCore.test.ts +++ b/packages/thirdweb/src/wallets/connection/autoConnectCore.test.ts @@ -19,6 +19,23 @@ describe("useAutoConnectCore", () => { const mockStorage = new MockStorage(); const manager = createConnectionManager(mockStorage); + const wallet1 = createWalletAdapter({ + adaptedAccount: TEST_ACCOUNT_A, + chain: ethereum, + client: TEST_CLIENT, + onDisconnect: () => {}, + switchChain: () => {}, + }); + + const wallet2 = createWalletAdapter({ + adaptedAccount: { ...TEST_ACCOUNT_A, address: "0x123" }, + chain: ethereum, + client: TEST_CLIENT, + onDisconnect: () => {}, + switchChain: () => {}, + }); + wallet2.id = "io.metamask" as unknown as "adapter"; + afterEach(() => { vi.restoreAllMocks(); }); @@ -161,23 +178,6 @@ describe("useAutoConnectCore", () => { }); it("should connect multiple wallets correctly", async () => { - const wallet1 = createWalletAdapter({ - adaptedAccount: TEST_ACCOUNT_A, - chain: ethereum, - client: TEST_CLIENT, - onDisconnect: () => {}, - switchChain: () => {}, - }); - - const wallet2 = createWalletAdapter({ - adaptedAccount: { ...TEST_ACCOUNT_A, address: "0x123" }, - chain: ethereum, - client: TEST_CLIENT, - onDisconnect: () => {}, - switchChain: () => {}, - }); - wallet2.id = "io.metamask" as unknown as "adapter"; - mockStorage.setItem("thirdweb:active-wallet-id", wallet1.id); mockStorage.setItem( "thirdweb:connected-wallet-ids", @@ -228,7 +228,10 @@ describe("useAutoConnectCore", () => { storage: mockStorage, }); - expect(mockOnConnect).toHaveBeenCalledWith(wallet); + expect(mockOnConnect).toHaveBeenCalledWith( + wallet, + manager.connectedWallets.getValue(), + ); }); it("should continue even if onConnect callback throws", async () => { @@ -262,7 +265,10 @@ describe("useAutoConnectCore", () => { storage: mockStorage, }); - expect(mockOnConnect).toHaveBeenCalledWith(wallet); + expect(mockOnConnect).toHaveBeenCalledWith( + wallet, + manager.connectedWallets.getValue(), + ); }); it("should call setLastAuthProvider if authProvider is present", async () => { @@ -300,6 +306,7 @@ describe("useAutoConnectCore", () => { }); it("should set connection status to disconnect if no connectedWallet is returned", async () => { + manager.activeWalletStore.setValue(undefined); const wallet = createWalletAdapter({ adaptedAccount: TEST_ACCOUNT_A, chain: ethereum, diff --git a/packages/thirdweb/src/wallets/connection/autoConnectCore.ts b/packages/thirdweb/src/wallets/connection/autoConnectCore.ts index 4e64f8575bd..60d5f0d968e 100644 --- a/packages/thirdweb/src/wallets/connection/autoConnectCore.ts +++ b/packages/thirdweb/src/wallets/connection/autoConnectCore.ts @@ -152,22 +152,12 @@ const _autoConnectCore = async ({ try { // connected wallet could be activeWallet or smart wallet - const connectedWallet = await (connectOverride + await (connectOverride ? connectOverride(activeWallet) : manager.connect(activeWallet, { accountAbstraction: props.accountAbstraction, client: props.client, })); - if (connectedWallet) { - autoConnected = true; - try { - onConnect?.(connectedWallet); - } catch { - // ignore - } - } else { - manager.activeWalletConnectionStatusStore.setValue("disconnected"); - } } catch (e) { if (e instanceof Error) { console.warn("Error auto connecting wallet:", e.message); @@ -216,6 +206,20 @@ const _autoConnectCore = async ({ }); } manager.isAutoConnecting.setValue(false); + + const connectedActiveWallet = manager.activeWalletStore.getValue(); + const allConnectedWallets = manager.connectedWallets.getValue(); + if (connectedActiveWallet) { + autoConnected = true; + try { + onConnect?.(connectedActiveWallet, allConnectedWallets); + } catch (e) { + console.error("Error calling onConnect callback:", e); + } + } else { + manager.activeWalletConnectionStatusStore.setValue("disconnected"); + } + return autoConnected; // useQuery needs a return value }; diff --git a/packages/thirdweb/src/wallets/connection/types.ts b/packages/thirdweb/src/wallets/connection/types.ts index 159600cd13c..30aa59ed65e 100644 --- a/packages/thirdweb/src/wallets/connection/types.ts +++ b/packages/thirdweb/src/wallets/connection/types.ts @@ -101,13 +101,14 @@ export type AutoConnectProps = { * * ```tsx * { - * console.log("auto connected to", wallet) + * onConnect={(activeWallet, otherWallets) => { + * console.log("auto connected to", activeWallet) + * console.log("other wallets that were also connected", otherWallets) * }} * /> * ``` */ - onConnect?: (wallet: Wallet) => void; + onConnect?: (activeWallet: Wallet, otherWallets: Wallet[]) => void; /** * Optional chain to autoconnect to diff --git a/packages/thirdweb/src/wallets/manager/connection-manager.test.ts b/packages/thirdweb/src/wallets/manager/connection-manager.test.ts index ccfa24b74ff..940b1fb283b 100644 --- a/packages/thirdweb/src/wallets/manager/connection-manager.test.ts +++ b/packages/thirdweb/src/wallets/manager/connection-manager.test.ts @@ -49,7 +49,7 @@ describe.runIf(process.env.TW_SECRET_KEY)("Connection Manager", () => { await manager.connect(wallet, { client, onConnect }); - expect(onConnect).toHaveBeenCalledWith(wallet); + expect(onConnect).toHaveBeenCalledWith(wallet, [wallet]); expect(storage.setItem).toHaveBeenCalled(); }); diff --git a/packages/thirdweb/src/wallets/manager/index.ts b/packages/thirdweb/src/wallets/manager/index.ts index 45c26c96be1..74e4905f729 100644 --- a/packages/thirdweb/src/wallets/manager/index.ts +++ b/packages/thirdweb/src/wallets/manager/index.ts @@ -1,6 +1,7 @@ import type { Chain } from "../../chains/types.js"; import { cacheChains } from "../../chains/utils.js"; import type { ThirdwebClient } from "../../client/client.js"; +import type { OnConnectCallback } from "../../react/core/hooks/connection/types.js"; import { computedStore } from "../../reactive/computedStore.js"; import { effect } from "../../reactive/effect.js"; import { createStore } from "../../reactive/store.js"; @@ -29,7 +30,7 @@ export type ConnectManagerOptions = { client: ThirdwebClient; accountAbstraction?: SmartWalletOptions; setWalletAsActive?: boolean; - onConnect?: (wallet: Wallet) => void; + onConnect?: OnConnectCallback; }; /** @@ -158,7 +159,7 @@ export function createConnectionManager(storage: AsyncStorage) { wallet.subscribe("accountChanged", async () => { // We reimplement connect here to prevent memory leaks const newWallet = await handleConnection(wallet, options); - options?.onConnect?.(newWallet); + options?.onConnect?.(newWallet, connectedWallets.getValue()); }); return activeWallet; @@ -167,7 +168,7 @@ export function createConnectionManager(storage: AsyncStorage) { const connect = async (wallet: Wallet, options?: ConnectManagerOptions) => { // connectedWallet can be either wallet or smartWallet const connectedWallet = await handleConnection(wallet, options); - options?.onConnect?.(connectedWallet); + options?.onConnect?.(connectedWallet, connectedWallets.getValue()); return connectedWallet; };