diff --git a/.changeset/fifty-bars-rest.md b/.changeset/fifty-bars-rest.md new file mode 100644 index 00000000000..1c611231031 --- /dev/null +++ b/.changeset/fifty-bars-rest.md @@ -0,0 +1,5 @@ +--- +"thirdweb": patch +--- + +Improve walletConnect connection and auto-connection flow diff --git a/packages/thirdweb/src/react/web/wallets/shared/WalletConnectConnection.tsx b/packages/thirdweb/src/react/web/wallets/shared/WalletConnectConnection.tsx index 7a37e665ce1..764d74873b5 100644 --- a/packages/thirdweb/src/react/web/wallets/shared/WalletConnectConnection.tsx +++ b/packages/thirdweb/src/react/web/wallets/shared/WalletConnectConnection.tsx @@ -3,7 +3,7 @@ import type { Chain } from "../../../../chains/types.js"; import type { ThirdwebClient } from "../../../../client/client.js"; import { wait } from "../../../../utils/promise/wait.js"; import { formatWalletConnectUrl } from "../../../../utils/url.js"; -import { isAndroid, isIOS, isMobile } from "../../../../utils/web/isMobile.js"; +import { isMobile } from "../../../../utils/web/isMobile.js"; import { openWindow } from "../../../../utils/web/openWindow.js"; import type { WCSupportedWalletIds } from "../../../../wallets/__generated__/wallet-ids.js"; import type { Wallet } from "../../../../wallets/interfaces/wallet.js"; @@ -47,35 +47,23 @@ export const WalletConnectConnection: React.FC<{ client: props.client, walletConnect: { onDisplayUri(uri) { - const preferNative = - walletInfo.mobile.native || walletInfo.mobile.universal; try { if (isMobile()) { - if (isAndroid()) { - if (preferNative) { - openWindow( - formatWalletConnectUrl(preferNative, uri).redirect, - ); - } - } else if (isIOS()) { - if (preferNative) { - openWindow( - formatWalletConnectUrl(preferNative, uri).redirect, - ); - } + const mobileAppLink = + walletInfo.mobile.native || walletInfo.mobile.universal; + if (mobileAppLink) { + openWindow( + formatWalletConnectUrl(mobileAppLink, uri).redirect, + ); } else { - const preferUniversal = - walletInfo.mobile.universal || walletInfo.mobile.native; - if (preferUniversal) { - openWindow( - formatWalletConnectUrl(preferUniversal, uri).redirect, - ); - } + // on android, wc:// links show the app picker + openWindow(uri); } } else { setQrCodeUri(uri); } - } catch { + } catch (e) { + console.error(e); setErrorConnecting(true); } }, @@ -216,28 +204,7 @@ export const WalletConnectStandaloneConnection: React.FC<{ chain: props.chain, client: props.client, onDisplayUri(uri) { - const platformUris = { - android: walletInfo.mobile.universal || "", - ios: walletInfo.mobile.native || "", - other: walletInfo.mobile.universal || "", - }; - setQrCodeUri(uri); - if (isMobile()) { - if (isAndroid()) { - openWindow( - `${platformUris.android}wc?uri=${encodeURIComponent(uri)}`, - ); - } else if (isIOS()) { - openWindow( - `${platformUris.ios}wc?uri=${encodeURIComponent(uri)}`, - ); - } else { - openWindow( - `${platformUris.other}wc?uri=${encodeURIComponent(uri)}`, - ); - } - } }, optionalChains: props.chains, projectId: props.walletConnect?.projectId, @@ -253,8 +220,6 @@ export const WalletConnectStandaloneConnection: React.FC<{ } }, [ props.walletConnect, - walletInfo.mobile.native, - walletInfo.mobile.universal, wallet, props.chain, props.client, diff --git a/packages/thirdweb/src/utils/web/isMobile.ts b/packages/thirdweb/src/utils/web/isMobile.ts index 1007ba0ed16..fea65cbdb21 100644 --- a/packages/thirdweb/src/utils/web/isMobile.ts +++ b/packages/thirdweb/src/utils/web/isMobile.ts @@ -3,7 +3,7 @@ import { detectOS } from "../detect-platform.js"; /** * @internal */ -export function isAndroid(): boolean { +function isAndroid(): boolean { // can only detect if useragent is defined if (typeof navigator === "undefined") { return false; @@ -15,7 +15,7 @@ export function isAndroid(): boolean { /** * @internal */ -export function isIOS(): boolean { +function isIOS(): boolean { // can only detect if useragent is defined if (typeof navigator === "undefined") { return false; diff --git a/packages/thirdweb/src/wallets/create-wallet.ts b/packages/thirdweb/src/wallets/create-wallet.ts index 6e8eb76567e..95dfc02425f 100644 --- a/packages/thirdweb/src/wallets/create-wallet.ts +++ b/packages/thirdweb/src/wallets/create-wallet.ts @@ -2,8 +2,10 @@ import { trackConnect } from "../analytics/track/connect.js"; import type { Chain } from "../chains/types.js"; import { getCachedChainIfExists } from "../chains/utils.js"; import { webLocalStorage } from "../utils/storage/webStorage.js"; +import { formatWalletConnectUrl } from "../utils/url.js"; import { isMobile } from "../utils/web/isMobile.js"; import { openWindow } from "../utils/web/openWindow.js"; +import { getWalletInfo } from "./__generated__/getWalletInfo.js"; import type { InjectedSupportedWalletIds, WCSupportedWalletIds, @@ -313,43 +315,63 @@ export function createWallet( ...wcOptions, walletConnect: { ...wcOptions.walletConnect, - onDisplayUri: wcOptions.walletConnect?.showQrModal - ? async (uri) => { - // Check if we're in a browser environment - if ( - typeof window !== "undefined" && - typeof document !== "undefined" - ) { - try { - const { createQROverlay } = await import( - "./wallet-connect/qr-overlay.js" + onDisplayUri: + wcOptions.walletConnect?.onDisplayUri || + (async (uri) => { + // Check if we're in a browser environment + if ( + typeof window !== "undefined" && + typeof document !== "undefined" + ) { + // on mobile, open the wallet app via deeplink + if (isMobile()) { + const walletInfo = await getWalletInfo(id); + + const mobileAppLink = + walletInfo.mobile.native || + walletInfo.mobile.universal; + if (mobileAppLink) { + openWindow( + formatWalletConnectUrl(mobileAppLink, uri) + .redirect, ); + } else { + // on android, wc:// links show the app picker + openWindow(uri); + } + return; + } - // Clean up any existing overlay - if (qrOverlay) { - qrOverlay.destroy(); - } + try { + // on desktop, create a QR overlay + const { createQROverlay } = await import( + "./wallet-connect/qr-overlay.js" + ); - // Create new QR overlay - qrOverlay = createQROverlay(uri, { - theme: - wcOptions.walletConnect?.qrModalOptions - ?.themeMode ?? "dark", - qrSize: 280, - showCloseButton: true, - onCancel: () => { - wcOptions.walletConnect?.onCancel?.(); - }, - }); - } catch (error) { - console.error( - "Failed to create QR overlay:", - error, - ); + // Clean up any existing overlay + if (qrOverlay) { + qrOverlay.destroy(); } + + // Create new QR overlay + qrOverlay = createQROverlay(uri, { + theme: + wcOptions.walletConnect?.qrModalOptions + ?.themeMode ?? "dark", + qrSize: 280, + showCloseButton: true, + onCancel: () => { + wcOptions.walletConnect?.onCancel?.(); + }, + }); + } catch (error) { + console.error( + "Failed to create QR overlay:", + error, + ); } } - : undefined, + }), }, }, emitter, diff --git a/packages/thirdweb/src/wallets/wallet-connect/controller.ts b/packages/thirdweb/src/wallets/wallet-connect/controller.ts index c7f7263e1fe..69b2b898808 100644 --- a/packages/thirdweb/src/wallets/wallet-connect/controller.ts +++ b/packages/thirdweb/src/wallets/wallet-connect/controller.ts @@ -123,30 +123,28 @@ export async function connectWC( optionalChains: optionalChains, }); - if (!provider.session) { - // For UniversalProvider, we need to connect with namespaces - await provider.connect({ - ...(wcOptions?.pairingTopic - ? { pairingTopic: wcOptions?.pairingTopic } - : {}), - namespaces: { - [NAMESPACE]: { - chains: chainsToRequest, - events: ["chainChanged", "accountsChanged"], - methods: [ - "eth_sendTransaction", - "eth_signTransaction", - "eth_sign", - "personal_sign", - "eth_signTypedData", - "wallet_switchEthereumChain", - "wallet_addEthereumChain", - ], - rpcMap, - }, + // For UniversalProvider, we need to connect with namespaces + await provider.connect({ + ...(wcOptions?.pairingTopic + ? { pairingTopic: wcOptions?.pairingTopic } + : {}), + namespaces: { + [NAMESPACE]: { + chains: chainsToRequest, + events: ["chainChanged", "accountsChanged"], + methods: [ + "eth_sendTransaction", + "eth_signTransaction", + "eth_sign", + "personal_sign", + "eth_signTypedData", + "wallet_switchEthereumChain", + "wallet_addEthereumChain", + ], + rpcMap, }, - }); - } + }, + }); setRequestedChainsIds( chainsToRequest.map((x) => Number(x.split(":")[1])), @@ -223,9 +221,14 @@ export async function autoConnectWC( sessionHandler, ); + if (!provider.session) { + await provider.disconnect(); + throw new Error("No wallet connect session found on provider."); + } + // For UniversalProvider, get accounts from enable() method - const addresses = await provider.enable(); - const address = addresses[0]; + const namespaceAccounts = provider.session?.namespaces?.[NAMESPACE]?.accounts; + const address = namespaceAccounts?.[0]?.split(":")[2]; if (!address) { throw new Error("No accounts found on provider.");