From e8ef574dbc01a5c3dfd632e53d6e70b1af08d224 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Fri, 1 Aug 2025 09:22:34 +1200 Subject: [PATCH] [WalletConnect] Add deep linking support for transaction signing --- .../src/wallets/wallet-connect/controller.ts | 115 +++++++++++++++--- 1 file changed, 96 insertions(+), 19 deletions(-) diff --git a/packages/thirdweb/src/wallets/wallet-connect/controller.ts b/packages/thirdweb/src/wallets/wallet-connect/controller.ts index 69b2b898808..6d7d55309a2 100644 --- a/packages/thirdweb/src/wallets/wallet-connect/controller.ts +++ b/packages/thirdweb/src/wallets/wallet-connect/controller.ts @@ -1,4 +1,7 @@ -import type { UniversalProvider } from "@walletconnect/universal-provider"; +import type { + RequestArguments, + UniversalProvider, +} from "@walletconnect/universal-provider"; import type { Address } from "abitype"; import { getTypesForEIP712Domain, @@ -39,6 +42,7 @@ import type { DisconnectFn, SwitchChainFn } from "../types.js"; import { getDefaultAppMetadata } from "../utils/defaultDappMetadata.js"; import { normalizeChainId } from "../utils/normalizeChainId.js"; import type { WalletEmitter } from "../wallet-emitter.js"; +import type { WalletInfo } from "../wallet-info.js"; import type { WalletId } from "../wallet-types.js"; import { DEFAULT_PROJECT_ID, NAMESPACE } from "./constants.js"; import type { WCAutoConnectOptions, WCConnectOptions } from "./types.js"; @@ -78,16 +82,16 @@ export async function connectWC( emitter: WalletEmitter, walletId: WCSupportedWalletIds | "walletConnect", storage: AsyncStorage, - sessionHandler?: (uri: string) => void, + sessionHandler?: (uri: string) => void | Promise, ): Promise> { const provider = await initProvider(options, walletId, sessionHandler); const wcOptions = options.walletConnect; let { onDisplayUri } = wcOptions || {}; + const walletInfo = await getWalletInfo(walletId); // use default sessionHandler unless onDisplayUri is explicitly provided if (!onDisplayUri && sessionHandler) { - const walletInfo = await getWalletInfo(walletId); const deeplinkHandler = (uri: string) => { const appUrl = walletInfo.mobile.native || walletInfo.mobile.universal; if (!appUrl) { @@ -185,7 +189,16 @@ export async function connectWC( provider.events.removeListener("display_uri", wcOptions.onDisplayUri); } - return onConnect(address, chain, provider, emitter, storage, options.client); + return onConnect( + address, + chain, + provider, + emitter, + storage, + options.client, + walletInfo, + sessionHandler, + ); } /** @@ -197,12 +210,14 @@ export async function autoConnectWC( emitter: WalletEmitter, walletId: WCSupportedWalletIds | "walletConnect", storage: AsyncStorage, - sessionHandler?: (uri: string) => void, + sessionHandler?: (uri: string) => void | Promise, ): Promise> { const savedConnectParams: SavedConnectParams | null = storage ? await getSavedConnectParamsFromStorage(storage, walletId) : null; + const walletInfo = await getWalletInfo(walletId); + const provider = await initProvider( savedConnectParams ? { @@ -243,7 +258,16 @@ export async function autoConnectWC( ? options.chain : getCachedChain(providerChainId); - return onConnect(address, chain, provider, emitter, storage, options.client); + return onConnect( + address, + chain, + provider, + emitter, + storage, + options.client, + walletInfo, + sessionHandler, + ); } // Connection utils ----------------------------------------------------------------------------------------------- @@ -284,6 +308,10 @@ async function initProvider( ], name: wcOptions?.appMetadata?.name || getDefaultAppMetadata().name, url: wcOptions?.appMetadata?.url || getDefaultAppMetadata().url, + redirect: { + native: walletInfo.mobile.native || undefined, + universal: walletInfo.mobile.universal || undefined, + }, }, projectId: wcOptions?.projectId || DEFAULT_PROJECT_ID, }); @@ -321,17 +349,22 @@ function createAccount({ address, client, chain, + sessionRequestHandler, + walletInfo, }: { provider: WCProvider; address: string; client: ThirdwebClient; chain: Chain; + sessionRequestHandler?: (uri: string) => void | Promise; + walletInfo: WalletInfo; }) { const account: Account = { address: getAddress(address), async sendTransaction(tx: SendTransactionOption) { - const transactionHash = (await provider.request( - { + const transactionHash = (await requestAndOpenWallet({ + provider, + payload: { method: "eth_sendTransaction", params: [ { @@ -343,8 +376,10 @@ function createAccount({ }, ], }, - `eip155:${tx.chainId}`, - )) as Hex; + chain: `eip155:${tx.chainId}`, + walletInfo, + sessionRequestHandler, + })) as Hex; trackTransaction({ chainId: tx.chainId, @@ -370,13 +405,16 @@ function createAccount({ } return message.raw; })(); - return provider.request( - { + return requestAndOpenWallet({ + provider, + payload: { method: "personal_sign", params: [messageToSign, this.address], }, - `eip155:${chain.id}`, - ); + chain: `eip155:${chain.id}`, + walletInfo, + sessionRequestHandler, + }); }, async signTypedData(_data) { const data = parseTypedData(_data); @@ -399,19 +437,47 @@ function createAccount({ types, }); - return await provider.request( - { + return await requestAndOpenWallet({ + provider, + payload: { method: "eth_signTypedData_v4", params: [this.address, typedData], }, - `eip155:${chain.id}`, - ); + chain: `eip155:${chain.id}`, + walletInfo, + sessionRequestHandler, + }); }, }; return account; } +async function requestAndOpenWallet(args: { + provider: WCProvider; + payload: RequestArguments; + chain?: string; + walletInfo: WalletInfo; + sessionRequestHandler?: (uri: string) => void | Promise; +}) { + const { provider, payload, chain, walletInfo, sessionRequestHandler } = args; + const resultPromise: Promise<`0x${string}`> = provider.request( + payload, + chain, + ); + + const walletLinkToOpen = + provider.session?.peer?.metadata?.redirect?.native || + walletInfo.mobile.native || + walletInfo.mobile.universal; + + if (sessionRequestHandler && walletLinkToOpen) { + await sessionRequestHandler(walletLinkToOpen); + } + + return resultPromise; +} + function onConnect( address: string, chain: Chain, @@ -419,8 +485,17 @@ function onConnect( emitter: WalletEmitter, storage: AsyncStorage, client: ThirdwebClient, + walletInfo: WalletInfo, + sessionRequestHandler?: (uri: string) => void | Promise, ): [Account, Chain, DisconnectFn, SwitchChainFn] { - const account = createAccount({ address, chain, client, provider }); + const account = createAccount({ + address, + chain, + client, + provider, + sessionRequestHandler, + walletInfo, + }); async function disconnect() { provider.removeListener("accountsChanged", onAccountsChanged); @@ -444,6 +519,8 @@ function onConnect( chain, client, provider, + sessionRequestHandler, + walletInfo, }); emitter.emit("accountChanged", newAccount); emitter.emit("accountsChanged", accounts);