walletconnect-pay
WalletConnect Pay — Wallet Integration
Goal
Help wallet developers integrate WalletConnect Pay so their users can pay at any compatible POS terminal or online checkout using USDC. The integration enables wallets to detect payment links, fetch payment options, collect signatures, and confirm transactions.
When to use
- Adding WalletConnect Pay support to a mobile wallet
- Implementing payment link (QR code) detection and handling
- Debugging WC Pay SDK initialization or API errors
- Choosing between WalletKit, Standalone SDK, or API-First approaches
- Implementing data collection (KYC/KYT) WebView flow
- Setting up CAIP-10 account formatting for multi-chain payment options
When not to use
- Building a merchant POS terminal (use the POS SDK instead — separate product)
- Building an e-commerce checkout (coming soon, different SDK)
- General WalletConnect pairing/session management unrelated to payments
Supported Platforms & Assets
Frameworks: Kotlin (Android), Swift (iOS), React Native, Flutter
Assets: USDC only (currently)
Networks: Ethereum (eip155:1), Base (eip155:8453), Optimism (eip155:10), Polygon (eip155:137), Arbitrum (eip155:42161)
Choose Your Integration Path
| Path | When to use | Complexity |
|---|---|---|
| WalletKit (recommended) | Already using WalletConnect WalletKit | Lowest — Pay initializes automatically |
| Standalone SDK | No WalletKit dependency, want SDK convenience | Medium |
| API-First | Full control, no SDK, direct Gateway calls | Highest |
Jump to the right reference:
- Kotlin (WalletKit)
- Swift (WalletKit)
- React Native (WalletKit)
- Flutter (WalletKit)
- Kotlin Standalone SDK
- Swift Standalone SDK
- React Native Standalone SDK
- Flutter Standalone SDK
- API-First (all platforms)
Prerequisites
- Project ID — create a project at dashboard.walletconnect.com
- App ID — from the same dashboard; required for Pay initialization
- Android min SDK 23 / iOS 13+ / Node 16+ / Flutter 3+
- For Standalone: contact WalletConnect team to obtain an API key
Universal Payment Flow (all platforms)
Every integration follows these 6 steps:
QR Scan / Deep Link
↓
1. isPaymentLink(uri) → branch: Pay vs. standard WC pairing
↓
2. getPaymentOptions(link, accounts) → list of options + merchant info
↓
3. User selects option
↓
4. getRequiredPaymentActions(paymentId, optionId) → signing actions
↓
5a. Sign actions (EIP-712 typed data, preserve order)
5b. [If collectData present] Show WebView → await IC_COMPLETE
↓
6. confirmPayment(paymentId, optionId, signatures)
Step 1 — Detect payment links
Always check before routing to standard WalletConnect pairing.
// Kotlin (WalletKit)
if (WalletKit.Pay.isPaymentLink(uri)) {
processPayment(uri)
} else {
WalletKit.pair(Wallet.Params.Pair(uri))
}
// Swift (WalletKit)
if WalletKit.isPaymentLink(scannedString) {
startPaymentFlow(paymentLink: scannedString)
}
// React Native
import { isPaymentLink } from "@reown/walletkit";
if (isPaymentLink(uri)) { await processPayment(uri); }
// Flutter
if (walletKit.isPaymentLink(uri)) { /* process */ }
Step 2 — Get payment options
Accounts must use CAIP-10 format: eip155:{chainId}:{address}
Provide accounts on all supported chains to maximize option availability.
val result = WalletKit.Pay.getPaymentOptions(
paymentLink = uri,
accounts = listOf("eip155:1:0x...", "eip155:8453:0x...", "eip155:137:0x...")
)
Response contains:
paymentId— use in all subsequent callsoptions[]— each hasid,amount,account,collectData?info— merchant name, amount, expiry (expiresAt)
Step 3 — Get required signing actions
val actions = WalletKit.Pay.getRequiredPaymentActions(
Wallet.Params.RequiredPaymentActions(paymentId, selectedOption.id)
)
Each action is a WalletRpcAction with chainId, method (eth_signTypedData_v4), and params.
Step 4 — Sign actions
Sign all actions. Order must match the actions array exactly.
// Sign EIP-712 typed data — Kotlin example
val paramsArray = JSONArray(rpc.params)
val typedData = paramsArray.getString(1)
val encoder = StructuredDataEncoder(typedData)
val hash = encoder.hashStructuredData()
val signature = Sign.signMessage(hash, keyPair, false)
Step 5 — Data collection (conditional)
Only show WebView when selectedOption.collectData != null.
Never build native forms — the WebView handles compliance, validation, and T&C.
selectedOption.collectData?.let { collectAction ->
// Optional: prefill known user data
val prefillBase64 = Base64.encode(JSONObject(userData).toString().toByteArray())
val url = Uri.parse(collectAction.url)
.buildUpon()
.appendQueryParameter("prefill", prefillBase64)
.build().toString()
showWebView(url) // Listen for IC_COMPLETE / IC_ERROR
}
WebView JavaScript bridge messages:
IC_COMPLETE→ proceed to confirmIC_ERROR→ show error, allow retry
Step 6 — Confirm payment
val result = WalletKit.Pay.confirmPayment(
Wallet.Params.ConfirmPayment(paymentId, selectedOption.id, signatures)
)
// Status: SUCCEEDED | PROCESSING | FAILED | EXPIRED | REQUIRES_ACTION
If isFinal == false and pollInMs is present, poll again after the delay.
Validation checklist
-
isPaymentLink()called before any WalletConnect pairing attempt - All accounts formatted as CAIP-10 (
eip155:{chainId}:{address}) - Accounts provided for all 5 supported networks
- Signatures array order matches actions array order
-
collectDatachecked per-option, not globally - WebView used for data collection (no native form built)
-
expiresAtmonitored; user warned before expiry - Loading states shown during API calls and signing
- Users shown WalletConnect Terms & Privacy Policy before data submission
- All 5 payment statuses handled in UI
Common errors
| Error | Cause | Fix |
|---|---|---|
PaymentNotFound |
Invalid or deleted payment link | Show "payment not found" to user |
PaymentExpired |
User took too long | Show "payment expired, ask merchant to retry" |
InvalidAccount |
Bad CAIP-10 format | Verify eip155:{chainId}:{address} format |
ComplianceFailed |
KYC/KYT blocked the payment | Show message; do not retry automatically |
InvalidSignature |
Wrong signing order or method | Ensure signatures match actions array order |
| Init fails | Missing appId or packageName |
Check dashboard for App ID |
Examples
Example 1 — QR scan handler (React Native)
async function handleScan(uri) {
if (isPaymentLink(uri)) {
const options = await walletkit.pay.getPaymentOptions({
paymentLink: uri,
accounts: ["eip155:1:0xABC", "eip155:8453:0xABC"],
});
const option = options.options[0];
const actions = await walletkit.pay.getRequiredPaymentActions({
paymentId: options.paymentId,
optionId: option.id,
});
const signatures = await Promise.all(
actions.map(a => wallet.signTypedData(a.walletRpc.chainId, a.walletRpc.params))
);
const result = await walletkit.pay.confirmPayment({
paymentId: options.paymentId,
optionId: option.id,
signatures,
});
showStatus(result.status); // "succeeded" | "processing" | etc.
} else {
await walletkit.pair({ uri });
}
}
Example 2 — Data collection gating (Swift)
// After getting actions and signing them:
if let collectData = selectedOption.collectData {
// Must show WebView before confirmPayment
let url = buildPrefillURL(base: collectData.url, userData: knownUserData)
showWebView(url) { [weak self] in
// IC_COMPLETE callback
self?.confirmPayment(paymentId, selectedOption.id, signatures)
}
} else {
// No data collection needed → confirm directly
confirmPayment(paymentId, selectedOption.id, signatures)
}
Evaluations
- Activation — "I'm building an Android wallet and want to support WalletConnect Pay. Walk me through the Kotlin WalletKit integration."
- Non-activation — "How do I set up WalletConnect session management for my dApp?" (standard WC, not Pay)
- Edge case — "My wallet only has Ethereum accounts. Will WC Pay still work?" (Answer: yes, but fewer options; recommend adding multi-chain accounts)
- Edge case — "When should I show the WebView for data collection?" (Answer: only when
collectDatais non-null on the selected option) - Framework choice — "I don't use WalletKit. How do I integrate WC Pay on Flutter?" (Answer: use Flutter Standalone SDK)
- Standalone Swift — "I'm building an iOS wallet without WalletKit. How do I add WC Pay?" (Answer: use Swift Standalone SDK with SPM)
- Standalone React Native — "How do I add WC Pay to my React Native wallet without WalletKit?" (Answer: use
@walletconnect/paystandalone package)
More from walletconnect/skills
security-audit-owasp-top-10
Performs comprehensive security audit of any codebase against OWASP Top 10 2025. Use when user asks for OWASP audit, OWASP Top 10 review, OWASP security check, or wants to audit code against OWASP categories. Do not trigger for PR review, npm/pip audit, SOC2 compliance, general security questions, or threat modeling.
26skill-writing
Designs and writes high-quality Agent Skills (SKILL.md + optional reference files/scripts). Use when asked to create a new Skill, rewrite an existing Skill, improve Skill structure/metadata, or generate templates/evaluations for Skills.
19code-simplifier
Simplifies and refines code for clarity, consistency, and maintainability while preserving all functionality. Focuses on recently modified code unless instructed otherwise.
16agent-creator
Guide for creating custom Claude Code subagents. Use when user wants to create a new agent (or update an existing agent) that handles specific types of tasks with custom prompts, tool restrictions, and permissions. Triggers on requests to create agents, subagents, custom agents, or when user wants specialized AI assistants for specific workflows.
15code-review
Provide actionable feedback on code changes. Focuses on bugs, security issues, and structural problems.
15command-creator
Guide for creating custom Claude Code slash commands. Use when user wants to create a new command (or update an existing command) that provides a reusable prompt snippet, workflow, or automation. Triggers on requests to create /commands, slash commands, custom commands, or when user wants to define frequently-used prompts as reusable commands.
15