Integrate WalletConnect Pay into your Android wallet to enable seamless crypto payments for your users.
The WalletConnect Pay SDK allows wallet users to pay merchants using their crypto assets. The SDK handles payment option discovery, permit signing coordination, and payment confirmation while leveraging your wallet’s existing signing infrastructure.
WalletConnect Pay currently supports the following tokens and networks:
Token
Network
Chain ID
CAIP-2 Format
USDC
Ethereum
1
eip155:1
USDC
Base
8453
eip155:8453
USDC
Optimism
10
eip155:10
USDC
Polygon
137
eip155:137
USDC
Arbitrum
42161
eip155:42161
EURC
Ethereum
1
eip155:1
EURC
Base
8453
eip155:8453
All account addresses must be provided in CAIP-10 format: eip155:<chainId>:<address>
Support for all EVM chains, Solana, and additional native and non-native assets is coming soon. Include accounts for all supported networks to maximize payment options for your users.
The payment flow consists of five main steps:Get Options -> Get Actions -> Sign Actions -> Collect Data (if required) -> Confirm Payment
1
Initialize the SDK
import com.walletconnect.pay.Payimport com.walletconnect.pay.WalletConnectPay// In your Application class or before payment operationsWalletConnectPay.initialize( Pay.SdkConfig( appId = "your-wcp-id", // Your WCP ID packageName = "com.your.app" ))// Check if initializedif (WalletConnectPay.isInitialized) { // SDK ready for use}
2
Get Payment Options
When a user scans a payment QR code or opens a payment link, fetch available payment options:
After the user selects a payment option, get the wallet RPC actions needed to complete the payment:
val actionsResult = WalletConnectPay.getRequiredPaymentActions( paymentId = paymentId, optionId = selectedOption.id)actionsResult.onSuccess { actions -> actions.forEach { action -> when (action) { is Pay.RequiredAction.WalletRpc -> { val rpcAction = action.action // rpcAction.chainId - e.g., "eip155:8453" // rpcAction.method - e.g., "eth_signTypedData_v4" or "personal_sign" // rpcAction.params - JSON string with signing parameters } } }}.onFailure { error -> handleError(error)}
4
Sign Actions
Sign each action using your wallet’s signing implementation:
val signatures = actions.map { action -> when (action) { is Pay.RequiredAction.WalletRpc -> { val rpc = action.action when (rpc.method) { "eth_signTypedData_v4" -> wallet.signTypedData(rpc.chainId, rpc.params) "personal_sign" -> wallet.personalSign(rpc.chainId, rpc.params) "eth_sendTransaction" -> wallet.sendTransaction(rpc.chainId, rpc.params) else -> throw UnsupportedOperationException("Unsupported method: ${rpc.method}") } } }}
Payment options may include multiple actions with different RPC methods. For example, a Permit2 payment where the user lacks sufficient allowance returns two actions: an eth_sendTransaction to approve the token allowance, followed by an eth_signTypedData_v4 to sign the Permit2 transfer. Your wallet must check action.walletRpc.method and dispatch to the appropriate handler.
Signatures must be in the same order as the actions array.
5
Collect User Data (If Required)
Some payments require collecting additional user information. Check for collectData on the selected payment option:
When a payment requires user information (e.g., for Travel Rule compliance), the SDK returns a collectData field on individual payment options. Each option may independently require data collection — some options may require it while others don’t.
The recommended approach is to display all payment options upfront, then handle data collection only when the user selects an option that requires it:
Call getPaymentOptions and display all available options to the user
Show a visual indicator (e.g., “Info required” badge) on options where option.collectData is present
When the user selects an option, check selectedOption.collectData
If present, open selectedOption.collectData.url in a WebView within your wallet
Optionally append a prefill=<base64-json> query parameter with known user data (e.g., name, date of birth, address). Use proper URL building to handle existing query parameters.
Listen for JS bridge messages: IC_COMPLETE (success) or IC_ERROR (failure)
On IC_COMPLETE, proceed to confirmPayment()without passing collectedData — the WebView submits data directly to the backend
Option does NOT require IC (others might) — skip IC for this option
null
null
No IC needed for any option
The collectData also includes a schema field — a JSON schema string describing the required fields. The required list in this schema tells you which fields the form expects. Wallets can use these field names as keys when building the prefill JSON object. For example, if the schema’s required array contains ["fullName", "dob", "pobAddress"], you can prefill with {"fullName": "...", "dob": "...", "pobAddress": "..."}.
The top-level collectData on the payment options response is still available for backward compatibility. However, the per-option collectData is the recommended approach as it provides more granular control over the flow.
When using the WebView approach, do not pass collectedData to confirmPayment(). The WebView handles data submission directly.
// Check per-option data collection requirement after user selects an optionselectedOption.collectData?.let { collectAction -> val url = collectAction.url if (url != null) { // Build prefill URL with known user data // Use the "required" list from collectAction.schema to determine which fields to prefill val prefillJson = JSONObject().apply { put("fullName", "John Doe") put("dob", "1990-01-15") put("pobAddress", "123 Main St, New York, NY 10001") }.toString() val prefillBase64 = Base64.encodeToString( prefillJson.toByteArray(), Base64.NO_WRAP or Base64.URL_SAFE ) val webViewUrl = Uri.parse(url).buildUpon() .appendQueryParameter("prefill", prefillBase64) .build().toString() // Show WebView for this specific option — see WebView Implementation section below showWebView(webViewUrl) }}
data class PaymentOptionsResponse( val info: PaymentInfo?, // Payment metadata val options: List<PaymentOption>, // Available payment options val paymentId: String, // Unique payment identifier val collectDataAction: CollectDataAction?, // Data collection requirements val resultInfo: PaymentResultInfo? // Transaction result details (present when payment already completed))data class PaymentResultInfo( val txId: String, // Transaction ID val optionAmount: Amount // Token amount details)
data class PaymentInfo( val status: PaymentStatus, val amount: Amount, val expiresAt: Long, // Unix timestamp val merchant: MerchantInfo)data class MerchantInfo( val name: String, val iconUrl: String?)
data class PaymentOption( val id: String, val amount: Amount, val account: String, // CAIP-10 account that can pay val estimatedTxs: Int?, // Estimated number of transactions val collectData: CollectDataAction? // Per-option data collection (null if not required))
data class Amount( val value: String, val unit: String, val display: AmountDisplay?)data class AmountDisplay( val assetSymbol: String, val assetName: String, val decimals: Int, val iconUrl: String?, val networkName: String?, val networkIconUrl: String?)
data class WalletRpcAction( val chainId: String, // CAIP-2 chain ID (e.g., "eip155:8453") val method: String, // RPC method (e.g., "eth_signTypedData_v4", "eth_sendTransaction") val params: String // JSON-encoded parameters)
data class ConfirmPaymentResponse( val status: PaymentStatus, val isFinal: Boolean, val pollInMs: Long?, val info: PaymentResultInfo? // Transaction result details (present on success))
Initialize once: Call initialize() only once, typically in Application.onCreate()
Account Format: Always use CAIP-10 format for accounts: eip155:{chainId}:{address}
Multiple Chains: Provide accounts for all supported chains to maximize payment options
Signature Order: Maintain the same order of signatures as the actions array
Error Handling: Always handle errors gracefully and show appropriate user feedback
Thread Safety: Events are delivered on IO dispatcher; update UI on main thread
WebView Data Collection: When selectedOption.collectData?.url is present, display the URL in a WebView rather than building native forms. The WebView handles form rendering, validation, and T&C acceptance.
Per-Option Data Collection: When displaying payment options, check each option’s collectData field. Show a visual indicator (e.g., “Info required” badge) on options that require data collection. Only open the WebView when the user selects an option with collectData present — use the option’s collectData.url which is already scoped to that option’s account.