Overview
Biometric authentication provides a secure and convenient way for users to authenticate without remembering PINs or passwords. The Chipi SDK supports fingerprint and face recognition on compatible devices.Prerequisites
- Expo SDK 49 or later
- Chipi SDK installed and configured
- Device with biometric capabilities (fingerprint sensor or face recognition)
Installation
First, install the required dependencies:Copy
Ask AI
npx expo install expo-local-authentication
Basic Implementation
This is the minimal configuration required to enroll in and use biometrics instead of a PIN to sign transactions.1
Register biometrics along with the PIN
Copy
Ask AI
await SecureStore.setItemAsync("wallet_pin", pin, {
requireAuthentication: true,
});
2
Read the PIN to trigger the biometric prompt
Copy
Ask AI
import { useTransfer } from "@chipi-stack/chipi-expo";
import type { ChainToken } from "@chipi-stack/chipi-expo";
const storedWallet = await SecureStore.getItemAsync("wallet");
const pin = await SecureStore.getItemAsync("wallet_pin",{
requireAuthentication: true,
});
const token = await getToken();
if (!token) {
setError("No bearer token found");
return;
}
const transferResponse = await transferAsync({
params: {
encryptKey: pin,
wallet: JSON.parse(storedWallet),
token: "USDC" as ChainToken,
recipient: recipientAddress,
amount: Number(amount),
},
bearerToken: token,
});
Example
Here’s a simple example of how to implement a secure transfer flow using biometric authentication:- Create Wallet Component
- Send Token with Biometric
To use biometrics for authentication, you first need to create and securely store a wallet with a PIN (which can be protected by biometrics).
Here’s an example wallet creation screen using the
useCreateWallet hook:Copy
Ask AI
// Create Wallet Screen with biometric authentication
import { useCreateWallet } from '@chipi-stack/chipi-expo';
import { useAuth, useUser } from '@clerk/clerk-expo';
import * as SecureStore from 'expo-secure-store';
import { useState } from 'react';
import { StyleSheet, Text, View, Alert, Linking } from 'react-native';
import { PrimaryButton } from '@/components/ui/PrimaryButton';
import { SimpleInput } from '@/components/ui/SimpleInput';
export const CreateWalletView = () => {
const { getToken } = useAuth();
const { user } = useUser();
const [pin, setPin] = useState<string>('');
const [error, setError] = useState('');
const { createWalletAsync, isLoading } = useCreateWallet();
const [walletData, setWalletData] = useState<any>(null);
const handleCreateWallet = async () => {
try {
setError('');
const token = await getToken();
if (!token) {
setError('No bearer token found');
return;
}
const result = await createWalletAsync({
params: {
encryptKey: pin,
externalUserId: user?.id || '',
},
bearerToken: token,
});
// Save wallet data to local storage, protected by biometrics
await SecureStore.setItemAsync('wallet', JSON.stringify(result.wallet));
await SecureStore.setItemAsync('wallet_pin', pin, {
requireAuthentication: true,
});
setWalletData(result);
Alert.alert('Success', 'Wallet successfully created!');
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
setError('Error creating wallet: ' + errorMessage);
}
};
const openStarkscan = (address: string) => {
const url = `https://starkscan.co/contract/${address}`;
Linking.openURL(url);
};
return (
<View style={styles.container}>
<Text style={styles.title}>Create New Wallet</Text>
<Text style={styles.subtitle}>Set a PIN to secure your wallet</Text>
<SimpleInput
placeholder="Enter your PIN (min 4 digits)"
value={pin}
onChangeText={setPin}
keyboardType="numeric"
maxLength={6}
/>
{error ? <Text style={styles.errorText}>{error}</Text> : null}
<PrimaryButton
title={isLoading ? 'Creating...' : 'Create Wallet'}
active={pin.length >= 4 && !isLoading}
onPress={handleCreateWallet}
/>
{walletData && (
<View style={styles.walletDetails}>
<View style={styles.detailHeader}>
<Text style={styles.detailTitle}>Wallet Details</Text>
<Text
style={styles.viewContract}
onPress={() => openStarkscan(walletData.wallet.accountAddress)}>
View Contract →
</Text>
</View>
<View style={styles.detailItem}>
<Text style={styles.detailLabel}>Address:</Text>
<Text style={styles.detailValue} numberOfLines={2}>
{walletData.wallet.accountAddress}
</Text>
</View>
{walletData.wallet.txHash && (
<View style={styles.detailItem}>
<Text style={styles.detailLabel}>TX Hash:</Text>
<Text style={styles.detailValue} numberOfLines={2}>
{walletData.wallet.txHash}
</Text>
</View>
)}
</View>
)}
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
backgroundColor: '#fff',
},
title: {
fontSize: 28,
fontWeight: 'bold',
marginBottom: 8,
color: '#11181C',
},
subtitle: {
fontSize: 16,
color: '#687076',
marginBottom: 24,
},
errorText: {
color: '#ff6b6b',
fontSize: 14,
marginTop: 8,
},
walletDetails: {
marginTop: 32,
padding: 16,
backgroundColor: '#f5f5f5',
borderRadius: 8,
},
detailHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 16,
},
detailTitle: {
fontSize: 18,
fontWeight: '600',
color: '#11181C',
},
viewContract: {
fontSize: 14,
color: '#0a7ea4',
fontWeight: '600',
},
detailItem: {
marginBottom: 12,
},
detailLabel: {
fontSize: 14,
fontWeight: '600',
color: '#687076',
marginBottom: 4,
},
detailValue: {
fontSize: 14,
color: '#11181C',
fontFamily: 'monospace',
},
});
Use the useTransfer hook to send money to another wallet. Setting requireAuthentication: true will prompt for your biometric instead of your PIN.Note: This example assumes you have utility functions
Copy
Ask AI
// Transfer example with biometric authentication
import { useState } from 'react';
import { View, TextInput, TouchableOpacity, StyleSheet, Alert, Text } from 'react-native';
import { useTransfer } from '@chipi-stack/chipi-expo';
import type { ChainToken } from '@chipi-stack/chipi-expo';
import { useAuth } from '@clerk/clerk-expo';
import { getPinStorage, getWalletStorage } from '@/utils/secureStorage';
import { ThemedText } from './themed-text';
export function TransferExample() {
const { getToken } = useAuth();
const { transferAsync, isLoading, error } = useTransfer();
const [recipientAddress, setRecipientAddress] = useState('');
const [amount, setAmount] = useState('');
const handleTransfer = async () => {
try {
if (!recipientAddress || !amount) {
Alert.alert('Error', 'Please enter recipient address and amount');
return;
}
// Get stored wallet and PIN (triggers biometric prompt)
const storedWallet = await getWalletStorage();
const pin = await getPinStorage();
if (!storedWallet || !pin) {
Alert.alert('Error', 'Wallet or PIN not found. Please create a wallet first.');
return;
}
// Get JWT token from Clerk
const token = await getToken();
if (!token) {
Alert.alert('Error', 'No bearer token found');
return;
}
// Execute transfer with updated API
const transferResponse = await transferAsync({
params: {
encryptKey: pin,
wallet: JSON.parse(storedWallet),
token: 'USDC' as ChainToken,
recipient: recipientAddress,
amount: Number(amount),
},
bearerToken: token,
});
Alert.alert('Success', `Transfer completed! TX: ${transferResponse || 'Pending'}`);
setRecipientAddress('');
setAmount('');
} catch (err) {
const errorMessage = err instanceof Error ? err.message : String(err);
Alert.alert('Transfer Error', errorMessage);
}
};
return (
<View style={styles.container}>
<View style={styles.inputContainer}>
<Text style={styles.label}>Recipient Address</Text>
<TextInput
style={styles.input}
value={recipientAddress}
onChangeText={setRecipientAddress}
placeholder="0x..."
/>
</View>
<View style={styles.inputContainer}>
<Text style={styles.label}>Amount (USDC)</Text>
<TextInput
style={styles.input}
value={amount}
onChangeText={setAmount}
placeholder="0.00"
keyboardType="decimal-pad"
/>
</View>
<TouchableOpacity
style={[styles.button, isLoading && styles.buttonDisabled]}
onPress={handleTransfer}
disabled={isLoading || !recipientAddress || !amount}>
<Text style={styles.buttonText}>
{isLoading ? 'Sending...' : 'Send USDC'}
</Text>
</TouchableOpacity>
{error && (
<ThemedText style={styles.errorText}>
Error: {error instanceof Error ? error.message : String(error)}
</ThemedText>
)}
</View>
);
}
const styles = StyleSheet.create({
container: {
gap: 16,
marginTop: 12,
},
inputContainer: {
gap: 8,
},
label: {
fontSize: 14,
fontWeight: '600',
},
input: {
borderWidth: 1,
borderColor: '#ccc',
borderRadius: 8,
padding: 12,
fontSize: 16,
},
button: {
backgroundColor: '#007AFF',
borderRadius: 8,
padding: 16,
alignItems: 'center',
marginTop: 8,
},
buttonDisabled: {
opacity: 0.5,
},
buttonText: {
color: '#fff',
fontSize: 16,
fontWeight: '600',
},
errorText: {
color: '#ff6b6b',
fontSize: 14,
marginTop: 8,
},
});
getPinStorage() and getWalletStorage() in your @/utils/secureStorage file. These functions should use SecureStore.getItemAsync() with requireAuthentication: true for the PIN to trigger biometric prompts.Useful resources
- Making an EAS Build in Expo
Next Steps
Now that you have biometric authentication working, you can:- Integrate it with other Chipi SDK features
- Add biometric authentication to wallet operations
- Implement multi-factor authentication combining biometrics and PINs
- Add biometric authentication to transaction signing
