Mobile Linking
This feature is only relevant to native platforms.
Usage
Mobile Linking allows your wallet to automatically redirect back to the Dapp allowing for less user interactions and hence a better UX for your users.
Establishing Communication Between Mobile Wallets and Apps
When integrating a wallet with a mobile application, it's essential to understand how they communicate. The process involves two main steps:
- QR Code Handshake: The mobile app (Dapp) generates a unique URI (Uniform Resource Identifier) and displays it as a QR code. This URI acts like a secret handshake. When the user scans the QR code using their wallet app, they establish a connection. It's like saying, "Hey, let's chat!"
- Deep Links and Universal Links: The URI from the QR code allows the wallet app to create a deep link or universal link. These links work on both Android and iOS. They enable seamless communication between the wallet and the app.
Developers should prefer Deep Linking over Universal Linking.
In the case of Universal Linking, the user may be redirected to the browser, which may not be the desired behavior. Deep Linking ensures that the user is redirected to the app, providing a seamless experience.
The connection and sign request flows are similar across platforms. The next section provides a high-level overview of both flows.
Connection Flow
- Dapp Prompts User: The Dapp asks the user to connect.
- User Chooses Wallet: The user selects a wallet from a list of compatible wallets.
- Redirect to Wallet: The user is redirected to their chosen wallet.
- Wallet Approval: The wallet prompts the user to approve or reject the session (similar to granting permission).
- Return to Dapp:
- Manual Return: The wallet asks the user to manually return to the Dapp. (See iOS Limitations)
- Automatic Return: Alternatively, the wallet automatically takes the user back to the Dapp.
- User Reunites with Dapp: After all the interactions, the user ends up back in the Dapp.
Sign Request Flow
When the Dapp needs the user to sign something (like a transaction), a similar pattern occurs:
- Automatic Redirect: The Dapp automatically sends the user to their previously chosen wallet.
- Approval Prompt: The wallet asks the user to approve or reject the request.
- Return to Dapp:
- Manual Return: The wallet asks the user to manually return to the Dapp. (See iOS Limitations)
- Automatic Return: Alternatively, the wallet automatically takes the user back to the Dapp.
- User Reconnects: Eventually, the user returns to the Dapp.
Platform preparations
- iOS
- Android
- Flutter
- React Native
In order for Dapps to be able to trigger your wallet for a connection or sign request using deep links you first need to add your custom scheme under CFBundleURLTypes
key in your Info.plist file.
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleURLSchemes</key>
<array>
<string>examplewallet</string> <!-- your custom scheme goes here -->
</array>
</dict>
</array>
In order for Dapps to be able to trigger your wallet for a connection or sign request using deep links you first need to declare an <intent-filter>
in your wallet's Manifest.xml as follows:
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="examplewallet" /> <!-- your own custom scheme -->
</intent-filter>
Since Flutter leverages on native APIs, you must follow iOS and Android steps for each native platform.
Additionally, you would have to set FlutterDeepLinkingEnabled key to true on iOS's Info.plist file.
<key>FlutterDeepLinkingEnabled</key>
<true/>
More information in official documentation: https://docs.flutter.dev/ui/navigation/deep-linking
Since React Native leverages on native APIs, you must follow iOS and Android steps for each native platform
More information in official documentation: https://reactnative.dev/docs/linking?syntax=android#enabling-deep-links
Dapps developers must do the same for their own custom schemes if they want the wallet to be able to navigate back after a session approval or a sign request response
Integration
- iOS
- Android
- Flutter
- React Native
iOS Wallet Support
iOS has some more caveats to the integration but we ensure to make it as straightforward as possible. Since its operating system is not designed to handle multiple applications subscribing to the same deep linking schema, we've designed the Web3Modal to list supporting wallets on our Explorer and target specific deep links or universal links for each wallet.
To add your own wallet to the Explorer, login to your WalletConnect Cloud account.
# For deep links
examplewallet://wc?uri=wc:94caa59c77dae0dd234b5818fb7292540d017b27d41f7f387ee75b22b9738c94@2?relay-protocol=irn&symKey=ce3a2c7724c03cf1769ba8b1bdedad5414cc7b920aa3fb72112b997d1916266f
# For universal links
https://example.wallet/wc?uri=wc:94caa59c77dae0dd234b5818fb7292540d017b27d41f7f387ee75b22b9738c94@2?relay-protocol=irn&symKey=ce3a2c7724c03cf1769ba8b1bdedad5414cc7b920aa3fb72112b997d1916266f
Additionally when there is a signing request triggered by the Dapp it will hit the deep link with an incomplete URI, this should be ignored and not considered valid as it's only used for automatically redirecting the users to approve or reject a signing request.
# For deep links
examplewallet://wc?uri=wc:00e46b69-d0cc-4b3e-b6a2-cee442f97188@2
# For universal links
https://example.wallet/wc?uri=wc:00e46b69-d0cc-4b3e-b6a2-cee442f97188@2
WalletConnectRouter
Overview
WalletConnectRouter simplifies navigation by automatically redirecting users back to the DApp after they've interacted with a wallet via a deep link. This eliminates the need for users to manually navigate back after approving a session or confirming a transaction.
Key Features
Automatic Redirection: By invoking WalletConnectRouter.goBack(uri: "example://")—where "example://" is the DApp's custom scheme as declared in their AppMetadata redirect field—users are seamlessly returned to the DApp.
Important Consideration
Mandatory redirect Field: Starting with WalletConnect SDK version 1.9.5, specifying the redirect field in the AppMetadata object is mandatory to avoid redirection issues.
Installation and Usage
import WalletConnectRouter
try await Sign.instance.approve(proposalId: <proposalId>, namespaces: <namespaces>)
if let uri = proposal.proposer.redirect?.native {
WalletConnectRouter.goBack(uri: uri)
} else {
// Inform the user to manually return to the DApp
}
Limitations
This section outlines some of the known limitations and constraints when using WalletConnect on iOS.
Redirects on iOS 17 and Above
Automatic redirection to browser-based DApps after wallet interaction is not possible from iOS 17 onwards. Developers should adjust their app's UI to inform users about manual navigation back to the browser.
For iOS versions below 17, WalletConnectRouter.goBack(uri: uri)
facilitates automatic redirection.
iOS Universal Links Constraints
Developers should prefer Deep Linking over Universal Linking.
In the case of Universal Linking, the user may be redirected to the browser, which may not be the desired behavior. Deep Linking ensures that the user is redirected to the app, providing a seamless experience.
When using WalletConnect on iOS and triggering a wallet interaction (e.g. when sending a transaction or signing a message), you may experience issues where the native app is not opened as expected and a browser navigation occurs instead.
This issue occurs because Universal Links (app links) on iOS will only open the native app when the following rules are followed:
- The wallet interaction must be triggered by a user-initiated event, e.g. in a click handler rather than on page load or in an asynchronous callback.
- The wallet interaction must be triggered as soon as possible within the event handler. Any preceding asynchronous work (e.g. estimating gas, resolving an ENS name, fetching a nonce) should have already completed before the event handler fires. This may require you to design the user experience around this constraint, preventing users from initiating a wallet interaction until it's ready rather than doing the work lazily.
Note that even if your own code follows these rules, libraries you depend on may be running their own asynchronous logic before triggering a wallet interaction. For example, Ethers asynchronously populates transactions before sending them. Known workarounds are documented below, but if you're still experiencing these issues, you should raise them with the relevant library maintainers.
For Ethers v5 (Legacy)
These are the known workarounds for avoiding app linking issues on iOS when using Ethers v5.
When sending a transaction
signer.sendTransaction
should be avoided in favor ofsigner.sendUncheckedTransaction
This avoids an asynchronous call to retrieve the internal block number which Ethers uses to resolve a completeTransactionResponse
object.
Note that as a result of this optimization,sendUncheckedTransaction
returns a mock transaction response that only contains thehash
property and await
method. All other properties arenull
.- The transaction's
to
property should be a plain address rather than an ENS name
This avoids an asynchronous call to automatically resolve ENS names during the send process.
If you still want to support ENS name resolution, you should manually runprovider.resolveName
ahead of time, storing the result before the user attempts to send a transaction. Do not resolve ENS names in the event handler. - The transaction's
gasLimit
property should be set
This avoids the asynchronous work performed insendTransaction
which automatically estimates the gas limit if it's missing.
If you still want to use the same gas limit estimation logic fromsendTransaction
, you should manually runprovider.estimateGas
ahead of time, storing the result before the user attempts to send the transaction. Do not estimate gas in the event handler.
When calling a write method on a contract
-
contract.METHOD_NAME
should be avoided if favor of callingcontract.populateTransaction.METHOD_NAME
ahead of time, then sending the populated transaction withsigner.sendUncheckedTransaction
. -
When sending the populated transaction, you should follow the same guidelines as regular transactions to avoid any asynchronous logic breaking the app link navigation. Do not populate the contract transaction in the event handler.
When signing a message
If the message depends on the result of an asynchronous call (e.g. retrieving a nonce when implementing Sign-In With Ethereum), you should do this work ahead of time, storing the result before the user attempts to sign the message. Do not perform this asynchronous work in the event handler.
Android Wallet Support
Disclaimer: The below solution is designed for the communication between native Android Dapps and native Android wallets. In the case of mobile browser Dapps and native Android wallets communication, we recommend moving wallets into the background after both approving and rejecting sessions or approving and rejecting requests to persist smooth deep-link UX.
In order to add support for mobile linking within your wallet and receive session proposals, register following deep link in your mobile wallet using intent filters in your Activity/Fragment or deepLink tag in your navigation graph.
To support universal native modal and WalletConnectModal register: wc://
Deep link example: examplewallet://wc?uri={pairingUri}
To receive signing request in your Wallet, you'll need to initialize Kotlin SDK with the Redirect
object where you pass a deep link that redirects to your wallet when it comes to receiving signing request from Dapp.
val redirect = "examplewallet://request" //should be unique for your wallet
val appMetaData = Core.Model.AppMetaData(
name = "Wallet Name",
description = "Wallet Description",
url = "Wallet Url",
icons = listOfIconUrlStrings,
redirect = redirect
)
CoreClient.initialize(relayServerUrl = serverUrl, connectionType = connectionType, application = application, metaData = appMetaData)
val init = Wallet.Params.Init(coreClient = CoreClient)
Web3Wallet.initialize(init)
Redirect when responding to a session proposal:
Web3Wallet.approveSession(approveProposal,
onSuccess = {
// trigger deeplink: proposal.redirect
}
)
Redirect when responding to a request:
val redirect = Web3Wallet.getActiveSessionByTopic(sessionRequest.topic)?.redirect?.toUri()
Web3Wallet.respondSessionRequest(response,
onSuccess = {
// trigger deeplink: redirect
}
)
Heads-up: To make this flow working well, Wallet must register one of its Android components with the same deep link that it initialized with.
To check the flow implementation described above have a look on our sample wallet: https://github.com/WalletConnect/WalletConnectKotlinV2/tree/master/sample/wallet
Dapp Support
To send session proposals to mobile wallet user the pairing URI as deep link that triggers a wallet to open and consume pairing URI
requireActivity().startActivity(Intent(Intent.ACTION_VIEW, deeplinkPairingUri.toUri()))
In order to add support for mobile linking within your Dapp and receive signing request responses from wallet, you'll need to initialize Kotlin SDK with the Redirect
object where you pass a deep link that redirects to your Dapp when it comes to receiving signing request responses from wallet.
val redirect = "kotlin-dapp-wc://request" //should be unique for your Dapp
val appMetaData = Core.Model.AppMetaData(
name = "Dapp Name",
description = "Dapp Description",
url = "Dapp URL",
icons = listOfIconUrlStrings,
redirect = redirect
)
CoreClient.initialize(relayServerUrl = serverUrl, connectionType = connectionType, application = application, metaData = appMetaData)
val init = Sign.Params.Init(core = CoreClient)
SignClient.initialize(init)
Heads-up: To make this flow working well, Dapp must register one of its Android components with the same deep link that it initialized with.
To check the flow implementation described above have a look on our Sample Dapp: https://github.com/WalletConnect/WalletConnectKotlinV2/tree/master/sample/dapp
References
Either you are approving a session proposal or responding to a session request, redirecting back to the Dapp is as simply as launching requester's redirect
object in PairingMetadata
:
For session approval:
// Where event is a SessionProposalEvent
final scheme = event.params.proposer.metadata.redirect?.native ?? '';
launchUrlString(scheme, mode: LaunchMode.externalApplication);
For session requests:
final session = web3Wallet.sessions.get(topic);
final scheme = session?.peer.metadata.redirect?.native ?? '';
launchUrlString(scheme, mode: LaunchMode.externalApplication);
In order to redirect to the Dapp, you'll need to use Linking
from react-native
and call openURL()
method with the Dapp scheme that comes in the proposal metadata.
import { Linking } from 'react-native'
async function onApprove(proposal, namespaces) {
await web3wallet.approveSession({ id: proposal.id, namespaces })
const dappScheme = proposal.params.proposer.metadata.redirect?.native
if (dappScheme) {
Linking.openURL(dappScheme)
} else {
// Inform the user to manually return to the DApp
}
}
Was this helpful?