Skip to main content

WalletConnect Pay Integration via WalletKit

This guide provides all details needed to integrate WalletConnect Pay into a React Native wallet that already has WalletKit integrated. Pay is accessed through walletKit.pay.

Agent Guidance: Adapt, Don’t Copy

This plan is a reference, not a script. Each wallet has its own architecture, conventions, and patterns. Follow these principles:

1. Match the Target Wallet’s Patterns

  • Study the existing codebase first. Look at how similar features are implemented (e.g., how session requests are handled, how modals work, how state is managed).
  • Follow existing conventions for file naming, folder structure, imports, and code style.
  • Use the wallet’s existing UI components rather than creating new ones from scratch.
  • Adapt the examples in this plan to fit the wallet’s architecture, not the other way around.

2. When in Doubt, Ask

If you’re uncertain about:
  • Which pattern to follow when multiple exist in the codebase
  • Whether to create new infrastructure or extend existing
  • How a specific wallet feature works
  • The correct location for new files
Stop and ask the user rather than guessing.

3. Test Incrementally

Don’t implement everything at once. Verify each step:
  1. walletKit.pay is accessible
  2. Payment links are detected via isPaymentLink
  3. Modal/screen opens
  4. Payment options load
  5. Signing works
  6. Payment confirms

4. Validate After Every Step (CRITICAL)

After completing each step in the implementation:
  1. Go back and review - Re-read the plan to ensure all requirements for that step were implemented correctly
  2. Check for linting errors - Run the linter on all modified/created files and fix any errors before proceeding
  3. Verify the code compiles - Ensure there are no TypeScript or build errors
  4. Test the functionality - If possible, verify the step works as expected before moving on
This prevents accumulating errors that become harder to debug later.

Prerequisites

The wallet must already have:
  • @reown/walletkit integrated and working
  • @walletconnect/react-native-compat installed
  • WalletKit initialization in place

Required Package Versions

These stable versions include Pay support via walletKit.pay.

Architecture Overview

Pay is automatically available on any WalletKit instance - no additional configuration required.
WalletKit automatically provides pay functionality via walletKit.pay. No additional configuration is needed. Just add the isPaymentLink import to detect payment links:

IMPORTANT: Always use isPaymentLink from WalletKit to detect payment links. Do NOT implement custom URL parsing or regex matching. Current known formats (for reference only - may change):
The isPaymentLink function handles all current and future formats internally, ensuring your integration remains compatible as the protocol evolves.

CRITICAL: Check ALL URI Entry Points

Payment link detection must be added to ALL places where URIs are processed:
  1. QR Scanner - barcode scanning callback
  2. URI Paste/Input Field - text input with “Connect” button
  3. Deep Link Handler - Linking.addEventListener for app URLs
  4. Clipboard Paste - if the app supports paste-to-connect
Search for all calls to walletKit.pair() and ensure each checks isPaymentLink(uri) first. IMPORTANT: Payment links are HTTPS URLs (e.g., https://pay.walletconnect.com/?pid=xxx). Ensure the isPaymentLink() check happens BEFORE any generic HTTP/HTTPS URL handling that might open a browser.

Step 3: Implement Payment Flow

3.2 Collect User Data via WebView (Information Capture)

IMPORTANT - ORDERING: After the user selects an option, check whether that option requires Information Capture. If selectedOption.collectData?.url is present, you MUST collect the data via the WebView before calling getRequiredPaymentActions. The WebView submits the captured data directly to the backend, and getRequiredPaymentActions (which triggers POST /payment/{id}/fetch) now requires any IC to already be submitted. Do not fetch actions or sign until the WebView signals completion. If the selected option has no collectData?.url, skip this step and go straight to fetching the required actions.
See section 3.6 for the PayDataCollectionWebView component implementation.

3.3 Get Required Actions for Selected Option

Only call this AFTER the WebView Information Capture step has completed (when the selected option requires it).

3.4 Sign and Confirm Payment

IMPORTANT: Always try passing the raw JSON string first. This is the safest approach.

3.5 Signing API Variations

Different wallet libraries have different signing APIs. The key insight is that parsedParams[1] is already a complete, correctly-formatted JSON string. Try the simplest approach first. This is the recommended approach. Many wallet signing APIs accept the typed data as a JSON string directly:
Always try this first. If your wallet’s signing API accepts a string, this avoids potential data transformation issues that can cause signature verification failures.

Approach 2: Parse for APIs Requiring Objects (Fallback Only)

Only use this if Approach 1 fails. If your wallet API requires parsed objects (like ethers.js _signTypedData):

Common Pitfalls When Parsing

If you must parse the typed data, be aware of these issues:
  1. chainId as hex string: The domain may contain chainId: "0x2105" (hex string) instead of chainId: 8453 (number). Some signing implementations require the number:
  2. Don’t guess primaryType: If your API needs primaryType, extract it from the original data—don’t use Object.keys(types)[0] which returns the alphabetically first key (often wrong):
  3. String vs Object format: Test whether your signing API expects:
    • data: "{...}" (JSON string)
    • data: {...} (parsed object)
    The wrong format may sign successfully but produce an invalid signature.

Step 4: Payment Modal/Screen UI

State Machine

The WebView data-collection step (Information Capture) runs AFTER the user selects an option but BEFORE fetching the required actions. getRequiredPaymentActions triggers the backend POST /payment/{id}/fetch, which now expects any required IC to already be submitted by the WebView.

State Definition

Required UI Components

  1. Loading View - Spinner with message
  2. Confirm View - Payment options list (with “Info required” badge on options with collectData), selected option details, approve button
  3. WebView Data Collection View - WebView form (if selectedOption.collectData?.url exists)
  4. Confirming View - Spinner while signing and confirming
  5. Result View - Success or error message with close button

Display Elements

  • Merchant name and icon from paymentOptions.info.merchant
  • Payment amount from option.amount.display
  • Network name and asset symbol
  • ETA in seconds

Step 5: Utility Functions

Format Amount

Format Date Input (for collectData)

Validate Date


Core API Reference

Pay Client Methods (via walletKit.pay)

Account Format (CAIP-10)

Key Types


Architecture Adaptation Examples

React Native Modal (overlay):
React Navigation (screen):
Expo Router:
Bottom Sheet:

Wallet Library Variations

MetaMask KeyringController (recommended for MetaMask-based wallets):
ethers.js v5:
viem:
web3.js:

3.6 WebView Data Collection

When selectedOption.collectData?.url is present, display the URL in a WebView. This happens immediately after the user selects an option and before getRequiredPaymentActions is called (see section 3.2). The URL comes from the selected option’s collectData.url, which is already scoped to that option’s account. The hosted form handles rendering, validation, and T&C acceptance. Wire onComplete to your onDataCollectionComplete handler (which then calls fetchPaymentActions), so action fetching only happens once the WebView has submitted the Information Capture data to the backend. The form URL accepts optional query parameters — append them to selectedOption.collectData.url before loading, preserving existing query parameters:
  • prefill — base64url-encoded JSON of known user fields. Keys must match the required fields from collectData.schema (e.g. fullName, dob, pobAddress).
  • themelight or dark, to match your wallet’s color mode.
  • themeVariables — a base64url string exported from the WalletConnect Pay Dashboard that overrides design tokens (font, font size, some colors, button/input border radius). Append it verbatim.
Install react-native-webview:
Important: When using the WebView approach, do not pass collectedData to confirmPayment(). The WebView submits data directly to the backend.

Expo Considerations

Development Build Required

The Pay SDK uses native modules. Expo Go will NOT work.

Troubleshooting

walletKit.pay is undefined

Cause: Native module not available. Solutions:
  1. Verify @walletconnect/react-native-compat is installed
  2. iOS: Run cd ios && pod install --repo-update
  3. Android: Sync gradle and rebuild
  4. For Expo: Ensure using development build, not Expo Go

Payment Options Empty

Check:
  1. Accounts array format is correct: ['eip155:chainId:address']
  2. Payment link URL is valid with pid parameter
  3. Wallet has supported chains configured

Signing Fails

Check:
  1. Are you passing the raw JSON string? Try that first
  2. If parsing: EIP712Domain is removed from types before signing
  3. Params are parsed correctly (double-encoded JSON)
  4. Wallet address matches account used in getPaymentOptions

”Recovered address does not match from address”

Cause: The signature was created with different data than the verifier expects. Debug steps:
  1. Try passing raw string first: Use parsedParams[1] directly without parsing/reconstructing
  2. Check chainId format: Domain may have hex string ("0x2105") instead of number (8453)
  3. Check primaryType: Ensure it’s extracted from data, not guessed via Object.keys(types)[0]
  4. Check data format: Does your signing API expect a JSON string or parsed object?
Quick fix: Start with the simplest approach (pass raw parsedParams[1] string directly), only parse if your API specifically requires it.

”Missing or invalid. pair() uri#relay-protocol” Error

Cause: Payment link was passed to walletKit.pair() instead of being handled as payment. Solution: Ensure isPaymentLink(uri) check happens BEFORE calling pair(). Cause: Payment links are HTTPS URLs, and your URI handling may have a generic HTTP/HTTPS handler that opens a browser before checking for payment links. Solution: Move the isPaymentLink(uri) check BEFORE any HTTP/HTTPS URL handling in your QR scanner, deep link handler, and paste handler.

File Checklist

When implementation is complete, verify: Modified Files:
  • WalletKit exports (re-export isPaymentLink)
  • QR scanner handler (detect payment links BEFORE http/https handling)
  • URI paste handler (detect payment links BEFORE http/https handling)
  • Deep link handler (detect payment links)
  • Modal/navigation system (register payment modal/screen)
New Files:
  • Payment modal/screen component
  • Payment sub-components (IntroView, ConfirmView, etc.)
  • Payment utility functions
  • Payment state reducer (if using reducer pattern)
  • WebView data collection component (using react-native-webview)
Native Build:
  • iOS: pod install completed
  • Android: Gradle sync completed
Validation (after each step):
  • No linting errors in modified files
  • No TypeScript errors
  • Code compiles successfully
  • Functionality works as expected

Common Pitfalls

1. Double-Encoded JSON in Action Params

2. Parsing When You Don’t Need To

Always try the raw string approach first. Only parse and transform if your wallet’s signing API specifically requires it.

3. viem Requires primaryType

4. walletRpc May Be Undefined

5. QR Scanner Fires Multiple Times


Testing

Create test payment links from WalletConnect Pay dashboard:

Supported Chains

Currently Base (chainId: 8453) is the primary test chain: