Skip to main content
This documentation covers integrating WalletConnect Pay through WalletKit. This approach provides a unified API where Pay is automatically initialized alongside WalletKit, simplifying the integration for wallet developers.

Requirements

  • Min SDK: 23 (Android 6.0)
  • WalletKit: 1.6.0+

Installation

First, add the required repositories to your project’s settings.gradle.kts or root build.gradle.kts:
allprojects {
    repositories {
        mavenCentral()
        maven { url = uri("https://jitpack.io") }
    }
}
Then add WalletKit to your app’s build.gradle.kts using the BOM (Bill of Materials):
releaseImplementation(platform("com.reown:android-bom:$BOM_VERSION"))
releaseImplementation("com.reown:android-core")
releaseImplementation("com.reown:walletkit")
WalletConnectPay is automatically included as a dependency of WalletKit.
Check the GitHub releases for the latest BOM version.

Initialization

WalletConnectPay is automatically initialized when you initialize WalletKit. No additional setup is required.
import com.reown.android.Core
import com.reown.android.CoreClient
import com.reown.walletkit.client.WalletKit
import com.reown.walletkit.client.Wallet

// First, initialize CoreClient in your Application class
val projectId = "" // Get Project ID at https://dashboard.walletconnect.com/
val appMetaData = Core.Model.AppMetaData(
    name = "Wallet Name",
    description = "Wallet Description",
    url = "Wallet URL",
    icons = listOf(/* list of icon url strings */),
    redirect = "kotlin-wallet-wc:/request" // Custom Redirect URI
)

CoreClient.initialize(
    projectId = projectId,
    application = this,
    metaData = appMetaData,
)

// Then initialize WalletKit
val initParams = Wallet.Params.Init(core = CoreClient)

WalletKit.initialize(initParams) { error ->
    // Error will be thrown if there's an issue during initialization
}
The Pay SDK is initialized internally with:
  • appId from your WalletConnect Project ID
  • packageName from your application context
For more details on WalletKit initialization, see the WalletKit Usage documentation.
Use WalletKit.Pay.isPaymentLink() to determine if a scanned URI is a payment link or a standard WalletConnect pairing URI:
fun handleScannedUri(uri: String, accounts: List<String>) {
    if (WalletKit.Pay.isPaymentLink(uri)) {
        // Handle as payment - call getPaymentOptions
        WalletKit.Pay.getPaymentOptions(uri, accounts)
    } else {
        // Handle as WalletConnect pairing
        WalletKit.pair(Wallet.Params.Pair(uri))
    }
}

Payment Flow

The payment flow consists of five main steps: Detect Payment Link -> Get Options -> Get Actions -> Sign Actions -> Confirm Payment
1

Get Payment Options

Retrieve available payment options for a payment link:
import com.reown.walletkit.client.WalletKit
import com.reown.walletkit.client.Wallet

viewModelScope.launch {
    val result = WalletKit.Pay.getPaymentOptions(
        paymentLink = "https://pay.walletconnect.com/pay_xxx",
        accounts = listOf(
            "eip155:1:0xYourAddress",      // Ethereum
            "eip155:8453:0xYourAddress",   // Base
            "eip155:10:0xYourAddress",     // Optimism
            "eip155:137:0xYourAddress",    // Polygon
            "eip155:42161:0xYourAddress"   // Arbitrum
        )
    )

    result.onSuccess { response ->
        // Payment metadata
        val paymentId = response.paymentId
        val paymentInfo = response.info  // Merchant info, amount, expiry
        
        // Available payment options
        val options = response.options
        options.forEach { option ->
            println("Option: ${option.id}")
            println("Amount: ${option.amount.value} ${option.amount.unit}")
            println("Account: ${option.account}")
        }
        
        // Check if data collection is required
        val collectDataAction = response.collectDataAction
    }.onFailure { error ->
        handleError(error)
    }
}
2

Get Required Actions

Get the wallet RPC actions needed to complete the payment:
val actionsResult = WalletKit.Pay.getRequiredPaymentActions(
    Wallet.Params.RequiredPaymentActions(
        paymentId = paymentId,
        optionId = selectedOption.id
    )
)

actionsResult.onSuccess { actions ->
    actions.forEach { action ->
        when (action) {
            is Wallet.Model.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)
}
3

Sign Actions

Sign each required action using your wallet’s signing implementation:
val signatures = actions.map { action ->
    when (action) {
        is Wallet.Model.RequiredAction.WalletRpc -> {
            val rpc = action.action
            when (rpc.method) {
                "eth_signTypedData_v4" -> signTypedDataV4(rpc.params)
                "personal_sign" -> personalSign(rpc.params)
                else -> throw UnsupportedOperationException("Unsupported: ${rpc.method}")
            }
        }
    }
}

Signing eth_signTypedData_v4

import org.json.JSONArray
import org.web3j.crypto.ECKeyPair
import org.web3j.crypto.Sign
import org.web3j.crypto.StructuredDataEncoder

fun signTypedDataV4(params: String): String {
    // params is a JSON array: [address, typedData]
    val paramsArray = JSONArray(params)
    val requestedAddress = paramsArray.getString(0)
    val typedData = paramsArray.getString(1)
    
    // Use StructuredDataEncoder for proper EIP-712 hashing
    val encoder = StructuredDataEncoder(typedData)
    val hash = encoder.hashStructuredData()
    
    val keyPair = ECKeyPair.create(privateKeyBytes)
    val signatureData = Sign.signMessage(hash, keyPair, false)
    
    val rHex = signatureData.r.bytesToHex()
    val sHex = signatureData.s.bytesToHex()
    val v = signatureData.v[0].toInt() and 0xff
    val vHex = v.toString(16).padStart(2, '0')
    
    return "0x$rHex$sHex$vHex".lowercase()
}
Signatures must be in the same order as the actions array.
4

Collect User Data (If Required)

Some payments require additional user information:
response.collectDataAction?.let { collectAction ->
    val collectedData = collectAction.fields.map { field ->
        val value = when (field.fieldType) {
            Wallet.Model.CollectDataFieldType.TEXT -> getUserTextInput(field.name)
            Wallet.Model.CollectDataFieldType.DATE -> getUserDateInput(field.name) // YYYY-MM-DD
        }
        Wallet.Model.CollectDataFieldResult(
            id = field.id,
            value = value
        )
    }
}
5

Confirm Payment

Submit signatures and finalize the payment:
val confirmResult = WalletKit.Pay.confirmPayment(
    Wallet.Params.ConfirmPayment(
        paymentId = paymentId,
        optionId = selectedOption.id,
        signatures = signatures,
        collectedData = collectedData // Optional
    )
)

confirmResult.onSuccess { response ->
    when (response.status) {
        Wallet.Model.PaymentStatus.SUCCEEDED -> {
            // Payment completed successfully
        }
        Wallet.Model.PaymentStatus.PROCESSING -> {
            // Payment is being processed
        }
        Wallet.Model.PaymentStatus.FAILED -> {
            // Payment failed
        }
        Wallet.Model.PaymentStatus.EXPIRED -> {
            // Payment expired
        }
        Wallet.Model.PaymentStatus.REQUIRES_ACTION -> {
            // Additional action required
        }
    }
}.onFailure { error ->
    handleError(error)
}

Complete Example

Here’s a complete implementation example:
import com.reown.walletkit.client.WalletKit
import com.reown.walletkit.client.Wallet
import kotlinx.coroutines.launch

class PaymentViewModel : ViewModel() {

    fun handleScannedUri(uri: String) {
        if (WalletKit.Pay.isPaymentLink(uri)) {
            processPayment(uri)
        } else {
            // Handle as WalletConnect pairing
            WalletKit.pair(Wallet.Params.Pair(uri))
        }
    }

    fun processPayment(paymentLink: String) {
        viewModelScope.launch {
            val walletAddress = "0xYourAddress"
            
            // Step 1: Get payment options
            val optionsResult = WalletKit.Pay.getPaymentOptions(
                paymentLink = paymentLink,
                accounts = listOf(
                    "eip155:1:$walletAddress",
                    "eip155:8453:$walletAddress",
                    "eip155:10:$walletAddress"
                )
            )

            optionsResult.onSuccess { response ->
                val paymentId = response.paymentId
                val selectedOption = response.options.first()

                // Step 2: Get required actions
                val actionsResult = WalletKit.Pay.getRequiredPaymentActions(
                    Wallet.Params.RequiredPaymentActions(
                        paymentId = paymentId,
                        optionId = selectedOption.id
                    )
                )

                actionsResult.onSuccess { actions ->
                    // Step 3: Sign actions
                    val signatures = signActions(actions)

                    // Step 4: Collect data if required
                    val collectedData = response.collectDataAction?.let {
                        collectUserData(it.fields)
                    }

                    // Step 5: Confirm payment
                    val confirmResult = WalletKit.Pay.confirmPayment(
                        Wallet.Params.ConfirmPayment(
                            paymentId = paymentId,
                            optionId = selectedOption.id,
                            signatures = signatures,
                            collectedData = collectedData
                        )
                    )

                    confirmResult.onSuccess { confirmation ->
                        handlePaymentStatus(confirmation.status)
                    }.onFailure { error ->
                        handleError(error)
                    }
                }.onFailure { error ->
                    handleError(error)
                }
            }.onFailure { error ->
                handleError(error)
            }
        }
    }

    private suspend fun signActions(
        actions: List<Wallet.Model.RequiredAction>
    ): List<String> {
        return actions.map { action ->
            when (action) {
                is Wallet.Model.RequiredAction.WalletRpc -> {
                    signWithWallet(action.action)
                }
            }
        }
    }

    private fun handlePaymentStatus(status: Wallet.Model.PaymentStatus) {
        when (status) {
            Wallet.Model.PaymentStatus.SUCCEEDED -> showSuccess()
            Wallet.Model.PaymentStatus.PROCESSING -> showProcessing()
            Wallet.Model.PaymentStatus.FAILED -> showFailure()
            Wallet.Model.PaymentStatus.EXPIRED -> showExpired()
            Wallet.Model.PaymentStatus.REQUIRES_ACTION -> { /* Handle */ }
        }
    }
}

API Reference

WalletKit.Pay

The payment operations object within WalletKit.

Methods

MethodDescription
isPaymentLink(uri: String): BooleanCheck if URI is a payment link
getPaymentOptions(paymentLink, accounts)Get available payment options
getRequiredPaymentActions(params)Get actions requiring signatures
confirmPayment(params)Confirm and finalize payment

Parameters

Wallet.Params.RequiredPaymentActions

data class RequiredPaymentActions(
    val paymentId: String,
    val optionId: String
)

Wallet.Params.ConfirmPayment

data class ConfirmPayment(
    val paymentId: String,
    val optionId: String,
    val signatures: List<String>,
    val collectedData: List<Wallet.Model.CollectDataFieldResult>? = null
)

Data Models

Wallet.Model.PaymentOptionsResponse

data class PaymentOptionsResponse(
    val paymentId: String,
    val info: PaymentInfo?,
    val options: List<PaymentOption>,
    val collectDataAction: CollectDataAction?
)

Wallet.Model.PaymentInfo

data class PaymentInfo(
    val status: PaymentStatus,
    val amount: PaymentAmount,
    val expiresAt: Long,
    val merchant: MerchantInfo
)

data class MerchantInfo(
    val name: String,
    val iconUrl: String?
)

Wallet.Model.PaymentOption

data class PaymentOption(
    val id: String,
    val amount: PaymentAmount,
    val account: String,
    val estimatedTxs: Int?
)

Wallet.Model.PaymentAmount

data class PaymentAmount(
    val value: String,
    val unit: String,
    val display: PaymentAmountDisplay?
)

data class PaymentAmountDisplay(
    val assetSymbol: String,
    val assetName: String,
    val decimals: Int,
    val iconUrl: String?,
    val networkName: String?,
    val networkIconUrl: String?
)

Wallet.Model.WalletRpcAction

data class WalletRpcAction(
    val chainId: String,   // CAIP-2 chain ID (e.g., "eip155:8453")
    val method: String,    // RPC method
    val params: String     // JSON-encoded parameters
)

Wallet.Model.PaymentStatus

StatusDescription
REQUIRES_ACTIONAdditional action needed
PROCESSINGPayment in progress
SUCCEEDEDPayment completed
FAILEDPayment failed
EXPIREDPayment expired

Best Practices

  1. Use WalletKit Integration: If your wallet already uses WalletKit, prefer this approach for automatic configuration
  2. Use isPaymentLink() for Detection: Use the utility method instead of manual URL parsing for reliable payment link detection
  3. Account Format: Always use CAIP-10 format for accounts: eip155:{chainId}:{address}
  4. Multiple Chains: Provide accounts for all supported chains to maximize payment options
  5. Signature Order: Maintain the same order of signatures as the actions array
  6. Error Handling: Always handle errors gracefully and show appropriate user feedback
  7. Loading States: Show loading indicators during API calls and signing operations
  8. Expiration: Check paymentInfo.expiresAt and warn users if time is running low
  9. User Data: Only collect data when collectDataAction is present in the response