From 2708e5a0b1b165ae353bd012d356e778e76e64e5 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 23 Apr 2025 09:59:20 +0300 Subject: [PATCH 1/3] Improve WalletKit Best Practices documentation for clarity, usability, and AI-friendliness --- walletkit/best-practices.mdx | 987 ++++++++++++++++------------------- 1 file changed, 447 insertions(+), 540 deletions(-) diff --git a/walletkit/best-practices.mdx b/walletkit/best-practices.mdx index ec75c8e41..09051bc78 100644 --- a/walletkit/best-practices.mdx +++ b/walletkit/best-practices.mdx @@ -1,93 +1,168 @@ --- sidebarTitle: Best Practices title: Best Practices for Wallets +summary: Guidelines for wallet providers to optimize user experience with Reown's WalletKit +keywords: wallets, best practices, pairing, session management, errors, latency +version: 2.0 --- -To ensure the smoothest and most seamless experience for our users, Reown is committed to working closely with wallet providers to encourage the adoption of our recommended best practices. +# WalletKit Overview -By implementing these guidelines, we aim to optimize performance and minimize potential challenges, even in suboptimal network conditions. +WalletKit is Reown's official SDK that enables wallets to connect securely with decentralized applications (DApps). It handles critical functions including: -We are actively partnering with wallet developers to optimize performance in scenarios such as: +- **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 -1. **Success and Error Messages** - Users need to know what’s going on, at all times. Too much communication is better than too little. The less users need to figure out themselves or assume what’s going on, the better. -2. **(Perceived) Latency** - A lot of factors can influence latency (or perceived latency), e.g. network conditions, position in the boot chain, waiting on the wallet to connect or complete a transaction and not knowing if or when it has done it. -3. **Old SDK Versions** - Older versions can have known and already fixed bugs, leading to unnecessary issues to users, which can be simply and quickly solved by updating to the latest SDK. +This document outlines best practices for implementing WalletKit in your wallet to ensure optimal user experience. -To take all of the above into account and to make experience better for users, we've put together some key guidelines for wallet providers. These best practices focus on the most important areas for improving user experience. +## Quick Start -Please follow these best practices and make the experience for your users and yourself a delightful and quick one. +Initialize WalletKit as early as possible in your application lifecycle: + + + +```javascript +// Initialize WalletKit in your app entry point +import { WalletKit } from '@reown/walletkit'; + +const walletKit = await WalletKit.init({ + projectId: 'YOUR_PROJECT_ID', + name: 'Your Wallet Name' +}); +``` + + +```javascript +// Initialize WalletKit in your app entry point +import { WalletKit } from '@reown/walletkit-react-native'; + +const walletKit = await WalletKit.init({ + projectId: 'YOUR_PROJECT_ID', + name: 'Your Wallet Name' +}); +``` + + +```swift +// Initialize WalletKit in your AppDelegate +import WalletKit + +func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + WalletKit.configure(with: WalletKitConfig( + projectId: "YOUR_PROJECT_ID", + name: "Your Wallet Name" + )) + return true +} +``` + + +```kotlin +// Initialize WalletKit in your Application class +import io.reown.walletkit.WalletKit + +class YourApplication : Application() { + override fun onCreate() { + super.onCreate() + WalletKit.initialize( + context = this, + projectId = "YOUR_PROJECT_ID", + name = "Your Wallet Name" + ) + } +} +``` + + ## Checklist Before Going Live -To make sure your wallet adheres to the best practices, we recommend implementing the following checklist before going live. You can find more detailed information on each point below. - -1. **Success and Error Messages** - - ✅ Display clear and concise messages for all user interactions - - ✅ Provide feedback for all user actions - - ✅ Connection success - - ✅ Connection error - - ✅ Loading indicators for waiting on connection, transaction, etc. - - ✅ Ensure that users are informed of the status of their connection and transactions - - ✅ Implement status indicators internet availability - - ✅ Make sure to provide feedback not only to users but also back to the dapp (e.g., if there's an error or a user has not enough funds to pay for gas, don't just display the info message to the user, but also send the error back to the dapp so that it can change the state accordingly) -2. **Mobile Linking** - - ✅ Implement mobile linking to allow for automatic redirection between the wallet and the dapp - - ✅ Use deep linking over universal linking for a better user experience - - ✅ Ensure that the user is redirected back to the dapp after completing a transaction -3. **Latency** - - ✅ Optimize performance to minimize latency - - ✅ Latency for connection in normal conditions: under 5 seconds - - ✅ Latency for connection in poor network (3G) conditions: under 15 seconds - - ✅ Latency for signing in normal conditions: under 5 seconds - - ✅ Latency for signing in poor network (3G) conditions: under 10 seconds -4. **Verify API** - - ✅ Present users with four key states that can help them determine whether the domain they’re about to connect to might be malicious (Domain match, Unverified, Mismatch, Threat) -5. **Latest SDK Version** - - ✅ Ensure that you are using the latest SDK version - - ✅ Update your SDK regularly to benefit from the latest features and bug fixes - - ✅ Subscribe to SDK updates to stay informed about new releases - -## 1. Success and Error Messages - -Users often face ambiguity in determining whether their connection or transactions were successful. They are also not guided to switching back into the dapp or automatically switched back when possible, causing unnecessary user anxiety. Additionally, wallets typically lack status indicators for connection and internet availability, leaving users in the dark. +To ensure your wallet adheres to best practices, implement the following before deployment: + +| Category | Best Practice | Priority | +|----------|---------------|----------| +| **User Feedback** | Display clear connection/transaction status messages | High | +| | Show loading indicators during operations | High | +| | Communicate errors to both user and DApp | High | +| **Mobile Experience** | Implement deep linking between wallet and DApp | High | +| | Ensure proper redirection after transactions | Medium | +| **Performance** | Connection time < 5s (normal conditions) | High | +| | Connection time < 15s (poor network) | Medium | +| | Transaction signing < 5s (normal conditions) | High | +| | Transaction signing < 10s (poor network) | Medium | +| **Security** | Implement Verify API with all four states | High | +| **Maintenance** | Use latest SDK version | High | +| | Subscribe to SDK update notifications | Medium | + +## 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 + ![](/images/assets/connection-successful.png) -### Pairing -A pairing is a connection between a wallet and a dapp that has fixed permissions to only allow a dapp to propose a session through it. Dapp can propose infinite number of sessions on one pairing. Wallet must use a pair method from WalletKit client to pair with dapp. +## 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. Each pairing can support multiple sessions. + +### Pairing Process ```jsx -const uri = 'xxx'; // pairing uri +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) { - // some error happens while pairing - check Expected errors section + // Handle and display specific error (see Expected Errors section) + console.error('Pairing failed:', error); } ``` ```jsx -const uri = 'xxx'; // pairing uri +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) { - // some error happens while pairing - check Expected errors section + // Handle and display specific error (see Expected Errors section) + console.error('Pairing failed:', error); } ``` ```swift +// Convert string URI to WalletConnectURI object let uri = WalletConnectURI(string: urlString) if let uri { -Task { -try await WalletKit.instance.pair(uri: uri) -} + Task { + do { + // Initiate pairing with the DApp + try await WalletKit.instance.pair(uri: uri) + // Display success message to user + } catch { + // Handle and display specific error + print("Pairing failed: \(error)") + } + } } - ```` @@ -95,631 +170,463 @@ try await WalletKit.instance.pair(uri: uri) val pairingParams = Wallet.Params.Pair(pairingUri) WalletKit.pair(pairingParams, onSuccess = { - //Subscribed on the pairing topic successfully. Wallet should await for a session proposal + // Pairing successful - wallet should now await session proposal + // Display success message to user }, onError = { error -> - //Some error happens while pairing - check Expected errors section + // Handle and display specific error (see Expected Errors section) + Log.e("WalletKit", "Pairing failed: $error") } } ```` - -#### Pairing Expiry +### Pairing State Management -A pairing expiry event is triggered whenever a pairing is expired. The expiry for inactive pairing is 5 mins, whereas for active pairing is 30 days. A pairing becomes active when a session proposal is received and user successfully approves it. This event helps to know when given pairing expires and update UI accordingly. +#### Pairing Expiry +Inactive pairings expire after 5 minutes, while active pairings (with approved sessions) expire after 30 days. Monitor these events to update your UI accordingly. ```typescript +// Subscribe to pairing expiry events core.pairing.events.on("pairing_expire", (event) => { - // pairing expired before user approved/rejected a session proposal - const { topic } = topic; + 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 }); ``` ```typescript +// Subscribe to pairing expiry events core.pairing.events.on("pairing_expire", (event) => { - // pairing expired before user approved/rejected a session proposal - const { topic } = topic; + 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 }); ``` ```Swift +// Subscribe to pairing expiry events WalletKit.instance.pairingExpirationPublisher .receive(on: DispatchQueue.main) .sink { pairing in - guard !pairing.active else { return } - // let user know that pairing has expired -}.store(in: &publishers) + guard !pairing.active else { return } + // Inform user that pairing has expired + // E.g., show alert or update connection status + }.store(in: &publishers) ``` ```kotlin val coreDelegate = object : CoreClient.CoreDelegate { override fun onPairingExpired(expiredPairing: Core.Model.ExpiredPairing) { - // Here a pairing expiry is triggered + // Handle pairing expiry event + // Update UI to inform user } - // ...other callbacks + // ...other required callbacks } +// Set the delegate to receive events CoreClient.setDelegate(coreDelegate) - ```` -#### Pairing messages - -1. Consider displaying a successful pairing message when pairing is successful. Before that happens, wallet should show a loading indicator. -2. Display an error message when a pairing fails. - -#### Expected Errors - -While pairing, the following errors might occur: - -- **No Internet connection error or pairing timeout when scanning QR with no Internet connection** - - User should pair again with Internet connection -- **Pairing expired error when scanning a QR code with expired pairing** - - User should refresh a QR code and scan again -- **Pairing with existing pairing is not allowed** - - User should refresh a QR code and scan again. It usually happens when user scans an already paired QR code. - -### Session Proposal - -A session proposal is a handshake sent by a dapp and its purpose is to define session rules. Whenever a user wants to establish a connection between a wallet and a dapp, one should approve a session proposal. - -Whenever user approves or rejects a session proposal, a wallet should show a loading indicator the moment the button is pressed, until Relay acknowledgement is received for any of these actions. +### Expected Pairing Errors + +| Error Type | Description | User Action | DApp Action | +|------------|-------------|-------------|-------------| +| No Internet | Connection attempt with no network | Retry with active connection | Display error, allow retry | +| Expired Pairing | QR code/URI has expired | Refresh QR code and scan again | Generate new pairing URI | +| Duplicate Pairing | Attempt to pair with existing pairing | Generate new pairing | Generate new pairing URI | +| Invalid URI | Malformed or corrupt pairing URI | Get new URI from DApp | Generate new pairing URI | + +## 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 +```javascript +{ + 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 session +### Approving a Session ```typescript try { - await walletKit.approveSession(params); - // update UI -> remove the loader + // 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) { - // present error to the user + // Hide loading indicator, show error message + hideLoadingIndicator(); + showErrorMessage(`Connection failed: ${error.message}`); } ```` - ```typescript try { - await walletKit.approveSession(params); - // update UI -> remove the loader + // 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) { - // present error to the user + // Hide loading indicator, show error message + hideLoadingIndicator(); + showErrorMessage(`Connection failed: ${error.message}`); } ``` ```swift do { - try await WalletKit.instance.approve(proposalId: proposal.id, namespaces: sessionNamespaces, sessionProperties: proposal.sessionProperties) - // Update UI, remove loader + // Show loading indicator when user taps "Approve" + showLoadingIndicator() + + // Approve the session with selected accounts and chains + try await WalletKit.instance.approve( + proposalId: proposal.id, + namespaces: sessionNamespaces, + sessionProperties: proposal.sessionProperties + ) + + // Hide loading indicator, show success message + hideLoadingIndicator() + showSuccessMessage("Connection approved") } catch { - // present error + // Hide loading indicator, show error message + hideLoadingIndicator() + showErrorMessage("Connection failed: \(error.localizedDescription)") } ``` ```kotlin -WalletKit.approveSession(approveProposal, - onSuccess = { - //Session approval response was sent successfully - update your UI - } - onError = { error -> - //Error while sending session approval - update your UI - }) -``` - - +// Create approval parameters with selected accounts and chains +val approveProposal = Wallet.Params.ApproveSession( + proposalId = proposal.id, + namespaces = namespaces // Map of namespace configurations +) -#### Rejecting session +// Show loading indicator when user taps "Approve" +showLoadingIndicator() - - -```typescript -try { - await walletKit.rejectSession(params); - // update UI -> remove the loader -} catch (error) { - // present error to the user -} -``` - - -```typescript -try { - await walletKit.rejectSession(params); - // update UI -> remove the loader -} catch (error) { - // present error to the user -} -``` - - -```swift -do { - try await WalletKit.instance.reject(proposalId: proposal.id, reason: .userRejected) - // Update UI, remove loader -} catch { - // present error -} -``` - - -```kotlin -WalletKit.rejectSession(reject, +WalletKit.approveSession(approveProposal, onSuccess = { - //Session rejection response was sent successfully - update your UI + // Hide loading indicator, show success message + hideLoadingIndicator() + showSuccessMessage("Connection approved") }, onError = { error -> - //Error while sending session rejection - update your UI + // Hide loading indicator, show error message + hideLoadingIndicator() + showErrorMessage("Connection failed: ${error.message}") }) ``` -#### Session proposal expiry - -A session proposal expiry is 5 minutes. It means a given proposal is stored for 5 minutes in the SDK storage and user has 5 minutes for the approval or rejection decision. After that time, the below event is emitted and proposal modal should be removed from the app's UI. - - - -```typescript -walletKit.on("proposal_expire", (event) => { - // proposal expired and any modal displaying it should be removed - const { id } = event; -}); -``` - - -```typescript -walletKit.on("proposal_expire", (event) => { - // proposal expired and any modal displaying it should be removed - const { id } = event; -}); -``` - - -```swift -WalletKit.instance.sessionProposalExpirationPublisher.sink { _ in - // let user know that session proposal has expired, update UI -}.store(in: &publishers) -``` - - -```kotlin -val walletDelegate = object : WalletKit.WalletDelegate { - override fun onProposalExpired(proposal: Wallet.Model.ExpiredProposal) { - // Here this event is triggered when a proposal expires - update your UI - } - // ...other callbacks -} -WalletKit.setWalletDelegate(walletDelegate) -``` - - - -#### Session Proposal messages - -1. Consider displaying a successful session proposal message before redirecting back to the dapp. Before the success message is displayed, wallet should show a loading indicator. -2. Display an error message when session proposal fails. - -#### Expected errors - -While approving or rejecting a session proposal, the following errors might occur: - -- **No Internet connection** - - It happens when a user tries to approve or reject a session proposal with no Internet connection -- **Session proposal expired** - - It happens when a user tries to approve or reject an expired session proposal -- **Invalid [namespaces](../advanced/glossary#namespaces)** - - It happens when a validation of session namespaces fails -- **Timeout** - - It happens when Relay doesn't acknowledge session settle publish within 10s - -### 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. +### Rejecting a Session ```typescript try { - await walletKit.respondSessionRequest(params); - // update UI -> remove the loader + // 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) { - // present error to the user + // Hide loading indicator, show error message + hideLoadingIndicator(); + showErrorMessage(`Error: ${error.message}`); } ``` ```typescript try { - await walletKit.respondSessionRequest(params); - // update UI -> remove the loader + // 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) { - // present error to the user + // Hide loading indicator, show error message + hideLoadingIndicator(); + showErrorMessage(`Error: ${error.message}`); } ``` ```swift do { - try await WalletKit.instance.respond(requestId: request.id, signature: signature, from: account) - // update UI -> remove the loader + // Show loading indicator when user taps "Reject" + showLoadingIndicator() + + // Reject the session with reason + try await WalletKit.instance.reject( + proposalId: proposal.id, + reason: .userRejected + ) + + // Hide loading indicator, show success message + hideLoadingIndicator() + showMessage("Connection rejected") } catch { - // present error to the user + // Hide loading indicator, show error message + hideLoadingIndicator() + showErrorMessage("Error: \(error.localizedDescription)") } ``` ```kotlin -WalletKit.respondSessionRequest(Wallet.Params.SessionRequestResponse, +// Create rejection parameters +val rejectParams = Wallet.Params.RejectSession( + proposalId = proposal.id, + reason = SessionRejectionReason.USER_REJECTED +) + +// Show loading indicator when user taps "Reject" +showLoadingIndicator() + +WalletKit.rejectSession(rejectParams, onSuccess = { - //Session request response was sent successfully - update your UI + // Hide loading indicator, show success message + hideLoadingIndicator() + showMessage("Connection rejected") }, onError = { error -> - //Error while sending session response - update your UI + // Hide loading indicator, show error message + hideLoadingIndicator() + showErrorMessage("Error: ${error.message}") }) ``` -#### Session request expiry +## 4. Mobile Linking -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. +### 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 vs Universal Links +**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 + +### Implementing Mobile Linking - -```typescript -walletKit.on("session_request_expire", (event) => { - // request expired and any modal displaying it should be removed - const { id } = event; -}); -``` - - -```typescript -walletKit.on("session_request_expire", (event) => { - // request expired and any modal displaying it should be removed - const { id } = event; -}); -``` - ```swift -WalletKit.instance.requestExpirationPublisher.sink { _ in - // let user know that request has expired -}.store(in: &publishers) +// 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 +} ``` - + ```kotlin -val walletDelegate = object : WalletKit.WalletDelegate { - override fun onRequestExpired(request: Wallet.Model.ExpiredRequest) { - // Here this event is triggered when a session request expires - update your UI - } - // ...other callbacks +// In your Activity +override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + // Check if app was opened from a deep link + intent?.data?.let { uri -> + if (WalletKit.isWalletConnectDeepLink(uri)) { + WalletKit.handleDeepLink(uri, + onSuccess = { + // Deep link processed successfully + }, + onError = { error -> + // Handle error + Log.e("DeepLink", "Error: ${error.message}") + } + ) + } + } } -WalletKit.setWalletDelegate(walletDelegate) ``` - + -#### Expected errors - -While approving or rejecting a session request, the following errors might occur: - -- **Invalid session** - - This error might happen when a user approves or rejects a session request on an expired session -- **Session request expired** - - This error might happen when a user approves or rejects a session request that already expired -- **Timeout** - - It happens when Relay doesn't acknowledge session settle publish within 10 seconds - -### Connection state - -The Web Socket connection state tracks the connection with the Relay server. An event is emitted whenever a connection state changes. +### Redirecting Back to DApp +After completing an action, ensure your wallet redirects the user back to the DApp: - -```typescript -core.relayer.on("relayer_connect", () => { - // connection to the relay server is established -}) - -core.relayer.on("relayer_disconnect", () => { -// connection to the relay server is lost -}) - -```` - - -```typescript -core.relayer.on("relayer_connect", () => { - // connection to the relay server is established -}) - -core.relayer.on("relayer_disconnect", () => { - // connection to the relay server is lost -}) -```` - - ```swift -WalletKit.instance.socketConnectionStatusPublisher - .receive(on: DispatchQueue.main) - .sink { status in - switch status { - case .connected: - // ... - case .disconnected: - // ... - } -}.store(in: &publishers) +// 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) + } +} ``` - + ```kotlin -val walletDelegate = object : WalletKit.WalletDelegate { - override fun onConnectionStateChange(state: Wallet.Model.ConnectionState) { - // Here this event is triggered when a connection state has changed - } - // ...other callbacks +// After session approval or transaction signing +fun redirectBackToDApp(redirectUrl: String?) { + redirectUrl?.let { urlString -> + try { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(urlString)) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + startActivity(intent) + } catch (e: Exception) { + Log.e("Redirect", "Failed to redirect: ${e.message}") + } + } } -WalletKit.setWalletDelegate(walletDelegate) ``` - - - -#### 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. - -## 2. Mobile Linking - -### 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](https://support.google.com/google-ads/answer/10023042) or [universal link](https://developer.apple.com/ios/universal-links/). 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. - -#### Recommended Approach - -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. - -![](/images/w3w/mobileLinking-light.png) - - -### 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. - - -![](/images/w3w/mobileLinking_sign-light.png) - - - -### Platform Specific Preparation - - - - Read the specific steps for iOS here: [Platform preparations](./ios/mobile-linking#platform-preparations) - - - - Read the specific steps for Android here: [Platform - preparations](./android/mobile-linking#platform-preparations) - - - - Read the specific steps for Flutter here: [Platform - preparations](./flutter/mobile-linking#platform-preparations) - - - - Read the specific steps for React Native here: [Platform preparations](./react-native/mobile-linking#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 and try your mobile linking integration on your device. - - - - - [Sample Wallet](https://testflight.apple.com/join/09bTAryp) - on TestFlight - - [Sample DApp](https://testflight.apple.com/join/7S1GYcjC) - on TestFlight - - - - - [Sample Wallet](https://appdistribution.firebase.dev/i/6f9437a5f9bf4eec) - - on Firebase - [Sample - DApp](https://appdistribution.firebase.dev/i/5e4fe4b30c8a208d) - on Firebase - - - - - Sample Wallet: - [Sample Wallet for - iOS](https://testflight.apple.com/join/Uv0XoBuD) - [Sample Wallet for - Android](https://appdistribution.firebase.dev/i/2b8b3dce9e2831cd) - AppKit - DApp: - [AppKit Dapp for iOS](https://testflight.apple.com/join/6aRJSllc) - - [AppKit Dapp for - Android](https://appdistribution.firebase.dev/i/2c6573f6956fa7b5) - - - - - Sample Wallet: - - [Sample Wallet for Android](https://appdistribution.firebase.dev/i/e7711e780547234e) - - Sample DApp: - - [Sample App for iOS](https://testflight.apple.com/join/Ivd8bg7s) - - [Sample App for Android](https://appdistribution.firebase.dev/i/0297fbd3de8f1e3f) - - - -## 3. 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](https://www.browserstack.com/guide/how-to-simulate-slow-network-conditions). - -## 4. 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](/images/verify-states-1.png) - - -![Verify States](/images/verify-states-2.png) - - - - -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[](https://docs.reown.com/walletkit/web/verify#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](./features/verify) 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](https://malicious-app-verify-simulation.vercel.app/), 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](/images/assets/verify-api-flagged-domain.png) - -_A sample error warning when trying to connect to a malicious domain_ - -## 5. Latest SDK - -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 - - - - - **WalletConnectSwiftV2**: [Latest release](https://github.com/reown-com/reown-swift/releases/latest/) - - - - - **WalletConnectKotlinV2**: [Latest - release](https://github.com/WalletConnect/WalletConnectKotlinV2/releases/latest) - - - - - **WalletConnectFlutterV2**: [Latest - release](https://github.com/WalletConnect/WalletConnectFlutterV2/releases/latest) - - - - - **AppKit for React Native**: [Latest release](https://github.com/WalletConnect/reown-react-native/releases/latest) -### 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](/images/assets/subsribe-to-release-updates.png) +## 5. Testing WalletKit Features + +### Test Scenarios + +| Feature | Test Scenario | Expected Outcome | +|---------|--------------|------------------| +| Pairing | Scan QR with no internet | Error message shown + retry option | +| | Scan expired QR code | Error with refresh suggestion | +| | Scan valid QR code | Successful pairing, proposal shown | +| Session Proposal | Approve with selected accounts | Success message, session active | +| | Reject proposal | Success message, back to home screen | +| | App killed during approval | Proper error handling when reopened | +| Mobile Linking | Open deep link from DApp | Wallet opens with correct context | +| | Complete action in wallet | Redirects back to DApp | +| Performance | Measure connection time | < 5s in normal conditions | +| | Measure signing time | < 5s in normal conditions | +| Multiple Connections | Connect to multiple DApps | All connections work independently | +| WebSocket Connection | Turn off Wi-Fi during session | Reconnection attempt + user notification | + +## 6. Troubleshooting + +### Common Issues + +| Issue | Possible Causes | Solution | +|-------|----------------|----------| +| Session proposal not received | Network issues, pairing expired | Check network, refresh QR code | +| Cannot approve session | Invalid namespace configuration | Ensure accounts match required chains | +| Slow connection | Poor network, obsolete SDK | Check network, update SDK | +| Deep link not working | Improper URL scheme registration | Verify URL scheme in app manifest/Info.plist | +| Session disconnects frequently | Background process limitations | Implement proper reconnection logic | + +### Debugging Tools + +- **WalletKit Logs**: Enable debug logs to trace issues + ```typescript + WalletKit.init({ + // other options... + logger: 'debug' // Options: 'error', 'warn', 'info', 'debug' + }) + ``` -## Resources +- **Network Inspector**: Use browser developer tools or Charles Proxy to inspect network traffic +- **Test DApp**: Use our [test DApp](https://test.reown.com) to verify your wallet's compliance with best practices -- [React Wallet](https://react-wallet.reown.com/) - for testing dapps, features, Verify API messages, etc. -- [React dapp](https://react-app.reown.com/) - for testing wallets -- [Malicious React dapp](https://malicious-app-verify-simulation.vercel.app/) - for testing Verify API with malicious domain +## Version Information +- **Current WalletKit Version**: 2.0 +- **Release Date**: 2023-10-15 +- [View Changelog](https://docs.reown.com/walletkit/changelog) +- [GitHub Repository](https://github.com/reown/walletkit) From 5c0694ea15fb87b591a050df2dba75ed8d5a8e0b Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 23 Apr 2025 12:15:23 +0300 Subject: [PATCH 2/3] Fix WalletKit Best Practices documentation issues and restore removed sections --- walletkit/best-practices.mdx | 604 +++++++++++++++++++++++++++++------ 1 file changed, 503 insertions(+), 101 deletions(-) diff --git a/walletkit/best-practices.mdx b/walletkit/best-practices.mdx index 09051bc78..d71fbe339 100644 --- a/walletkit/best-practices.mdx +++ b/walletkit/best-practices.mdx @@ -17,84 +17,28 @@ WalletKit is Reown's official SDK that enables wallets to connect securely with This document outlines best practices for implementing WalletKit in your wallet to ensure optimal user experience. -## Quick Start - -Initialize WalletKit as early as possible in your application lifecycle: - - - -```javascript -// Initialize WalletKit in your app entry point -import { WalletKit } from '@reown/walletkit'; - -const walletKit = await WalletKit.init({ - projectId: 'YOUR_PROJECT_ID', - name: 'Your Wallet Name' -}); -``` - - -```javascript -// Initialize WalletKit in your app entry point -import { WalletKit } from '@reown/walletkit-react-native'; - -const walletKit = await WalletKit.init({ - projectId: 'YOUR_PROJECT_ID', - name: 'Your Wallet Name' -}); -``` - - -```swift -// Initialize WalletKit in your AppDelegate -import WalletKit - -func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - WalletKit.configure(with: WalletKitConfig( - projectId: "YOUR_PROJECT_ID", - name: "Your Wallet Name" - )) - return true -} -``` - - -```kotlin -// Initialize WalletKit in your Application class -import io.reown.walletkit.WalletKit - -class YourApplication : Application() { - override fun onCreate() { - super.onCreate() - WalletKit.initialize( - context = this, - projectId = "YOUR_PROJECT_ID", - name = "Your Wallet Name" - ) - } -} -``` - - - ## Checklist Before Going Live To ensure your wallet adheres to best practices, implement the following before deployment: -| Category | Best Practice | Priority | -|----------|---------------|----------| -| **User Feedback** | Display clear connection/transaction status messages | High | -| | Show loading indicators during operations | High | -| | Communicate errors to both user and DApp | High | -| **Mobile Experience** | Implement deep linking between wallet and DApp | High | -| | Ensure proper redirection after transactions | Medium | -| **Performance** | Connection time < 5s (normal conditions) | High | -| | Connection time < 15s (poor network) | Medium | -| | Transaction signing < 5s (normal conditions) | High | -| | Transaction signing < 10s (poor network) | Medium | -| **Security** | Implement Verify API with all four states | High | -| **Maintenance** | Use latest SDK version | High | -| | Subscribe to SDK update notifications | Medium | +| Category | Best Practice | Details | +|----------|---------------|---------| +| **Success and Error Messages** | Display 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 Linking** | Implement 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 | +| **Latency** | Optimize 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 API** | Present security status indicators | • Domain match: verified domain
• Unverified: domain not in registry
• Mismatch: domain doesn't match registry
• Threat: flagged as malicious | +| **SDK Version** | Use 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 @@ -115,7 +59,7 @@ Users often face ambiguity determining whether their connection or transactions ### 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. Each pairing can support multiple sessions. +**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 @@ -163,7 +107,7 @@ if let uri { } } } -```` +``` ```kotlin @@ -178,14 +122,14 @@ WalletKit.pair(pairingParams, Log.e("WalletKit", "Pairing failed: $error") } } -```` +``` ### Pairing State Management #### Pairing Expiry -Inactive pairings expire after 5 minutes, while active pairings (with approved sessions) expire after 30 days. Monitor these events to update your UI accordingly. +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. @@ -234,7 +178,7 @@ val coreDelegate = object : CoreClient.CoreDelegate { // Set the delegate to receive events CoreClient.setDelegate(coreDelegate) -```` +``` @@ -244,8 +188,9 @@ CoreClient.setDelegate(coreDelegate) |------------|-------------|-------------|-------------| | No Internet | Connection attempt with no network | Retry with active connection | Display error, allow retry | | Expired Pairing | QR code/URI has expired | Refresh QR code and scan again | Generate new pairing URI | -| Duplicate Pairing | Attempt to pair with existing pairing | Generate new pairing | Generate new pairing URI | -| Invalid URI | Malformed or corrupt pairing URI | Get new URI from DApp | Generate new pairing URI | +| Pairing Already Exists | Attempting to use an already paired URI | Scan a new QR code | Generate new pairing URI | +| Malformed Pairing URI | URI does not follow WalletConnect protocol format | Try a different DApp or report issue | Ensure correct implementation of WalletConnect URI format | +| Invalid URI | Pairing URI structure is incorrect | Get new URI from DApp | Check URI format matches WalletConnect spec | ## 3. Session Proposal Handling @@ -306,7 +251,7 @@ try { hideLoadingIndicator(); showErrorMessage(`Connection failed: ${error.message}`); } -```` +``` ```typescript @@ -497,6 +442,126 @@ Mobile linking allows seamless transitions between DApps and wallets on mobile d - 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](https://support.google.com/google-ads/answer/10023042) or [universal link](https://developer.apple.com/ios/universal-links/). 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. + +#### Recommended Approach + +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. + +![](/images/w3w/mobileLinking-light.png) + + +### 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. + + +![](/images/w3w/mobileLinking_sign-light.png) + + + +### Platform Specific Preparation + + + + Read the specific steps for iOS here: [Platform preparations](./ios/mobile-linking#platform-preparations) + + + + Read the specific steps for Android here: [Platform + preparations](./android/mobile-linking#platform-preparations) + + + + Read the specific steps for Flutter here: [Platform + preparations](./flutter/mobile-linking#platform-preparations) + + + + Read the specific steps for React Native here: [Platform preparations](./react-native/mobile-linking#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 and try your mobile linking integration on your device. + + + + - [Sample Wallet](https://testflight.apple.com/join/09bTAryp) - on TestFlight + - [Sample DApp](https://testflight.apple.com/join/7S1GYcjC) - on TestFlight + + + + - [Sample Wallet](https://appdistribution.firebase.dev/i/6f9437a5f9bf4eec) - + on Firebase - [Sample + DApp](https://appdistribution.firebase.dev/i/5e4fe4b30c8a208d) - on Firebase + + + + - Sample Wallet: - [Sample Wallet for + iOS](https://testflight.apple.com/join/Uv0XoBuD) - [Sample Wallet for + Android](https://appdistribution.firebase.dev/i/2b8b3dce9e2831cd) - AppKit + DApp: - [AppKit Dapp for iOS](https://testflight.apple.com/join/6aRJSllc) - + [AppKit Dapp for + Android](https://appdistribution.firebase.dev/i/2c6573f6956fa7b5) + + + + - Sample Wallet: + - [Sample Wallet for Android](https://appdistribution.firebase.dev/i/e7711e780547234e) + - Sample DApp: + - [Sample App for iOS](https://testflight.apple.com/join/Ivd8bg7s) + - [Sample App for Android](https://appdistribution.firebase.dev/i/0297fbd3de8f1e3f) + + + ### Implementing Mobile Linking @@ -581,7 +646,357 @@ fun redirectBackToDApp(redirectUrl: String?) { -## 5. Testing WalletKit Features +## 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. + + + +```typescript +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}`); +} +``` + + +```typescript +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}`); +} +``` + + +```swift +do { + // Show loading indicator when user approves/rejects + showLoadingIndicator() + + // Respond to the session request + try await WalletKit.instance.respond( + requestId: request.id, + signature: signature, + from: account + ) + + // Hide loading indicator, show success message + hideLoadingIndicator() + showSuccessMessage("Request completed") +} catch { + // Hide loading indicator, show error message + hideLoadingIndicator() + showErrorMessage("Request failed: \(error.localizedDescription)") +} +``` + + +```kotlin +// Create response parameters +val responseParams = Wallet.Params.SessionRequestResponse( + requestId = request.id, + result = result, // The response data + topic = request.topic +) + +// Show loading indicator when user approves/rejects +showLoadingIndicator() + +WalletKit.respondSessionRequest(responseParams, + onSuccess = { + // Hide loading indicator, show success message + hideLoadingIndicator() + showSuccessMessage("Request completed") + }, + onError = { 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. + + + +```typescript +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); +}); +``` + + +```typescript +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); +}); +``` + + +```swift +WalletKit.instance.requestExpirationPublisher.sink { expiredRequest in + // Let user know that request has expired + // Remove request from UI + removeRequestFromUI(expiredRequest.id) +}.store(in: &publishers) +``` + + +```kotlin +val walletDelegate = object : WalletKit.WalletDelegate { + override fun onRequestExpired(request: Wallet.Model.ExpiredRequest) { + // Here this event is triggered when a session request expires + // Update UI to remove the request + removeRequestFromUI(request.id) + } + // ...other callbacks +} +WalletKit.setWalletDelegate(walletDelegate) +``` + + + +### Expected Session Request Errors + +| Error Type | Description | User Action | DApp Action | +|------------|-------------|-------------|-------------| +| Invalid Session | Request made on expired/invalid session | Reconnect to the DApp | Detect session expiry and prompt for reconnection | +| Session Request Expired | Approval/rejection after request timeout | Handle new requests only | Set appropriate timeout periods | +| Timeout | No relay acknowledgement within 10 seconds | Retry the action | Implement proper timeout handling | +| Network Error | Connection issues during request | Check network and retry | Display 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. + + + +```typescript +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'); +}) +``` + + +```typescript +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'); +}) +``` + + +```swift +WalletKit.instance.socketConnectionStatusPublisher + .receive(on: DispatchQueue.main) + .sink { status in + switch status { + case .connected: + // Update UI to show connected state + updateConnectionStatus(.connected) + case .disconnected: + // Update UI to show disconnected state + updateConnectionStatus(.disconnected) + } + }.store(in: &publishers) +``` + + +```kotlin +val walletDelegate = object : WalletKit.WalletDelegate { + override fun onConnectionStateChange(state: Wallet.Model.ConnectionState) { + // Handle connection state change + when (state) { + is Wallet.Model.ConnectionState.Connected -> { + // Update UI to show connected state + updateConnectionStatus(ConnectionStatus.CONNECTED) + } + is Wallet.Model.ConnectionState.Disconnected -> { + // Update UI to show disconnected state + updateConnectionStatus(ConnectionStatus.DISCONNECTED) + } + } + } + // ...other callbacks +} +WalletKit.setWalletDelegate(walletDelegate) +``` + + + +### 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](https://www.browserstack.com/guide/how-to-simulate-slow-network-conditions). + +## 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](/images/verify-states-1.png) + + +![Verify States](/images/verify-states-2.png) + + + + +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](./features/verify) 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](https://malicious-app-verify-simulation.vercel.app/), 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](/images/assets/verify-api-flagged-domain.png) + +_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 + + + + - **WalletConnectSwiftV2**: [Latest release](https://github.com/reown-com/reown-swift/releases/latest/) + + + + - **WalletConnectKotlinV2**: [Latest + release](https://github.com/WalletConnect/WalletConnectKotlinV2/releases/latest) + + + + - **WalletConnectFlutterV2**: [Latest + release](https://github.com/WalletConnect/WalletConnectFlutterV2/releases/latest) + + + + - **AppKit for React Native**: [Latest release](https://github.com/WalletConnect/reown-react-native/releases/latest) + + + +### 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](/images/assets/subsribe-to-release-updates.png) + +## 10. Testing WalletKit Features ### Test Scenarios @@ -600,7 +1015,7 @@ fun redirectBackToDApp(redirectUrl: String?) { | Multiple Connections | Connect to multiple DApps | All connections work independently | | WebSocket Connection | Turn off Wi-Fi during session | Reconnection attempt + user notification | -## 6. Troubleshooting +## 11. Troubleshooting ### Common Issues @@ -612,21 +1027,8 @@ fun redirectBackToDApp(redirectUrl: String?) { | Deep link not working | Improper URL scheme registration | Verify URL scheme in app manifest/Info.plist | | Session disconnects frequently | Background process limitations | Implement proper reconnection logic | -### Debugging Tools - -- **WalletKit Logs**: Enable debug logs to trace issues - ```typescript - WalletKit.init({ - // other options... - logger: 'debug' // Options: 'error', 'warn', 'info', 'debug' - }) - ``` - -- **Network Inspector**: Use browser developer tools or Charles Proxy to inspect network traffic -- **Test DApp**: Use our [test DApp](https://test.reown.com) to verify your wallet's compliance with best practices +## Resources -## Version Information -- **Current WalletKit Version**: 2.0 -- **Release Date**: 2023-10-15 -- [View Changelog](https://docs.reown.com/walletkit/changelog) -- [GitHub Repository](https://github.com/reown/walletkit) +- [React Wallet](https://react-wallet.reown.com/) - for testing DApps, features, Verify API messages, etc. +- [React DApp](https://react-app.reown.com/) - for testing wallets +- [Malicious React DApp](https://malicious-app-verify-simulation.vercel.app/) - for testing Verify API with malicious domain From a09ade8fbc6c762f440b00d4b7ad16892cf595c0 Mon Sep 17 00:00:00 2001 From: Rohit Ramesh <70317502+rohit-710@users.noreply.github.com> Date: Tue, 29 Apr 2025 08:25:06 -0600 Subject: [PATCH 3/3] Update walletkit/best-practices.mdx --- walletkit/best-practices.mdx | 1 - 1 file changed, 1 deletion(-) diff --git a/walletkit/best-practices.mdx b/walletkit/best-practices.mdx index d71fbe339..b8b7ea5f7 100644 --- a/walletkit/best-practices.mdx +++ b/walletkit/best-practices.mdx @@ -6,7 +6,6 @@ keywords: wallets, best practices, pairing, session management, errors, latency version: 2.0 --- -# WalletKit Overview WalletKit is Reown's official SDK that enables wallets to connect securely with decentralized applications (DApps). It handles critical functions including: