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:
Copy
Ask AI
npm i @crossmint/client-sdk-react-native-ui
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
Copy
Ask AI
import {
CrossmintAuthProvider,
CrossmintProvider,
CrossmintWalletProvider,
} from "@crossmint/client-sdk-react-native-ui";
type ProvidersProps = {
children: React.ReactNode;
};
export default function CrossmintProviders({ children }: ProvidersProps) {
return (
<CrossmintProvider apiKey={"<your-client-api-key>"}>
<CrossmintAuthProvider>
<CrossmintWalletProvider
createOnLogin={{
chain: "<your-chain>",
signer: {
type: "<signer-type>",
}
}}
>
{children}
</CrossmintWalletProvider>
</CrossmintAuthProvider>
</CrossmintProvider>
);
}
Configuring the Wallet Provider
If set creates a wallet on login using the specified configuration.
Show properties
Show properties
The chain to use the wallet on.See all supported chains for more details. On staging only testnet chains are supported.Note: For EVM-compatible chains, wallets are created for all
chains as part of the shared address space derived from the same private key. However, to
interact with a specific chain using the SDK, you must instantiate a wallet object per chain.
This allows the SDK to correctly route interactions to the appropriate network configuration.
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
Copy
Ask AI
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
useEffect(() => {
const url = Linking.useURL();
if (url != null) {
createAuthSession(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) {
Alert.alert("Error", "Failed to send OTP. Please try again.");
} 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) {
Alert.alert("Error", "Invalid OTP code. Please try again.");
} 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, the steps are almost identical, but some changes are required:- Create a developer account on the production console
- 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
- Replace your test API key with the production key
Learn More
Check Balances
Check the balance of a wallet.
Transfer Tokens
Send tokens between wallets.
Delegated Signers
Add delegated signers to a wallet.