Skip to content

Latest commit

 

History

History
422 lines (291 loc) · 18.6 KB

mobileLinking.mdx

File metadata and controls

422 lines (291 loc) · 18.6 KB

import PlatformTabs from '../components/PlatformTabs' import PlatformTabItem from '../components/PlatformTabItem' import ThemedImage from '@theme/ThemedImage' import useBaseUrl from '@docusaurus/useBaseUrl'

Mobile Linking

:::info Note

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:

  1. 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!"
  2. 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.

:::tip

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.

<ThemedImage alt="Mobile Linking Connect Flow" sources={{ light: useBaseUrl('/img/w3w/mobileLinking-light.png'), dark: useBaseUrl('/img/w3w/mobileLinking-dark.png') }} />

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.

<ThemedImage alt="Mobile Linking Sign Flow" sources={{ light: useBaseUrl('/img/w3w/mobileLinking_sign-light.png'), dark: useBaseUrl('/img/w3w/mobileLinking_sign-dark.png') }} />

Platform preparations

<PlatformTabs groupId="" activeOptions={["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 [``](https://developer.android.com/training/app-links/deep-linking#adding-filters) 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

:::tip

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

<PlatformTabs groupId="" activeOptions={["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.

Redirect

iOS Universal Links Constraints

:::caution

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

  1. signer.sendTransaction should be avoided in favor of signer.sendUncheckedTransaction
     This avoids an asynchronous call to retrieve the internal block number which Ethers uses to resolve a complete TransactionResponse object.
     Note that as a result of this optimization, sendUncheckedTransaction returns a mock transaction response that only contains the hash property and a wait method. All other properties are null.
  2. 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 run provider.resolveName ahead of time, storing the result before the user attempts to send a transaction. Do not resolve ENS names in the event handler.
  3. The transaction's gasLimit property should be set
    This avoids the asynchronous work performed in sendTransaction which automatically estimates the gas limit if it's missing.
     If you still want to use the same gas limit estimation logic from sendTransaction, you should manually run provider.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

  1. contract.METHOD_NAME should be avoided if favor of calling contract.populateTransaction.METHOD_NAME ahead of time, then sending the populated transaction with signer.sendUncheckedTransaction.

  2. 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
  }
}