A self-custodial mobile wallet for the Telos privacy layer. Inspired by Zashi / Zodl (Zcash) but built for Telos EVM, supporting the native TLOS asset and USDC via the zkBob-based privacy pools deployed on Telos.
- Private by default. Every received deposit is autoshielded as soon as it lands in your transparent address.
- "Pay Anywhere" powered by Li.Fi - spend your shielded TLOS/USDC at any address on any EVM chain via cross-chain routing.
- iOS + Android, single codebase (Expo + React Native).
- Non-custodial: seed phrase is held only in the device secure enclave
(iOS Keychain / Android Keystore via
expo-secure-store).
Status: v0.1 scaffold. Every screen, the wallet core, the zkBob bridge, the Li.Fi integration, and the autoshield runner are in place and type-checked. The one step that must be run before shipping to devices is
node scripts/bundle-zkbob.js, which builds the WebView-hostedzkbob-client-jsbundle (see zkBob WebView bridge).
# Install deps
npm install
# Build the WASM-backed zkBob host bundle (required once, and whenever
# zkbob-client-js is upgraded). This installs a temporary workspace,
# bundles the library + libzkbob-rs-wasm-web, and inlines it into
# assets/zkbob/zkbob-host.html.
node scripts/bundle-zkbob.js
# iOS simulator
npx expo run:ios
# Android emulator
npx expo run:android
# Metro dev server only (after prebuild)
npm startYou need an Expo dev build (not the vanilla Expo Go app) because the
wallet uses expo-secure-store, react-native-webview, and custom Android /
iOS permissions. Run npx expo prebuild once to generate the native
projects, then expo run:* to build them.
┌──────────────────────────────────────┐
│ React Native UI │
│ (expo-router screens + components) │
└──────────────┬───────────────────────┘
│
┌──────┴──────┐
│ useWallet │ zustand store + derived account
└──────┬──────┘
│
┌───────────┼───────────┬─────────────┐
│ │ │ │
┌──┴──┐ ┌────┴────┐ ┌───┴────┐ ┌────┴────┐
│ eth │ │ zkBob │ │ Li.Fi │ │ Secure │
│ RPC │ │ bridge │ │ SDK │ │ Storage │
└──┬──┘ └────┬────┘ └───┬────┘ └─────────┘
│ │ │
│ ┌─────┴─────┐ │
│ │ WebView │ │
│ │ host │ │
│ │ (hidden) │ │
│ └─────┬─────┘ │
│ │ │
│ zkbob-client-js │
│ libzkbob-rs-wasm │
│ │ │
▼ ▼ ▼
Telos EVM Relayer Li.Fi API
(TLOS/USDC)
zkbob-client-js depends on libzkbob-rs-wasm-web, a Rust → WebAssembly
crypto core that is designed for a browser runtime. Hermes (React Native's
JS engine) does not support WebAssembly, so we host the library inside a
hidden react-native-webview and marshal all calls across postMessage.
This is the same pattern used by many mobile wallets that depend on WASM-first libraries (the Zcash Light Client, Namada SDK, etc.). The interface is strongly typed end-to-end:
src/zkbob/types.ts-BridgeMethodsdeclares the typed RPC surface.src/zkbob/bridge.tsx- React Native side. Exposes auseZkBobBridgehook and aZkBobBridgeProviderthat mounts the hidden WebView.src/zkbob/client.ts- Thin facade used by screens (ZkBobClient).assets/zkbob/zkbob-host.html- Template HTML loaded into the WebView. Contains the dispatcher shim. Before packaging, runnode scripts/bundle-zkbob.jswhich inlines the realzkbob-client-jsbundle into this file. Without that step the shim returns a clear "not bundled" error and the UI stays offline.
src/wallet/
- Mnemonic + key derivation. A single BIP-39 seed is the root of trust.
deriveEvmAccountproduces the transparent EVM account onm/44'/60'/0'/0/0for regular transactions.deriveShieldedSeedproduces per-pool entropy that is fed into libzkbob-rs inside the WebView, so the TLOS pool and the USDC pool get independent shielded keys. - Secure storage.
src/wallet/keystore.tswrapsexpo-secure-store(iOS Keychain / Android Keystore) and adds a 6-digit PIN that hashes into the same store. Biometric unlock is optional and never bypasses the PIN-based seed encryption.
src/config/pools.ts
Both Telos privacy pools are pre-wired with their relayer URLs:
TLOS_POOL.relayerUrl = 'https://dsw2020napxes.cloudfront.net/';
USDC_POOL.relayerUrl = 'https://d39zrx7k37xhn.cloudfront.net/';On-chain addresses (poolAddress, tokenAddress, denominator) are
hydrated from the relayer's /info endpoint at startup, so the app stays
correct even if the Telos team rotates pools. src/zkbob/relayer.ts
provides a typed HTTP client for /info, /version, /fee, /limits
and /job/:id.
src/zkbob/autoshield.ts
Every balance refresh (foreground poll every 30s, pull-to-refresh, and
resume-from-background) runs evaluateAutoshield. If a transparent
balance exceeds the user-configured threshold and leaves enough
behind to cover the keepTransparent floor (for gas), the wallet
immediately submits a deposit through the bridge. Defaults:
| Asset | Threshold | Keep Transparent |
|---|---|---|
| TLOS | 5 TLOS | 2 TLOS |
| USDC | 1 USDC | 0 USDC |
All values are editable from Settings → Autoshielding.
src/lifi/client.ts + app/pay.tsx
- User picks the source asset (shielded TLOS or shielded USDC) and a destination chain + token + address.
- The wallet requests a quote via
@lifi/sdkgetQuote. - On confirm, the wallet unshields exactly
fromAmountto its own transparent Telos address, then callsexecuteRouteto bridge the funds to the destination. Because the bridge source is an ephemeral transparent address and never the same as the user's long-lived transparent identity, the payee cannot link the final destination back to the user's shielded account.
mobile-privacy-wallet/
├── app/ # expo-router screens (file-based routing)
│ ├── _layout.tsx # wraps everything in ZkBobBridgeProvider
│ ├── index.tsx # launcher, routes by wallet phase
│ ├── onboarding/
│ │ ├── welcome.tsx
│ │ ├── create.tsx
│ │ ├── import.tsx
│ │ └── pin.tsx
│ ├── unlock.tsx
│ ├── (tabs)/
│ │ ├── _layout.tsx
│ │ ├── index.tsx # Home - balances, actions
│ │ ├── history.tsx # Shielded tx history
│ │ └── settings.tsx # Autoshield, relayers, wipe wallet
│ ├── send.tsx # Shielded → shielded transfer
│ ├── receive.tsx # QR + shielded / transparent address
│ ├── shield.tsx # Transparent → shielded deposit
│ ├── unshield.tsx # Shielded → transparent withdraw
│ └── pay.tsx # Li.Fi cross-chain pay
├── src/
│ ├── config/ # Telos + pools + Li.Fi config
│ ├── wallet/ # Seed, keystore, account, balances
│ ├── zkbob/ # Bridge, relayer, autoshield, client
│ ├── lifi/ # Li.Fi SDK wrapper
│ ├── state/ # zustand store + useWallet hook
│ ├── components/ # Buttons, PinPad, AssetRow, ...
│ ├── theme/ # Colors, spacing, typography
│ └── polyfills.ts # Buffer/process/crypto shims
├── assets/
│ └── zkbob/
│ └── zkbob-host.html # WebView host (bundler rewrites this)
├── scripts/
│ └── bundle-zkbob.js # Builds the WebView bundle
├── app.json # Expo config
├── babel.config.js
├── metro.config.js
└── tsconfig.json
- Wallet core (seed, PIN, secure storage)
- zkBob WebView bridge + typed RPC surface
- Telos relayer hydration (/info, /fee, /limits, /job)
- Autoshielding engine
- Li.Fi "Pay Anywhere" integration
- Full UI (onboarding → home → send/receive/shield/unshield/pay/history/settings)
- Background autoshielding via iOS BGAppRefresh / Android WorkManager
- Biometric auto-unlock convenience toggle
- Ledger / WalletConnect for optional hardware co-signing on unshield
- Desktop build via Expo Web → Tauri (stretch)
- Telos privacy docs
- zkBob docs
- zkBob client library
- Protofire Telos privacy wallet (reference desktop)
- Protofire Telos privacy contracts
- Li.Fi SDK
- zkwallet.telos.net - web reference app
Apache-2.0 OR MIT - matching the upstream zkbob-client-js license so the
library can be embedded in this wallet without friction.