> ## Documentation Index
> Fetch the complete documentation index at: https://docs.privy.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Pay with WalletConnect Pay

Privy embedded wallets give every user a wallet at signup, with no extensions or seed phrases required. [WalletConnect Pay](https://pay.walletconnect.com) lets merchants generate payment links that any wallet can fulfill. This recipe shows how to combine them so users can pay for anything in-app, with a single tap.

Using Privy and WalletConnect Pay together, your app can:

* Let users paste a WalletConnect Pay link and complete the payment with their Privy wallet
* Support multiple chains (Ethereum, Base, Polygon, Arbitrum, Optimism, and more)
* Handle ERC-20 token transfers (like USDC) alongside native ETH payments

<Info>
  WalletConnect Pay offers interchange revenue for wallets and `$WCT` cashback for users. See the
  [WalletConnect Pay overview](https://docs.walletconnect.com/payments/wallets/overview) for details
  on earning revenue through your integration.
</Info>

***

# Setup

## 1. Install dependencies

```bash theme={"system"}
npm install @privy-io/react-auth @reown/walletkit @walletconnect/core
```

## 2. Get project IDs

Your app needs two project IDs:

* **Privy App ID** — from the [Privy Dashboard](https://dashboard.privy.io)
* **WalletConnect Project ID** — from [WalletConnect Cloud](https://dashboard.walletconnect.com) (select your project and click **Get Started** to obtain your WCP ID).

Add them to your environment:

```bash theme={"system"}
VITE_PRIVY_APP_ID=your-privy-app-id
VITE_WALLETCONNECT_PROJECT_ID=your-walletconnect-project-id
```

## 3. Configure the `PrivyProvider`

Wrap your app with `PrivyProvider` and enable automatic embedded wallet creation. This ensures every user gets a wallet on login.

```tsx theme={"system"}
import {PrivyProvider} from '@privy-io/react-auth';

<PrivyProvider
  appId={import.meta.env.VITE_PRIVY_APP_ID}
  config={{
    appearance: {
      theme: 'dark'
    },
    embeddedWallets: {
      ethereum: {
        createOnLogin: 'users-without-wallets'
      }
    }
  }}
>
  <App />
</PrivyProvider>;
```

<Tip>
  Setting `createOnLogin` to `'users-without-wallets'` means Privy automatically provisions an
  Ethereum wallet the first time a user logs in. No extra steps needed.
</Tip>

## 4. Initialize WalletKit

Create a shared module that initializes `WalletKit` for the payment link flow.

```ts {skip-check} theme={"system"}
import {Core} from '@walletconnect/core';
import {WalletKit} from '@reown/walletkit';

let core: InstanceType<typeof Core> | null = null;
let walletkit: Awaited<ReturnType<typeof WalletKit.init>> | null = null;

const projectId = process.env.VITE_WALLETCONNECT_PROJECT_ID;

const metadata = {
  name: 'My Pay App',
  description: 'Pay with crypto',
  url: window.location.origin,
  icons: ['https://your-app.com/icon.png']
};

function getCore() {
  if (!core) {
    core = new Core({projectId});
  }
  return core;
}

export async function getWalletKit() {
  if (walletkit) return walletkit;
  walletkit = await WalletKit.init({
    core: getCore() as any,
    metadata,
    payConfig: {
      appId: projectId,
      apiKey: 'your-wc-pay-api-key'
    }
  });
  return walletkit;
}
```

***

# Pay with link

This flow lets users paste a WalletConnect Pay link (like `https://pay.walletconnect.com/...`) and complete the payment entirely in your app.

## 1. Get the user's Privy wallet

Use Privy's `useWallets` hook to access the embedded wallet:

```ts theme={"system"}
import {useWallets} from '@privy-io/react-auth';

const {wallets} = useWallets();
const embeddedWallet = wallets.find((w) => w.walletClientType === 'privy');
```

## 2. Validate the payment link

Use `isPaymentLink` from `@reown/walletkit` to verify the link before making API calls:

```ts theme={"system"}
import {isPaymentLink} from '@reown/walletkit';

const uri = 'https://pay.walletconnect.com/...';

if (!isPaymentLink(uri)) {
  throw new Error('Not a valid WalletConnect Pay link');
}
```

## 3. Fetch payment options

Build the user's account list from supported chain prefixes and their wallet address, then call `getPaymentOptions`:

```ts theme={"system"}
import {useWallets} from '@privy-io/react-auth';
declare function getWalletKit(): Promise<any>;
declare const uri: string;

const SUPPORTED_CHAINS = [
  'eip155:1', // Ethereum
  'eip155:8453', // Base
  'eip155:137', // Polygon
  'eip155:42161', // Arbitrum
  'eip155:10' // Optimism
];

const {wallets} = useWallets();
const embeddedWallet = wallets.find((w) => w.walletClientType === 'privy');

const walletkit = await getWalletKit();
const address = embeddedWallet.address;

const accounts = SUPPORTED_CHAINS.map((prefix) => `${prefix}:${address}`);

try {
  const options = await walletkit.pay.getPaymentOptions({
    paymentLink: uri,
    accounts,
    includePaymentInfo: true
  });
} catch (error) {
  if (error.message.includes('payment not found')) {
    // The payment link is invalid or has been cancelled
  } else if (error.message.includes('expired')) {
    // The payment has expired
  } else {
    throw error;
  }
}
```

The response contains:

* **`paymentId`** — unique identifier for this payment session
* **`options`** — array of payment methods (different tokens and chains the user can pay with)
* **`info`** — merchant name, requested amount, and expiry (`expiresAt`)

<Warning>
  Check `options.info.expiresAt` and warn users when time is running low. Payments expire after a
  set period, and signing after expiry results in a failed payment.
</Warning>

## 4. Handle data collection (if required)

Some payment options require additional user information (e.g. shipping address). Check the selected option for a `collectData` object and render the provided URL in an iframe:

```ts {skip-check} theme={"system"}
const collectData = selectedOption.collectData;

if (collectData?.url) {
  // Render collectData.url in an iframe.
  // WalletConnect handles the form UI inside the iframe.
  // Listen for postMessage events from the iframe:
  //   { type: 'IC_COMPLETE' } → data collected successfully, proceed to signing
  //   { type: 'IC_ERROR' }   → collection failed, show an error
  // To pre-populate known user data, append a base64-encoded JSON query param:
  // const prefill = btoa(JSON.stringify({ email: 'user@example.com' }));
  // const url = `${collectData.url}?prefill=${prefill}`;
}
```

<Info>
  The iframe submits collected data directly to WalletConnect. Do not pass `collectedData` to
  `confirmPayment()` when using this flow — it is handled automatically.
</Info>

## 5. Get required actions and sign

Once the user selects a payment option, fetch the transaction actions and sign them with the Privy wallet. The API can return multiple actions (e.g. a token approval followed by a Permit2 signature), and different RPC methods require different parameter handling:

```ts theme={"system"}
import {useWallets} from '@privy-io/react-auth';
declare function getWalletKit(): Promise<any>;
declare const options: any;
declare const selectedOption: {id: string};

const {wallets} = useWallets();
const embeddedWallet = wallets.find((w) => w.walletClientType === 'privy');

const walletkit = await getWalletKit();

const actions = await walletkit.pay.getRequiredPaymentActions({
  paymentId: options.paymentId,
  optionId: selectedOption.id
});

const provider = await embeddedWallet.getEthereumProvider();

const signatures = await Promise.all(
  actions.map(async (action) => {
    const {chainId, method, params} = action.walletRpc;
    const parsedParams = JSON.parse(params);

    const numericChainId = parseInt(chainId.split(':')[1], 10);
    await embeddedWallet.switchChain(numericChainId);

    switch (method) {
      case 'eth_sendTransaction':
        return await provider.request({method, params: [parsedParams[0]]});
      case 'eth_signTypedData_v4':
        return await provider.request({method, params: parsedParams});
      case 'personal_sign':
        return await provider.request({method, params: parsedParams});
      default:
        throw new Error(`Unsupported RPC method: ${method}`);
    }
  })
);
```

<Info>
  The three methods above (`eth_sendTransaction`, `eth_signTypedData_v4`, `personal_sign`) are the
  most common, but the API can return any wallet RPC method. Add additional cases to the `switch`
  statement as needed for your integration.
</Info>

## 6. Confirm the payment

Submit all signatures to finalize the payment. The response may indicate the payment is still processing — poll until `isFinal` is `true`:

```ts {skip-check} theme={"system"}
import {WalletKit} from '@reown/walletkit';
declare const walletkit: Awaited<ReturnType<typeof WalletKit.init>>;
declare const options: {paymentId: string};
declare const selectedOption: {id: string};
declare const signatures: string[];

async function confirmAndPoll(
  wk: Awaited<ReturnType<typeof WalletKit.init>>,
  paymentId: string,
  optionId: string,
  sigs: string[]
) {
  let result = await wk.pay.confirmPayment({
    paymentId,
    optionId,
    signatures: sigs
  });

  while (!result.isFinal && result.pollInMs) {
    await new Promise((resolve) => setTimeout(resolve, result.pollInMs));
    result = await wk.pay.confirmPayment({
      paymentId,
      optionId,
      signatures: sigs
    });
  }

  return result;
}

try {
  const result = await confirmAndPoll(walletkit, options.paymentId, selectedOption.id, signatures);

  if (result.status === 'succeeded') {
    // Payment confirmed
  } else {
    // Payment failed — check result.status for details
    // Possible statuses: 'failed', 'expired', 'cancelled'
  }
} catch (error) {
  // Network error or unexpected failure
}
```

<Accordion title="TypeScript types reference">
  Key types from the WalletConnect Pay API:

  ```ts {skip-check} theme={"system"}
  interface PaymentOptionsResponse {
    paymentId: string;
    info?: PaymentInfo;
    options: PaymentOption[];
    collectData?: CollectDataAction;
  }

  interface PaymentOption {
    id: string;
    amount: PayAmount;
    etaS: number;
    actions: Action[];
    collectData?: CollectDataAction;
  }

  interface WalletRpcAction {
    chainId: string;
    method: string;
    params: string; // JSON-encoded string
  }

  interface ConfirmPaymentResponse {
    status: 'requires_action' | 'processing' | 'succeeded' | 'failed' | 'expired' | 'cancelled';
    isFinal: boolean;
    pollInMs?: number;
    info?: PaymentResultInfo;
  }
  ```

  For the full API reference, see the [WalletConnect Pay SDK documentation](https://docs.walletconnect.com/payments/wallets/overview).
</Accordion>

***

# Supported chains

Configure which chains your app supports. The `SUPPORTED_CHAINS` array in [step 3](#3-fetch-payment-options) determines which payment options WalletConnect Pay returns.

| Chain    | Chain ID | CAIP-2 prefix  | USDC address                                 |
| -------- | -------- | -------------- | -------------------------------------------- |
| Ethereum | 1        | `eip155:1`     | `0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48` |
| Base     | 8453     | `eip155:8453`  | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` |
| Polygon  | 137      | `eip155:137`   | `0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359` |
| Arbitrum | 42161    | `eip155:42161` | `0xaf88d065e77c8cC2239327C5EDb3A432268e5831` |
| Optimism | 10       | `eip155:10`    | `0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85` |

<Tip>
  WalletConnect Pay matches available payment options to the chains your app lists. Add more chains
  to `SUPPORTED_CHAINS` to offer users additional payment methods.
</Tip>

<Info>
  For testnet development, use the [WalletConnect Dashboard POS
  tool](https://dashboard.walletconnect.com) to create test payments on supported testnets.
</Info>

***

# Testing

<Steps>
  <Step title="Create a test payment">
    Go to the [WalletConnect Dashboard](https://dashboard.walletconnect.com) and use the POS tool to
    create a test payment. Copy the payment link.
  </Step>

  <Step title="Log in with Privy">
    Log in to your app with Privy so an embedded wallet is provisioned.
  </Step>

  <Step title="Complete the payment flow">
    Paste the link into your app and complete the payment flow.
  </Step>
</Steps>

***

## Learn more

<CardGroup cols={2}>
  <Card title="WalletConnect Pay documentation" icon="arrow-up-right-from-square" href="https://docs.walletconnect.com/payments/wallets/overview">
    Official WalletConnect Pay integration guide for wallets
  </Card>

  <Card title="WalletConnect Dashboard" icon="arrow-up-right-from-square" href="https://dashboard.walletconnect.com">
    Create test payments and manage your WCP ID
  </Card>
</CardGroup>
