> ## 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.

# Cards React integration

> Integrate stablecoin card prebuilt components with the Privy React SDK

This guide covers the complete cardholder flow. First, check for an existing card. If none exists, use `useSignUpForCard` with `SignUpForCardView` to onboard the cardholder. Then, render `CardSummaryView` to display and manage the card.

These APIs are exported from `@privy-io/react-auth/cards` and must be used within `PrivyProvider`.

Before continuing, complete the [cards setup](/financial-flows/cards/pre-built-components/setup) and [configure embedded wallets](/basics/react/advanced/automatic-wallet-creation) for your app.

## Install and import the SDK

<Info>Card prebuilt components require `@privy-io/react-auth` version 3.40.0 or later.</Info>

Install the React SDK:

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

Import `usePrivy` from the main entrypoint. Import the card hooks and components from the `/cards` entrypoint.

```tsx theme={"system"}
import {usePrivy} from '@privy-io/react-auth';
import {
  CardSummaryView,
  SignUpForCardView,
  useGetCardsForUser,
  useSignUpForCard
} from '@privy-io/react-auth/cards';
```

## Get the funding wallet

The `signUp` method takes the Privy wallet ID, not the wallet address. Read it from the authenticated user's `linkedAccounts`.

The following helper returns the ID of an embedded EVM wallet:

```tsx theme={"system"}
import type {LinkedAccountWithMetadata, WalletWithMetadata} from '@privy-io/react-auth';

const getCardWalletId = (accounts: LinkedAccountWithMetadata[]) =>
  accounts.find(
    (x): x is WalletWithMetadata =>
      x.type === 'wallet' &&
      x.walletClientType === 'privy' &&
      x.connectorType === 'embedded' &&
      x.chainType === 'ethereum'
  )?.id;
```

For a Solana-funded card, select an embedded wallet with `chainType === 'solana'` instead.

## Add the card flow

Use `isOpen` to mount the container for `SignUpForCardView`. Connect the container's close action to `close`. The view renders the onboarding steps using the options passed to `signUp`. The promise returns the new card ID after signup completes and rejects if the flow does not complete.

This sandbox example uses Tempo Moderato and PathUSD. The SDK supplies built-in stablecoin and Bridge spend-approval targets for supported sandbox networks.

```tsx theme={"system"}
'use client';

import {useState} from 'react';
import {
  usePrivy,
  type LinkedAccountWithMetadata,
  type WalletWithMetadata
} from '@privy-io/react-auth';
import {
  CardSummaryView,
  SignUpForCardView,
  useGetCardsForUser,
  useSignUpForCard
} from '@privy-io/react-auth/cards';

const getCardWalletId = (accounts: LinkedAccountWithMetadata[]) =>
  accounts.find(
    (x): x is WalletWithMetadata =>
      x.type === 'wallet' &&
      x.walletClientType === 'privy' &&
      x.connectorType === 'embedded' &&
      x.chainType === 'ethereum'
  )?.id;

const Cards = () => {
  const {user} = usePrivy();
  const {signUp, close, isOpen} = useSignUpForCard();
  const {getCardsForUser} = useGetCardsForUser();
  const [cardId, setCardId] = useState<string | null>(null);
  const [error, setError] = useState<string | null>(null);
  const walletId = getCardWalletId(user?.linkedAccounts ?? []);

  const openCard = async () => {
    if (!walletId) return;
    setError(null);

    try {
      const {data} = await getCardsForUser({environment: 'sandbox', limit: 20});
      const existing = data.find(
        (x) => x.wallet_id === walletId && (x.status === 'active' || x.status === 'inactive')
      );

      if (existing) {
        setCardId(existing.id);
        return;
      }

      const {id} = await signUp({
        environment: 'sandbox',
        walletId,
        chainId: 'eip155:42431',
        asset: 'path_usd'
      });
      setCardId(id);
    } catch (error) {
      setError(error instanceof Error ? error.message : 'Could not open card');
    }
  };

  return (
    <>
      {error && <p role="alert">{error}</p>}

      {!cardId && (
        <button disabled={!walletId} onClick={() => void openCard()}>
          Open card
        </button>
      )}

      {isOpen && (
        <aside aria-label="Card signup">
          <button type="button" onClick={close}>
            Close
          </button>
          <SignUpForCardView />
        </aside>
      )}

      {cardId && <CardSummaryView key={cardId} cardId={cardId} environment="sandbox" />}
    </>
  );
};
```

The example checks for an open card before starting signup. A failed list request stops the flow. The cardholder can use the same button to retry.

The example checks the newest 20 cards. Apps with more records should use the pagination helper below before starting signup.

Replace the `aside` with the modal or side panel that fits the app. Keep `SignUpForCardView` mounted while `isOpen` is `true`. `CardSummaryView` uses `cardId` to load the card, balance, activity, and statements. It also handles card-detail reveal, wallet provisioning, freezing, replacement, and cancellation.

## Find the user's existing cards

The signup promise only returns a card ID during the current page session. To support returning users, use `useGetCardsForUser` to fetch the authenticated user's cards.

`getCardsForUser` returns one page for a single environment, ordered newest first. Results include canceled cards. The default page size is 5 and the maximum is 20; pass each `next_cursor` back as `cursor` until it returns `null`.

```tsx theme={"system"}
const {getCardsForUser} = useGetCardsForUser();

const getAllCards = async (environment: 'sandbox' | 'production') => {
  const first = await getCardsForUser({environment, limit: 20});
  const cards = [...first.data];
  let cursor = first.next_cursor;

  while (cursor) {
    const page = await getCardsForUser({environment, limit: 20, cursor});
    cards.push(...page.data);
    cursor = page.next_cursor;
  }

  return cards;
};
```

Call the helper after the user authenticates. Keep the full result if your app lists card history, or select the newest open card to pass to `CardSummaryView`:

```tsx theme={"system"}
const cards = await getAllCards('sandbox');
const existing = cards.find(
  (x) => x.wallet_id === wallet.id && (x.status === 'active' || x.status === 'inactive')
);

setCardId(existing?.id ?? null);
```

## Handle card states

The list response can include these card states:

* `active`: The card is open and can spend. Pass it to `CardSummaryView`.
* `inactive`: The card is frozen but remains open. Pass it to `CardSummaryView` to allow unfreezing.
* `canceled`: The card is permanently closed. Keep it for history, but do not select it for management.
* `replaced`: A newer card replaced this closed card. Select the newer `active` or `inactive` card instead.

Cards are ordered newest first. Select the first `active` or `inactive` card for the funding wallet.

<Warning>
  Do not treat a failed `getCardsForUser` request as an empty list. Show the error and retry the
  request. Offer signup only after a successful request returns no open cards.
</Warning>

## Configure production spend approval

Sandbox uses built-in targets for Ethereum Sepolia, OP Sepolia, Polygon Amoy, Base Sepolia, Arbitrum Sepolia, Avalanche Fuji, Tempo Moderato, and Solana devnet. Production requires the spend-approval target for the mainnet chain behind the app's Bridge integration.

Pass the target that a Privy account manager provides. Do not guess or hardcode another integration's spender or merchant ID: the card can be issued but cannot spend if its wallet approves the wrong target.

<Tabs>
  <Tab title="EVM">
    Pass the stablecoin contract and Bridge spender for the card's chain:

    ```tsx theme={"system"}
    const openProductionSignUp = async () => {
      const {id} = await signUp({
        environment: 'production',
        walletId: wallet.id,
        chainId: 'eip155:4217',
        asset: 'path_usd',
        spendApproval: {
          stablecoinAddress: process.env.NEXT_PUBLIC_CARD_STABLECOIN_ADDRESS!,
          spenderAddress: process.env.NEXT_PUBLIC_CARD_SPENDER_ADDRESS!,
        },
      });
      setCardId(id);
    };
    ```
  </Tab>

  <Tab title="Solana">
    Pass the stablecoin mint, Bridge card program, and merchant ID:

    ```tsx theme={"system"}
    const openProductionSignUp = async () => {
      const {id} = await signUp({
        environment: 'production',
        walletId: wallet.id,
        chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
        asset: 'usdc',
        spendApproval: {
          stablecoinAddress: process.env.NEXT_PUBLIC_CARD_STABLECOIN_ADDRESS!,
          programId: process.env.NEXT_PUBLIC_CARD_PROGRAM_ID!,
          merchantId: process.env.NEXT_PUBLIC_CARD_MERCHANT_ID!,
        },
      });
      setCardId(id);
    };
    ```
  </Tab>
</Tabs>

The `environment` option determines which configured card ledger the flow uses. Sandbox does not accept a caller-supplied `spendApproval`; production requires one.

## API reference

### `useGetCardsForUser`

`useGetCardsForUser()` returns a `getCardsForUser` method for the authenticated user:

```tsx theme={"system"}
const {getCardsForUser} = useGetCardsForUser();
```

`getCardsForUser(options)` returns a page with the user's cards in `data` and the next page cursor in `next_cursor`.

| Option        | Type                        | Description                                                     |
| ------------- | --------------------------- | --------------------------------------------------------------- |
| `environment` | `'sandbox' \| 'production'` | Card ledger to query.                                           |
| `limit`       | `number`                    | Optional page size from 1 to 20. Defaults to 5.                 |
| `cursor`      | `string`                    | Optional cursor returned as `next_cursor` by the previous page. |

### `useSignUpForCard`

`useSignUpForCard()` returns the `signUp` and `close` methods and the `isOpen` state:

```tsx theme={"system"}
const {signUp, close, isOpen} = useSignUpForCard();
```

`signUp(options)` returns `Promise<{id: string}>`. Only one card signup can be active at a time; starting another before the first finishes rejects with an error.

`isOpen` is `true` while signup is in progress. Use it to control the container that mounts `SignUpForCardView`. Call `close()` when the user dismisses that container. Closing before a card exists rejects the pending `signUp` promise.

| Option          | Type                        | Description                                                                                             |
| --------------- | --------------------------- | ------------------------------------------------------------------------------------------------------- |
| `environment`   | `'sandbox' \| 'production'` | Card ledger to use.                                                                                     |
| `walletId`      | `string`                    | Privy ID of the embedded wallet that funds the card.                                                    |
| `chainId`       | `string`                    | CAIP-2 chain ID for the funding wallet.                                                                 |
| `asset`         | `string`                    | Spend asset for the card, such as `'path_usd'` or `'usdc'`.                                             |
| `spendApproval` | `DevSpendApprovalTarget`    | Required in production and unavailable in sandbox. Shape depends on whether `chainId` is EVM or Solana. |

`DevSpendApprovalTarget` is a union of the EVM and Solana target shapes:

```tsx theme={"system"}
type EvmDevSpendApprovalTarget = {
  stablecoinAddress: string;
  spenderAddress: string;
};

type SvmDevSpendApprovalTarget = {
  stablecoinAddress: string;
  programId: string;
  merchantId: number | string | bigint;
};

type DevSpendApprovalTarget = EvmDevSpendApprovalTarget | SvmDevSpendApprovalTarget;
```

For EVM chains, `spenderAddress` identifies the Bridge contract approved to spend the stablecoin. For Solana, `programId` and `merchantId` identify the Bridge delegate.

### `SignUpForCardView`

`SignUpForCardView` takes no props. Mount one instance within `PrivyProvider` while `isOpen` is `true`. The hook controls its options and completion state.

The view handles disclosures, bank and provider terms, KYC, card creation, and the wallet spend approval.

### `CardSummaryView`

| Prop          | Type                        | Description                                     |
| ------------- | --------------------------- | ----------------------------------------------- |
| `cardId`      | `string`                    | Privy card ID to display.                       |
| `environment` | `'sandbox' \| 'production'` | Card ledger that contains the card.             |
| `onClose`     | `() => void`                | Optional. Called when the user closes the view. |

<Warning>
  Always pass the same `environment` used to create the card. Cards are scoped by app, user, and
  environment, so the other environment cannot load the card.
</Warning>
