Native iOS and Android wallet for Enbox.
Bare React Native 0.85 with the New Architecture, Turbo Native Modules, and the full @enbox/* SDK integrated.
- React Native 0.85 -- bare project, no Expo
- New Architecture -- Fabric renderer and Turbo Modules enabled by default
- Turbo Native Modules with Codegen specs for type-safe JS-to-native bindings
- @enbox/ SDK* -- agent, auth, crypto, dids, dwn-clients, protocols, api, common
- React Navigation -- native stack + bottom tabs
- TanStack Query + Zustand -- data fetching and state management
- Jest + React Native Testing Library -- 34 tests
The full @enbox/* SDK runs in React Native via:
| Concern | Web wallet approach | Mobile approach |
|---|---|---|
Crypto (crypto.subtle) |
Browser Web Crypto API | react-native-quick-crypto polyfill |
Streams (ReadableStream) |
Browser Streams API | web-streams-polyfill |
Persistent storage (level) |
browser-level (IndexedDB) |
react-native-leveldb (native LevelDB via JSI), intercepted via Metro resolver |
| Auth session storage | localStorage |
SecureStorageAdapter backed by NativeSecureStorage Turbo Module (Keychain/Keystore) |
| Connect flow | Popup + postMessage (@enbox/browser) |
WalletConnect relay (QR code + deep links + PIN confirmation) |
- Polyfills load in
index.jsbefore any SDK import - Metro resolver intercepts
import { Level } from 'level'across all@enbox/*packages and redirects toRNLevel(our adapter wrappingreact-native-leveldb) - Agent initialization creates
AuthManagerwith aSecureStorageAdapterandEnboxUserAgentwith the RN-compatible storage layer - Connect flow uses the built-in
WalletConnectrelay -- no browser APIs needed
Key files:
src/lib/polyfills.ts-- crypto + streams polyfill setupsrc/lib/enbox/rn-level.ts-- LevelDB adapter with sublevel supportsrc/lib/enbox/storage-adapter.ts-- Keychain/Keystore auth storagesrc/lib/enbox/agent-init.ts-- agent + auth manager initializationsrc/lib/enbox/agent-store.ts-- global agent state (Zustand)src/lib/enbox/connect.ts-- mobile connect flow via WalletConnect relaysrc/lib/enbox/wallet-connect-store.ts-- wallet-side pending request intake and approval flowsrc/lib/enbox/prepare-protocol.ts-- protocol installation helper reused from the web walletmetro.config.js--level→RNLevelresolver override
The mobile app now implements the wallet side of the relay-mediated Enbox connect flow.
Supported intake today:
enbox://connect?request_uri=...&encryption_key=...deep links- QR code payloads that encode that same URI format via native camera scanning
Current flow:
- A client app generates a WalletConnect relay URI.
- The mobile wallet receives it via the
enbox://connectprotocol handler. - The wallet fetches and decrypts the pending request using
EnboxConnectProtocol.getConnectRequest(). - The user reviews the requested protocols and scopes, chooses an identity, and approves or denies.
- On approval, the wallet installs requested protocols on all DWN endpoints and submits the encrypted response with
EnboxConnectProtocol.submitConnectResponse(). - The wallet shows the 4-digit confirmation PIN for the requesting app.
Key files:
src/hooks/use-wallet-connect-linking.ts-- listens for incoming deep linkssrc/features/connect/screens/wallet-connect-request-screen.tsx-- native consent UIsrc/features/connect/screens/wallet-connect-scanner-screen.tsx-- native QR scanner for wallet-side intakeandroid/app/src/main/AndroidManifest.xml-- Androidenbox://connectintent filterios/EnboxMobile/Info.plist-- iOS URL scheme registration
Consent UI highlights:
- protocol-by-protocol permission grouping
- low / medium / high risk badges
- readable summaries for read, write, delete, subscribe, and protocol configure scopes
- encrypted type visibility for protocols that require decryption keys
Two custom Turbo Native Modules with typed Codegen specs:
Spec: specs/NativeSecureStorage.ts
| Platform | Implementation | Backing store |
|---|---|---|
| iOS | ios/EnboxMobile/NativeSecureStorage/ (Obj-C++) |
Keychain (kSecClassGenericPassword), serialized dispatch queue |
| Android | android/.../nativemodules/NativeSecureStorageModule.kt |
Android Keystore (AES-256-GCM) + SharedPreferences.commit() |
Spec: specs/NativeCrypto.ts
| Platform | Implementation | Backing API |
|---|---|---|
| iOS | ios/EnboxMobile/NativeCrypto/ (Obj-C++) |
CommonCrypto (SHA-256, PBKDF2) + Security.framework |
| Android | android/.../nativemodules/NativeCryptoModule.kt |
PBKDF2WithHmacSHA256 + MessageDigest + SecureRandom |
- Create a Codegen spec in
specs/NativeMyModule.ts - Write the iOS implementation in
ios/EnboxMobile/NativeMyModule/ - Write the Android implementation in
android/.../nativemodules/ - Register in
codegenConfig(package.json) and the Android package - Run
pod install(iOS) or rebuild (Android) to generate bindings
bun install
bun run verify # lint + typecheck + test
bun run ios # build and run on iOS simulator
bun run android # build and run on Android emulatorspecs/ Turbo Module Codegen specs (TypeScript)
src/
components/ui/ Shared UI primitives
constants/ Auth config
features/ Feature screens, domain logic, tests
hooks/ App-wide hooks (auto-lock)
lib/auth/ PIN hashing (PBKDF2) and format validation
lib/enbox/ SDK adapters: LevelDB, secure storage, agent init, connect
lib/storage/ Thin wrapper around NativeSecureStorage
navigation/ React Navigation setup
providers/ App-wide providers
theme/ Design tokens
ios/ Xcode project + native module implementations
android/ Gradle project + native module implementations
- No Expo -- full control over native projects.
ios/andandroid/are committed and maintained directly. - Turbo Modules + Codegen -- native module interfaces are declared as TypeScript specs. Codegen generates C++/ObjC/Java glue code at build time.
- @enbox/ SDK runs natively* -- no browser dependencies, no WebView wrapper. Storage uses native LevelDB, crypto uses platform APIs, connect uses relay + deep links.
- Real PIN auth -- PBKDF2-SHA256 (100k iterations + random salt) via native crypto, stored in Keychain/Keystore. Constant-time hash comparison.
- Auto-lock -- app locks immediately when backgrounded via AppState listener.
- Exponential lockout -- 5 attempts per cycle, progressive durations (30s, 1m, 5m, 15m, 1h). Persists across restarts.
- Conditional navigation -- screens swap declaratively based on session state. Lock/unlock/reset all work via state changes, not imperative navigation.
| Job | Runner | What it validates |
|---|---|---|
verify |
ubuntu-latest |
Lint, typecheck, 34 unit tests |
build-android |
ubuntu-latest |
Full Gradle debug build (Codegen + Kotlin + APK). Cached. Uploads artifact. |
build-ios |
macos-14 |
CocoaPods + full Xcode debug build (Codegen + Obj-C++). Cached. |
See docs/planning.md for the web-wallet portability assessment and delivery roadmap.