Complete Example (Legacy)
A single runnable integration you can copy into a fresh React Native app: dev-stub adapters, event listeners wired to a navigator, and the screens assembled into one App.
This page is only the assembly. The reference material it builds on lives elsewhere and is not repeated here:
- Native Modules Setup — the Android and iOS config you must complete first.
- SelfClient Provider Setup — what each adapter must implement, and the full
SdkEventscatalog. - Onboarding Screen Components — every screen’s props and error handling.
1. Adapters
All five adapters are required (auth, scanner, network, crypto, documents). The stubs below are the smallest set that runs — see Required Adapters for what each one is actually for and what a production implementation needs.
import {
createListenersMap,
SdkEvents,
SelfClientProvider,
webNFCScannerShim,
type Adapters,
type Config,
} from '@selfxyz/mobile-sdk-alpha';
const createAdapters = (): Adapters => ({
auth: {
// you MUST provide a private key to the sdk that will be used when generating the zk circuits
async getPrivateKey(): Promise<string | null> {
// In production, get from secure storage
return '0x' + 'a'.repeat(64); // Dummy key for demo
},
},
scanner: webNFCScannerShim, // Use web shim for development
network: {
http: {
fetch: (input: RequestInfo, init?: RequestInit) => fetch(input, init),
},
ws: {
connect: (url: string) => {
const socket = new WebSocket(url);
return {
send: (data) => socket.send(data),
close: () => socket.close(),
onMessage: (cb) => socket.addEventListener('message', (ev) => cb(ev.data)),
onError: (cb) => socket.addEventListener('error', cb),
onClose: (cb) => socket.addEventListener('close', cb),
};
},
},
},
crypto: {
async hash(data: Uint8Array): Promise<Uint8Array> {
// Use Web Crypto API
const buffer = await crypto.subtle.digest('SHA-256', data);
return new Uint8Array(buffer);
},
async sign(_data: Uint8Array, _keyRef: string): Promise<Uint8Array> {
throw new Error('Signing not implemented in minimal example');
},
},
documents: {
async loadDocumentCatalog() {
return { documents: [] };
},
async saveDocumentCatalog(catalog) {
console.log('Save catalog:', catalog);
},
async loadDocumentById(id: string) {
return null;
},
async saveDocument(id: string, document) {
console.log('Save document:', id, document);
},
async deleteDocument(id: string) {
console.log('Delete document:', id);
},
},
// Optional: minimal analytics
analytics: {
trackEvent: (event: string, payload?: any) => {
console.log('Analytics:', event, payload);
},
},
});
2. Navigation listeners
The SDK is event-driven: screens emit events, and your listeners decide where to go next. Building them in a factory that takes the navigator keeps the routing in one place.
function createNavigationListeners(navigation) {
const { map, addListener } = createListenersMap();
// Country selection -> ID picker
addListener(SdkEvents.DOCUMENT_COUNTRY_SELECTED, ({ countryCode, documentTypes }) => {
navigation.navigate('IDPicker', { countryCode, documentTypes });
});
// Document type selection -> appropriate flow
addListener(SdkEvents.DOCUMENT_TYPE_SELECTED, ({ documentType, countryCode }) => {
switch (documentType) {
case 'p': // Passport
case 'i': // ID Card
navigation.navigate('DocumentCamera');
break;
case 'a': // Aadhaar
navigation.navigate('AadhaarUpload', { countryCode });
break;
case 'kyc': // KYC
navigation.navigate('KycIntro', { countryCode });
break;
default:
navigation.navigate('ComingSoon', { documentType, countryCode });
}
});
// MRZ scan success -> NFC scanning
addListener(SdkEvents.DOCUMENT_MRZ_READ_SUCCESS, () => {
navigation.navigate('NFCScan');
});
// MRZ scan failure -> troubleshooting
addListener(SdkEvents.DOCUMENT_MRZ_READ_FAILURE, () => {
navigation.navigate('DocumentTrouble');
});
return map;
}
Event flow
CountryPickerScreen
↓ (DOCUMENT_COUNTRY_SELECTED)
IDSelectionScreen
↓ (DOCUMENT_TYPE_SELECTED)
DocumentCameraScreen
↓ (DOCUMENT_MRZ_READ_SUCCESS)
[Your NFC Screen]
3. Screen wrappers
Each SDK screen is imported from its own path and wrapped so the navigator can pass it route params and back handlers.
import { SafeAreaView } from 'react-native';
import { DocumentCameraScreen } from '@selfxyz/mobile-sdk-alpha/onboarding/document-camera-screen';
import IDSelectionScreen from '@selfxyz/mobile-sdk-alpha/onboarding/id-selection-screen';
import SDKCountryPickerScreen from '@selfxyz/mobile-sdk-alpha/onboarding/country-picker-screen';
function CountryPickerScreen() {
return (
<SafeAreaView style={{ flex: 1 }}>
<SDKCountryPickerScreen />
</SafeAreaView>
);
}
function IDPickerScreen({ route }) {
const { countryCode, documentTypes } = route.params;
return (
<SafeAreaView style={{ flex: 1 }}>
<IDSelectionScreen countryCode={countryCode} documentTypes={documentTypes} />
</SafeAreaView>
);
}
function DocumentCameraStackScreen({ navigation }) {
return (
<SafeAreaView style={{ flex: 1 }}>
<DocumentCameraScreen
onBack={() => navigation.goBack()}
onSuccess={() => {
console.log('MRZ scan successful!');
}}
/>
</SafeAreaView>
);
}
4. Assemble the app
SelfClientProvider wraps everything, so every SDK screen must render inside it.
import React, { useMemo } from 'react';
import { NavigationContainer, createNavigationContainerRef } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
const Stack = createStackNavigator();
const navigationRef = createNavigationContainerRef();
export default function App() {
const config: Config = useMemo(() => ({}), []);
const adapters: Adapters = useMemo(() => createAdapters(), []);
const listeners = useMemo(() => createNavigationListeners(navigationRef), []);
return (
<SelfClientProvider config={config} adapters={adapters} listeners={listeners}>
<NavigationContainer ref={navigationRef}>
<Stack.Navigator initialRouteName="CountryPicker">
<Stack.Screen
name="CountryPicker"
component={CountryPickerScreen}
options={{ title: 'Select Country' }}
/>
<Stack.Screen
name="IDPicker"
component={IDPickerScreen}
options={{ title: 'Select Document Type' }}
/>
<Stack.Screen
name="DocumentCamera"
component={DocumentCameraStackScreen}
options={{ title: 'Scan Document' }}
/>
</Stack.Navigator>
</NavigationContainer>
</SelfClientProvider>
);
}
The AadhaarUpload, KycIntro, NFCScan, DocumentTrouble, and ComingSoon routes referenced by the listeners are yours to build — register them alongside the three above as you add each flow.
Thanks — what went wrong?
Thanks for your feedback!