Skip to main content
WalletKit is Reown’s official SDK that enables wallets to connect securely with decentralized applications (DApps). It handles critical functions including:
  • Pairing - Creating secure connections between wallets and DApps
  • Session Management - Handling proposal, approval, rejection, and termination of sessions
  • WebSocket Connection - Maintaining stable connection with the relay network
  • Error Handling - Processing and communicating errors between components
This document outlines best practices for implementing WalletKit in your wallet to ensure optimal user experience.

Checklist Before Going Live

To ensure your wallet adheres to best practices, implement the following before deployment:
CategoryBest PracticeDetails
Success and Error MessagesDisplay clear messages for all interactions• Show connection success messages
• Show connection error messages
• Provide loading indicators during waiting periods
Inform users of connection status• Display real-time connection state
• Show transaction status updates
Implement internet availability indicators• Alert users when connection is lost
• Provide recovery options
Provide feedback to both users and DApps• Communicate errors to both user interfaces
Mobile LinkingImplement automatic redirection• Enable seamless transitions between wallet and DApp
Use deep linking over universal linking• More reliable direct app launching
• Better parameter passing support
Ensure proper return to DApp• Redirect after transaction completion
LatencyOptimize performance• Minimize delays in all interactions
Connection time in normal conditions• Target: under 5 seconds
Connection time in poor network (3G)• Target: under 15 seconds
Signing time in normal conditions• Target: under 5 seconds
Signing time in poor network (3G)• Target: under 10 seconds
Verify APIPresent security status indicators• Domain match: verified domain
• Unverified: domain not in registry
• Mismatch: domain doesn’t match registry
• Threat: flagged as malicious
SDK VersionUse latest SDK version• Benefit from latest features and security updates
Update SDK regularly• Fix known bugs
• Improve performance
Subscribe to SDK updates• Stay informed of new releases and deprecations

1. User Feedback and Messaging

Context

Users often face ambiguity determining whether their connection or transactions were successful. Clear messaging reduces anxiety and improves user experience.

Best Practices

  • ✅ Display clear success and error messages for all user interactions
  • ✅ Provide loading indicators during connection and transaction processing
  • ✅ Include status indicators for internet availability
  • ✅ Return errors to the DApp (not just the user) to allow proper state management

An example of a successful connection message in a Rainbow wallet

2. Pairing Implementation

Pairing Overview

What is Pairing? A pairing is a secure connection between a wallet and a DApp with fixed permissions that allows the DApp to propose sessions.

Pairing Process

  • Web
  • React Native
  • iOS
  • Android
const uri = 'wc:a13aef...'; // pairing URI received from DApp (typically via QR code)
try {
    // Initiate pairing with the DApp
    await walletKit.pair({ uri });
    // Display success message to user
} catch (error) {
    // Handle and display specific error (see Expected Errors section)
    console.error('Pairing failed:', error);
}

Pairing State Management

Pairing Expiry

All pairings expire after 5 minutes if no successful session is established. After a successful session establishment, monitor these events to update your UI accordingly.
  • Web
  • React Native
  • iOS
  • Android
// Subscribe to pairing expiry events
core.pairing.events.on("pairing_expire", (event) => {
    const { topic } = event; // The topic identifies which pairing expired
    
    // Update UI to inform user the connection has expired
    // E.g., remove the pairing from active connections list
});

Expected Pairing Errors

Error TypeDescriptionUser ActionDApp Action
No InternetConnection attempt with no networkRetry with active connectionDisplay error, allow retry
Expired PairingQR code/URI has expiredRefresh QR code and scan againGenerate new pairing URI
Pairing Already ExistsAttempting to use an already paired URIScan a new QR codeGenerate new pairing URI
Malformed Pairing URIURI does not follow WalletConnect protocol formatTry a different DApp or report issueEnsure correct implementation of WalletConnect URI format
Invalid URIPairing URI structure is incorrectGet new URI from DAppCheck URI format matches WalletConnect spec

3. Session Proposal Handling

What is a Session Proposal?

A session proposal is a handshake sent by a DApp that defines the rules and permissions for the connection. The wallet must approve or reject this proposal to establish a session.

Sample Session Proposal Object

{
  id: '1234567890',
  params: {
    proposer: {
      publicKey: '0x...',
      metadata: {
        name: 'Example DApp',
        description: 'A DApp for testing WalletKit',
        url: 'https://example.com',
        icons: ['https://example.com/icon.png']
      }
    },
    requiredNamespaces: {
      eip155: {
        chains: ['eip155:1'],
        methods: ['eth_sendTransaction', 'personal_sign'],
        events: ['accountsChanged', 'chainChanged']
      }
    }
  }
}

Approving a Session

  • Web
  • React Native
  • iOS
  • Android
try {
    // Show loading indicator when user taps "Approve"
    showLoadingIndicator();
    
    // Approve the session with selected accounts and chains
    await walletKit.approveSession({
      id: proposal.id,
      namespaces: {
        eip155: {
          accounts: ['eip155:1:0x...'], // Format: chainId:accountAddress
          methods: proposal.requiredNamespaces.eip155.methods,
          events: proposal.requiredNamespaces.eip155.events
        }
      }
    });
    
    // Hide loading indicator, show success message
    hideLoadingIndicator();
    showSuccessMessage('Connection approved');
} catch (error) {
    // Hide loading indicator, show error message
    hideLoadingIndicator();
    showErrorMessage(`Connection failed: ${error.message}`);
}

Rejecting a Session

  • Web
  • React Native
  • iOS
  • Android
try {
    // Show loading indicator when user taps "Reject"
    showLoadingIndicator();
    
    // Reject the session with reason
    await walletKit.rejectSession({
      id: proposal.id,
      reason: {
        code: 4001,
        message: 'User rejected the request'
      }
    });
    
    // Hide loading indicator, show success message
    hideLoadingIndicator();
    showMessage('Connection rejected');
} catch (error) {
    // Hide loading indicator, show error message
    hideLoadingIndicator();
    showErrorMessage(`Error: ${error.message}`);
}

4. Mobile Linking

What is Mobile Linking?

Mobile linking allows seamless transitions between DApps and wallets on mobile devices, ensuring users can complete actions without manually switching applications. Deep links (e.g., yourwallet://) provide a more reliable user experience than universal links. Deep links:
  • Launch the target app directly
  • Support passing parameters in the URL
  • Work consistently across platforms

Why use Mobile Linking?

Mobile Linking uses the mobile device’s native OS to automatically redirect between the native wallet app and a native app. This results in few user actions a better UX.

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 or copy/pastes the URI 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.
Developers should prefer Deep Linking over Universal Linking.Universal Linking may redirect the user to a browser, which might not provide the intended user experience. Deep Linking ensures the user is taken directly to the app.

Key Behavior to Address

In some scenarios, wallets use redirect metadata provided in session proposals to open applications. This can cause unintended behavior, such as:
  • Redirecting to the wrong app when multiple apps share the same redirect metadata (e.g., a desktop and mobile version of the same Dapp).
  • Opening an unrelated application if a QR code is scanned on a different device than where the wallet is installed.
To avoid this behavior, wallets should:
  • Restrict Redirect Metadata to Deep Link Use Cases: Redirect metadata should only be used when the session proposal is initiated through a deep link. QR code scans should not trigger app redirects using session proposal metadata.

Connection Flow

  1. Dapp prompts user: The Dapp asks the user to connect.
  2. User chooses wallet: The user selects a wallet from a list of compatible wallets.
  3. Redirect to wallet: The user is redirected to their chosen wallet.
  4. Wallet approval: The wallet prompts the user to approve or reject the session (similar to granting permission).
  5. 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.
  6. 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:
  1. Automatic redirect: The Dapp automatically sends the user to their previously chosen wallet.
  2. Approval prompt: The wallet asks the user to approve or reject the request.
  3. 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.
  4. User reconnects: Eventually, the user returns to the Dapp.

Platform Specific Preparation

  • iOS
  • Android
  • Flutter
  • React Native
Read the specific steps for iOS here: Platform preparations

How to Test

To experience the desired behavior, try our Sample Wallet and Dapps which use our Mobile linking best practices. These are available on all platforms. Once you have completed your integration, you can test it against our sample apps to see if it is working as expected. Download the app and try your mobile linking integration on your device.
  • iOS
  • Android
  • Flutter
  • React Native

Implementing Mobile Linking

  • iOS
  • Android
// In your AppDelegate or SceneDelegate
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
    // Check if URL is a WalletKit deep link
    if WalletKit.instance.isWalletConnectDeepLink(url) {
        // Handle WalletConnect URL
        Task {
            do {
                try await WalletKit.instance.handleDeepLink(url)
                return true
            } catch {
                // Handle error
                print("Deep link error: \(error)")
                return false
            }
        }
    }
    return false
}

Redirecting Back to DApp

After completing an action, ensure your wallet redirects the user back to the DApp:
  • iOS
  • Android
// After session approval or transaction signing
func redirectBackToDApp(using redirectUrl: String?) {
    guard let redirectUrl = redirectUrl,
          let url = URL(string: redirectUrl) else { return }
    
    DispatchQueue.main.async {
        UIApplication.shared.open(url)
    }
}

5. Session Request

A session request represents the request sent by a DApp to a wallet. Whenever user approves or rejects a session request, a wallet should show a loading indicator the moment the button is pressed, until Relay acknowledgement is received for any of these actions.
  • Web
  • React Native
  • iOS
  • Android
try {
    // Show loading indicator when user approves/rejects
    showLoadingIndicator();
    
    // Respond to the session request
    await walletKit.respondSessionRequest({
      topic: request.topic,
      response: {
        id: request.id,
        jsonrpc: '2.0',
        result: response // The response could be a signature or other data
      }
    });
    
    // Hide loading indicator, show success message
    hideLoadingIndicator();
    showSuccessMessage('Request completed');
} catch (error) {
    // Hide loading indicator, show error message
    hideLoadingIndicator();
    showErrorMessage(`Request failed: ${error.message}`);
}

Session Request Expiry

A session request expiry is defined by a DApp. Its value must be between now() + 5mins and now() + 7 days. After the session request expires, the below event is emitted and session request modal should be removed from the app’s UI.
  • Web
  • React Native
  • iOS
  • Android
walletKit.on("session_request_expire", (event) => {
  // Request expired and any modal displaying it should be removed
  const { id } = event;
  // Update UI to remove the request
  removeRequestFromUI(id);
});

Expected Session Request Errors

Error TypeDescriptionUser ActionDApp Action
Invalid SessionRequest made on expired/invalid sessionReconnect to the DAppDetect session expiry and prompt for reconnection
Session Request ExpiredApproval/rejection after request timeoutHandle new requests onlySet appropriate timeout periods
TimeoutNo relay acknowledgement within 10 secondsRetry the actionImplement proper timeout handling
Network ErrorConnection issues during requestCheck network and retryDisplay network status indicators

6. Connection State

The Web Socket connection state tracks the connection with the Relay server. An event is emitted whenever a connection state changes.
  • Web
  • React Native
  • iOS
  • Android
core.relayer.on("relayer_connect", () => {
  // Connection to the relay server is established
  updateConnectionStatus('connected');
})

core.relayer.on("relayer_disconnect", () => {
  // Connection to the relay server is lost
  updateConnectionStatus('disconnected');
})

Connection State Messages

When the connection state changes, show a message in the UI. For example, display a message when the connection is lost or re-established.

7. Latency

Our SDK’s position in the boot chain can lead to up to 15 seconds in throttled network conditions. Lack of loading indicators exacerbates the perceived latency issues, impacting user experience negatively. Additionally, users often do not receive error messages or codes when issues occur or timeouts happen.

Target Latency

For connecting, the target latency is:
  • Under 5 seconds in normal conditions
  • Under 15 seconds when throttled (3G network speed)
For signing, the target latency is:
  • Under 5 seconds in normal conditions
  • Under 10 seconds when throttled (3G network speed)

How to Test

To test latency under suboptimal network conditions, you can enable throttling on your mobile phone. You can simulate different network conditions to see how your app behaves in various scenarios. For example, on iOS you need to enable Developer Mode and then go to Settings > Developer > Network Link Conditioner. You can then select the network condition you want to simulate. For 3G, you can select 3G from the list, for no network or timeout simulations, choose 100% Loss. Check this article for how to simulate slow internet connection on iOS & Android, with multiple options for both platforms: How to simulate slow internet connection on iOS & Android.

8. Verify API

Verify API is a security-focused feature that allows wallets to notify end-users when they may be connecting to a suspicious or malicious domain, helping to prevent phishing attacks across the industry. Once a wallet knows whether an end-user is on uniswap.com or eviluniswap.com, it can help them to detect potentially harmful connections through Verify’s combined offering of Reown’s domain registry. When a user initiates a connection with an application, Verify API enables wallets to present their users with four key states that can help them determine whether the domain they’re about to connect to might be malicious. Possible states:
  • Domain match
  • Unverified
  • Mismatch
  • Threat
Verify States
Verify States
Verify API is not designed to be bulletproof but to make the impersonation attack harder and require a somewhat sophisticated attacker. We are working on a new standard with various partners to close those gaps and make it bulletproof.

Domain Risk Detection

The Verify security system will discriminate session proposals & session requests with distinct validations that can be either VALID, INVALID or UNKNOWN.
  • Domain match: The domain linked to this request has been verified as this application’s domain.
    • This interface appears when the domain a user is attempting to connect to has been ‘verified’ in our domain registry as the registered domain of the application the user is trying to connect to, and the domain has not returned as suspicious from either of the security tools we work with. The verifyContext included in the request will have a validation of VALID.
  • Unverified: The domain sending the request cannot be verified.
    • This interface appears when the domain a user is attempting to connect to has not been verified in our domain registry, but the domain has not returned as suspicious from either of the security tools we work with. The verifyContext included in the request will have a validation of UNKNOWN.
  • Mismatch: The application’s domain doesn’t match the sender of this request.
    • This interface appears when the domain a user is attempting to connect to has been flagged as a different domain to the one this application has verified in our domain registry, but the domain has not returned as suspicious from either of the security tools we work with. The verifyContext included in the request will have a validation of INVALID
  • Threat: This domain is flagged as malicious and potentially harmful.
    • This interface appears when the domain a user is attempting to connect to has been flagged as malicious on one or more of the security tools we work with. The verifyContext included in the request will contain parameter isScam with value true.

Verify API Implementation

To see how to implement Verify API for your framework, see Verify API page and select your platform to see code examples.

How to Test

To test Verify API with a malicious domain, you can check out the Malicious React dapp, created specifically for testing. This app is flagged as malicious and will have the isScam parameter set to true in the verifyContext of the request. You can use this app to test how your wallet behaves when connecting to a malicious domain.

Error Messages

Verify API flagged domain
A sample error warning when trying to connect to a malicious domain

9. Latest SDK Version

Numerous features have been introduced, bugs have been identified and fixed over time, stability has improved, but many DApps and wallets continue to use older SDK versions with known issues, affecting overall reliability. Make sure you are using the latest version of the SDK for your platform
  • iOS
  • Android
  • Flutter
  • React Native

Subscribe to Updates

To stay up to date with the latest SDK releases, you can use GitHub’s native feature to subscribe to releases. This way, you will be notified whenever a new release is published. You can find the “Watch” button on the top right of the repository page. Click on it, then select “Custom” and “Releases only”. You’ll get a helpful ping whenever a new release is out. Subscribe to releases

10. Testing WalletKit Features

Test Scenarios

FeatureTest ScenarioExpected Outcome
PairingScan QR with no internetError message shown + retry option
Scan expired QR codeError with refresh suggestion
Scan valid QR codeSuccessful pairing, proposal shown
Session ProposalApprove with selected accountsSuccess message, session active
Reject proposalSuccess message, back to home screen
App killed during approvalProper error handling when reopened
Mobile LinkingOpen deep link from DAppWallet opens with correct context
Complete action in walletRedirects back to DApp
PerformanceMeasure connection time< 5s in normal conditions
Measure signing time< 5s in normal conditions
Multiple ConnectionsConnect to multiple DAppsAll connections work independently
WebSocket ConnectionTurn off Wi-Fi during sessionReconnection attempt + user notification

11. Troubleshooting

Common Issues

IssuePossible CausesSolution
Session proposal not receivedNetwork issues, pairing expiredCheck network, refresh QR code
Cannot approve sessionInvalid namespace configurationEnsure accounts match required chains
Slow connectionPoor network, obsolete SDKCheck network, update SDK
Deep link not workingImproper URL scheme registrationVerify URL scheme in app manifest/Info.plist
Session disconnects frequentlyBackground process limitationsImplement proper reconnection logic

Resources