Skip to main content
This page has been updated for Wallets SDK V1. If you are using the previous version, see the previous version of this page or the V1 migration guide.

Before you start

Set up your project and get an API key.

Expo Wallets Quickstart

See a full working example.
1

Install the SDK

Run the following command to install the SDK:
npm i @crossmint/client-sdk-react-native-ui @crossmint/expo-device-signer expo-secure-store expo-web-browser expo-device
2

Add the Crossmint providers to your app

Add the necessary Crossmint providers to your app. This example uses Crossmint Auth but you can use any authentication provider of your choice.With the current setup, a wallet will be created automatically on login.See all supported chains here.
providers.tsx
import {
    CrossmintProvider,
    CrossmintAuthProvider,
    CrossmintWalletProvider,
} from "@crossmint/client-sdk-react-native-ui";

type ProvidersProps = {
    children: React.ReactNode;
};

export default function CrossmintProviders(
    { children }: ProvidersProps
) {
    return (
        <CrossmintProvider
            apiKey={process.env.EXPO_PUBLIC_CROSSMINT_API_KEY!}
        >
            <CrossmintAuthProvider>
                <CrossmintWalletProvider
                    createOnLogin={{
                        chain: "base-sepolia",
                        recovery: { type: "email" },
                    }}
                >
                    {children}
                </CrossmintWalletProvider>
            </CrossmintAuthProvider>
        </CrossmintProvider>
    );
}
For detailed configuration options, see the React Native SDK Reference.
3

Allow users to login and logout and access their wallet

Crossmint Auth in React Native is headless. You will need to implement your own login and logout buttons.
index.tsx
import React, { useEffect, useState } from "react";
import {
    View,
    Text,
    TextInput,
    TouchableOpacity,
    ActivityIndicator,
    Alert,
} from "react-native";
import {
    useCrossmintAuth,
    useWallet,
} from "@crossmint/client-sdk-react-native-ui";
import * as Linking from "expo-linking";

export default function Index() {
    const {
        loginWithOAuth,
        createAuthSession,
        user,
        crossmintAuth,
        logout,
        status,
    } = useCrossmintAuth();
    const { wallet } = useWallet();

    // Login state
    const [email, setEmail] = useState("");
    const [emailId, setEmailId] = useState("");
    const [otpSent, setOtpSent] = useState(false);
    const [otp, setOtp] = useState("");
    const [isPending, setIsPending] = useState(false);

    // Handle OAuth callback
    const url = Linking.useURL();

    // Handle OAuth callback
    useEffect(() => {
        if (url != null) {
            createAuthSession(url);
        }
    }, [url, createAuthSession]);

    // Login functions
    const sendOtp = async () => {
        if (!email.trim()) {
            Alert.alert("Error", "Please enter a valid email address");
            return;
        }

        setIsPending(true);
        try {
            const res = await crossmintAuth?.sendEmailOtp(email);
            setEmailId(res.emailId);
            setOtpSent(true);
        } catch (error) {
            console.error("sendOtp error:", error);
            const message =
                error instanceof Error
                    ? error.message
                    : "Failed to send OTP.";
            Alert.alert("Error", message);
        } finally {
            setIsPending(false);
        }
    };

    const verifyOtp = async () => {
        if (!otp.trim()) {
            Alert.alert("Error", "Please enter the OTP code");
            return;
        }

        setIsPending(true);
        try {
            const oneTimeSecret =
                await crossmintAuth?.confirmEmailOtp(
                    email,
                    emailId,
                    otp
                );
            await createAuthSession(oneTimeSecret);
        } catch (error) {
            console.error("verifyOtp error:", error);
            const message =
                error instanceof Error
                    ? error.message
                    : "Invalid OTP. Try again.";
            Alert.alert("Error", message);
        } finally {
            setIsPending(false);
        }
    };

    const handleLogout = async () => {
        try {
            logout();
            // Reset login state
            setEmail("");
            setEmailId("");
            setOtpSent(false);
            setOtp("");
        } catch (error) {
            Alert.alert("Error", "Failed to logout. Please try again.");
        }
    };

    // Loading state
    if (status === "initializing") {
        return (
            <View style={{
                flex: 1,
                justifyContent: "center",
                alignItems: "center",
            }}>
                <ActivityIndicator size="large" />
                <Text>Initializing...</Text>
            </View>
        );
    }

    // Login screen
    if (status === "logged-out") {
        return (
            <View style={{
                flex: 1,
                padding: 20,
                justifyContent: "center",
            }}>
                <Text style={{
                    fontSize: 24,
                    marginBottom: 20,
                    textAlign: "center",
                }}>
                    Login
                </Text>

                <TextInput
                    style={{
                        borderWidth: 1,
                        padding: 10,
                        marginBottom: 10,
                    }}
                    placeholder="Enter your email"
                    value={email}
                    onChangeText={setEmail}
                    editable={!otpSent}
                />

                {!otpSent ? (
                    <TouchableOpacity
                        style={{
                            backgroundColor: "#05b959",
                            padding: 15,
                            alignItems: "center",
                        }}
                        onPress={sendOtp}
                        disabled={isPending}
                    >
                        {isPending ? (
                            <ActivityIndicator
                                color="#fff"
                                size="small"
                            />
                        ) : (
                            <Text style={{ color: "#fff" }}>
                                Send OTP
                            </Text>
                        )}
                    </TouchableOpacity>
                ) : (
                    <>
                        <TextInput
                            style={{
                                borderWidth: 1,
                                padding: 10,
                                marginBottom: 10,
                            }}
                            placeholder="Enter OTP code"
                            value={otp}
                            onChangeText={setOtp}
                        />
                        <TouchableOpacity
                            style={{
                                backgroundColor: "#05b959",
                                padding: 15,
                                alignItems: "center",
                                marginBottom: 10,
                            }}
                            onPress={verifyOtp}
                            disabled={isPending}
                        >
                            {isPending ? (
                                <ActivityIndicator
                                    color="#fff"
                                    size="small"
                                />
                            ) : (
                                <Text style={{ color: "#fff" }}>
                                    Verify OTP
                                </Text>
                            )}
                        </TouchableOpacity>
                        <TouchableOpacity
                            style={{
                                backgroundColor: "#ccc",
                                padding: 15,
                                alignItems: "center",
                            }}
                            onPress={() => setOtpSent(false)}
                            disabled={isPending}
                        >
                            <Text>Back</Text>
                        </TouchableOpacity>
                    </>
                )}

                <View style={{ marginVertical: 20 }}>
                    <Text style={{
                        textAlign: "center",
                        marginBottom: 10,
                    }}>
                        OR
                    </Text>
                    <TouchableOpacity
                        style={{
                            backgroundColor: "#4285f4",
                            padding: 15,
                            alignItems: "center",
                        }}
                        onPress={
                            () => loginWithOAuth("google")
                        }
                    >
                        <Text style={{ color: "#fff" }}>
                            Sign in with Google
                        </Text>
                    </TouchableOpacity>
                </View>
            </View>
        );
    }

    // Main app screen (logged in)
    return (
        <View style={{ flex: 1, padding: 20 }}>
            <View
                style={{
                    flexDirection: "row",
                    justifyContent: "space-between",
                    marginBottom: 20,
                }}
            >
                <Text style={{ fontSize: 20 }}>
                    Welcome!
                </Text>
                <TouchableOpacity onPress={handleLogout}>
                    <Text style={{ color: "red" }}>
                        Logout
                    </Text>
                </TouchableOpacity>
            </View>

            <View style={{ marginBottom: 20 }}>
                <Text style={{
                    fontSize: 16,
                    marginBottom: 5,
                }}>
                    User Info:
                </Text>
                <Text>Email: {user?.email}</Text>
                <Text>User ID: {user?.id}</Text>
            </View>

            {wallet && (
                <View style={{ marginBottom: 20 }}>
                    <Text style={{
                        fontSize: 16,
                        marginBottom: 5,
                    }}>
                        Wallet Info:
                    </Text>
                    <Text>
                        Address: {wallet.address}
                    </Text>
                </View>
            )}

            <View>
                <Text style={{
                    fontSize: 16,
                    marginBottom: 10,
                }}>
                    App Content:
                </Text>
                <Text>
                    You are now logged in and can
                    access your wallet!
                </Text>
            </View>
        </View>
    );
}

Launching in Production

For production, some changes are required:
  1. Create a developer account on the production console
  2. Create a production client API key on the API Keys page with the API scopes users.create, users.read, wallets.read, wallets.create, wallets:transactions.create, wallets:transactions.sign, wallets:balance.read, wallets.fund
  3. Replace your test API key with the production key
  4. Use your own authentication provider: For production applications, Crossmint recommends using third-party authentication with providers like Auth0, Firebase, or Supabase, rather than Crossmint Auth. Configure JWT authentication in the Crossmint Console under API Keys > JWT Authentication.

Learn More

Check Balances

Check the balance of a wallet.

Transfer Tokens

Send tokens between wallets.

Operational Signers

Register operational signers on a wallet.

API Reference

Deep dive into API reference docs.

Talk to an expert

Contact our sales team for support.