diff --git a/.github/workflows/mobile-sdk-demo-e2e.yml b/.github/workflows/mobile-sdk-demo-e2e.yml
index 2d7b6e8ed..58eb6440d 100644
--- a/.github/workflows/mobile-sdk-demo-e2e.yml
+++ b/.github/workflows/mobile-sdk-demo-e2e.yml
@@ -31,6 +31,7 @@ on:
jobs:
android-e2e:
+ name: Android E2E Tests Demo App
# Currently build-only for Android. E2E steps are preserved but skipped (if: false).
# To re-enable full E2E: change `if: false` to `if: true` on emulator steps.
concurrency:
@@ -192,6 +193,7 @@ jobs:
ios-e2e:
timeout-minutes: 60
runs-on: macos-latest-large
+ name: iOS E2E Tests Demo App
concurrency:
group: ${{ github.workflow }}-ios-${{ github.ref }}
cancel-in-progress: true
diff --git a/app/ios/Podfile.lock b/app/ios/Podfile.lock
index 0ec54a0a5..60745d8c4 100644
--- a/app/ios/Podfile.lock
+++ b/app/ios/Podfile.lock
@@ -2201,7 +2201,7 @@ DEPENDENCIES:
- lottie-ios
- lottie-react-native (from `../node_modules/lottie-react-native`)
- Mixpanel-swift (~> 5.0.0)
- - "NFCPassportReader (from `git@github.com:selfxyz/NFCPassportReader.git`, commit `9eff7c4e3a9037fdc1e03301584e0d5dcf14d76b`)"
+ - NFCPassportReader (from `https://github.com/selfxyz/NFCPassportReader.git`, commit `9eff7c4e3a9037fdc1e03301584e0d5dcf14d76b`)
- QKMRZScanner
- RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
- RCT-Folly/Fabric (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
@@ -2340,7 +2340,7 @@ EXTERNAL SOURCES:
:path: "../node_modules/lottie-react-native"
NFCPassportReader:
:commit: 9eff7c4e3a9037fdc1e03301584e0d5dcf14d76b
- :git: "git@github.com:selfxyz/NFCPassportReader.git"
+ :git: https://github.com/selfxyz/NFCPassportReader.git
RCT-Folly:
:podspec: "../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec"
RCTDeprecation:
@@ -2517,15 +2517,15 @@ EXTERNAL SOURCES:
CHECKOUT OPTIONS:
NFCPassportReader:
:commit: 9eff7c4e3a9037fdc1e03301584e0d5dcf14d76b
- :git: "git@github.com:selfxyz/NFCPassportReader.git"
+ :git: https://github.com/selfxyz/NFCPassportReader.git
SwiftQRScanner:
:commit: c71ff91297640a944de4bca61434155c3f9b0979
:git: https://github.com/vinodiOS/SwiftQRScanner
SPEC CHECKSUMS:
AppAuth: 1c1a8afa7e12f2ec3a294d9882dfa5ab7d3cb063
- boost: 1dca942403ed9342f98334bf4c3621f011aa7946
- DoubleConversion: f16ae600a246532c4020132d54af21d0ddb2a385
+ boost: 4cb898d0bf20404aab1850c656dcea009429d6c1
+ DoubleConversion: 76ab83afb40bddeeee456813d9c04f67f78771b5
fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6
FBLazyVector: 7605ea4810e0e10ae4815292433c09bf4324ba45
Firebase: 91fefd38712feb9186ea8996af6cbdef41473442
@@ -2540,7 +2540,7 @@ SPEC CHECKSUMS:
FirebaseRemoteConfigInterop: 6efda51fb5e2f15b16585197e26eaa09574e8a4d
FirebaseSharedSwift: 20530f495084b8d840f78a100d8c5ee613375f6e
fmt: 01b82d4ca6470831d1cc0852a1af644be019e8f6
- glog: 08b301085f15bcbb6ff8632a8ebaf239aae04e6a
+ glog: 69ef571f3de08433d766d614c73a9838a06bf7eb
GoogleAppMeasurement: f3abf08495ef2cba7829f15318c373b8d9226491
GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a
GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15
diff --git a/app/package.json b/app/package.json
index 32acd1c1a..04456a425 100644
--- a/app/package.json
+++ b/app/package.json
@@ -74,10 +74,14 @@
"web:preview": "vite preview"
},
"resolutions": {
- "punycode": "npm:punycode.js@2.3.1"
+ "punycode": "npm:punycode.js@2.3.1",
+ "react-native-blur-effect": "1.1.3",
+ "react-native-webview": "13.16.0"
},
"overrides": {
- "punycode": "npm:punycode.js@2.3.1"
+ "punycode": "npm:punycode.js@2.3.1",
+ "react-native-blur-effect": "1.1.3",
+ "react-native-webview": "13.16.0"
},
"dependencies": {
"@babel/runtime": "^7.28.3",
@@ -137,6 +141,7 @@
"react-native": "0.76.9",
"react-native-app-auth": "^8.0.3",
"react-native-biometrics": "^3.0.1",
+ "react-native-blur-effect": "^1.1.3",
"react-native-check-version": "^1.3.0",
"react-native-cloud-storage": "^2.2.2",
"react-native-device-info": "^14.0.4",
diff --git a/app/src/providers/selfClientProvider.tsx b/app/src/providers/selfClientProvider.tsx
index 1006174a5..34cf31c67 100644
--- a/app/src/providers/selfClientProvider.tsx
+++ b/app/src/providers/selfClientProvider.tsx
@@ -89,6 +89,23 @@ export const SelfClientProvider = ({ children }: PropsWithChildren) => {
},
},
documents: selfClientDocumentsAdapter,
+ navigation: {
+ goBack: () => {
+ if (navigationRef.isReady()) {
+ navigationRef.goBack();
+ }
+ },
+ goTo: (routeName, params) => {
+ if (navigationRef.isReady()) {
+ if (params !== undefined) {
+ // @ts-expect-error
+ navigationRef.navigate(routeName, params);
+ } else {
+ navigationRef.navigate(routeName as never);
+ }
+ }
+ },
+ },
crypto: {
async hash(
data: Uint8Array,
diff --git a/app/src/screens/app/LoadingScreen.tsx b/app/src/screens/app/LoadingScreen.tsx
index 2173e088d..9d9e9deae 100644
--- a/app/src/screens/app/LoadingScreen.tsx
+++ b/app/src/screens/app/LoadingScreen.tsx
@@ -9,6 +9,7 @@ import type { StaticScreenProps } from '@react-navigation/native';
import { useFocusEffect, useIsFocused } from '@react-navigation/native';
import type { DocumentCategory } from '@selfxyz/common/utils/types';
+import type { ProvingStateType } from '@selfxyz/mobile-sdk-alpha';
import {
advercase,
dinot,
@@ -17,7 +18,6 @@ import {
} from '@selfxyz/mobile-sdk-alpha';
import failAnimation from '@selfxyz/mobile-sdk-alpha/animations/loading/fail.json';
import proveLoadingAnimation from '@selfxyz/mobile-sdk-alpha/animations/loading/prove.json';
-import type { ProvingStateType } from '@selfxyz/mobile-sdk-alpha/browser';
import {
black,
slate400,
diff --git a/app/src/screens/documents/selection/CountryPickerScreen.tsx b/app/src/screens/documents/selection/CountryPickerScreen.tsx
index 3b1b5ef45..0ab66bf3e 100644
--- a/app/src/screens/documents/selection/CountryPickerScreen.tsx
+++ b/app/src/screens/documents/selection/CountryPickerScreen.tsx
@@ -2,17 +2,8 @@
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
-import { YStack } from '@selfxyz/mobile-sdk-alpha/components';
-import { slate100 } from '@selfxyz/mobile-sdk-alpha/constants/colors';
import SDKCountryPickerScreen from '@selfxyz/mobile-sdk-alpha/onboarding/country-picker-screen';
-import { DocumentFlowNavBar } from '@/components/navbar/DocumentFlowNavBar';
-
export default function CountryPickerScreen() {
- return (
-
-
-
-
- );
+ return ;
}
diff --git a/circuits/package.json b/circuits/package.json
index 32209e56e..6d1d22fdf 100644
--- a/circuits/package.json
+++ b/circuits/package.json
@@ -52,7 +52,7 @@
"@zk-email/zk-regex-circom": "^1.2.1",
"@zk-kit/binary-merkle-root.circom": "2.0.0",
"@zk-kit/circuits": "^1.0.0-beta",
- "anon-aadhaar-circuits": "https://gitpkg.vercel.app/selfxyz/anon-aadhaar/packages/circuits?main",
+ "anon-aadhaar-circuits": "https://github.com/selfxyz/anon-aadhaar.git#commit=1b9efa501cff3cf25dc260b060bf611229e316a4&workspace=@anon-aadhaar/circuits",
"asn1": "^0.2.6",
"asn1.js": "^5.4.1",
"asn1js": "^3.0.5",
diff --git a/circuits/tests/disclose/vc_and_disclose_aadhaar.test.ts b/circuits/tests/disclose/vc_and_disclose_aadhaar.test.ts
index cf1aace15..a3e9010bc 100644
--- a/circuits/tests/disclose/vc_and_disclose_aadhaar.test.ts
+++ b/circuits/tests/disclose/vc_and_disclose_aadhaar.test.ts
@@ -17,9 +17,28 @@ import nameAndYobAadhaarjson from '../consts/ofac/nameAndYobAadhaarSMT.json' wit
import fs from 'fs';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
-// const privateKeyPath = path.join(__dirname, '../../../node_modules/anon-aadhaar-circuits/assets/testPrivateKey.pem');
+
+// Dynamically resolve the anon-aadhaar-circuits package location
+function resolvePackagePath(packageName: string, subpath: string): string {
+ try {
+ // Try to resolve the package's package.json
+ const packageJsonPath = require.resolve(`${packageName}/package.json`, {
+ paths: [__dirname],
+ });
+ const packageDir = path.dirname(packageJsonPath);
+ return path.join(packageDir, subpath);
+ } catch (error) {
+ // Fallback to traditional node_modules search
+ const modulePath = path.join(__dirname, '../../node_modules', packageName, subpath);
+ if (fs.existsSync(modulePath)) {
+ return modulePath;
+ }
+ throw new Error(`Could not resolve ${packageName}/${subpath}`);
+ }
+}
+
const privateKeyPem = fs.readFileSync(
- path.join(__dirname, '../../node_modules/anon-aadhaar-circuits/assets/testPrivateKey.pem'),
+ resolvePackagePath('anon-aadhaar-circuits', 'assets/testPrivateKey.pem'),
'utf8'
);
diff --git a/package.json b/package.json
index e6a7715bb..866a27c10 100644
--- a/package.json
+++ b/package.json
@@ -47,7 +47,9 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-native": "0.76.9",
- "react-native-passkey": "3.3.1"
+ "react-native-blur-effect": "1.1.3",
+ "react-native-passkey": "3.3.1",
+ "react-native-webview": "13.16.0"
},
"dependencies": {
"@babel/runtime": "^7.28.3",
diff --git a/packages/mobile-sdk-alpha/package.json b/packages/mobile-sdk-alpha/package.json
index d2768df27..b1709b082 100644
--- a/packages/mobile-sdk-alpha/package.json
+++ b/packages/mobile-sdk-alpha/package.json
@@ -151,6 +151,7 @@
"dependencies": {
"@babel/runtime": "^7.28.3",
"@selfxyz/common": "workspace:^",
+ "@selfxyz/euclid": "^0.4.1",
"@xstate/react": "^5.0.5",
"node-forge": "^1.3.1",
"react-native-nfc-manager": "^3.17.1",
@@ -191,9 +192,11 @@
"lottie-react-native": "7.2.2",
"react": "^18.3.1",
"react-native": "0.76.9",
+ "react-native-blur-effect": "^1.1.3",
"react-native-haptic-feedback": "*",
"react-native-localize": "*",
- "react-native-svg": "*"
+ "react-native-svg": "*",
+ "react-native-webview": "^13.16.0"
},
"packageManager": "yarn@4.6.0",
"publishConfig": {
diff --git a/packages/mobile-sdk-alpha/src/client.ts b/packages/mobile-sdk-alpha/src/client.ts
index 5845869ad..a03337ad3 100644
--- a/packages/mobile-sdk-alpha/src/client.ts
+++ b/packages/mobile-sdk-alpha/src/client.ts
@@ -42,7 +42,7 @@ const optionalDefaults: Required> = {
},
};
-const REQUIRED_ADAPTERS = ['auth', 'scanner', 'network', 'crypto', 'documents'] as const;
+const REQUIRED_ADAPTERS = ['auth', 'scanner', 'network', 'crypto', 'documents', 'navigation'] as const;
export const createListenersMap = (): {
map: Map void>>;
@@ -212,7 +212,12 @@ export function createSelfClient({
getMRZState: () => {
return useMRZStore.getState();
},
-
+ goBack: () => {
+ adapters.navigation.goBack();
+ },
+ goTo: (routeName, params) => {
+ adapters.navigation.goTo(routeName, params);
+ },
// for reactivity (if needed)
useProvingStore,
useSelfAppStore,
diff --git a/packages/mobile-sdk-alpha/src/flows/onboarding/country-picker-screen.tsx b/packages/mobile-sdk-alpha/src/flows/onboarding/country-picker-screen.tsx
index 83f1bef80..32ff30b05 100644
--- a/packages/mobile-sdk-alpha/src/flows/onboarding/country-picker-screen.tsx
+++ b/packages/mobile-sdk-alpha/src/flows/onboarding/country-picker-screen.tsx
@@ -2,67 +2,26 @@
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
-import { memo, useCallback } from 'react';
-import { ActivityIndicator, FlatList, StyleSheet, TouchableOpacity, View } from 'react-native';
+import { useCallback, useState } from 'react';
import { commonNames } from '@selfxyz/common/constants/countries';
+import { CountryPickerScreen as CountryPickerUI } from '@selfxyz/euclid';
-import { BodyText, RoundFlag, XStack, YStack } from '../../components';
-import { black, slate100, slate500 } from '../../constants/colors';
-import { advercase, dinot } from '../../constants/fonts';
+import { RoundFlag } from '../../components';
import { useSelfClient } from '../../context';
import { useCountries } from '../../documents/useCountries';
import { buttonTap } from '../../haptic';
import { SdkEvents } from '../../types/events';
-interface CountryListItem {
- key: string;
- countryCode: string;
-}
-
-const ITEM_HEIGHT = 65;
-const FLAG_SIZE = 32;
-
-const CountryItem = memo<{
- countryCode: string;
- onSelect: (code: string) => void;
-}>(({ countryCode, onSelect }) => {
- const countryName = commonNames[countryCode as keyof typeof commonNames];
-
- if (!countryName) return null;
-
- return (
- onSelect(countryCode)} style={styles.countryItemContainer}>
-
-
- {countryName}
-
-
- );
-});
-
-CountryItem.displayName = 'CountryItem';
-
-const Loading = () => (
-
-
-
-);
-Loading.displayName = 'Loading';
-
const CountryPickerScreen: React.FC = () => {
const selfClient = useSelfClient();
+ const [searchValue, setSearchValue] = useState('');
const { countryData, countryList, loading, userCountryCode, showSuggestion } = useCountries();
- const onPressCountry = useCallback(
+ const onCountrySelect = useCallback(
(countryCode: string) => {
buttonTap();
- // if (__DEV__) {
- // console.log('Selected country code:', countryCode);
- // console.log('Current countryData:', countryData);
- // console.log('Available country codes:', Object.keys(countryData));
- // }
const documentTypes = countryData[countryCode];
if (__DEV__) {
console.log('documentTypes for', countryCode, ':', documentTypes);
@@ -87,105 +46,34 @@ const CountryPickerScreen: React.FC = () => {
[countryData, selfClient],
);
- const renderItem = useCallback(
- ({ item }: { item: CountryListItem }) => ,
- [onPressCountry],
- );
+ const renderFlag = useCallback((countryCode: string, size: number) => {
+ return ;
+ }, []);
- const keyExtractor = useCallback((item: CountryListItem) => item.countryCode, []);
+ const getCountryName = useCallback((countryCode: string) => {
+ return commonNames[countryCode as keyof typeof commonNames] || countryCode;
+ }, []);
- const getItemLayout = useCallback(
- (_data: ArrayLike | null | undefined, index: number) => ({
- length: ITEM_HEIGHT,
- offset: ITEM_HEIGHT * index,
- index,
- }),
- [],
- );
+ const onSearchChange = useCallback((value: string) => {
+ setSearchValue(value);
+ }, []);
return (
-
-
- Select the country that issued your ID
-
- Self has support for over 300 ID types. You can select the type of ID in the next step
-
-
- {loading ? (
-
- ) : (
-
- {showSuggestion && (
-
- SUGGESTION
-
- SELECT AN ISSUING COUNTRY
-
- )}
-
-
- )}
-
+ console.log('Info pressed TODO: Implement')}
+ onSearchChange={onSearchChange}
+ />
);
};
CountryPickerScreen.displayName = 'CountryPickerScreen';
-const styles = StyleSheet.create({
- countryItemContainer: {
- paddingVertical: 13,
- },
- countryItemContent: {
- alignItems: 'center',
- gap: 16,
- },
- countryItemText: {
- fontSize: 16,
- color: black,
- flex: 1,
- },
- loadingContainer: {
- flex: 1,
- justifyContent: 'center',
- alignItems: 'center',
- },
- titleText: {
- fontSize: 29,
- fontFamily: advercase,
- color: black,
- },
- subtitleText: {
- fontSize: 16,
- color: slate500,
- marginTop: 20,
- },
- sectionLabel: {
- fontSize: 16,
- color: black,
- fontFamily: dinot,
- letterSpacing: 0.8,
- marginBottom: 8,
- },
- sectionLabelBottom: {
- fontSize: 16,
- color: black,
- fontFamily: dinot,
- letterSpacing: 0.8,
- marginTop: 20,
- },
-});
-
export default CountryPickerScreen;
diff --git a/packages/mobile-sdk-alpha/src/index.ts b/packages/mobile-sdk-alpha/src/index.ts
index 51bf05283..b1517f99c 100644
--- a/packages/mobile-sdk-alpha/src/index.ts
+++ b/packages/mobile-sdk-alpha/src/index.ts
@@ -17,8 +17,10 @@ export type {
MRZValidation,
NFCScanResult,
NFCScannerAdapter,
+ NavigationAdapter,
NetworkAdapter,
Progress,
+ RouteName,
SelfClient,
StorageAdapter,
TrackEventParams,
diff --git a/packages/mobile-sdk-alpha/src/types/public.ts b/packages/mobile-sdk-alpha/src/types/public.ts
index 149f63154..c24c40a61 100644
--- a/packages/mobile-sdk-alpha/src/types/public.ts
+++ b/packages/mobile-sdk-alpha/src/types/public.ts
@@ -199,6 +199,36 @@ export interface Adapters {
auth: AuthAdapter;
/** Required document persistence layer. Implementations must be idempotent. */
documents: DocumentsAdapter;
+ /** Required navigation adapter for handling screen transitions. */
+ navigation: NavigationAdapter;
+}
+
+/**
+ * Map these route names to your navigation configuration.
+ * Includes all screens that the SDK may navigate to across host applications.
+ */
+export type RouteName =
+ // Document acquisition flow
+ | 'DocumentCamera'
+ | 'DocumentOnboarding'
+ | 'CountryPicker'
+ | 'IDPicker'
+ | 'DocumentNFCScan'
+ | 'ManageDocuments'
+ // Account/onboarding flow
+ | 'Home'
+ | 'AccountVerifiedSuccess'
+ | 'AccountRecoveryChoice'
+ | 'SaveRecoveryPhrase'
+ // Error/fallback screens
+ | 'ComingSoon'
+ | 'DocumentDataNotFound'
+ // Settings
+ | 'Settings';
+
+export interface NavigationAdapter {
+ goBack(): void;
+ goTo(routeName: RouteName, params?: Record): void;
}
/**
@@ -284,6 +314,8 @@ export interface SelfClient {
scanNFC(opts: NFCScanOpts & { signal?: AbortSignal }): Promise;
/** Parses MRZ text and returns structured fields plus checksum metadata. */
extractMRZInfo(mrz: string): MRZInfo;
+ goBack(): void;
+ goTo(routeName: RouteName, params?: Record): void;
/**
* Convenience wrapper around {@link AnalyticsAdapter.trackEvent}. Calls are
diff --git a/packages/mobile-sdk-alpha/tests/client.test.ts b/packages/mobile-sdk-alpha/tests/client.test.ts
index bf8fab887..a6e842a0a 100644
--- a/packages/mobile-sdk-alpha/tests/client.test.ts
+++ b/packages/mobile-sdk-alpha/tests/client.test.ts
@@ -6,7 +6,7 @@ import { describe, expect, it, vi } from 'vitest';
import type { CryptoAdapter, DocumentsAdapter, NetworkAdapter, NFCScannerAdapter } from '../src';
import { createListenersMap, createSelfClient, SdkEvents } from '../src/index';
-import type { AuthAdapter } from '../src/types/public';
+import type { AuthAdapter, NavigationAdapter } from '../src/types/public';
describe('createSelfClient', () => {
// Test eager validation during client creation
@@ -27,21 +27,21 @@ describe('createSelfClient', () => {
it('throws when network adapter missing during creation', () => {
// @ts-expect-error -- missing adapters
- expect(() => createSelfClient({ config: {}, adapters: { scanner, crypto, documents, auth } })).toThrow(
+ expect(() => createSelfClient({ config: {}, adapters: { scanner, crypto, documents, auth, navigation } })).toThrow(
'network adapter not provided',
);
});
it('throws when crypto adapter missing during creation', () => {
// @ts-expect-error -- missing adapters
- expect(() => createSelfClient({ config: {}, adapters: { scanner, network, documents, auth } })).toThrow(
+ expect(() => createSelfClient({ config: {}, adapters: { scanner, network, documents, auth, navigation } })).toThrow(
'crypto adapter not provided',
);
});
it('throws when documents adapter missing during creation', () => {
// @ts-expect-error -- missing adapters
- expect(() => createSelfClient({ config: {}, adapters: { scanner, network, crypto, auth } })).toThrow(
+ expect(() => createSelfClient({ config: {}, adapters: { scanner, network, crypto, auth, navigation } })).toThrow(
'documents adapter not provided',
);
});
@@ -49,7 +49,7 @@ describe('createSelfClient', () => {
it('creates client successfully with all required adapters', () => {
const client = createSelfClient({
config: {},
- adapters: { scanner, network, crypto, documents, auth },
+ adapters: { scanner, network, crypto, documents, auth, navigation },
listeners: new Map(),
});
expect(client).toBeTruthy();
@@ -59,7 +59,7 @@ describe('createSelfClient', () => {
const scanMock = vi.fn().mockResolvedValue({ passportData: { mock: true } });
const client = createSelfClient({
config: {},
- adapters: { scanner: { scan: scanMock }, network, crypto, documents, auth },
+ adapters: { scanner: { scan: scanMock }, network, crypto, documents, auth, navigation },
listeners: new Map(),
});
const result = await client.scanNFC({
@@ -85,7 +85,7 @@ describe('createSelfClient', () => {
const scanMock = vi.fn().mockRejectedValue(err);
const client = createSelfClient({
config: {},
- adapters: { scanner: { scan: scanMock }, network, crypto, documents, auth },
+ adapters: { scanner: { scan: scanMock }, network, crypto, documents, auth, navigation },
listeners: new Map(),
});
await expect(
@@ -106,7 +106,7 @@ describe('createSelfClient', () => {
const client = createSelfClient({
config: {},
- adapters: { scanner, network, crypto, documents, auth },
+ adapters: { scanner, network, crypto, documents, auth, navigation },
listeners: listeners.map,
});
@@ -134,7 +134,7 @@ describe('createSelfClient', () => {
it('parses MRZ via client', () => {
const client = createSelfClient({
config: {},
- adapters: { scanner, network, crypto, documents, auth },
+ adapters: { scanner, network, crypto, documents, auth, navigation },
listeners: new Map(),
});
const sample = `P {
const client = createSelfClient({
config: {},
adapters: {
+ navigation,
scanner,
network,
crypto,
@@ -171,7 +172,7 @@ describe('createSelfClient', () => {
const getPrivateKey = vi.fn(() => Promise.resolve('stubbed-private-key'));
const client = createSelfClient({
config: {},
- adapters: { scanner, network, crypto, documents, auth: { getPrivateKey } },
+ adapters: { scanner, network, crypto, documents, navigation, auth: { getPrivateKey } },
listeners: new Map(),
});
@@ -181,7 +182,7 @@ describe('createSelfClient', () => {
const getPrivateKey = vi.fn(() => Promise.resolve('stubbed-private-key'));
const client = createSelfClient({
config: {},
- adapters: { scanner, network, crypto, documents, auth: { getPrivateKey } },
+ adapters: { scanner, network, crypto, documents, navigation, auth: { getPrivateKey } },
listeners: new Map(),
});
await expect(client.hasPrivateKey()).resolves.toBe(true);
@@ -222,3 +223,8 @@ const documents: DocumentsAdapter = {
saveDocument: async () => {},
deleteDocument: async () => {},
};
+
+const navigation: NavigationAdapter = {
+ goBack: vi.fn(),
+ goTo: vi.fn(),
+};
diff --git a/packages/mobile-sdk-alpha/tests/documents/utils.test.ts b/packages/mobile-sdk-alpha/tests/documents/utils.test.ts
index 2aec22457..5bb68e197 100644
--- a/packages/mobile-sdk-alpha/tests/documents/utils.test.ts
+++ b/packages/mobile-sdk-alpha/tests/documents/utils.test.ts
@@ -35,6 +35,10 @@ const createMockSelfClientWithDocumentsAdapter = (documentsAdapter: DocumentsAda
}),
},
},
+ navigation: {
+ goBack: () => {},
+ goTo: (_routeName: string, _params?: Record) => {},
+ },
scanner: {
scan: async () => ({
passportData: {
diff --git a/packages/mobile-sdk-alpha/tests/utils/testHelpers.ts b/packages/mobile-sdk-alpha/tests/utils/testHelpers.ts
index bf9ec616c..cfcf2853b 100644
--- a/packages/mobile-sdk-alpha/tests/utils/testHelpers.ts
+++ b/packages/mobile-sdk-alpha/tests/utils/testHelpers.ts
@@ -3,6 +3,8 @@
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
/* eslint-disable sort-exports/sort-exports */
+import type { NavigationAdapter } from 'src/types/public';
+
import type { CryptoAdapter, DocumentsAdapter, NetworkAdapter, NFCScannerAdapter } from '../../src';
// Shared test data
@@ -60,12 +62,18 @@ const mockAuth = {
getPrivateKey: async () => 'stubbed-private-key',
};
+const mockNavigation: NavigationAdapter = {
+ goBack: vi.fn(),
+ goTo: vi.fn(),
+};
+
export const mockAdapters = {
scanner: mockScanner,
network: mockNetwork,
crypto: mockCrypto,
documents: mockDocuments,
auth: mockAuth,
+ navigation: mockNavigation,
};
// Shared test expectations
diff --git a/packages/mobile-sdk-demo/package.json b/packages/mobile-sdk-demo/package.json
index 5450494ea..adbd866e5 100644
--- a/packages/mobile-sdk-demo/package.json
+++ b/packages/mobile-sdk-demo/package.json
@@ -42,6 +42,7 @@
"lottie-react-native": "7.2.2",
"react": "^18.3.1",
"react-native": "0.76.9",
+ "react-native-blur-effect": "1.1.3",
"react-native-get-random-values": "^1.11.0",
"react-native-haptic-feedback": "^2.3.3",
"react-native-keychain": "^10.0.0",
@@ -49,6 +50,7 @@
"react-native-safe-area-context": "^5.6.1",
"react-native-svg": "15.12.1",
"react-native-vector-icons": "^10.3.0",
+ "react-native-webview": "13.16.0",
"stream-browserify": "^3.0.0",
"util": "^0.12.5"
},
diff --git a/packages/mobile-sdk-demo/src/providers/SelfClientProvider.tsx b/packages/mobile-sdk-demo/src/providers/SelfClientProvider.tsx
index 33f7f94ce..ab9316d6d 100644
--- a/packages/mobile-sdk-demo/src/providers/SelfClientProvider.tsx
+++ b/packages/mobile-sdk-demo/src/providers/SelfClientProvider.tsx
@@ -11,6 +11,7 @@ import {
createListenersMap,
SdkEvents,
type Adapters,
+ type RouteName,
type TrackEventParams,
type WsConn,
reactNativeScannerAdapter,
@@ -18,8 +19,40 @@ import {
import { persistentDocumentsAdapter } from '../utils/documentStore';
import { getOrCreateSecret } from '../utils/secureStorage';
+import type { ScreenName } from '../navigation/NavigationProvider';
import { useNavigation } from '../navigation/NavigationProvider';
+/**
+ * Maps SDK RouteName values to demo app ScreenName values.
+ * Routes not in this map are not supported in the demo app.
+ */
+const ROUTE_TO_SCREEN_MAP: Partial> = {
+ 'Home': 'Home',
+ 'CountryPicker': 'CountrySelection',
+ 'IDPicker': 'IDSelection',
+ 'DocumentCamera': 'MRZ',
+ 'DocumentNFCScan': 'NFC',
+ 'ManageDocuments': 'Documents',
+ 'AccountVerifiedSuccess': 'Success',
+ // Routes not implemented in demo app:
+ // 'DocumentOnboarding': null,
+ // 'SaveRecoveryPhrase': null,
+ // 'AccountRecoveryChoice': null,
+ // 'ComingSoon': null,
+ // 'DocumentDataNotFound': null,
+ // 'Settings': null,
+} as const;
+
+/**
+ * Translates SDK RouteName to demo app ScreenName.
+ *
+ * @param routeName - The route name from the SDK
+ * @returns The corresponding demo app screen name, or null if not supported
+ */
+function translateRouteToScreen(routeName: RouteName): ScreenName | null {
+ return ROUTE_TO_SCREEN_MAP[routeName] ?? null;
+}
+
const createFetch = () => {
const fetchImpl = globalThis.fetch;
if (!fetchImpl) {
@@ -129,6 +162,21 @@ export function SelfClientProvider({ children, onNavigate }: SelfClientProviderP
},
ws: createWsAdapter(),
},
+ navigation: {
+ goBack: () => {
+ navigation.goBack();
+ },
+ goTo: (routeName, params) => {
+ const screenName = translateRouteToScreen(routeName);
+ if (screenName) {
+ // SDK passes generic Record, but demo navigation expects specific types
+ // This is safe because we control the route mapping
+ navigation.navigate(screenName, params as any);
+ } else {
+ console.warn(`[SelfClientProvider] SDK route "${routeName}" is not mapped to a demo screen. Ignoring navigation request.`);
+ }
+ },
+ },
documents: persistentDocumentsAdapter,
crypto: {
async hash(data: Uint8Array): Promise {
diff --git a/yarn.lock b/yarn.lock
index 43f516ff3..608a82ccb 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7579,7 +7579,7 @@ __metadata:
"@zk-email/zk-regex-circom": "npm:^1.2.1"
"@zk-kit/binary-merkle-root.circom": "npm:2.0.0"
"@zk-kit/circuits": "npm:^1.0.0-beta"
- anon-aadhaar-circuits: "https://gitpkg.vercel.app/selfxyz/anon-aadhaar/packages/circuits?main"
+ anon-aadhaar-circuits: "https://github.com/selfxyz/anon-aadhaar.git#commit=1b9efa501cff3cf25dc260b060bf611229e316a4&workspace=@anon-aadhaar/circuits"
asn1: "npm:^0.2.6"
asn1.js: "npm:^5.4.1"
asn1js: "npm:^3.0.5"
@@ -7750,6 +7750,19 @@ __metadata:
languageName: unknown
linkType: soft
+"@selfxyz/euclid@npm:^0.4.1":
+ version: 0.4.1
+ resolution: "@selfxyz/euclid@npm:0.4.1"
+ peerDependencies:
+ react: ">=18.2.0"
+ react-native: ">=0.72.0"
+ react-native-blur-effect: ^1.1.3
+ react-native-svg: ">=15.14.0"
+ react-native-webview: ^13.16.0
+ checksum: 10c0/f25a30b936d5ab1c154008296c64e0b4f97d91cf16e420b9bc3d2f4d9196ae426d1c2b28af653e36a9a580f78a42953f6bca3e9e9fbb36a15860636b9a0cb5fd
+ languageName: node
+ linkType: hard
+
"@selfxyz/mobile-app@workspace:app":
version: 0.0.0-use.local
resolution: "@selfxyz/mobile-app@workspace:app"
@@ -7862,6 +7875,7 @@ __metadata:
react-native: "npm:0.76.9"
react-native-app-auth: "npm:^8.0.3"
react-native-biometrics: "npm:^3.0.1"
+ react-native-blur-effect: "npm:^1.1.3"
react-native-check-version: "npm:^1.3.0"
react-native-cloud-storage: "npm:^2.2.2"
react-native-device-info: "npm:^14.0.4"
@@ -7909,6 +7923,7 @@ __metadata:
dependencies:
"@babel/runtime": "npm:^7.28.3"
"@selfxyz/common": "workspace:^"
+ "@selfxyz/euclid": "npm:^0.4.1"
"@testing-library/react": "npm:^14.1.2"
"@types/react": "npm:^18.3.4"
"@types/react-dom": "npm:^18.3.0"
@@ -7946,9 +7961,11 @@ __metadata:
lottie-react-native: 7.2.2
react: ^18.3.1
react-native: 0.76.9
+ react-native-blur-effect: ^1.1.3
react-native-haptic-feedback: "*"
react-native-localize: "*"
react-native-svg: "*"
+ react-native-webview: ^13.16.0
languageName: unknown
linkType: soft
@@ -14505,13 +14522,13 @@ __metadata:
languageName: node
linkType: hard
-"anon-aadhaar-circuits@https://gitpkg.vercel.app/selfxyz/anon-aadhaar/packages/circuits?main":
+"anon-aadhaar-circuits@https://github.com/selfxyz/anon-aadhaar.git#commit=1b9efa501cff3cf25dc260b060bf611229e316a4&workspace=@anon-aadhaar/circuits":
version: 2.4.3
- resolution: "anon-aadhaar-circuits@https://gitpkg.vercel.app/selfxyz/anon-aadhaar/packages/circuits?main"
+ resolution: "anon-aadhaar-circuits@https://github.com/selfxyz/anon-aadhaar.git#workspace=%40anon-aadhaar%2Fcircuits&commit=1b9efa501cff3cf25dc260b060bf611229e316a4"
dependencies:
"@anon-aadhaar/core": "npm:^2.4.3"
"@zk-email/circuits": "npm:^6.1.1"
- checksum: 10c0/93138d1c251988402482f1719ed37764b962250a51deb67bf5b855b91a6f89df2776ffe6135e8accc7a0d57dd13e7c210fc02fc6562af249ea4305f24d7d55f4
+ checksum: 10c0/1e092f002e6a413fd034016320eedfb789158996f707d0c8c2055450baa35660fd90657e34e05c4a23094ed397e9088b6e9feb3463287bf9a0b272cc1fde592f
languageName: node
linkType: hard
@@ -25613,6 +25630,7 @@ __metadata:
react: "npm:^18.3.1"
react-dom: "npm:^18.3.1"
react-native: "npm:0.76.9"
+ react-native-blur-effect: "npm:1.1.3"
react-native-get-random-values: "npm:^1.11.0"
react-native-haptic-feedback: "npm:^2.3.3"
react-native-keychain: "npm:^10.0.0"
@@ -25621,6 +25639,7 @@ __metadata:
react-native-svg: "npm:15.12.1"
react-native-svg-transformer: "npm:^1.5.1"
react-native-vector-icons: "npm:^10.3.0"
+ react-native-webview: "npm:13.16.0"
stream-browserify: "npm:^3.0.0"
typescript: "npm:^5.9.2"
util: "npm:^0.12.5"
@@ -28332,6 +28351,17 @@ __metadata:
languageName: node
linkType: hard
+"react-native-blur-effect@npm:1.1.3":
+ version: 1.1.3
+ resolution: "react-native-blur-effect@npm:1.1.3"
+ peerDependencies:
+ react: ^17.0.2
+ react-native: ^0.66.4
+ react-native-webview: ^13.6.2
+ checksum: 10c0/5036214ac36fd430c7cea41bf0f14b2aa18338ae7f3e5df4142775dd4462f26ea3bc53710397bfe01c3a2c4450c219978f86dbc5d1989deefa39ca3c4ac80bb6
+ languageName: node
+ linkType: hard
+
"react-native-check-version@npm:^1.3.0":
version: 1.4.0
resolution: "react-native-check-version@npm:1.4.0"
@@ -28716,7 +28746,7 @@ __metadata:
languageName: node
linkType: hard
-"react-native-webview@npm:^13.16.0":
+"react-native-webview@npm:13.16.0":
version: 13.16.0
resolution: "react-native-webview@npm:13.16.0"
dependencies: