From a6ff8293ef867e04eb8a1fa0a0ad4f120740c45c Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 15 Oct 2025 14:18:43 +0000 Subject: [PATCH 1/9] docs: add wallet implementation guide for wallet_pay standard Co-Authored-By: rohit@reown.com --- docs.json | 2 +- payments/wallet-implementation.mdx | 597 +++++++++++++++++++++++++++++ 2 files changed, 598 insertions(+), 1 deletion(-) create mode 100644 payments/wallet-implementation.mdx diff --git a/docs.json b/docs.json index 046efc2..5ab5ae2 100644 --- a/docs.json +++ b/docs.json @@ -269,7 +269,7 @@ "groups": [ { "group": "WalletConnect Payments Standard", - "pages": ["payments/overview"] + "pages": ["payments/overview", "payments/wallet-implementation"] }, { "group": "Point of Sale (POS) SDK", diff --git a/payments/wallet-implementation.mdx b/payments/wallet-implementation.mdx new file mode 100644 index 0000000..f592645 --- /dev/null +++ b/payments/wallet-implementation.mdx @@ -0,0 +1,597 @@ +--- +title: "Wallet Pay Implementation for Wallets" +metatags: + description: "Learn how to implement the wallet_pay standard in your wallet to accept payment requests from merchants and applications." +sidebarTitle: "Wallet Implementation" +--- + +This guide explains how wallet developers can implement support for the `wallet_pay` standard (CAIP-358) to enable their users to make blockchain-based payments through WalletConnect-compatible merchants and applications. + +## Overview + +The `wallet_pay` standard enables wallets to receive and process standardized payment requests from merchants, point-of-sale systems, and other applications. By implementing this standard, your wallet becomes compatible with the entire WalletConnect Payments ecosystem. + +### Key Capabilities + +When you implement `wallet_pay`, your wallet can: + +- Receive payment requests via WalletConnect sessions +- Display payment details including amount, recipient, and supported assets +- Validate payment requests for correctness and security +- Process both direct transfers and smart contract interactions +- Support multiple blockchain networks (EVM, Solana, and more) +- Handle payment expiry and error conditions + +## Implementation Flow + +The typical payment flow involves these steps: + +1. **Session Established**: User connects their wallet to a merchant application via WalletConnect +2. **Payment Request Received**: Merchant sends a `wallet_checkout` request through the session +3. **Request Validation**: Wallet validates the payment request structure and parameters +4. **User Approval**: Wallet displays payment details and requests user confirmation +5. **Payment Processing**: Upon approval, wallet executes the payment transaction +6. **Response Sent**: Wallet sends the transaction hash and details back to the merchant + +## Request Structure + +The `wallet_checkout` method receives a request with the following structure: + +```typescript +{ + orderId: string // Unique identifier (max 128 characters) + acceptedPayments: PaymentOption[] // Array of payment options + products?: ProductMetadata[] // Optional product information + expiry?: number // Optional UNIX timestamp (seconds) +} +``` + +### Payment Options + +Each payment option specifies either a direct transfer or a contract interaction: + +```typescript +type PaymentOption = { + asset: string // CAIP-19 asset identifier + amount: string // Hex-encoded amount + recipient?: string // CAIP-10 account ID (for direct payments) + contractInteraction?: ContractInteraction // For contract-based payments +} +``` + +**Direct Payment Example (ERC20 Transfer):** +```typescript +{ + asset: "eip155:1/erc20:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + amount: "0xF4240", // 1000000 (1 USDC with 6 decimals) + recipient: "eip155:1:0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb" +} +``` + +**Contract Interaction Example (EVM):** +```typescript +{ + asset: "eip155:1/slip44:60", + amount: "0x0", + contractInteraction: { + type: "evm-calls", + data: [{ + to: "0x...", + value: "0x0", + data: "0x..." + }] + } +} +``` + +### Product Metadata + +Optional product information for UI display: + +```typescript +type ProductMetadata = { + name: string // Product name + description?: string // Product description + imageUrl?: string // Product image URL + price?: string // Human-readable price (e.g., "$10.00") +} +``` + +## Implementation Guide + +### Step 1: Register the Method Handler + +Register the `wallet_checkout` method in your WalletConnect event handler: + +```typescript +import { walletkit } from '@/utils/WalletConnectUtil' +import { SignClientTypes } from '@walletconnect/types' + +const onSessionRequest = async ( + requestEvent: SignClientTypes.EventArguments['session_request'] +) => { + const { topic, params, id } = requestEvent + const { request } = params + + switch (request.method) { + case 'wallet_checkout': + await handleWalletCheckout(requestEvent) + break + // ... other methods + } +} + +walletkit.on('session_request', onSessionRequest) +``` + +### Step 2: Validate the Request + +Implement comprehensive validation using a schema validator like Zod: + +```typescript +import { z } from 'zod' + +// CAIP-19 Asset ID validation +const isValidCAIP19AssetId = (assetId: string): boolean => { + const chainAssetParts = assetId.split('/') + if (chainAssetParts.length !== 2) return false + + const chainParts = chainAssetParts[0]?.split(':') + const assetParts = chainAssetParts[1]?.split(':') + + return ( + chainParts?.length === 2 && + chainParts[0]?.length > 0 && + chainParts[1]?.length > 0 && + assetParts.length === 2 && + assetParts[0]?.length > 0 && + assetParts[1]?.length > 0 + ) +} + +// CAIP-10 Account ID validation +const isValidCAIP10AccountId = (accountId: string): boolean => { + const parts = accountId.split(':') + return parts.length === 3 && + parts.every(part => part.length > 0) +} + +// Payment Option Schema +const PaymentOptionSchema = z.object({ + asset: z.string() + .refine(isValidCAIP19AssetId, 'Invalid CAIP-19 asset'), + amount: z.string() + .regex(/^0x[0-9a-fA-F]+$/, 'Amount must be a hex string'), + recipient: z.string() + .refine(isValidCAIP10AccountId, 'Invalid CAIP-10 recipient') + .optional(), + contractInteraction: z.object({ + type: z.string(), + data: z.any() + }).optional() +}).refine( + data => (data.recipient && !data.contractInteraction) || + (!data.recipient && data.contractInteraction), + 'Either recipient or contractInteraction must be provided, but not both' +) + +// Checkout Request Schema +const CheckoutRequestSchema = z.object({ + orderId: z.string().max(128, 'Order ID must not exceed 128 characters'), + acceptedPayments: z.array(PaymentOptionSchema) + .min(1, 'At least one payment option is required'), + products: z.array(z.object({ + name: z.string().min(1), + description: z.string().optional(), + imageUrl: z.string().url().optional(), + price: z.string().optional() + })).optional(), + expiry: z.number().int().optional() +}) + +// Validate the request +const validateCheckoutRequest = (checkoutRequest: any) => { + // Check expiry + if (checkoutRequest.expiry) { + const currentTime = Math.floor(Date.now() / 1000) + if (currentTime > checkoutRequest.expiry) { + throw new CheckoutError(4200, 'Checkout request has expired') + } + } + + // Validate structure + CheckoutRequestSchema.parse(checkoutRequest) +} +``` + +### Step 3: Check Asset Availability + +Determine which payment options the user can fulfill: + +```typescript +const findFeasiblePayments = async ( + acceptedPayments: PaymentOption[] +): Promise => { + const feasiblePayments: DetailedPaymentOption[] = [] + + for (const payment of acceptedPayments) { + const [chainPart, assetPart] = payment.asset.split('/') + const [chainNamespace, chainId] = chainPart.split(':') + const [assetNamespace, assetReference] = assetPart.split(':') + + // Get user's balance for this asset + const balance = await getUserAssetBalance( + chainNamespace, + chainId, + assetNamespace, + assetReference + ) + + // Check if user has sufficient balance + const requiredAmount = BigInt(payment.amount) + if (balance >= requiredAmount) { + // Estimate gas fees + const gasFee = await estimateGasFee(payment) + + feasiblePayments.push({ + ...payment, + assetMetadata: { + assetIcon: getAssetIcon(assetReference), + assetName: getAssetName(assetReference), + assetSymbol: getAssetSymbol(assetReference), + assetNamespace, + assetDecimals: getAssetDecimals(assetReference), + assetBalance: balance + }, + chainMetadata: { + chainId, + chainName: getChainName(chainId), + chainNamespace, + chainIcon: getChainIcon(chainId) + }, + fee: gasFee + }) + } + } + + if (feasiblePayments.length === 0) { + throw new CheckoutError(4300, 'Insufficient funds') + } + + return feasiblePayments +} +``` + +### Step 4: Display Payment UI + +Show payment details to the user for approval: + +```typescript +const handleWalletCheckout = async ( + requestEvent: SignClientTypes.EventArguments['session_request'] +) => { + const { topic, params, id } = requestEvent + const checkoutRequest = params.request.params[0] + + try { + // Validate request + validateCheckoutRequest(checkoutRequest) + + // Find feasible payments + const feasiblePayments = await findFeasiblePayments( + checkoutRequest.acceptedPayments + ) + + // Show payment modal to user + showPaymentModal({ + orderId: checkoutRequest.orderId, + products: checkoutRequest.products, + paymentOptions: feasiblePayments, + onApprove: (selectedPayment) => + processPayment(topic, id, selectedPayment, checkoutRequest.orderId), + onReject: () => + sendErrorResponse(topic, id, 4001, 'User rejected payment') + }) + } catch (error) { + await sendErrorResponse(topic, id, error.code, error.message) + } +} +``` + +### Step 5: Process the Payment + +Execute the blockchain transaction based on the payment type: + +```typescript +const processPayment = async ( + topic: string, + requestId: number, + payment: DetailedPaymentOption, + orderId: string +) => { + try { + let txHash: string + + const { chainMetadata, assetMetadata, contractInteraction, recipient } = payment + + // Direct payment + if (recipient && !contractInteraction) { + const recipientAddress = recipient.split(':')[2] + + // EVM chains + if (chainMetadata.chainNamespace === 'eip155') { + if (assetMetadata.assetNamespace === 'slip44') { + // Native token transfer (ETH) + txHash = await sendNativeToken( + recipientAddress, + BigInt(payment.amount) + ) + } else if (assetMetadata.assetNamespace === 'erc20') { + // ERC20 token transfer + txHash = await sendERC20Token( + payment.asset.split(':')[2], // token address + recipientAddress, + BigInt(payment.amount) + ) + } + } + + // Solana + else if (chainMetadata.chainNamespace === 'solana') { + if (assetMetadata.assetNamespace === 'slip44') { + // SOL transfer + txHash = await sendSol( + recipientAddress, + BigInt(payment.amount) + ) + } else if (assetMetadata.assetNamespace === 'token') { + // SPL token transfer + txHash = await sendSplToken( + assetMetadata.assetReference, // mint address + recipientAddress, + BigInt(payment.amount) + ) + } + } + } + + // Contract interaction + else if (contractInteraction && !recipient) { + if (contractInteraction.type === 'evm-calls') { + // Execute EVM contract calls + for (const call of contractInteraction.data) { + txHash = await sendTransaction({ + to: call.to, + value: call.value || '0x0', + data: call.data + }) + } + } else if (contractInteraction.type === 'solana-instruction') { + // Execute Solana instruction + txHash = await executeSolanaInstruction(contractInteraction.data) + } + } + + // Send success response + await walletkit.respondSessionRequest({ + topic, + response: { + jsonrpc: '2.0', + id: requestId, + result: { + orderId, + txid: txHash, + recipient: payment.recipient, + asset: payment.asset, + amount: payment.amount + } + } + }) + } catch (error) { + await sendErrorResponse(topic, requestId, 4600, 'Payment failed') + } +} +``` + +### Step 6: Helper Functions for Token Transfers + +**ERC20 Transfer:** +```typescript +import { encodeFunctionData, erc20Abi } from 'viem' + +const sendERC20Token = async ( + tokenAddress: string, + recipientAddress: string, + amount: bigint +): Promise => { + const calldata = encodeFunctionData({ + abi: erc20Abi, + functionName: 'transfer', + args: [recipientAddress as `0x${string}`, amount] + }) + + const tx = await wallet.sendTransaction({ + to: tokenAddress, + value: '0x0', + data: calldata + }) + + return tx.hash +} +``` + +**Solana SPL Token Transfer:** +```typescript +import { Connection, PublicKey, Transaction } from '@solana/web3.js' +import { createTransferInstruction, getAssociatedTokenAddress } from '@solana/spl-token' + +const sendSplToken = async ( + mintAddress: string, + recipientAddress: string, + amount: bigint +): Promise => { + const connection = new Connection(rpcUrl) + const senderPubkey = new PublicKey(await wallet.getAddress()) + const recipientPubkey = new PublicKey(recipientAddress) + const mintPubkey = new PublicKey(mintAddress) + + // Get associated token accounts + const senderTokenAccount = await getAssociatedTokenAddress( + mintPubkey, + senderPubkey + ) + const recipientTokenAccount = await getAssociatedTokenAddress( + mintPubkey, + recipientPubkey + ) + + // Create transfer instruction + const instruction = createTransferInstruction( + senderTokenAccount, + recipientTokenAccount, + senderPubkey, + amount + ) + + // Build and send transaction + const transaction = new Transaction().add(instruction) + const { blockhash } = await connection.getLatestBlockhash() + transaction.recentBlockhash = blockhash + transaction.feePayer = senderPubkey + + const txHash = await wallet.signAndSendTransaction(transaction) + return txHash +} +``` + +## Error Handling + +Implement proper error responses using standardized error codes: + +```typescript +enum CheckoutErrorCode { + USER_REJECTED = 4001, + NO_MATCHING_ASSETS = 4100, + CHECKOUT_EXPIRED = 4200, + INSUFFICIENT_FUNDS = 4300, + UNSUPPORTED_CONTRACT_INTERACTION = 4400, + INVALID_CONTRACT_INTERACTION_DATA = 4401, + CONTRACT_INTERACTION_FAILED = 4402, + METHOD_NOT_FOUND = -32601, + INVALID_CHECKOUT_REQUEST = 4500, + DIRECT_PAYMENT_ERROR = 4600 +} + +const sendErrorResponse = async ( + topic: string, + requestId: number, + code: number, + message: string +) => { + await walletkit.respondSessionRequest({ + topic, + response: { + jsonrpc: '2.0', + id: requestId, + error: { + code, + message + } + } + }) +} +``` + +### Error Code Reference + +| Code | Name | Description | +|------|------|-------------| +| 4001 | USER_REJECTED | User declined the payment request | +| 4100 | NO_MATCHING_ASSETS | User doesn't hold any of the requested assets | +| 4200 | CHECKOUT_EXPIRED | Payment request has expired | +| 4300 | INSUFFICIENT_FUNDS | User has insufficient balance for the payment | +| 4400 | UNSUPPORTED_CONTRACT_INTERACTION | Contract interaction type not supported | +| 4401 | INVALID_CONTRACT_INTERACTION_DATA | Contract interaction data is malformed | +| 4402 | CONTRACT_INTERACTION_FAILED | Contract interaction execution failed | +| 4500 | INVALID_CHECKOUT_REQUEST | Request structure is invalid | +| 4600 | DIRECT_PAYMENT_ERROR | Direct payment transaction failed | +| -32601 | METHOD_NOT_FOUND | Wallet doesn't support wallet_checkout | + +## Supported Asset Formats + +### EVM Chains (eip155) + +**Native Token (ETH):** +``` +eip155:1/slip44:60 +``` + +**ERC20 Token:** +``` +eip155:1/erc20:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 +``` + +### Solana (solana) + +**Native Token (SOL):** +``` +solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501 +``` + +**SPL Token:** +``` +solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v +``` + +## Best Practices + +### Security + +1. **Always validate payment requests** before displaying to users +2. **Verify asset and recipient formats** match CAIP standards +3. **Check expiry timestamps** to prevent stale payment processing +4. **Confirm gas fees** can be covered before attempting transactions +5. **Display clear payment details** to users including recipient address, amount, and network + +### User Experience + +1. **Show feasible payment options first** - filter out options the user can't fulfill +2. **Display product information** when available for context +3. **Estimate and show gas fees** upfront +4. **Provide clear error messages** when payments fail +5. **Support multiple chains** to maximize compatibility + +### Performance + +1. **Validate requests asynchronously** to avoid blocking the UI +2. **Cache asset metadata** to reduce lookups +3. **Batch balance checks** when possible +4. **Use efficient RPC providers** for transaction submission + +## Testing + +Test your implementation with the reference dApp: + +1. Deploy the [WalletConnect Pay dApp example](https://github.com/reown-com/web-examples/tree/chore/wallet-pay-dapp/advanced/dapps/walletconnect-pay-dapp) +2. Connect your wallet via WalletConnect +3. Initiate a test payment with various asset types +4. Verify payment requests are validated correctly +5. Test error scenarios (insufficient funds, expired requests, etc.) + +## Reference Implementation + +For a complete working example, see the [React Wallet v2 implementation](https://github.com/reown-com/web-examples/tree/chore/wallet-pay-dapp/advanced/wallets/react-wallet-v2) which demonstrates: + +- Request validation with Zod schemas +- Multi-chain payment processing (EVM and Solana) +- Contract interaction support +- Error handling and user feedback +- Balance checking and fee estimation + +## Additional Resources + +- [CAIP-358: Universal Payment Request Method](https://eip.tools/caip/358) +- [CAIP-19: Asset Type and Asset ID Specification](https://chainagnostic.org/CAIPs/caip-19) +- [CAIP-10: Account ID Specification](https://chainagnostic.org/CAIPs/caip-10) +- [WalletConnect Wallet SDK](/wallet-sdk/overview) +- [POS SDK Documentation](/payments/point-of-sale) From 7f00684d35c96bf8251f3254122072b6a407faec Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 15 Oct 2025 14:38:14 +0000 Subject: [PATCH 2/9] fix: rewrite documentation to reflect actual walletPay flow in session proposals Co-Authored-By: rohit@reown.com --- payments/wallet-implementation.mdx | 818 +++++++++++++---------------- 1 file changed, 357 insertions(+), 461 deletions(-) diff --git a/payments/wallet-implementation.mdx b/payments/wallet-implementation.mdx index f592645..f249b69 100644 --- a/payments/wallet-implementation.mdx +++ b/payments/wallet-implementation.mdx @@ -1,597 +1,493 @@ --- title: "Wallet Pay Implementation for Wallets" metatags: - description: "Learn how to implement the wallet_pay standard in your wallet to accept payment requests from merchants and applications." + description: "Learn how to implement WalletConnect Pay in your wallet to process payment requests during session establishment." sidebarTitle: "Wallet Implementation" --- -This guide explains how wallet developers can implement support for the `wallet_pay` standard (CAIP-358) to enable their users to make blockchain-based payments through WalletConnect-compatible merchants and applications. +This guide explains how wallet developers can implement support for WalletConnect Pay to enable their users to make blockchain-based payments through WalletConnect-compatible merchants and applications. ## Overview -The `wallet_pay` standard enables wallets to receive and process standardized payment requests from merchants, point-of-sale systems, and other applications. By implementing this standard, your wallet becomes compatible with the entire WalletConnect Payments ecosystem. +WalletConnect Pay enables wallets to receive and process payment requests as part of the session establishment flow. When a merchant initiates a connection with a payment request, the wallet can process the payment and return the transaction result during session approval. ### Key Capabilities -When you implement `wallet_pay`, your wallet can: +When you implement WalletConnect Pay, your wallet can: -- Receive payment requests via WalletConnect sessions -- Display payment details including amount, recipient, and supported assets -- Validate payment requests for correctness and security -- Process both direct transfers and smart contract interactions -- Support multiple blockchain networks (EVM, Solana, and more) -- Handle payment expiry and error conditions +- Receive payment requests during WalletConnect session proposals +- Display payment details to users for approval +- Process ERC20 token transfers automatically +- Return transaction hashes to merchants upon successful payment +- Support multiple blockchain networks ## Implementation Flow -The typical payment flow involves these steps: +The WalletConnect Pay flow integrates payment processing into the session establishment: -1. **Session Established**: User connects their wallet to a merchant application via WalletConnect -2. **Payment Request Received**: Merchant sends a `wallet_checkout` request through the session -3. **Request Validation**: Wallet validates the payment request structure and parameters -4. **User Approval**: Wallet displays payment details and requests user confirmation -5. **Payment Processing**: Upon approval, wallet executes the payment transaction -6. **Response Sent**: Wallet sends the transaction hash and details back to the merchant +1. **Merchant Initiates Connection**: Merchant calls `provider.connect()` with a `walletPay` parameter +2. **Wallet Receives Proposal**: Wallet receives a session proposal containing the payment request +3. **User Reviews Payment**: Wallet displays both connection permissions and payment details +4. **User Approves**: User approves the session and payment together +5. **Payment Processed**: Wallet executes the payment transaction +6. **Session Established**: Session is approved with the payment result included -## Request Structure +## Payment Request Structure -The `wallet_checkout` method receives a request with the following structure: +The `walletPay` object is included in the session proposal at `proposal.params.requests.walletPay` with the following structure: ```typescript { - orderId: string // Unique identifier (max 128 characters) - acceptedPayments: PaymentOption[] // Array of payment options - products?: ProductMetadata[] // Optional product information - expiry?: number // Optional UNIX timestamp (seconds) + version: string // Protocol version (e.g., "1.0.0") + orderId?: string // Optional order identifier + acceptedPayments: [{ + asset: string // CAIP-19 asset identifier + amount: string // Hex-encoded amount + recipient: string // CAIP-10 account ID + }] + expiry?: number // Optional UNIX timestamp (seconds) } ``` -### Payment Options +### Example Payment Request -Each payment option specifies either a direct transfer or a contract interaction: - -```typescript -type PaymentOption = { - asset: string // CAIP-19 asset identifier - amount: string // Hex-encoded amount - recipient?: string // CAIP-10 account ID (for direct payments) - contractInteraction?: ContractInteraction // For contract-based payments -} -``` - -**Direct Payment Example (ERC20 Transfer):** -```typescript -{ - asset: "eip155:1/erc20:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", - amount: "0xF4240", // 1000000 (1 USDC with 6 decimals) - recipient: "eip155:1:0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb" -} -``` - -**Contract Interaction Example (EVM):** ```typescript { - asset: "eip155:1/slip44:60", - amount: "0x0", - contractInteraction: { - type: "evm-calls", - data: [{ - to: "0x...", - value: "0x0", - data: "0x..." - }] - } -} -``` - -### Product Metadata - -Optional product information for UI display: - -```typescript -type ProductMetadata = { - name: string // Product name - description?: string // Product description - imageUrl?: string // Product image URL - price?: string // Human-readable price (e.g., "$10.00") + version: "1.0.0", + acceptedPayments: [ + { + asset: "eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + amount: "0x186a0", // 100000 (0.1 USDC with 6 decimals) + recipient: "eip155:8453:0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb" + } + ], + expiry: 1735689600 } ``` ## Implementation Guide -### Step 1: Register the Method Handler +### Step 1: Detect Payment Requests in Session Proposals -Register the `wallet_checkout` method in your WalletConnect event handler: +Check for the `walletPay` parameter in incoming session proposals: ```typescript -import { walletkit } from '@/utils/WalletConnectUtil' import { SignClientTypes } from '@walletconnect/types' -const onSessionRequest = async ( - requestEvent: SignClientTypes.EventArguments['session_request'] +const onSessionProposal = ( + proposal: SignClientTypes.EventArguments['session_proposal'] ) => { - const { topic, params, id } = requestEvent - const { request } = params - - switch (request.method) { - case 'wallet_checkout': - await handleWalletCheckout(requestEvent) - break - // ... other methods + // Check if proposal includes a payment request + const walletPay = proposal?.params?.requests?.walletPay + + if (walletPay) { + // Handle payment request + handlePaymentProposal(proposal, walletPay) + } else { + // Handle normal session proposal + handleRegularProposal(proposal) } } -walletkit.on('session_request', onSessionRequest) +walletkit.on('session_proposal', onSessionProposal) ``` -### Step 2: Validate the Request +### Step 2: Display Payment Details to User -Implement comprehensive validation using a schema validator like Zod: +Show the payment request details alongside the session approval UI: -```typescript -import { z } from 'zod' +```tsx +import { useState } from 'react' +import { EngineTypes } from '@walletconnect/types' -// CAIP-19 Asset ID validation -const isValidCAIP19AssetId = (assetId: string): boolean => { - const chainAssetParts = assetId.split('/') - if (chainAssetParts.length !== 2) return false - - const chainParts = chainAssetParts[0]?.split(':') - const assetParts = chainAssetParts[1]?.split(':') +function SessionProposalModal({ proposal }) { + const walletPay = proposal?.params?.requests?.walletPay + const [processPayment, setProcessPayment] = useState(true) return ( - chainParts?.length === 2 && - chainParts[0]?.length > 0 && - chainParts[1]?.length > 0 && - assetParts.length === 2 && - assetParts[0]?.length > 0 && - assetParts[1]?.length > 0 +
+ {/* Regular session approval UI */} +

Connection Request

+

App: {proposal.params.proposer.metadata.name}

+ + {/* Payment request section */} + {walletPay && ( +
+

Payment Request

+ + + +
+ )} + + +
) } -// CAIP-10 Account ID validation -const isValidCAIP10AccountId = (accountId: string): boolean => { - const parts = accountId.split(':') - return parts.length === 3 && - parts.every(part => part.length > 0) -} - -// Payment Option Schema -const PaymentOptionSchema = z.object({ - asset: z.string() - .refine(isValidCAIP19AssetId, 'Invalid CAIP-19 asset'), - amount: z.string() - .regex(/^0x[0-9a-fA-F]+$/, 'Amount must be a hex string'), - recipient: z.string() - .refine(isValidCAIP10AccountId, 'Invalid CAIP-10 recipient') - .optional(), - contractInteraction: z.object({ - type: z.string(), - data: z.any() - }).optional() -}).refine( - data => (data.recipient && !data.contractInteraction) || - (!data.recipient && data.contractInteraction), - 'Either recipient or contractInteraction must be provided, but not both' -) - -// Checkout Request Schema -const CheckoutRequestSchema = z.object({ - orderId: z.string().max(128, 'Order ID must not exceed 128 characters'), - acceptedPayments: z.array(PaymentOptionSchema) - .min(1, 'At least one payment option is required'), - products: z.array(z.object({ - name: z.string().min(1), - description: z.string().optional(), - imageUrl: z.string().url().optional(), - price: z.string().optional() - })).optional(), - expiry: z.number().int().optional() -}) - -// Validate the request -const validateCheckoutRequest = (checkoutRequest: any) => { - // Check expiry - if (checkoutRequest.expiry) { - const currentTime = Math.floor(Date.now() / 1000) - if (currentTime > checkoutRequest.expiry) { - throw new CheckoutError(4200, 'Checkout request has expired') - } - } +function PaymentDetails({ payment }) { + const { asset, amount, recipient } = payment - // Validate structure - CheckoutRequestSchema.parse(checkoutRequest) + // Parse CAIP-19 asset identifier + const [chainPart, assetPart] = asset.split('/') + const [chainNamespace, chainId] = chainPart.split(':') + const [assetType, assetAddress] = assetPart.split(':') + + return ( +
+

Token: {assetAddress}

+

Amount: {formatAmount(amount)}

+

Recipient: {recipient.split(':')[2]}

+

Network: {chainNamespace}:{chainId}

+
+ ) } ``` -### Step 3: Check Asset Availability +### Step 3: Process Payment on Approval -Determine which payment options the user can fulfill: +When the user approves the session, process the payment and include the result: ```typescript -const findFeasiblePayments = async ( - acceptedPayments: PaymentOption[] -): Promise => { - const feasiblePayments: DetailedPaymentOption[] = [] +import { EngineTypes } from '@walletconnect/types' +import { parseChainId } from '@walletconnect/utils' + +async function processWalletPay( + walletPay: EngineTypes.WalletPayParams +) { + // Extract payment details + const payment = walletPay.acceptedPayments[0] + const chainId = parseChainId(payment.recipient) - for (const payment of acceptedPayments) { - const [chainPart, assetPart] = payment.asset.split('/') - const [chainNamespace, chainId] = chainPart.split(':') - const [assetNamespace, assetReference] = assetPart.split(':') - - // Get user's balance for this asset - const balance = await getUserAssetBalance( - chainNamespace, - chainId, - assetNamespace, - assetReference - ) - - // Check if user has sufficient balance - const requiredAmount = BigInt(payment.amount) - if (balance >= requiredAmount) { - // Estimate gas fees - const gasFee = await estimateGasFee(payment) - - feasiblePayments.push({ - ...payment, - assetMetadata: { - assetIcon: getAssetIcon(assetReference), - assetName: getAssetName(assetReference), - assetSymbol: getAssetSymbol(assetReference), - assetNamespace, - assetDecimals: getAssetDecimals(assetReference), - assetBalance: balance - }, - chainMetadata: { - chainId, - chainName: getChainName(chainId), - chainNamespace, - chainIcon: getChainIcon(chainId) - }, - fee: gasFee - }) - } - } + // Get the wallet for this chain + const wallet = await getWalletForChain( + `${chainId.namespace}:${chainId.reference}` + ) + + // Execute the payment + return await wallet.walletPay(walletPay) +} + +async function approveSession(proposal, walletPay, shouldProcessPayment) { + // Build approved namespaces + const namespaces = buildApprovedNamespaces({ + proposal: proposal.params, + supportedNamespaces: getSupportedNamespaces() + }) - if (feasiblePayments.length === 0) { - throw new CheckoutError(4300, 'Insufficient funds') + const proposalRequestsResponses = [] + + // Process payment if user approved it + if (walletPay && shouldProcessPayment) { + const paymentResult = await processWalletPay(walletPay) + proposalRequestsResponses.push(paymentResult) } - return feasiblePayments + // Approve session with payment result + await walletkit.approveSession({ + id: proposal.id, + namespaces, + proposalRequestsResponses + }) } ``` -### Step 4: Display Payment UI +### Step 4: Implement Payment Execution -Show payment details to the user for approval: +Implement the actual token transfer logic in your wallet class: ```typescript -const handleWalletCheckout = async ( - requestEvent: SignClientTypes.EventArguments['session_request'] -) => { - const { topic, params, id } = requestEvent - const checkoutRequest = params.request.params[0] +import { ethers, providers, Wallet } from 'ethers' +import { EngineTypes } from '@walletconnect/types' +import { parseChainId } from '@walletconnect/utils' + +const ERC20_ABI = [ + 'function transfer(address to, uint256 amount) public returns (bool)' +] + +class EIP155Wallet { + wallet: Wallet - try { - // Validate request - validateCheckoutRequest(checkoutRequest) + async walletPay(walletPay: EngineTypes.WalletPayParams) { + const payment = walletPay.acceptedPayments[0] + + // Parse payment details + const [chainPart, assetPart] = payment.asset.split('/') + const assetAddress = assetPart.split(':')[1] + const recipientAddress = payment.recipient.split(':')[2] + const amount = payment.amount + + // Get chain RPC URL + const chainId = parseChainId(payment.recipient) + const rpcUrl = getRpcUrl(`${chainId.namespace}:${chainId.reference}`) - // Find feasible payments - const feasiblePayments = await findFeasiblePayments( - checkoutRequest.acceptedPayments + // Connect wallet to network + const provider = new ethers.providers.JsonRpcProvider(rpcUrl) + const connectedWallet = this.wallet.connect(provider) + + // Create token contract instance + const tokenContract = new ethers.Contract( + assetAddress, + ERC20_ABI, + connectedWallet ) - // Show payment modal to user - showPaymentModal({ - orderId: checkoutRequest.orderId, - products: checkoutRequest.products, - paymentOptions: feasiblePayments, - onApprove: (selectedPayment) => - processPayment(topic, id, selectedPayment, checkoutRequest.orderId), - onReject: () => - sendErrorResponse(topic, id, 4001, 'User rejected payment') + // Execute transfer + const tx = await tokenContract.transfer(recipientAddress, amount, { + gasLimit: 100_000n // Adjust as needed }) - } catch (error) { - await sendErrorResponse(topic, id, error.code, error.message) - } -} -``` - -### Step 5: Process the Payment - -Execute the blockchain transaction based on the payment type: - -```typescript -const processPayment = async ( - topic: string, - requestId: number, - payment: DetailedPaymentOption, - orderId: string -) => { - try { - let txHash: string - const { chainMetadata, assetMetadata, contractInteraction, recipient } = payment + // Wait for confirmation + const receipt = await tx.wait() - // Direct payment - if (recipient && !contractInteraction) { - const recipientAddress = recipient.split(':')[2] - - // EVM chains - if (chainMetadata.chainNamespace === 'eip155') { - if (assetMetadata.assetNamespace === 'slip44') { - // Native token transfer (ETH) - txHash = await sendNativeToken( - recipientAddress, - BigInt(payment.amount) - ) - } else if (assetMetadata.assetNamespace === 'erc20') { - // ERC20 token transfer - txHash = await sendERC20Token( - payment.asset.split(':')[2], // token address - recipientAddress, - BigInt(payment.amount) - ) - } - } - - // Solana - else if (chainMetadata.chainNamespace === 'solana') { - if (assetMetadata.assetNamespace === 'slip44') { - // SOL transfer - txHash = await sendSol( - recipientAddress, - BigInt(payment.amount) - ) - } else if (assetMetadata.assetNamespace === 'token') { - // SPL token transfer - txHash = await sendSplToken( - assetMetadata.assetReference, // mint address - recipientAddress, - BigInt(payment.amount) - ) - } - } - } - - // Contract interaction - else if (contractInteraction && !recipient) { - if (contractInteraction.type === 'evm-calls') { - // Execute EVM contract calls - for (const call of contractInteraction.data) { - txHash = await sendTransaction({ - to: call.to, - value: call.value || '0x0', - data: call.data - }) - } - } else if (contractInteraction.type === 'solana-instruction') { - // Execute Solana instruction - txHash = await executeSolanaInstruction(contractInteraction.data) - } + // Return payment result + return { + version: walletPay.version, + orderId: walletPay.orderId, + txid: receipt.transactionHash, + recipient: payment.recipient, + asset: payment.asset, + amount: payment.amount } - - // Send success response - await walletkit.respondSessionRequest({ - topic, - response: { - jsonrpc: '2.0', - id: requestId, - result: { - orderId, - txid: txHash, - recipient: payment.recipient, - asset: payment.asset, - amount: payment.amount - } - } - }) - } catch (error) { - await sendErrorResponse(topic, requestId, 4600, 'Payment failed') } } ``` -### Step 6: Helper Functions for Token Transfers +## Payment Result Structure -**ERC20 Transfer:** -```typescript -import { encodeFunctionData, erc20Abi } from 'viem' - -const sendERC20Token = async ( - tokenAddress: string, - recipientAddress: string, - amount: bigint -): Promise => { - const calldata = encodeFunctionData({ - abi: erc20Abi, - functionName: 'transfer', - args: [recipientAddress as `0x${string}`, amount] - }) - - const tx = await wallet.sendTransaction({ - to: tokenAddress, - value: '0x0', - data: calldata - }) - - return tx.hash -} -``` +The wallet must return a payment result object when processing is successful: -**Solana SPL Token Transfer:** ```typescript -import { Connection, PublicKey, Transaction } from '@solana/web3.js' -import { createTransferInstruction, getAssociatedTokenAddress } from '@solana/spl-token' - -const sendSplToken = async ( - mintAddress: string, - recipientAddress: string, - amount: bigint -): Promise => { - const connection = new Connection(rpcUrl) - const senderPubkey = new PublicKey(await wallet.getAddress()) - const recipientPubkey = new PublicKey(recipientAddress) - const mintPubkey = new PublicKey(mintAddress) - - // Get associated token accounts - const senderTokenAccount = await getAssociatedTokenAddress( - mintPubkey, - senderPubkey - ) - const recipientTokenAccount = await getAssociatedTokenAddress( - mintPubkey, - recipientPubkey - ) - - // Create transfer instruction - const instruction = createTransferInstruction( - senderTokenAccount, - recipientTokenAccount, - senderPubkey, - amount - ) - - // Build and send transaction - const transaction = new Transaction().add(instruction) - const { blockhash } = await connection.getLatestBlockhash() - transaction.recentBlockhash = blockhash - transaction.feePayer = senderPubkey - - const txHash = await wallet.signAndSendTransaction(transaction) - return txHash +{ + version: string // Same version from request + orderId?: string // Same orderId if provided + txid: string // Transaction hash + recipient: string // CAIP-10 recipient address + asset: string // CAIP-19 asset identifier + amount: string // Hex-encoded amount transferred } ``` +This result is included in the `proposalRequestsResponses` array when approving the session, and the merchant receives it in `session.walletPayResult[0]`. + ## Error Handling -Implement proper error responses using standardized error codes: +Handle payment errors gracefully and inform the user: ```typescript -enum CheckoutErrorCode { - USER_REJECTED = 4001, - NO_MATCHING_ASSETS = 4100, - CHECKOUT_EXPIRED = 4200, - INSUFFICIENT_FUNDS = 4300, - UNSUPPORTED_CONTRACT_INTERACTION = 4400, - INVALID_CONTRACT_INTERACTION_DATA = 4401, - CONTRACT_INTERACTION_FAILED = 4402, - METHOD_NOT_FOUND = -32601, - INVALID_CHECKOUT_REQUEST = 4500, - DIRECT_PAYMENT_ERROR = 4600 -} - -const sendErrorResponse = async ( - topic: string, - requestId: number, - code: number, - message: string -) => { - await walletkit.respondSessionRequest({ - topic, - response: { - jsonrpc: '2.0', - id: requestId, - error: { - code, - message +async function processWalletPay(walletPay: EngineTypes.WalletPayParams) { + try { + // Validate expiry + if (walletPay.expiry) { + const now = Math.floor(Date.now() / 1000) + if (now > walletPay.expiry) { + throw new Error('Payment request has expired') } } - }) + + // Process payment + const result = await wallet.walletPay(walletPay) + return result + + } catch (error) { + console.error('Payment processing error:', error) + + // Show error to user + showError(`Payment failed: ${error.message}`) + + // Re-throw to prevent session approval + throw error + } } ``` -### Error Code Reference +### Common Error Scenarios -| Code | Name | Description | -|------|------|-------------| -| 4001 | USER_REJECTED | User declined the payment request | -| 4100 | NO_MATCHING_ASSETS | User doesn't hold any of the requested assets | -| 4200 | CHECKOUT_EXPIRED | Payment request has expired | -| 4300 | INSUFFICIENT_FUNDS | User has insufficient balance for the payment | -| 4400 | UNSUPPORTED_CONTRACT_INTERACTION | Contract interaction type not supported | -| 4401 | INVALID_CONTRACT_INTERACTION_DATA | Contract interaction data is malformed | -| 4402 | CONTRACT_INTERACTION_FAILED | Contract interaction execution failed | -| 4500 | INVALID_CHECKOUT_REQUEST | Request structure is invalid | -| 4600 | DIRECT_PAYMENT_ERROR | Direct payment transaction failed | -| -32601 | METHOD_NOT_FOUND | Wallet doesn't support wallet_checkout | +- **Expired Request**: Check `walletPay.expiry` before processing +- **Insufficient Balance**: Validate user has enough tokens before attempting transfer +- **Network Issues**: Handle RPC failures and connection timeouts +- **Gas Estimation Failures**: Provide fallback gas limits +- **User Rejection**: Allow users to approve session without processing payment -## Supported Asset Formats +## Asset Format Reference ### EVM Chains (eip155) -**Native Token (ETH):** -``` -eip155:1/slip44:60 -``` - **ERC20 Token:** ``` eip155:1/erc20:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 ``` -### Solana (solana) +Components: +- `eip155:1` - Ethereum mainnet (CAIP-2) +- `erc20` - Asset namespace +- `0xA0b...` - Token contract address -**Native Token (SOL):** -``` -solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501 +### Amount Encoding + +Amounts are hex-encoded values in the token's smallest unit: + +```typescript +// For USDC (6 decimals), 10 USDC = 10000000 +const amount = "0x" + (10 * 10**6).toString(16) // "0x989680" + +// For tokens with 18 decimals +const amount = "0x" + (1 * 10**18).toString(16) ``` -**SPL Token:** +### Recipient Format + +Recipients use CAIP-10 account identifiers: + ``` -solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v +eip155:8453:0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb ``` +Components: +- `eip155` - Chain namespace +- `8453` - Chain ID (Base) +- `0x742d...` - Account address + ## Best Practices ### Security 1. **Always validate payment requests** before displaying to users -2. **Verify asset and recipient formats** match CAIP standards -3. **Check expiry timestamps** to prevent stale payment processing -4. **Confirm gas fees** can be covered before attempting transactions -5. **Display clear payment details** to users including recipient address, amount, and network +2. **Check expiry timestamps** to prevent processing stale requests +3. **Verify sufficient balance** before attempting transfers +4. **Display clear payment details** including token symbol, amount, and recipient +5. **Use gas estimation** with safety margins to prevent transaction failures ### User Experience -1. **Show feasible payment options first** - filter out options the user can't fulfill -2. **Display product information** when available for context -3. **Estimate and show gas fees** upfront -4. **Provide clear error messages** when payments fail -5. **Support multiple chains** to maximize compatibility +1. **Show payment as optional** - Allow users to approve session without payment +2. **Display formatted amounts** - Convert hex amounts to human-readable values +3. **Show token symbols** - Resolve token addresses to symbols (e.g., "USDC") +4. **Indicate payment status** - Show loading state during transaction processing +5. **Provide transaction links** - Link to block explorer after successful payment ### Performance -1. **Validate requests asynchronously** to avoid blocking the UI -2. **Cache asset metadata** to reduce lookups -3. **Batch balance checks** when possible -4. **Use efficient RPC providers** for transaction submission +1. **Cache token metadata** - Store token symbols and decimals to avoid repeated lookups +2. **Estimate gas efficiently** - Use cached gas estimates when possible +3. **Handle timeouts gracefully** - Set reasonable timeout limits for RPC calls +4. **Queue transactions** - Prevent concurrent payment processing ## Testing Test your implementation with the reference dApp: -1. Deploy the [WalletConnect Pay dApp example](https://github.com/reown-com/web-examples/tree/chore/wallet-pay-dapp/advanced/dapps/walletconnect-pay-dapp) -2. Connect your wallet via WalletConnect -3. Initiate a test payment with various asset types -4. Verify payment requests are validated correctly -5. Test error scenarios (insufficient funds, expired requests, etc.) +1. Clone the example: `git clone https://github.com/reown-com/web-examples.git` +2. Checkout the branch: `git checkout chore/wallet-pay-dapp` +3. Navigate to the dApp: `cd advanced/dapps/walletconnect-pay-dapp` +4. Install and run: `npm install && npm run dev` +5. Connect your wallet and test payment flows + +## Complete Example -## Reference Implementation +Here's a complete minimal implementation: -For a complete working example, see the [React Wallet v2 implementation](https://github.com/reown-com/web-examples/tree/chore/wallet-pay-dapp/advanced/wallets/react-wallet-v2) which demonstrates: +```typescript +import { walletkit } from '@/utils/WalletConnectUtil' +import { SignClientTypes, EngineTypes } from '@walletconnect/types' +import { buildApprovedNamespaces, parseChainId } from '@walletconnect/utils' +import { ethers } from 'ethers' -- Request validation with Zod schemas -- Multi-chain payment processing (EVM and Solana) -- Contract interaction support -- Error handling and user feedback -- Balance checking and fee estimation +// Listen for session proposals +walletkit.on('session_proposal', async (proposal) => { + const walletPay = proposal?.params?.requests?.walletPay + + // Show approval UI to user + const { approved, processPayment } = await showApprovalUI(proposal, walletPay) + + if (!approved) { + await walletkit.rejectSession({ + id: proposal.id, + reason: { code: 5000, message: 'User rejected' } + }) + return + } + + // Build namespaces + const namespaces = buildApprovedNamespaces({ + proposal: proposal.params, + supportedNamespaces: { + eip155: { + chains: ['eip155:1', 'eip155:8453'], + methods: ['personal_sign', 'eth_sendTransaction'], + events: ['accountsChanged', 'chainChanged'] + } + } + }) + + const responses = [] + + // Process payment if requested + if (walletPay && processPayment) { + try { + const result = await processPayment(walletPay) + responses.push(result) + } catch (error) { + console.error('Payment failed:', error) + // Optionally reject session on payment failure + } + } + + // Approve session + await walletkit.approveSession({ + id: proposal.id, + namespaces, + proposalRequestsResponses: responses + }) +}) + +async function processPayment(walletPay: EngineTypes.WalletPayParams) { + const payment = walletPay.acceptedPayments[0] + const [chainPart, assetPart] = payment.asset.split('/') + const tokenAddress = assetPart.split(':')[1] + const recipientAddress = payment.recipient.split(':')[2] + + const chainId = parseChainId(payment.recipient) + const provider = new ethers.providers.JsonRpcProvider( + getRpcUrl(`${chainId.namespace}:${chainId.reference}`) + ) + + const wallet = new ethers.Wallet(getPrivateKey(), provider) + const token = new ethers.Contract( + tokenAddress, + ['function transfer(address to, uint256 amount)'], + wallet + ) + + const tx = await token.transfer(recipientAddress, payment.amount, { + gasLimit: 100_000n + }) + const receipt = await tx.wait() + + return { + version: walletPay.version, + orderId: walletPay.orderId, + txid: receipt.transactionHash, + recipient: payment.recipient, + asset: payment.asset, + amount: payment.amount + } +} +``` ## Additional Resources -- [CAIP-358: Universal Payment Request Method](https://eip.tools/caip/358) -- [CAIP-19: Asset Type and Asset ID Specification](https://chainagnostic.org/CAIPs/caip-19) +- [WalletConnect Pay Example DApp](https://github.com/reown-com/web-examples/tree/chore/wallet-pay-dapp/advanced/dapps/walletconnect-pay-dapp) +- [Reference Wallet Implementation](https://github.com/reown-com/web-examples/tree/chore/wallet-pay-dapp/advanced/wallets/react-wallet-v2) +- [CAIP-19: Asset Type Specification](https://chainagnostic.org/CAIPs/caip-19) - [CAIP-10: Account ID Specification](https://chainagnostic.org/CAIPs/caip-10) - [WalletConnect Wallet SDK](/wallet-sdk/overview) -- [POS SDK Documentation](/payments/point-of-sale) From ec12ede5f9d1166d027bf76875d5fd4bdcf07b56 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 15 Oct 2025 14:44:06 +0000 Subject: [PATCH 3/9] docs: simplify wallet implementation guide - focus on SDK integration Co-Authored-By: rohit@reown.com --- payments/wallet-implementation.mdx | 499 +++++++---------------------- 1 file changed, 112 insertions(+), 387 deletions(-) diff --git a/payments/wallet-implementation.mdx b/payments/wallet-implementation.mdx index f249b69..aa6f7b9 100644 --- a/payments/wallet-implementation.mdx +++ b/payments/wallet-implementation.mdx @@ -5,489 +5,214 @@ metatags: sidebarTitle: "Wallet Implementation" --- -This guide explains how wallet developers can implement support for WalletConnect Pay to enable their users to make blockchain-based payments through WalletConnect-compatible merchants and applications. +This guide shows wallet developers how to implement WalletConnect Pay to process payment requests during WalletConnect session establishment. ## Overview -WalletConnect Pay enables wallets to receive and process payment requests as part of the session establishment flow. When a merchant initiates a connection with a payment request, the wallet can process the payment and return the transaction result during session approval. +WalletConnect Pay allows merchants to request payments during the session connection flow. The payment request is included in the session proposal, and wallets can process the payment and return the transaction result when approving the session. -### Key Capabilities +## Integration Flow -When you implement WalletConnect Pay, your wallet can: +1. Merchant calls `provider.connect({ walletPay, ... })` with payment details +2. Wallet receives session proposal containing payment request +3. Wallet shows payment details to user +4. User approves session (optionally with payment) +5. Wallet processes payment and returns result in session approval -- Receive payment requests during WalletConnect session proposals -- Display payment details to users for approval -- Process ERC20 token transfers automatically -- Return transaction hashes to merchants upon successful payment -- Support multiple blockchain networks +## Payment Request Schema -## Implementation Flow - -The WalletConnect Pay flow integrates payment processing into the session establishment: - -1. **Merchant Initiates Connection**: Merchant calls `provider.connect()` with a `walletPay` parameter -2. **Wallet Receives Proposal**: Wallet receives a session proposal containing the payment request -3. **User Reviews Payment**: Wallet displays both connection permissions and payment details -4. **User Approves**: User approves the session and payment together -5. **Payment Processed**: Wallet executes the payment transaction -6. **Session Established**: Session is approved with the payment result included - -## Payment Request Structure - -The `walletPay` object is included in the session proposal at `proposal.params.requests.walletPay` with the following structure: +The `walletPay` object is located at `proposal.params.requests.walletPay`: ```typescript { - version: string // Protocol version (e.g., "1.0.0") - orderId?: string // Optional order identifier + version: "1.0.0", + orderId?: string, acceptedPayments: [{ - asset: string // CAIP-19 asset identifier - amount: string // Hex-encoded amount - recipient: string // CAIP-10 account ID - }] - expiry?: number // Optional UNIX timestamp (seconds) + asset: string, // CAIP-19 format: "eip155:1/erc20:0x..." + amount: string, // Hex-encoded: "0x186a0" + recipient: string // CAIP-10 format: "eip155:1:0x..." + }], + expiry?: number // UNIX timestamp } ``` -### Example Payment Request +### Example -```typescript +```json { - version: "1.0.0", - acceptedPayments: [ - { - asset: "eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", - amount: "0x186a0", // 100000 (0.1 USDC with 6 decimals) - recipient: "eip155:8453:0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb" - } - ], - expiry: 1735689600 + "version": "1.0.0", + "acceptedPayments": [{ + "asset": "eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + "amount": "0x186a0", + "recipient": "eip155:8453:0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb" + }], + "expiry": 1735689600 } ``` -## Implementation Guide +## Implementation -### Step 1: Detect Payment Requests in Session Proposals +### 1. Detect Payment Requests -Check for the `walletPay` parameter in incoming session proposals: +Listen for session proposals and check for payment requests: ```typescript import { SignClientTypes } from '@walletconnect/types' -const onSessionProposal = ( - proposal: SignClientTypes.EventArguments['session_proposal'] -) => { - // Check if proposal includes a payment request +walletkit.on('session_proposal', async (proposal) => { const walletPay = proposal?.params?.requests?.walletPay if (walletPay) { - // Handle payment request - handlePaymentProposal(proposal, walletPay) + // Show payment approval UI + await handlePaymentProposal(proposal, walletPay) } else { - // Handle normal session proposal - handleRegularProposal(proposal) + // Regular session approval + await handleSessionProposal(proposal) } -} - -walletkit.on('session_proposal', onSessionProposal) +}) ``` -### Step 2: Display Payment Details to User +### 2. Display to User -Show the payment request details alongside the session approval UI: +Show the payment details from `walletPay.acceptedPayments[0]`: -```tsx -import { useState } from 'react' -import { EngineTypes } from '@walletconnect/types' +- Asset (parse CAIP-19 to get token address) +- Amount (convert hex to decimal with token decimals) +- Recipient address +- Network/chain -function SessionProposalModal({ proposal }) { - const walletPay = proposal?.params?.requests?.walletPay - const [processPayment, setProcessPayment] = useState(true) - - return ( -
- {/* Regular session approval UI */} -

Connection Request

-

App: {proposal.params.proposer.metadata.name}

- - {/* Payment request section */} - {walletPay && ( -
-

Payment Request

- - - -
- )} - - -
- ) -} +Allow user to approve the session with or without processing the payment. -function PaymentDetails({ payment }) { - const { asset, amount, recipient } = payment - - // Parse CAIP-19 asset identifier - const [chainPart, assetPart] = asset.split('/') - const [chainNamespace, chainId] = chainPart.split(':') - const [assetType, assetAddress] = assetPart.split(':') - - return ( -
-

Token: {assetAddress}

-

Amount: {formatAmount(amount)}

-

Recipient: {recipient.split(':')[2]}

-

Network: {chainNamespace}:{chainId}

-
- ) -} -``` - -### Step 3: Process Payment on Approval - -When the user approves the session, process the payment and include the result: +### 3. Process Payment & Approve Session ```typescript import { EngineTypes } from '@walletconnect/types' -import { parseChainId } from '@walletconnect/utils' - -async function processWalletPay( - walletPay: EngineTypes.WalletPayParams -) { - // Extract payment details - const payment = walletPay.acceptedPayments[0] - const chainId = parseChainId(payment.recipient) - - // Get the wallet for this chain - const wallet = await getWalletForChain( - `${chainId.namespace}:${chainId.reference}` - ) - - // Execute the payment - return await wallet.walletPay(walletPay) -} +import { buildApprovedNamespaces } from '@walletconnect/utils' -async function approveSession(proposal, walletPay, shouldProcessPayment) { - // Build approved namespaces +async function approveWithPayment(proposal, walletPay, shouldProcess) { const namespaces = buildApprovedNamespaces({ proposal: proposal.params, - supportedNamespaces: getSupportedNamespaces() + supportedNamespaces: YOUR_SUPPORTED_NAMESPACES }) - const proposalRequestsResponses = [] + const responses = [] - // Process payment if user approved it - if (walletPay && shouldProcessPayment) { - const paymentResult = await processWalletPay(walletPay) - proposalRequestsResponses.push(paymentResult) + // Process payment if user approved + if (walletPay && shouldProcess) { + const result = await executePayment(walletPay) + responses.push(result) } // Approve session with payment result await walletkit.approveSession({ id: proposal.id, namespaces, - proposalRequestsResponses + proposalRequestsResponses: responses }) } ``` -### Step 4: Implement Payment Execution +### 4. Execute Payment -Implement the actual token transfer logic in your wallet class: +Implement payment execution based on your wallet's architecture. The basic flow: ```typescript -import { ethers, providers, Wallet } from 'ethers' -import { EngineTypes } from '@walletconnect/types' -import { parseChainId } from '@walletconnect/utils' - -const ERC20_ABI = [ - 'function transfer(address to, uint256 amount) public returns (bool)' -] - -class EIP155Wallet { - wallet: Wallet +async function executePayment(walletPay: EngineTypes.WalletPayParams) { + const payment = walletPay.acceptedPayments[0] + + // Parse payment details + const [chainId, assetType, assetAddress] = parseAsset(payment.asset) + const recipientAddress = payment.recipient.split(':')[2] - async walletPay(walletPay: EngineTypes.WalletPayParams) { - const payment = walletPay.acceptedPayments[0] - - // Parse payment details - const [chainPart, assetPart] = payment.asset.split('/') - const assetAddress = assetPart.split(':')[1] - const recipientAddress = payment.recipient.split(':')[2] - const amount = payment.amount - - // Get chain RPC URL - const chainId = parseChainId(payment.recipient) - const rpcUrl = getRpcUrl(`${chainId.namespace}:${chainId.reference}`) - - // Connect wallet to network - const provider = new ethers.providers.JsonRpcProvider(rpcUrl) - const connectedWallet = this.wallet.connect(provider) - - // Create token contract instance - const tokenContract = new ethers.Contract( - assetAddress, - ERC20_ABI, - connectedWallet - ) - - // Execute transfer - const tx = await tokenContract.transfer(recipientAddress, amount, { - gasLimit: 100_000n // Adjust as needed - }) - - // Wait for confirmation - const receipt = await tx.wait() - - // Return payment result - return { - version: walletPay.version, - orderId: walletPay.orderId, - txid: receipt.transactionHash, - recipient: payment.recipient, - asset: payment.asset, - amount: payment.amount - } + // Execute transfer (implementation depends on your wallet) + const txHash = await transferToken( + assetAddress, + recipientAddress, + payment.amount, + chainId + ) + + // Return result + return { + version: walletPay.version, + orderId: walletPay.orderId, + txid: txHash, + recipient: payment.recipient, + asset: payment.asset, + amount: payment.amount } } ``` -## Payment Result Structure +## Payment Result Schema -The wallet must return a payment result object when processing is successful: +Return this structure in `proposalRequestsResponses`: ```typescript { - version: string // Same version from request - orderId?: string // Same orderId if provided - txid: string // Transaction hash - recipient: string // CAIP-10 recipient address - asset: string // CAIP-19 asset identifier - amount: string // Hex-encoded amount transferred + version: string, // Echo from request + orderId?: string, // Echo from request + txid: string, // Transaction hash + recipient: string, // CAIP-10 recipient + asset: string, // CAIP-19 asset + amount: string // Hex-encoded amount } ``` -This result is included in the `proposalRequestsResponses` array when approving the session, and the merchant receives it in `session.walletPayResult[0]`. +The merchant receives this in `session.walletPayResult[0]`. -## Error Handling +## Format Specifications -Handle payment errors gracefully and inform the user: +### CAIP-19 Asset Format -```typescript -async function processWalletPay(walletPay: EngineTypes.WalletPayParams) { - try { - // Validate expiry - if (walletPay.expiry) { - const now = Math.floor(Date.now() / 1000) - if (now > walletPay.expiry) { - throw new Error('Payment request has expired') - } - } - - // Process payment - const result = await wallet.walletPay(walletPay) - return result - - } catch (error) { - console.error('Payment processing error:', error) - - // Show error to user - showError(`Payment failed: ${error.message}`) - - // Re-throw to prevent session approval - throw error - } -} +``` +{chainNamespace}:{chainId}/{assetNamespace}:{assetReference} ``` -### Common Error Scenarios - -- **Expired Request**: Check `walletPay.expiry` before processing -- **Insufficient Balance**: Validate user has enough tokens before attempting transfer -- **Network Issues**: Handle RPC failures and connection timeouts -- **Gas Estimation Failures**: Provide fallback gas limits -- **User Rejection**: Allow users to approve session without processing payment - -## Asset Format Reference +Example: `eip155:1/erc20:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48` -### EVM Chains (eip155) +### CAIP-10 Account Format -**ERC20 Token:** ``` -eip155:1/erc20:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 +{chainNamespace}:{chainId}:{address} ``` -Components: -- `eip155:1` - Ethereum mainnet (CAIP-2) -- `erc20` - Asset namespace -- `0xA0b...` - Token contract address +Example: `eip155:8453:0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb` ### Amount Encoding -Amounts are hex-encoded values in the token's smallest unit: +Amounts are hex-encoded smallest units: ```typescript -// For USDC (6 decimals), 10 USDC = 10000000 -const amount = "0x" + (10 * 10**6).toString(16) // "0x989680" - -// For tokens with 18 decimals -const amount = "0x" + (1 * 10**18).toString(16) +// 10 USDC (6 decimals) = 10000000 +"0x" + (10 * 10**6).toString(16) // "0x989680" ``` -### Recipient Format - -Recipients use CAIP-10 account identifiers: - -``` -eip155:8453:0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb -``` - -Components: -- `eip155` - Chain namespace -- `8453` - Chain ID (Base) -- `0x742d...` - Account address - -## Best Practices - -### Security - -1. **Always validate payment requests** before displaying to users -2. **Check expiry timestamps** to prevent processing stale requests -3. **Verify sufficient balance** before attempting transfers -4. **Display clear payment details** including token symbol, amount, and recipient -5. **Use gas estimation** with safety margins to prevent transaction failures - -### User Experience - -1. **Show payment as optional** - Allow users to approve session without payment -2. **Display formatted amounts** - Convert hex amounts to human-readable values -3. **Show token symbols** - Resolve token addresses to symbols (e.g., "USDC") -4. **Indicate payment status** - Show loading state during transaction processing -5. **Provide transaction links** - Link to block explorer after successful payment +## Error Handling -### Performance +Handle common scenarios: -1. **Cache token metadata** - Store token symbols and decimals to avoid repeated lookups -2. **Estimate gas efficiently** - Use cached gas estimates when possible -3. **Handle timeouts gracefully** - Set reasonable timeout limits for RPC calls -4. **Queue transactions** - Prevent concurrent payment processing +- **Expired requests**: Check `walletPay.expiry` against current timestamp +- **Insufficient balance**: Validate before attempting transfer +- **User rejection**: Allow session approval without payment +- **Transaction failures**: Catch errors and optionally reject session ## Testing -Test your implementation with the reference dApp: - -1. Clone the example: `git clone https://github.com/reown-com/web-examples.git` -2. Checkout the branch: `git checkout chore/wallet-pay-dapp` -3. Navigate to the dApp: `cd advanced/dapps/walletconnect-pay-dapp` -4. Install and run: `npm install && npm run dev` -5. Connect your wallet and test payment flows +Test with the reference implementation: -## Complete Example - -Here's a complete minimal implementation: - -```typescript -import { walletkit } from '@/utils/WalletConnectUtil' -import { SignClientTypes, EngineTypes } from '@walletconnect/types' -import { buildApprovedNamespaces, parseChainId } from '@walletconnect/utils' -import { ethers } from 'ethers' - -// Listen for session proposals -walletkit.on('session_proposal', async (proposal) => { - const walletPay = proposal?.params?.requests?.walletPay - - // Show approval UI to user - const { approved, processPayment } = await showApprovalUI(proposal, walletPay) - - if (!approved) { - await walletkit.rejectSession({ - id: proposal.id, - reason: { code: 5000, message: 'User rejected' } - }) - return - } - - // Build namespaces - const namespaces = buildApprovedNamespaces({ - proposal: proposal.params, - supportedNamespaces: { - eip155: { - chains: ['eip155:1', 'eip155:8453'], - methods: ['personal_sign', 'eth_sendTransaction'], - events: ['accountsChanged', 'chainChanged'] - } - } - }) - - const responses = [] - - // Process payment if requested - if (walletPay && processPayment) { - try { - const result = await processPayment(walletPay) - responses.push(result) - } catch (error) { - console.error('Payment failed:', error) - // Optionally reject session on payment failure - } - } - - // Approve session - await walletkit.approveSession({ - id: proposal.id, - namespaces, - proposalRequestsResponses: responses - }) -}) - -async function processPayment(walletPay: EngineTypes.WalletPayParams) { - const payment = walletPay.acceptedPayments[0] - const [chainPart, assetPart] = payment.asset.split('/') - const tokenAddress = assetPart.split(':')[1] - const recipientAddress = payment.recipient.split(':')[2] - - const chainId = parseChainId(payment.recipient) - const provider = new ethers.providers.JsonRpcProvider( - getRpcUrl(`${chainId.namespace}:${chainId.reference}`) - ) - - const wallet = new ethers.Wallet(getPrivateKey(), provider) - const token = new ethers.Contract( - tokenAddress, - ['function transfer(address to, uint256 amount)'], - wallet - ) - - const tx = await token.transfer(recipientAddress, payment.amount, { - gasLimit: 100_000n - }) - const receipt = await tx.wait() - - return { - version: walletPay.version, - orderId: walletPay.orderId, - txid: receipt.transactionHash, - recipient: payment.recipient, - asset: payment.asset, - amount: payment.amount - } -} +```bash +git clone https://github.com/reown-com/web-examples.git +cd web-examples +git checkout chore/wallet-pay-dapp +cd advanced/dapps/walletconnect-pay-dapp +npm install && npm run dev ``` ## Additional Resources -- [WalletConnect Pay Example DApp](https://github.com/reown-com/web-examples/tree/chore/wallet-pay-dapp/advanced/dapps/walletconnect-pay-dapp) - [Reference Wallet Implementation](https://github.com/reown-com/web-examples/tree/chore/wallet-pay-dapp/advanced/wallets/react-wallet-v2) -- [CAIP-19: Asset Type Specification](https://chainagnostic.org/CAIPs/caip-19) -- [CAIP-10: Account ID Specification](https://chainagnostic.org/CAIPs/caip-10) +- [Reference DApp](https://github.com/reown-com/web-examples/tree/chore/wallet-pay-dapp/advanced/dapps/walletconnect-pay-dapp) +- [CAIP-19 Specification](https://chainagnostic.org/CAIPs/caip-19) +- [CAIP-10 Specification](https://chainagnostic.org/CAIPs/caip-10) - [WalletConnect Wallet SDK](/wallet-sdk/overview) From 77ede6f646aea6d19722000bb59d495367a94026 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 15 Oct 2025 14:51:18 +0000 Subject: [PATCH 4/9] docs: emphasize chain-agnostic support for any asset on any blockchain Co-Authored-By: rohit@reown.com --- payments/wallet-implementation.mdx | 51 +++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/payments/wallet-implementation.mdx b/payments/wallet-implementation.mdx index aa6f7b9..d2ed70e 100644 --- a/payments/wallet-implementation.mdx +++ b/payments/wallet-implementation.mdx @@ -11,6 +11,8 @@ This guide shows wallet developers how to implement WalletConnect Pay to process WalletConnect Pay allows merchants to request payments during the session connection flow. The payment request is included in the session proposal, and wallets can process the payment and return the transaction result when approving the session. +**WalletConnect Pay is chain-agnostic** and supports any asset on any blockchain network that can be identified using the CAIP-19 standard. This includes EVM chains, Solana, Bitcoin, Cosmos, and any other blockchain ecosystem. + ## Integration Flow 1. Merchant calls `provider.connect({ walletPay, ... })` with payment details @@ -36,8 +38,9 @@ The `walletPay` object is located at `proposal.params.requests.walletPay`: } ``` -### Example +### Examples +**EVM (Base network, USDC token):** ```json { "version": "1.0.0", @@ -50,6 +53,18 @@ The `walletPay` object is located at `proposal.params.requests.walletPay`: } ``` +**Solana (SOL token):** +```json +{ + "version": "1.0.0", + "acceptedPayments": [{ + "asset": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501", + "amount": "0x3b9aca00", + "recipient": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:7S3P4HxJpyyigGzodYwHtCxZyUQe9JiBMHyRWXArAaKv" + }] +} +``` + ## Implementation ### 1. Detect Payment Requests @@ -163,29 +178,55 @@ The merchant receives this in `session.walletPayResult[0]`. ## Format Specifications +WalletConnect Pay uses CAIP standards to ensure chain-agnostic compatibility across all blockchain networks. + ### CAIP-19 Asset Format +All assets use CAIP-19 identifiers: + ``` {chainNamespace}:{chainId}/{assetNamespace}:{assetReference} ``` -Example: `eip155:1/erc20:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48` +**Examples across different chains:** + +| Chain | Example Asset | +|-------|--------------| +| Ethereum (USDC) | `eip155:1/erc20:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48` | +| Base (USDC) | `eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` | +| Solana (SOL) | `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501` | +| Solana (SPL Token) | `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/spl:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v` | +| Bitcoin | `bip122:000000000019d6689c085ae165831e93/slip44:0` | +| Cosmos (ATOM) | `cosmos:cosmoshub-4/slip44:118` | + +Any blockchain that can be represented with a CAIP-2 chain ID can support WalletConnect Pay. ### CAIP-10 Account Format +All recipients use CAIP-10 account identifiers: + ``` {chainNamespace}:{chainId}:{address} ``` -Example: `eip155:8453:0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb` +**Examples:** +- Ethereum: `eip155:1:0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb` +- Solana: `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:7S3P4HxJpyyigGzodYwHtCxZyUQe9JiBMHyRWXArAaKv` +- Cosmos: `cosmos:cosmoshub-4:cosmos1t2uflqwqe0fsj0shcfkrvpukewcw40yjj6hdc0` ### Amount Encoding -Amounts are hex-encoded smallest units: +Amounts are hex-encoded in the asset's smallest unit (varies by chain): ```typescript -// 10 USDC (6 decimals) = 10000000 +// EVM tokens (e.g., 10 USDC with 6 decimals) "0x" + (10 * 10**6).toString(16) // "0x989680" + +// Solana (1 SOL with 9 decimals) +"0x" + (1 * 10**9).toString(16) // "0x3b9aca00" + +// Bitcoin (0.001 BTC with 8 decimals) +"0x" + (0.001 * 10**8).toString(16) // "0x186a0" ``` ## Error Handling From dc5afdb58e35620236f2400a5cada5d84313bbb5 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 15 Oct 2025 14:56:26 +0000 Subject: [PATCH 5/9] docs: update to only show chains with verified CAIP-19 support (EVM + Solana) Co-Authored-By: rohit@reown.com --- payments/wallet-implementation.mdx | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/payments/wallet-implementation.mdx b/payments/wallet-implementation.mdx index d2ed70e..d755ff0 100644 --- a/payments/wallet-implementation.mdx +++ b/payments/wallet-implementation.mdx @@ -53,13 +53,13 @@ The `walletPay` object is located at `proposal.params.requests.walletPay`: } ``` -**Solana (SOL token):** +**Solana (USDC SPL token):** ```json { "version": "1.0.0", "acceptedPayments": [{ - "asset": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501", - "amount": "0x3b9aca00", + "asset": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "amount": "0xf4240", "recipient": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:7S3P4HxJpyyigGzodYwHtCxZyUQe9JiBMHyRWXArAaKv" }] } @@ -194,12 +194,12 @@ All assets use CAIP-19 identifiers: |-------|--------------| | Ethereum (USDC) | `eip155:1/erc20:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48` | | Base (USDC) | `eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` | -| Solana (SOL) | `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501` | -| Solana (SPL Token) | `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/spl:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v` | -| Bitcoin | `bip122:000000000019d6689c085ae165831e93/slip44:0` | -| Cosmos (ATOM) | `cosmos:cosmoshub-4/slip44:118` | +| Optimism (USDC) | `eip155:10/erc20:0x7F5c764cBc14f9669B88837ca1490cCa17c31607` | +| Polygon (USDC) | `eip155:137/erc20:0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174` | +| Arbitrum (USDC) | `eip155:42161/erc20:0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8` | +| Solana (USDC) | `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v` | -Any blockchain that can be represented with a CAIP-2 chain ID can support WalletConnect Pay. +WalletConnect Pay supports any asset on chains with CAIP-19 specifications, including all EVM chains and Solana. ### CAIP-10 Account Format @@ -211,8 +211,8 @@ All recipients use CAIP-10 account identifiers: **Examples:** - Ethereum: `eip155:1:0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb` +- Base: `eip155:8453:0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb` - Solana: `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:7S3P4HxJpyyigGzodYwHtCxZyUQe9JiBMHyRWXArAaKv` -- Cosmos: `cosmos:cosmoshub-4:cosmos1t2uflqwqe0fsj0shcfkrvpukewcw40yjj6hdc0` ### Amount Encoding @@ -222,11 +222,8 @@ Amounts are hex-encoded in the asset's smallest unit (varies by chain): // EVM tokens (e.g., 10 USDC with 6 decimals) "0x" + (10 * 10**6).toString(16) // "0x989680" -// Solana (1 SOL with 9 decimals) -"0x" + (1 * 10**9).toString(16) // "0x3b9aca00" - -// Bitcoin (0.001 BTC with 8 decimals) -"0x" + (0.001 * 10**8).toString(16) // "0x186a0" +// Solana tokens (e.g., 1 USDC with 6 decimals) +"0x" + (1 * 10**6).toString(16) // "0xf4240" ``` ## Error Handling From f59542ff4787a061ff49fb3636e919cb9b45537d Mon Sep 17 00:00:00 2001 From: Rohit Ramesh Date: Wed, 15 Oct 2025 17:40:13 +0200 Subject: [PATCH 6/9] Addressing some final feedback --- payments/wallet-implementation.mdx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/payments/wallet-implementation.mdx b/payments/wallet-implementation.mdx index d755ff0..9076032 100644 --- a/payments/wallet-implementation.mdx +++ b/payments/wallet-implementation.mdx @@ -87,16 +87,20 @@ walletkit.on('session_proposal', async (proposal) => { }) ``` -### 2. Display to User + + Wallets must integrate WalletConnect Wallet SDK to be compatible with WalletConnect and WalletConnect Pay. If your wallet has not yet integrated the WalletConnect Wallet SDK, please refer to the [WalletConnect Wallet SDK](/wallet-sdk/overview) documentation for instructions on how to integrate it. + -Show the payment details from `walletPay.acceptedPayments[0]`: +### 2. Display Payment Options to the User -- Asset (parse CAIP-19 to get token address) -- Amount (convert hex to decimal with token decimals) +Parse the `acceptedPayments` array and match available options against the user's token balances. Display payment details for tokens the user can fulfill: + +- Token symbol and network (parsed from CAIP-19 asset identifier) +- Amount (converted from hex to human-readable format with correct decimals) - Recipient address -- Network/chain +- Estimated network fees -Allow user to approve the session with or without processing the payment. +If multiple payment options are available, prioritize by user's balance and preferences. Allow users to switch between options or approve the session without processing payment. ### 3. Process Payment & Approve Session From e3f46d7c7fbfbab80f7931bd52a4a4ac37d17181 Mon Sep 17 00:00:00 2001 From: Rohit Ramesh Date: Wed, 15 Oct 2025 17:41:42 +0200 Subject: [PATCH 7/9] Adding a warning --- payments/wallet-implementation.mdx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/payments/wallet-implementation.mdx b/payments/wallet-implementation.mdx index 9076032..0b83037 100644 --- a/payments/wallet-implementation.mdx +++ b/payments/wallet-implementation.mdx @@ -7,6 +7,10 @@ sidebarTitle: "Wallet Implementation" This guide shows wallet developers how to implement WalletConnect Pay to process payment requests during WalletConnect session establishment. + + This flow is not currently live and will be released soon. More information will be available shortly. + + ## Overview WalletConnect Pay allows merchants to request payments during the session connection flow. The payment request is included in the session proposal, and wallets can process the payment and return the transaction result when approving the session. From 84f1077b6d8e6eea2243f185dd1ab0e5892381e7 Mon Sep 17 00:00:00 2001 From: Rohit Ramesh Date: Wed, 15 Oct 2025 17:43:36 +0200 Subject: [PATCH 8/9] Updating the sidebar --- docs.json | 6 +++++- payments/wallet-implementation.mdx | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs.json b/docs.json index 5ab5ae2..abe7907 100644 --- a/docs.json +++ b/docs.json @@ -269,7 +269,11 @@ "groups": [ { "group": "WalletConnect Payments Standard", - "pages": ["payments/overview", "payments/wallet-implementation"] + "pages": ["payments/overview"] + }, + { + "group": "WalletConnect Pay", + "pages": ["payments/wallet-implementation"] }, { "group": "Point of Sale (POS) SDK", diff --git a/payments/wallet-implementation.mdx b/payments/wallet-implementation.mdx index 0b83037..2189df7 100644 --- a/payments/wallet-implementation.mdx +++ b/payments/wallet-implementation.mdx @@ -1,5 +1,5 @@ --- -title: "Wallet Pay Implementation for Wallets" +title: "WalletConnect Pay Implementation for Wallets" metatags: description: "Learn how to implement WalletConnect Pay in your wallet to process payment requests during session establishment." sidebarTitle: "Wallet Implementation" From 334f5fa4e7a7d2bcc99b12ff5b019272f267c3b5 Mon Sep 17 00:00:00 2001 From: Rohit Ramesh Date: Wed, 15 Oct 2025 17:45:54 +0200 Subject: [PATCH 9/9] Adding a CTA section to wallet pay overview --- payments/overview.mdx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/payments/overview.mdx b/payments/overview.mdx index 87c066e..8307808 100644 --- a/payments/overview.mdx +++ b/payments/overview.mdx @@ -52,3 +52,14 @@ This means that any wallet or merchant integrating `wallet_pay` is automatically ## Specification Reference The `wallet_pay` standard is defined in [CAIP-358: Universal Payment Request Method](https://eip.tools/caip/358). It specifies how payment requests are structured and exchanged between merchants and wallets. Lifecycle management and status tracking (e.g., pending, confirmed, failed) are implementation details defined by [WalletConnect’s POS SDK](/payments/point-of-sale). + +## Get Started + + + + Get started with WalletConnect Pay. + + + Get started with WalletConnect POS SDK. + + \ No newline at end of file