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

# Credit Cards

> Build a complete Visa credit card application for your users using Rain cards and Crossmint wallets.

<Note>
  This integration requires access to both Rain's card issuance API and
  Crossmint's wallet services.
</Note>

## Introduction

This guide shows you how to integrate Rain's card issuance platform with Crossmint wallets to build a complete crypto-to-credit card application. You'll learn how to create an app where users can sign up, issue a Visa card, and fund them directly from their wallets.

<Note>
  Check out our [live demo](https://rain-wallets-demo.vercel.app/) to see the
  integration in action before building your own.
</Note>

**What you'll build:**

* Virtual Visa card issuance
* Crypto-to-card funding with RUSD tokens
* Real-time balance and transaction monitoring

**Tech Stack:**

* Next.js >=15 (with App Router)
* React Server Actions for API integration
* [Crossmint React SDK](https://www.npmjs.com/package/@crossmint/client-sdk-react-ui) for wallet and authentication
* Rain API for card issuance ([contact the rain team for access](https://www.rain.xyz/contact-us))

## Prerequisites

<Steps>
  <Step icon="circle-dot" iconType="duotone" title="Rain API Key">
    [Get your Rain Staging API key](https://use-dev.raincards.xyz) with card
    issuance permissions
  </Step>

  <Step icon="circle-dot" iconType="duotone" title="Crossmint API Key">
    [Get your Crossmint Staging API
    key](https://docs.crossmint.com/introduction/platform/api-keys/client-side)
    with wallet and transaction scopes
  </Step>

  <Step icon="circle-dot" iconType="duotone" title="Next.js Project">
    Set up a Next.js project with App Router enabled for server actions: `npx
            create-next-app@latest my-rain-crossmint-app`
  </Step>

  <Step icon="circle-dot" iconType="duotone" title="Install Dependencies">
    ```bash theme={null}
    # Using npm
    npm install @crossmint/client-sdk-react-ui viem

    # Using pnpm
    pnpm add @crossmint/client-sdk-react-ui viem

    # Using yarn
    yarn add @crossmint/client-sdk-react-ui viem
    ```
  </Step>
</Steps>

## Integration Steps

<Steps>
  <Step title="Set up Crossmint Authentication and Wallet Providers">
    Set up the complete Crossmint provider stack with authentication and wallet functionality. This enables users to sign in and automatically creates wallets for them. This example uses [Crossmint Auth](/authentication/introduction) for simplicity, but for production you should [bring your own auth provider](/wallets/guides/bring-your-own-auth).

    ```tsx app/providers.tsx theme={null}
    "use client";

    import {
      CrossmintProvider,
      CrossmintAuthProvider,
      CrossmintWalletProvider,
    } from "@crossmint/client-sdk-react-ui";

    export function Providers({ children }: { children: React.ReactNode }) {
      return (
        <CrossmintProvider apiKey={process.env.NEXT_PUBLIC_CROSSMINT_API_KEY!}>
          <CrossmintAuthProvider>
            <CrossmintWalletProvider
              createOnLogin={{
                chain: "base-sepolia", // Using Base Sepolia for this demo
                signer: {
                  type: "email",
                },
              }}
            >
              {children}
            </CrossmintWalletProvider>
          </CrossmintAuthProvider>
        </CrossmintProvider>
      );
    }
    ```

    ```tsx app/layout.tsx theme={null}
    import { Providers } from "./providers";

    export default function RootLayout({ children }: { children: React.ReactNode }) {
      return (
        <html lang="en">
          <body>
            <Providers>{children}</Providers>
          </body>
        </html>
      );
    }
    ```
  </Step>

  <Step title="Add Environment Configuration">
    Set up your environment variables for both Rain and Crossmint APIs.

    ```bash .env theme={null}
    # Crossmint Configuration
    NEXT_PUBLIC_CROSSMINT_API_KEY=your_crossmint_api_key
    NEXT_PUBLIC_CHAIN=base-sepolia

    # Rain Configuration
    # Server-side secret for Rain API calls in actions/rain.ts
    RAIN_API_KEY=your_rain_api_key
    ```
  </Step>

  <Step title="Build the Rain Integration Layer">
    Create Next.js server action functions to handle Rain API calls securely on the server side. This keeps your Rain API key secure and provides a clean interface for your React components.

    ```typescript actions/rain.ts theme={null}
    "use server";

    const RAIN_API_URL = "https://api-dev.raincards.xyz/v1";

    /**
    * Creates a new Rain user application with KYC data
    * This is the first step - submits user information to Rain for account creation
    * Using "approved" as lastName will skip KYC verification in sandbox mode
    */
    export async function createRainUserApplication(params: {
      firstName: string;
      lastName: string;
      email: string;
      walletAddress: string;
      // ... other required fields
    }) {
      const response = await fetch(`${RAIN_API_URL}/issuing/applications/user`, {
        method: "POST",
        headers: {
          "Api-Key": process.env.RAIN_API_KEY!, // This variable should be defined in your .env file
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          ...params,
          // Demo data for quick testing
          birthDate: "1990-01-01",
          nationalId: "123456789",
          countryOfIssue: "US",
          address: {
            line1: "123 Test Street",
            city: "San Francisco",
            region: "CA",
            postalCode: "94105",
            countryCode: "US",
          },
          ipAddress: "127.0.0.1",
          phoneCountryCode: "1",
          phoneNumber: "5551234567",
          annualSalary: "75000",
          accountPurpose: "personal",
          expectedMonthlyVolume: "2000",
          isTermsOfServiceAccepted: true,
        }),
      });

      if (!response.ok) {
        const error = await response.json();
        throw new Error(`Rain application failed: ${error.message}`);
      }

      const result = await response.json();
      return { userId: result.id, status: result.applicationStatus };
    }

    /**
    * Creates a smart contract for the approved Rain user
    * Rain manages the smart contract deployment and operations for collateral handling
    * This contract will hold the user's crypto collateral (RUSD tokens)
    */
    export async function createRainUserContract(userId: string, chainId: number) {
      await fetch(`${RAIN_API_URL}/issuing/users/${userId}/contracts`, {
        method: "POST",
        headers: {
          "Api-Key": process.env.RAIN_API_KEY!,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({ chainId }),
      });
    }

    /**
    * Issues a virtual Visa credit card for the Rain user
    * The card will be linked to their smart contract for collateral-backed spending
    * Card starts active and ready to be funded with crypto collateral
    */
    export async function issueRainCard(userId: string, displayName: string) {
      const response = await fetch(
        `${RAIN_API_URL}/issuing/users/${userId}/cards`,
        {
          method: "POST",
          headers: {
            "Api-Key": process.env.RAIN_API_KEY!,
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            type: "virtual",
            limit: { frequency: "allTime", amount: 1000 },
            displayName,
            status: "active",
          }),
        }
      );

      return await response.json();
    }

    /**
    * Gets user's existing Rain cards
    * Returns array of cards with basic info like status and last four digits
    */
    export async function getRainUserCards(userId: string) {
      const response = await fetch(
        `${RAIN_API_URL}/issuing/cards?userId=${userId}&limit=20`,
        {
          headers: { "Api-Key": process.env.RAIN_API_KEY! },
        }
      );
      return await response.json();
    }

    /**
    * Gets Rain user smart contracts with retry logic
    * Returns Base Sepolia contract with RUSD token info
    */
    export async function getRainUserContracts(userId: string) {
      const BASE_SEPOLIA_CHAIN_ID = 84532;
      const RUSD_CONTRACT_BASE_SEPOLIA =
        "0x10b5Be494C2962A7B318aFB63f0Ee30b959D000b";

      // Simple retry logic - try up to 3 times
      for (let attempt = 0; attempt < 3; attempt++) {
        try {
          const response = await fetch(
            `${RAIN_API_URL}/issuing/users/${userId}/contracts`,
            {
              headers: { "Api-Key": process.env.RAIN_API_KEY! },
            }
          );

          const contracts = await response.json();
          const baseContract = contracts.find(
            (c: { chainId: number }) => c.chainId === BASE_SEPOLIA_CHAIN_ID
          );

          if (baseContract) {
            const rusdToken = baseContract.tokens.find(
              (t: { address: string }) => t.address === RUSD_CONTRACT_BASE_SEPOLIA
            );
            return {
              contractId: baseContract.id,
              depositAddress: baseContract.depositAddress,
              tokens: baseContract.tokens,
              rusdBalance: rusdToken?.balance || "0.0",
            };
          }

          // Wait 2 seconds before retry if no contract found
          if (attempt < 2)
            await new Promise((resolve) => setTimeout(resolve, 2000));
        } catch (error) {
          if (attempt === 2) throw error;
          await new Promise((resolve) => setTimeout(resolve, 2000));
        }
      }
      throw new Error("No Base Sepolia contract found after retries");
    }

    /**
    * Gets user's credit balances and spending power
    * Returns simplified balance info in dollars (converted from cents)
    */
    export async function getRainUserCreditBalances(userId: string) {
      const response = await fetch(
        `${RAIN_API_URL}/issuing/users/${userId}/balances`,
        {
          headers: { "Api-Key": process.env.RAIN_API_KEY! },
        }
      );

      const data = await response.json();
      return {
        creditLimit: data.creditLimit / 100,
        spendingPower: data.spendingPower / 100,
        balanceDue: data.balanceDue / 100,
      };
    }

    /**
    * Gets Rain user status by checking application status
    * Returns basic user info and approval status
    */
    export async function getRainUserStatus(userId: string) {
      const response = await fetch(
        `${RAIN_API_URL}/issuing/applications/user/${userId}`,
        {
          headers: { "Api-Key": process.env.RAIN_API_KEY! },
        }
      );

      const result = await response.json();
      return {
        userId: result.id,
        applicationStatus: result.applicationStatus,
        email: result.email,
        isActive: result.isActive,
      };
    }

    /**
    * Finds Rain user by wallet address
    * Returns array of users (should be 0 or 1 for unique wallets)
    */
    export async function getRainUserByWalletAddress(walletAddress: string) {
      const response = await fetch(`${RAIN_API_URL}/issuing/users?limit=100`, {
        headers: { "Api-Key": process.env.RAIN_API_KEY! },
      });

      const users = await response.json();
      return users.filter(
        (user: { walletAddress: string }) => user.walletAddress === walletAddress
      );
    }
    ```
  </Step>

  <Step title="Create the Main Integration Component">
    Build a React component that handles both authentication and the Rain integration flow in one place.

    ```tsx components/rain-integration.tsx theme={null}
    "use client";

    import { useState, useEffect } from "react";
    import {
      useCrossmintAuth,
      useWallet,
      EVMWallet,
    } from "@crossmint/client-sdk-react-ui";
    import { encodeFunctionData } from "viem";
    import {
      createRainUserApplication,
      createRainUserContract,
      issueRainCard,
      getRainUserByWalletAddress,
      getRainUserContracts,
      getRainUserCards,
      getRainUserCreditBalances,
      getRainUserStatus,
    } from "../actions/rain";

    const RUSD_CONTRACT_BASE_SEPOLIA =
      "0x10b5Be494C2962A7B318aFB63f0Ee30b959D000b";

    export function RainIntegration() {
      const { wallet } = useWallet();
      const { user, login, logout } = useCrossmintAuth();
      const [step, setStep] = useState<"signup" | "contract" | "card" | "funded">(
        "signup"
      );
      const [rainUserId, setRainUserId] = useState("");
      const [contractAddress, setContractAddress] = useState("");
      const [contractData, setContractData] = useState<{
        contractId: string;
        depositAddress: string;
        tokens: Array<{ address: string; balance: string }>;
        rusdBalance: string;
      } | null>(null);
      const [cardData, setCardData] = useState<{
        id: string;
        type: string;
        status: string;
        last4: string;
        expirationMonth: string;
        expirationYear: string;
        limit: {
          amount: number;
          frequency: string;
        };
      } | null>(null);
      const [loading, setLoading] = useState(false);
      const [initialLoading, setInitialLoading] = useState(true);
      const [creditBalances, setCreditBalances] = useState<{
        creditLimit: number;
        spendingPower: number;
        balanceDue: number;
      } | null>(null);
      const [isRefreshingBalances, setIsRefreshingBalances] = useState(false);

      // Comprehensive state determination on wallet connection
      useEffect(() => {
        const determineCurrentState = async () => {
          if (!wallet?.address || !user?.email) {
            setInitialLoading(false);
            return;
          }
          setInitialLoading(true);
          try {
            // Step 1: Check if user exists
            const existingUsers =
              await getRainUserByWalletAddress(wallet.address);
            if (existingUsers.length === 0) {
              // No user exists - start from signup
              setStep("signup");
              return;
            }
            // User exists - get their details
            const rainUser = existingUsers[0];
            setRainUserId(rainUser.id);
            // Step 2: Check user status
            const userStatus = await getRainUserStatus(rainUser.id);
            if (userStatus.applicationStatus !== "approved") {
              setStep("signup");
              return;
            }
            // Step 3: Check for contracts
            try {
              const contractInfo = await getRainUserContracts(rainUser.id);
              setContractAddress(contractInfo.depositAddress);
              setContractData(contractInfo);

              // Step 4: Check for cards
              try {
                const cards = await getRainUserCards(rainUser.id);
                if (cards.length > 0) {
                  // User has cards - check if funded
                  const card = cards[0];
                  setCardData({
                    id: card.id,
                    type: card.type,
                    status: card.status,
                    last4: card.last4,
                    expirationMonth: card.expirationMonth,
                    expirationYear: card.expirationYear,
                    limit: card.limit,
                  });
                  // Get credit balances to determine if funded
                  const balances =
                    await getRainUserCreditBalances(rainUser.id);
                  setCreditBalances(balances);
                  // If there's spending power, consider it funded
                  if (balances.spendingPower > 0) {
                    setStep("funded");
                  } else {
                    setStep("card");
                  }
                } else {
                  // Contract exists but no card
                  setStep("contract");
                }
              } catch {
                // Error getting cards - assume no cards exist
                setStep("contract");
              }
            } catch {
              // No contract exists - need to create one
              setStep("contract");
            }
          } catch (error) {
            console.error("Error determining state:", error);
            setStep("signup"); // Default to signup on error
          } finally {
            setInitialLoading(false);
          }
        };
        determineCurrentState();
      }, [wallet?.address, user?.email]);

      const handleSignup = async () => {
        if (!wallet || !user?.email) return;
        setLoading(true);
        try {
          const result = await createRainUserApplication({
            firstName: user.email,
            lastName: "approved", // Skip KYC for demo
            email: user.email,
            walletAddress: wallet.address,
          });
          setRainUserId(result.userId);
          // Move to contract step - don't auto-create contract here
          // Let the user manually trigger contract creation
          setStep("contract");
        } catch (error) {
          console.error("Signup failed:", error);
          alert("Signup failed: " + error);
        } finally {
          setLoading(false);
        }
      };

      const handleCreateContract = async () => {
        if (!rainUserId) return;
        setLoading(true);
        try {
          // Create the contract
          await createRainUserContract(rainUserId, 84532);
          // Wait a bit for contract to be created
          await new Promise((resolve) => setTimeout(resolve, 3000));
          // Try to get contract info with retry logic
          let contractInfo;
          let attempts = 0;
          const maxAttempts = 5;
          while (attempts < maxAttempts) {
            try {
              contractInfo = await getRainUserContracts(rainUserId);
              if (contractInfo?.depositAddress) {
                break;
              }
            } catch {
              console.log(`Attempt ${attempts + 1} failed, retrying...`);
            }
            attempts++;
            if (attempts < maxAttempts) {
              await new Promise((resolve) => setTimeout(resolve, 2000));
            }
          }
          if (contractInfo?.depositAddress) {
            setContractAddress(contractInfo.depositAddress);
            setContractData(contractInfo);
            alert("✅ Smart contract created successfully!");
          } else {
            throw new Error(
              "Contract was created but details are not yet available. Please refresh the page."
            );
          }
        } catch (error) {
          console.error("Contract creation failed:", error);
          alert(
            "Contract creation may have succeeded but details are not yet available. Please refresh the page to check."
          );
        } finally {
          setLoading(false);
        }
      };

      const handleIssueCard = async () => {
        if (!rainUserId) return;

        setLoading(true);
        try {
          const card = await issueRainCard(
            rainUserId,
            user?.email || "Demo User"
          );
          setCardData({
            id: card.id,
            type: card.type,
            status: card.status,
            last4: card.last4,
            expirationMonth: card.expirationMonth,
            expirationYear: card.expirationYear,
            limit: card.limit,
          });
          // Fetch initial credit balances after card issuance
          await handleRefreshCreditBalances();
          setStep("card");
        } catch (error) {
          console.error("Card issuance failed:", error);
          alert("Card issuance failed: " + error);
        } finally {
          setLoading(false);
        }
      };

      const handleRefreshCreditBalances = async () => {
        if (!rainUserId) return;
        setIsRefreshingBalances(true);
        try {
          const balances = await getRainUserCreditBalances(rainUserId);
          setCreditBalances(balances);
        } catch (error) {
          console.error("Failed to refresh credit balances:", error);
        } finally {
          setIsRefreshingBalances(false);
        }
      };

      const handleFundCard = async () => {
        if (!wallet || !contractAddress) return;
        setLoading(true);
        try {
          const evmWallet = EVMWallet.from(wallet);
          // Mint RUSD tokens
          const mintData = encodeFunctionData({
            abi: [
              {
                name: "mint",
                type: "function",
                inputs: [{ name: "_amountDollars_Max100", type: "uint256" }],
                outputs: [],
              },
            ],
            functionName: "mint",
            args: [BigInt(10)], // Mint $10 RUSD
          });
          await evmWallet.sendTransaction({
            to: RUSD_CONTRACT_BASE_SEPOLIA,
            data: mintData,
            value: BigInt(0),
            chain: "base-sepolia", // Using Base Sepolia - see Rain docs for other supported chains
          });
          // Send RUSD to contract
          await wallet.send(contractAddress, RUSD_CONTRACT_BASE_SEPOLIA, "10");
          // Refresh credit balances after funding
          await handleRefreshCreditBalances();
          setStep("funded");
        } catch (error) {
          console.error("Funding failed:", error);
        } finally {
          setLoading(false);
        }
      };

      // Show login screen if user is not authenticated
      if (!user) {
        return (
          <div className="max-w-md mx-auto p-6 bg-white rounded-lg shadow text-center">
            <h2 className="text-2xl font-bold mb-4">Rain Card Demo</h2>
            <p className="text-gray-600 mb-6">
              Sign in to create your account and get started with crypto-backed
              credit cards
            </p>
            <button
              onClick={login}
              className="bg-blue-600 text-white py-2 px-6 rounded-lg hover:bg-blue-700 transition-colors"
            >
              Login to Get Started
            </button>
          </div>
        );
      }

      // Show loading screen while determining current state
      if (initialLoading) {
        return (
          <div className="max-w-md mx-auto p-6 bg-white rounded-lg shadow text-center">
            <h2 className="text-2xl font-bold mb-4">Rain Card Demo</h2>
            <div className="flex items-center justify-center py-8">
              <div className="w-8 h-8 border-4 border-blue-600 border-t-transparent rounded-full animate-spin" />
              <p className="text-gray-600 ml-3">
                Checking your Rain account status...
              </p>
            </div>
          </div>
        );
      }

      return (
        <div className="max-w-md mx-auto p-6 bg-white rounded-lg shadow">
          {/* Header with user info and logout */}
          <div className="flex justify-between items-center mb-6">
            <h2 className="text-xl font-bold">Rain Card Demo</h2>
            <div className="flex items-center gap-3">
              {wallet && (
                <span className="text-xs text-gray-500">
                  {wallet.address.slice(0, 6)}...{wallet.address.slice(-4)}
                </span>
              )}
              <button
                onClick={logout}
                className="text-sm text-gray-600 hover:text-gray-800 transition-colors"
              >
                Logout
              </button>
            </div>
          </div>
          {/* Card Data Display - Show whenever card data exists */}
          {cardData?.last4 && (
            <div className="mb-6">
              {/* Credit Card Visual */}
              <div className="relative w-full max-w-sm mx-auto aspect-[1.586/1] bg-gray-900 rounded-lg shadow-2xl overflow-hidden">
                {/* Card Background Pattern */}
                <div className="absolute inset-0 opacity-10">
                  <div className="absolute top-4 right-4 w-12 h-12 border-2 border-white rounded-full"></div>
                  <div className="absolute top-4 right-20 w-8 h-8 border-2 border-white rounded-full"></div>
                </div>
                {/* Card Content */}
                <div className="relative h-full p-6 flex flex-col justify-between text-white">
                  {/* Top Section */}
                  <div className="flex justify-between items-start">
                    <div>
                      <div className="text-xs opacity-70 mb-1">VIRTUAL CARD</div>
                      <div className="text-sm font-semibold">Rain Card</div>
                    </div>
                    <div
                      className={`px-2 py-1 rounded text-xs font-medium ${
                        cardData.status === "active"
                          ? "bg-green-500 text-white"
                          : "bg-yellow-500 text-black"
                      }`}
                    >
                      {cardData.status.toUpperCase()}
                    </div>
                  </div>
                  {/* Card Number */}
                  <div className="my-4">
                    <div className="font-mono text-lg tracking-wider">
                      •••• •••• •••• {cardData.last4}
                    </div>
                  </div>
                  {/* Bottom Section */}
                  <div className="flex justify-between items-end">
                    <div>
                      {cardData.expirationMonth && cardData.expirationYear && (
                        <div>
                          <div className="text-xs opacity-70">VALID THRU</div>
                          <div className="font-mono text-sm">
                            {cardData.expirationMonth}/
                            {cardData.expirationYear.slice(-2)}
                          </div>
                        </div>
                      )}
                    </div>
                    <div className="text-right">
                      <div className="text-xs opacity-70">SPENDING POWER</div>
                      <div className="text-sm font-semibold">
                        ${creditBalances?.spendingPower?.toFixed(0) || "0"}
                      </div>
                    </div>
                  </div>
                </div>
              </div>
              {/* Credit Balance Refresh - Only show when card exists */}
              {creditBalances && (
                <div className="text-center mt-2">
                  <button
                    onClick={handleRefreshCreditBalances}
                    disabled={isRefreshingBalances}
                    className="text-xs px-3 py-1 bg-gray-100 text-gray-700 rounded hover:bg-gray-200 disabled:opacity-50"
                  >
                    {isRefreshingBalances ? "Refreshing..." : "Refresh Balance"}
                  </button>
                </div>
              )}
            </div>
          )}
          {step === "signup" && (
            <div>
              <p className="mb-4">
                Create your Rain account and get approved instantly
              </p>
              <button
                onClick={handleSignup}
                disabled={loading || !wallet}
                className="w-full bg-blue-600 text-white py-2 px-4 rounded disabled:opacity-50"
              >
                {loading ? "Creating Account..." : "Sign Up for Rain Card"}
              </button>
            </div>
          )}
          {step === "contract" && (
            <div>
              {!contractData ? (
                <div>
                  <p className="mb-4">
                    ✅ Account approved! Ready to create your smart contract.
                  </p>
                  <button
                    onClick={handleCreateContract}
                    disabled={loading}
                    className="w-full bg-blue-600 text-white py-2 px-4 rounded disabled:opacity-50 mb-4"
                  >
                    {loading
                      ? "Creating Smart Contract..."
                      : "Create Smart Contract"}
                  </button>
                </div>
              ) : (
                <div>
                  <p className="mb-4">
                    🏗️ Smart contract created! Ready to issue your card.
                  </p>
                  <button
                    onClick={handleIssueCard}
                    disabled={loading}
                    className="w-full bg-green-600 text-white py-2 px-4 rounded disabled:opacity-50"
                  >
                    {loading ? "Issuing Card..." : "Issue Virtual Card"}
                  </button>
                </div>
              )}
            </div>
          )}
          {step === "card" && (
            <div>
              <p className="mb-4">🎉 Card issued! Fund it to start spending.</p>

              <button
                onClick={handleFundCard}
                disabled={loading}
                className="w-full bg-purple-600 text-white py-2 px-4 rounded disabled:opacity-50"
              >
                {loading ? "Funding..." : "Fund Card ($10 RUSD)"}
              </button>
            </div>
          )}
          {step === "funded" && (
            <div className="text-center">
              <p className="text-green-600 font-bold mb-2">🎉 Card Ready!</p>
              <p className="text-sm text-gray-600">
                Your card is funded and ready to use for purchases. Balance may take
                a moment to update.
              </p>
            </div>
          )}
        </div>
      );
    }
    ```
  </Step>

  <Step title="Wire Everything Together">
    Update your main page component to use the `RainIntegration` component directly.

    ```tsx app/page.tsx theme={null}
    import { RainIntegration } from "./components/rain-integration";

    export default function HomePage() {
      return (
        <div className="min-h-screen bg-gray-50 py-8">
          <RainIntegration />
        </div>
      );
    }
    ```
  </Step>
</Steps>

## Next Steps

<CardGroup cols={3}>
  <Card title="Learn Token Transfers" icon="credit-card" href="https://docs.crossmint.com/wallets/guides/transfer-tokens">
    Send tokens between wallets and external addresses
  </Card>

  <Card title="Bring Your Own Auth" icon="key" href="https://docs.crossmint.com/wallets/guides/bring-your-own-auth#bring-your-own-auth">
    Use your own authentication system with Crossmint
  </Card>

  <Card title="Staging vs Production" icon="shield-check" href="https://docs.crossmint.com/introduction/platform/staging-vs-production">
    Learn about staging and production environments
  </Card>
</CardGroup>

## Need Help?

<Card title="Contact Sales" icon="comment" href="https://www.crossmint.com/contact/sales">
  Contact our team if you're interested in planning your integration.
</Card>

## Resources

* [Rain API Documentation](https://api-dev.raincards.xyz/v1)
* [Crossmint Wallet SDK](https://docs.crossmint.com/wallets)
* [Demo Repository](https://github.com/Crossmint/rain-wallets-demo)
* [Base Sepolia Explorer](https://sepolia.basescan.org/)
