Create user wallets from your frontend in under 5 minutes
Install the SDK
npm i @crossmint/client-sdk-react-native-ui
Add the Crossmint providers to your app
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>
);
}
Show properties
email:<email>
userId:<userId>
phoneNumber:<phoneNumber>
twitter:<handle>
x:<handle>
(alias for twitter:<handle>
)Allow users to login and logout and access their wallet
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>
);
}
users.create
, users.read
, wallets.read
, wallets.create
, wallets:transactions.create
, wallets:transactions.sign
, wallets:balance.read
, wallets.fund