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.
- 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.
- Automatic Return: Alternatively, the wallet automatically takes the user back to the Dapp.
- User Reconnects: Eventually, the user returns to the Dapp.
Platform preparations
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>
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
How to test
Before submitting your project to the Cloud Explorer you can test mobile linking in our sample Dapp:
- On your mobile device, visit the appropriate link:
- For EVM: https://appkit-lab.reown.com/library/wagmi/
- For Solana: https://appkit-lab.reown.com/library/solana/
- Click the "Custom Wallet" button and fill in the form with your wallet information. The website will reload and your wallet will be stored locally.
- Click the "Connect Wallet" button and choose your mobile wallet. It should automatically open and redirect to your wallet.
Learn more about mobile linking in the Best Practices section.
Integration
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 AppKit 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 Reown 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.