> ## Documentation Index
> Fetch the complete documentation index at: https://walletconnect-pay-docs-wcagent-webview-integration-docs.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# AI Integration Prompt - Swift

> Comprehensive AI integration prompt for iOS/Swift wallet developers integrating WalletConnect Pay via WalletKit.

# WalletConnect Pay Integration via WalletKit (Swift)

Integration guide for iOS/Swift wallets that already have WalletKit integrated.

***

## Agent Guidance (For AI Assistants)

### Adaptation Principles

1. **WalletKit Required** - This guide assumes WalletKit is already configured. Pay is accessed via `WalletKit.instance.Pay.*`.

2. **Configuration Order Matters**:
   ```swift theme={null}
   Networking.configure(projectId:)  // FIRST
   WalletKit.configure(...)          // SECOND - Pay auto-configured
   ```

3. **CAIP-10 Format Required** - All accounts must be in `eip155:{chainId}:{address}` format.

4. **Signature Order Critical** - Signatures array must match actions array order exactly.

5. **Travel Rule Conditional** - Only collect user data when `selectedOption.collectData` is non-nil. IC is per-option, not global.

### Code Adaptation Guidelines

When adapting for a specific wallet:

1. **Replace Signing Logic** - Use the wallet's native EIP-712 signing implementation.
2. **Adapt UI Flow** - Match the wallet's existing design patterns and navigation.
3. **Account Discovery** - Use the wallet's account management to build CAIP-10 accounts.
4. **Error Handling** - Add wallet-specific error handling and user feedback.

### Common Patterns

```swift theme={null}
// Pattern: Check for payment link in QR scanner
func handleScannedQR(_ content: String) {
    if WalletKit.isPaymentLink(content) {
        startPaymentFlow(paymentLink: content)
    } else if content.hasPrefix("wc:") {
        startPairingFlow(uri: content)
    }
}

// Pattern: Multi-chain accounts
func getCAIP10Accounts(address: String) -> [String] {
    return [
        "eip155:1:\(address)",      // Ethereum
        "eip155:137:\(address)",    // Polygon
        "eip155:8453:\(address)",   // Base
        "eip155:42161:\(address)"   // Arbitrum
    ]
}
```

***

## Prerequisites

* iOS 13.0+
* Swift 5.7+
* WalletKit already integrated
* WalletConnect Cloud `PROJECT_ID` ([Get one](https://cloud.walletconnect.com))

```swift theme={null}
// Package.swift
dependencies: [
    .package(url: "https://github.com/reown-com/reown-swift", from: "1.0.0")
]
```

***

## Architecture Overview

```
┌─────────────────────────────────────────────────────────────────────────────┐
│                              PAYMENT FLOW                                    │
└─────────────────────────────────────────────────────────────────────────────┘

  ┌──────────┐                                                    ┌──────────┐
  │  User    │                                                    │ Merchant │
  │  Wallet  │                                                    │   POS    │
  └────┬─────┘                                                    └────┬─────┘
       │                                                               │
       │  1. Scan QR / Open Deep Link                                  │
       │◀──────────────────────────────────────────────────────────────┤
       │                                                               │
       │  2. getPaymentOptions(paymentLink, accounts)                  │
       │──────────────────────▶┌─────────────┐                         │
       │                       │  Pay API    │                         │
       │◀──────────────────────└─────────────┘                         │
       │  PaymentOptionsResponse                                       │
       │                                                               │
       │  3. User selects payment option                               │
       │                                                               │
       │  4. Collect user data via WebView (if required)               │
       │                                                               │
       │  5. getRequiredPaymentActions(paymentId, optionId)            │
       │──────────────────────▶┌─────────────┐                         │
       │◀──────────────────────│  Pay API    │                         │
       │  Actions[] (walletRpc — method varies)                             │
       │                       └─────────────┘                         │
       │                                                               │
       │  6. Sign typed data (wallet's EIP-712 signer)                 │
       │                                                               │
       │  7. confirmPayment(signatures)                                │
       │──────────────────────▶┌─────────────┐     ┌────────────┐      │
       │                       │  Pay API    │────▶│ Blockchain │      │
       │◀──────────────────────└─────────────┘     └────────────┘      │
       │  ConfirmPaymentResultResponse (status: succeeded)             │
       │                                                               │
       ▼                                                               ▼
  ┌──────────┐                                                    ┌──────────┐
  │ Payment  │                                                    │ Payment  │
  │ Complete │                                                    │ Received │
  └──────────┘                                                    └──────────┘
```

### State Machine

```
[Options] ──▶ [WebView Data Collection]* ──▶ [Confirmation] ──▶ [Confirming] ──▶ [Success]
                                                     │
                                                     ▼
                                                  [Error]

* WebView step - skip if selectedOption.collectData is nil
```

***

## Step 1: Configure WalletKit

**IMPORTANT**: Pay is auto-configured when you configure WalletKit. No separate Pay configuration needed.

```swift theme={null}
import ReownWalletKit

func configureWalletKit() {
    // 1. Configure Networking with your project ID
    Networking.configure(
        groupIdentifier: "group.com.yourcompany.wallet",
        projectId: "YOUR_PROJECT_ID",
        socketFactory: DefaultSocketFactory()
    )

    // 2. Configure WalletKit - Pay is automatically configured
    let metadata = AppMetadata(
        name: "Your Wallet",
        description: "A crypto wallet",
        url: "https://yourwallet.com",
        icons: ["https://yourwallet.com/icon.png"],
        redirect: try! AppMetadata.Redirect(
            native: "yourwallet://",
            universal: "https://yourwallet.com/wc",
            linkMode: true
        )
    )

    WalletKit.configure(
        metadata: metadata,
        crypto: DefaultCryptoProvider(),
        environment: .production,
        payLogging: true  // Enable for debugging
    )
}
```

***

## Step 2: Handle Deep Links

### Info.plist Configuration

```xml theme={null}
<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>yourwallet</string>
        </array>
    </dict>
</array>
```

### SceneDelegate Implementation

```swift theme={null}
import UIKit
import ReownWalletKit

final class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?

    // MARK: - Cold Start

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession,
               options connectionOptions: UIScene.ConnectionOptions) {
        // ... window setup ...

        // Check for payment deep link on cold start
        if let urlContext = connectionOptions.urlContexts.first {
            if let paymentLink = extractPaymentLink(from: urlContext.url) {
                // Delay to ensure UI is ready
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
                    self?.handlePaymentLink(paymentLink)
                }
            }
        }
    }

    // MARK: - Warm Start

    func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
        guard let url = URLContexts.first?.url else { return }

        if let paymentLink = extractPaymentLink(from: url) {
            handlePaymentLink(paymentLink)

            // For POS scan format, return early - no pairing needed
            if url.host == "walletconnectpay" {
                return
            }
        }

        // Continue with regular WalletConnect pairing if needed...
    }

    // MARK: - Payment Link Extraction

    /// Supports two formats:
    /// 1. WC URI format: yourwallet://?uri=wc:abc...&pay=pay_xyz
    /// 2. POS scan format: yourwallet://walletconnectpay?paymentId=pay_xyz
    private func extractPaymentLink(from url: URL) -> String? {
        guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
              let queryItems = components.queryItems else {
            return nil
        }

        // Format 1: WC URI with embedded pay param
        if let uriValue = queryItems.first(where: { $0.name == "uri" })?.value,
           WalletKit.isPaymentLink(uriValue) {
            return uriValue
        }

        // Format 2: POS scan format
        if url.host == "walletconnectpay",
           let paymentId = queryItems.first(where: { $0.name == "paymentId" })?.value {
            return "yourwallet://walletconnectpay?paymentId=\(paymentId)"
        }

        return nil
    }

    private func handlePaymentLink(_ paymentLink: String) {
        // Get wallet address and build CAIP-10 accounts
        let address = getWalletAddress()
        let accounts = [
            "eip155:1:\(address)",
            "eip155:137:\(address)",
            "eip155:8453:\(address)"
        ]

        // Present your payment UI
        presentPaymentFlow(paymentLink: paymentLink, accounts: accounts)
    }
}
```

***

## Step 3: Detect Payment Links

Use `WalletKit.isPaymentLink()` to detect payment links from QR codes or deep links.

```swift theme={null}
// Static method - can be called BEFORE WalletKit.configure()
if WalletKit.isPaymentLink(scannedString) {
    handlePaymentFlow(paymentLink: scannedString)
}

// Instance method - after configure()
if WalletKit.instance.Pay.isPaymentLink(scannedString) {
    handlePaymentFlow(paymentLink: scannedString)
}
```

**Detection Patterns:**

* `pay.` hosts (e.g., `pay.walletconnect.com`)
* `pay=` parameter in WalletConnect URIs
* `pay_` prefix in bare payment IDs

***

## Step 4: Get Payment Options

```swift theme={null}
func loadPaymentOptions(paymentLink: String, walletAddress: String) async throws -> PaymentOptionsResponse {
    // Build CAIP-10 accounts for supported chains
    let accounts = [
        "eip155:1:\(walletAddress)",      // Ethereum
        "eip155:137:\(walletAddress)",    // Polygon
        "eip155:8453:\(walletAddress)",   // Base
        "eip155:42161:\(walletAddress)"   // Arbitrum
    ]

    let response = try await WalletKit.instance.Pay.getPaymentOptions(
        paymentLink: paymentLink,
        accounts: accounts,
        includePaymentInfo: true
    )

    // Display merchant info
    if let info = response.info {
        print("Merchant: \(info.merchant.name)")
        print("Amount: \(info.amount.display.assetSymbol) \(info.amount.value)")
    }

    // Check which options require data collection
    for option in response.options {
        if option.collectData != nil {
            print("Option \(option.id) requires info capture")
        }
    }

    return response
}
```

**CAIP-10 Format**: `eip155:{chainId}:{address}`

***

## Step 5: Complete Payment Flow

```swift theme={null}
func confirmPayment(
    paymentId: String,
    selectedOption: PaymentOption,
    response: PaymentOptionsResponse
) async throws {

    // 1. Collect data via WebView if required for selected option
    if let collectData = selectedOption.collectData, let url = collectData.url {
        // Show WebView and wait for IC_COMPLETE message.
        // The WebView submits data directly to the backend.
        try await showDataCollectionWebView(url: url)
    }

    // 2. Get required signing actions
    let actions = try await WalletKit.instance.Pay.getRequiredPaymentActions(
        paymentId: paymentId,
        optionId: selectedOption.id
    )

    // 3. Sign each action using YOUR wallet's signer
    var signatures: [String] = []
    for action in actions {
        let rpc = action.walletRpc

        // rpc.chainId  - e.g., "eip155:8453"
        // rpc.method   - e.g., "eth_signTypedData_v4", "eth_sendTransaction"
        // rpc.params   - JSON-encoded parameters

        guard let paramsData = rpc.params.data(using: .utf8),
              let params = try JSONSerialization.jsonObject(with: paramsData) as? [Any] else {
            throw PaymentError.invalidParams
        }

        switch rpc.method {
        case "eth_signTypedData_v4":
            guard params.count >= 2,
                  let typedDataJson = params[1] as? String else {
                throw PaymentError.invalidParams
            }
            let signature = try await yourWallet.signTypedData(typedDataJson)
            signatures.append(signature)

        case "eth_sendTransaction":
            guard let txParams = params[0] as? [String: Any] else {
                throw PaymentError.invalidParams
            }
            let txHash = try await yourWallet.sendTransaction(txParams)
            signatures.append(txHash)

        case "personal_sign":
            guard params.count >= 2,
                  let message = params[0] as? String else {
                throw PaymentError.invalidParams
            }
            let signature = try await yourWallet.personalSign(message)
            signatures.append(signature)

        default:
            throw PaymentError.unsupportedMethod(rpc.method)
        }
    }

    // 4. Confirm payment
    let result = try await WalletKit.instance.Pay.confirmPayment(
        paymentId: paymentId,
        optionId: selectedOption.id,
        signatures: signatures,
        maxPollMs: 60000
    )

    switch result.status {
    case .succeeded:
        print("Payment successful!")
    case .processing:
        print("Payment processing...")
    case .failed:
        print("Payment failed")
    case .expired:
        print("Payment expired")
    case .requiresAction:
        print("Additional action required")
    }
}
```

**CRITICAL**: Signatures must be in the same order as the actions array.

***

## WebView Data Collection

When `selectedOption.collectData.url` is present, display the URL in a `WKWebView`. The hosted form handles rendering, validation, and T\&C acceptance.

The form URL accepts optional query parameters — append them to `selectedOption.collectData.url` before loading, preserving existing query parameters:

* `prefill` — base64url-encoded JSON of known user fields. Keys must match the `required` fields from `collectData.schema` (e.g. `fullName`, `dob`, `pobAddress`).
* `theme` — `light` or `dark`, to match your wallet's color mode.
* `themeVariables` — a base64url string exported from the [WalletConnect Pay Dashboard](https://dashboard.walletconnect.com) that overrides design tokens (font, font size, some colors, button/input border radius). Append it verbatim.

```swift theme={null}
func buildFormUrl(
    baseUrl: String,
    prefillData: [String: String] = [:],
    theme: String? = nil,            // "light" or "dark"
    themeVariables: String? = nil    // base64url string exported from the Pay Dashboard
) -> String {
    guard var components = URLComponents(string: baseUrl) else { return baseUrl }
    var queryItems = components.queryItems ?? []
    if !prefillData.isEmpty,
       let jsonData = try? JSONSerialization.data(withJSONObject: prefillData) {
        // base64url: URL-safe, no padding
        let prefill = jsonData.base64EncodedString()
            .replacingOccurrences(of: "+", with: "-")
            .replacingOccurrences(of: "/", with: "_")
            .replacingOccurrences(of: "=", with: "")
        queryItems.append(URLQueryItem(name: "prefill", value: prefill))
    }
    if let theme = theme { queryItems.append(URLQueryItem(name: "theme", value: theme)) }
    if let themeVariables = themeVariables {
        queryItems.append(URLQueryItem(name: "themeVariables", value: themeVariables))
    }
    components.queryItems = queryItems
    return components.string ?? baseUrl
}
```

```swift theme={null}
import WebKit
import SwiftUI

struct PayDataCollectionWebView: UIViewRepresentable {
    let url: URL
    let onComplete: () -> Void
    let onError: (String) -> Void

    func makeCoordinator() -> Coordinator {
        Coordinator(onComplete: onComplete, onError: onError)
    }

    func makeUIView(context: Context) -> WKWebView {
        let config = WKWebViewConfiguration()
        config.userContentController.add(
            context.coordinator,
            name: "payDataCollectionComplete"
        )

        let webView = WKWebView(frame: .zero, configuration: config)
        webView.navigationDelegate = context.coordinator
        webView.load(URLRequest(url: url))
        return webView
    }

    func updateUIView(_ uiView: WKWebView, context: Context) {}

    class Coordinator: NSObject, WKScriptMessageHandler, WKNavigationDelegate {
        let onComplete: () -> Void
        let onError: (String) -> Void

        init(onComplete: @escaping () -> Void, onError: @escaping (String) -> Void) {
            self.onComplete = onComplete
            self.onError = onError
        }

        func userContentController(
            _ userContentController: WKUserContentController,
            didReceive message: WKScriptMessage
        ) {
            guard let body = message.body as? String,
                  let data = body.data(using: .utf8),
                  let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
                  let type = json["type"] as? String else { return }

            DispatchQueue.main.async {
                switch type {
                case "IC_COMPLETE":
                    self.onComplete()
                case "IC_ERROR":
                    let error = json["error"] as? String ?? "Unknown error"
                    self.onError(error)
                default:
                    break
                }
            }
        }

        func webView(
            _ webView: WKWebView,
            decidePolicyFor navigationAction: WKNavigationAction,
            decisionHandler: @escaping (WKNavigationActionPolicy) -> Void
        ) {
            guard let url = navigationAction.request.url else {
                decisionHandler(.allow)
                return
            }
            if let host = url.host, !host.contains("pay.walletconnect.com") {
                UIApplication.shared.open(url)
                decisionHandler(.cancel)
                return
            }
            decisionHandler(.allow)
        }
    }
}
```

> **Important:** When using the WebView approach, do **not** pass `collectedData` to `confirmPayment()`. The WebView submits data directly to the backend.

***

## Core API Reference

### Methods

```swift theme={null}
// Check if string is a payment link (static - works before configure)
WalletKit.isPaymentLink(_ string: String) -> Bool

// Get available payment options
WalletKit.instance.Pay.getPaymentOptions(
    paymentLink: String,
    accounts: [String],           // CAIP-10 format
    includePaymentInfo: Bool = true
) async throws -> PaymentOptionsResponse

// Get signing actions for selected option
WalletKit.instance.Pay.getRequiredPaymentActions(
    paymentId: String,
    optionId: String
) async throws -> [Action]

// Confirm payment with signatures
WalletKit.instance.Pay.confirmPayment(
    paymentId: String,
    optionId: String,
    signatures: [String],
    maxPollMs: Int64? = nil
) async throws -> ConfirmPaymentResultResponse
```

### Types

```swift theme={null}
struct PaymentOptionsResponse {
    let paymentId: String
    let info: PaymentInfo?
    let options: [PaymentOption]
    let collectData: CollectDataAction?    // nil if no travel rule
    let resultInfo: PaymentResultInfo?     // present when payment already completed
}

struct PaymentResultInfo {
    let txId: String
    let optionAmount: PayAmount
}

struct PaymentInfo {
    let status: PaymentStatus
    let amount: PayAmount
    let expiresAt: Int64
    let merchant: MerchantInfo
}

struct PaymentOption {
    let id: String
    let amount: PayAmount
    let etaS: Int64
    let collectData: CollectDataAction?  // Per-option data collection (nil if not required)
}

struct PayAmount {
    let unit: String
    let value: String
    let display: AmountDisplay
}

struct AmountDisplay {
    let assetSymbol: String
    let assetName: String
    let decimals: Int64
    let iconUrl: String?
    let networkName: String?
}

struct Action {
    let walletRpc: WalletRpcAction
}

struct WalletRpcAction {
    let chainId: String    // "eip155:8453"
    let method: String     // e.g., "eth_signTypedData_v4", "eth_sendTransaction"
    let params: String     // JSON: ["address", "typedDataJson"]
}

struct CollectDataAction {
    let url: String                    // WebView URL for data collection
    let schema: String?                // JSON schema describing required fields
}

struct ConfirmPaymentResultResponse {
    let status: PaymentStatus
    let isFinal: Bool
}

enum PaymentStatus {
    case requiresAction
    case processing
    case succeeded
    case failed
    case expired
}
```

***

## Troubleshooting

### Configuration Issues

| Symptom                                               | Cause                    | Solution                                                                 |
| ----------------------------------------------------- | ------------------------ | ------------------------------------------------------------------------ |
| "You must call configure() before accessing instance" | WalletKit not configured | Call `Networking.configure()` then `WalletKit.configure()` at app launch |
| Pay methods not available                             | Wrong import             | Use `import ReownWalletKit`                                              |

### Deep Link Issues

| Symptom                    | Cause                 | Solution                               |
| -------------------------- | --------------------- | -------------------------------------- |
| App not opening from links | URL scheme missing    | Add `CFBundleURLSchemes` to Info.plist |
| Payment link not detected  | Format not recognized | Use `WalletKit.isPaymentLink()`        |
| Cold start link ignored    | UI not ready          | Add delay before handling              |

### Payment Failures

| Error              | Meaning            | Solution                              |
| ------------------ | ------------------ | ------------------------------------- |
| `paymentExpired`   | Link expired       | Merchant generates new link           |
| `routeExpired`     | Route expired      | Retry `getPaymentOptions`             |
| `invalidSignature` | Signature mismatch | Check signature order matches actions |
| `optionNotFound`   | Stale option       | Refresh options and reselect          |

### Signing Issues

| Symptom                 | Cause             | Solution                                                                            |
| ----------------------- | ----------------- | ----------------------------------------------------------------------------------- |
| Invalid signature error | Wrong method      | Check `action.walletRpc.method` and dispatch to the correct handler for each method |
| Params parsing fails    | Unexpected format | Parse as `["address", "typedDataJson"]`                                             |

***

## File Checklist

| File                     | Purpose                  |
| ------------------------ | ------------------------ |
| `Info.plist`             | URL scheme configuration |
| `SceneDelegate.swift`    | Deep link handling       |
| `PaymentPresenter.swift` | Payment flow logic       |
| `PaymentView.swift`      | Payment UI               |

***

## Common Pitfalls

1. **Forgetting CAIP-10 format** - Accounts must be `eip155:{chainId}:{address}`, not just addresses.

2. **Wrong signature order** - Signatures array must match actions array order exactly.

3. **Skipping WebView data collection** - Always check `selectedOption.collectData?.url` after the user selects an option and show the WebView before calling `getRequiredPaymentActions`.

4. **Not handling cold start** - Deep links on app launch need delayed handling.

5. **Not dispatching on RPC method** — Always check `action.walletRpc.method` and use the appropriate handler. For example, `eth_signTypedData_v4` requires EIP-712 signing, while `eth_sendTransaction` requires sending a transaction on-chain.

6. **Missing chains** - Provide accounts for all chains you support to maximize payment options.

***

## Testing

1. **Enable Logging**:
   ```swift theme={null}
   WalletKit.configure(..., payLogging: true)
   ```

2. **Test Both Deep Link Formats**:
   * WC URI: `yourwallet://?uri=wc:abc...&pay=pay_xyz`
   * POS: `yourwallet://walletconnectpay?paymentId=pay_xyz`

3. **Test Cold vs Warm Start**:
   * Cold: Kill app, open deep link
   * Warm: App in background, open deep link

***

## Support

* [GitHub Issues](https://github.com/reown-com/reown-swift/issues)
* [WalletConnect Docs](https://docs.walletconnect.com)
