> ## 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 Native integration

> Integrate stablecoin card signup and management with the Privy React Native SDK

Use `useSignUpForCard` to open card onboarding, `useGetCardsForUser` to find existing cards, and `CardSummaryView` to display and manage a card.

Import these APIs from `@privy-io/expo/cards`. Card signup runs in the `PrivyElements` modal. `CardSummaryView` renders inside the app's own screen.

For an approved Earn-backed card program, follow [Spend from vault balances](https://docs.privy.io/financial-flows/cards/pre-built-components/spend-from-vault.md). The same hooks and components handle vault-share approval and available balances.

> [!NOTE]
>
> These APIs are experimental and may change in a minor version. React Native card signup supports
> embedded EVM wallets.

## Set up the SDK

Complete the [cards setup](https://docs.privy.io/financial-flows/cards/pre-built-components/setup.md), [React Native SDK installation](https://docs.privy.io/basics/react-native/installation.md), and [Privy UI setup](https://docs.privy.io/basics/react-native/advanced/setup-privyelements.md), including the UI dependencies and fonts.

Use `@privy-io/expo` version **0.76.4 or later** for signup, card management, and card-detail reveal:

```bash theme={"system"}
pnpm exec expo install @privy-io/expo@latest
```

Mount `PrivyElements` once inside `PrivyProvider`, alongside the app's navigation. Use the app ID and mobile client ID from the [Privy dashboard](https://docs.privy.io/basics/get-started/dashboard/app-clients.md).

```tsx theme={"system"}
import {PrivyProvider} from '@privy-io/expo';
import {PrivyElements} from '@privy-io/expo/ui';
import {Slot} from 'expo-router';

export default function RootLayout() {
  return (
    <PrivyProvider appId="your-privy-app-id" clientId="your-privy-app-client-id">
      <Slot />
      <PrivyElements />
    </PrivyProvider>
  );
}
```

Authenticate the user and [create an embedded Ethereum wallet](https://docs.privy.io/wallets/wallets/create/create-a-wallet.md) before starting signup. The wallet must be connected to approve spending. Enable [gas sponsorship](https://docs.privy.io/wallets/gas-and-asset-management/gas/setup.md) for the funding chain, or fund the wallet with the chain's gas token.

## Add the card flow

The example below checks every page for an existing open card before starting signup. It uses Tempo Moderato and PathUSD in sandbox.

```tsx theme={"system"}
import {getAllUserEmbeddedEthereumWallets, usePrivy} from '@privy-io/expo';
import {CardSummaryView, useGetCardsForUser, useSignUpForCard} from '@privy-io/expo/cards';
import {useState} from 'react';
import {Button, Text, View} from 'react-native';

export default function CardsScreen() {
  const {isReady, user} = usePrivy();
  const {signUp, isOpen} = useSignUpForCard();
  const {getCardsForUser} = useGetCardsForUser();
  const [cardId, setCardId] = useState<string | null>(null);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const walletId = getAllUserEmbeddedEthereumWallets(user)[0]?.id;

  const openCard = async (resumeSetup = false) => {
    if (!isReady || !walletId || isLoading || isOpen) return;
    setIsLoading(true);
    setError(null);

    try {
      let cursor: string | undefined;

      do {
        const page = await getCardsForUser({environment: 'sandbox', limit: 20, cursor});
        const existing = page.data.find(
          (card) =>
            card.wallet_id === walletId &&
            card.chain_id === 'eip155:42431' &&
            card.asset === 'pathusd' &&
            (card.status === 'active' || card.status === 'inactive')
        );

        if (existing && !resumeSetup) {
          setCardId(existing.id);
          return;
        }

        cursor = page.next_cursor ?? undefined;
      } while (cursor);

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

  if (cardId) {
    return (
      <View style={{flex: 1}}>
        {error ? <Text accessibilityRole="alert">{error}</Text> : null}
        <Button
          title="Finish card setup"
          disabled={isLoading || isOpen}
          onPress={() => void openCard(true)}
        />
        <CardSummaryView
          cardId={cardId}
          environment="sandbox"
          onClose={() => setCardId(null)}
          onReplaced={setCardId}
        />
      </View>
    );
  }

  return (
    <View>
      {error ? <Text accessibilityRole="alert">{error}</Text> : null}
      <Button
        title="Open card"
        disabled={!isReady || !walletId || isLoading || isOpen}
        onPress={() => void openCard()}
      />
    </View>
  );
}
```

The SDK opens `PrivyElements` when `signUp` runs. The modal handles disclosures, bank and provider terms, identity verification, card creation, and spend approval. Hosted terms and identity verification open in the in-app browser through `expo-web-browser`.

The funding `walletId` is the Privy wallet ID from the user's linked accounts. `getAllUserEmbeddedEthereumWallets(user)` returns those accounts, including their IDs.

`CardSummaryView` fills its parent and scrolls internally. Give it available height, such as a screen with `flex: 1`. It uses the appearance configured on `PrivyElements`.

## Find returning users' cards

`getCardsForUser` returns one page for the authenticated user in the selected environment, ordered newest first. Pass `next_cursor` as `cursor` until it is `null`. The default page size is 5; the maximum is 20.

Select the first `active` or `inactive` card for the funding wallet. An `inactive` card is frozen and can be unfrozen in `CardSummaryView`. Keep `canceled` and `replaced` cards for history.

> [!WARNING]
>
> A failed list request does not mean the user has no cards. Show the error and retry. Start signup
> only after a successful search finds no open card.

`onReplaced` receives the replacement card's ID. Update the selected ID to display it, as shown above. Otherwise, the view stays on the closed card.

If signup closes before spend approval succeeds, the card can already exist. The example's **Finish card setup** action calls `signUp` again with the same wallet, chain, asset, and environment. Signup reuses the existing card and resumes the approval flow.

## Configure production spend approval

Sandbox uses built-in approval targets for Ethereum Sepolia, OP Sepolia, Polygon Amoy, Base Sepolia, Arbitrum Sepolia, Avalanche Fuji, and Tempo Moderato. Tempo Moderato supports PathUSD (`pathusd`) and OUSD (`ousd`); the other listed testnets use USDC (`usdc`).

Production requires the stablecoin contract and Bridge spender for the card program's funding chain. Obtain these addresses from the Privy account manager. The stablecoin must match `asset`, and the spender must belong to the app's card program.

```tsx theme={"system"}
import {useSignUpForCard, type EvmDevSpendApprovalTarget} from '@privy-io/expo/cards';
import {Button} from 'react-native';

export function ProductionCardButton({
  walletId,
  spendApproval,
  onCreated,
  onError
}: {
  walletId: string;
  spendApproval: EvmDevSpendApprovalTarget;
  onCreated: (cardId: string) => void;
  onError: (error: unknown) => void;
}) {
  const {signUp, isOpen} = useSignUpForCard();

  return (
    <Button
      title="Sign up for a card"
      disabled={isOpen}
      onPress={() => {
        void signUp({
          environment: 'production',
          walletId,
          chainId: 'eip155:4217',
          asset: 'pathusd',
          spendApproval
        })
          .then(({id}) => onCreated(id))
          .catch(onError);
      }}
    />
  );
}
```

The `spendApproval` object contains `stablecoinAddress: string` and `spenderAddress: string`. Sandbox uses its built-in targets and does not accept this option.

> [!WARNING]
>
> Pass the same `environment` when signing up, listing cards, and rendering `CardSummaryView`. Cards
> are scoped by app, user, and environment.

## API reference

### `useSignUpForCard`

Returns `signUp`, `close`, and `isOpen`. `signUp(options)` requires `PrivyElements` to be mounted and returns `Promise<{id: string}>`.

| Option | Type | Description |
| - | - | - |
| `environment` | `'sandbox' \| 'production'` | Required. Card ledger to use. |
| `walletId` | `string` | Required. Privy ID of the connected embedded EVM funding wallet. |
| `chainId` | `string` | Required. CAIP-2 funding chain ID, such as `'eip155:42431'`. |
| `asset` | `string` | Required. Funding asset, such as `'pathusd'`, `'ousd'`, or `'usdc'`. |
| `spendApproval` | `EvmDevSpendApprovalTarget` | Required in production. Unavailable in sandbox. |

`isOpen` is `true` while signup is in progress. Only one signup can run at a time. `close()` dismisses the flow and settles its pending promise.

Closing before a card exists rejects the promise. Applications under review or rejected also reject when closed. If a card already exists at the ready or approval-error step, closing resolves with its ID. A returned card ID alone does not guarantee that spend approval succeeded.

An unsupported funding chain, another active signup, or an unmounted `PrivyElements` causes `signUp` to reject. Catch rejections to handle cancellation and errors in the app.

### `useGetCardsForUser`

Returns `getCardsForUser(options)`, which resolves to a page with `data` and `next_cursor`.

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

### `CardSummaryView`

Displays the card's balance, paginated activity, transaction details, and dispute contact. The details screen reveals the card number and CVC and supports freezing, replacement, and cancellation.

| Prop | Type | Description |
| - | - | - |
| `cardId` | `string` | Required. Privy card ID to display. |
| `environment` | `'sandbox' \| 'production'` | Required. Card ledger containing the card. |
| `onClose` | `() => void` | Optional. Shows a header close button that calls this callback. |
| `onReplaced` | `(newCardId: string) => void` | Optional. Called with the new card ID after replacement. |

Card details render in Stripe Issuing Elements inside a `react-native-webview`. The app's configured Stripe publishable key is loaded through Privy. Keep Stripe and Bridge secret keys in the Privy dashboard.
