This guide covers everything you need to develop, test, and contribute to the DFX Wallet app.
git clone https://github.com/DFXswiss/dfx-wallet.git
cd dfx-wallet
npm install
npx expo start # dev server
npx expo start --ios # iOS simulator
npx expo start --android # Android emulator| Tool | Version | Purpose |
|---|---|---|
| Node.js | 20+ | Runtime |
| npm | 10+ | Package manager |
| Xcode | 15+ | iOS builds (macOS only) |
| Android SDK | API 34+ | Android builds |
| Expo CLI | bundled | React Native tooling (via npx expo) |
npm run typecheck # TypeScript check (tsc --noEmit)
npm run lint # ESLint
npm run lint:fix # ESLint auto-fix
npm run format # Prettier check
npm run format:fix # Prettier auto-fix
npm run test # Jest
npm run check # typecheck + lint + format (run before pushing)
npx expo prebuild --clean # regenerate native projectsRun npm run check before every push. Never push if it fails.
- Framework: React Native (Expo ~54, Expo Router)
- Language: TypeScript (strict mode)
- Wallet SDK: Tether WDK (
@tetherto/wdk-react-native-provider) - State: Zustand
- i18n: i18next + react-i18next (DE, EN)
- Storage: react-native-mmkv (fast KV), expo-secure-store (secrets)
- Navigation: Expo Router (file-based, mirrors Next.js)
app/ # Expo Router screens (file-based routing)
(onboarding)/ # Welcome, Create/Restore Wallet, Verify Seed, Setup PIN
(pin)/ # PIN verification
(auth)/ # Authenticated area
(tabs)/ # Bottom tabs: Dashboard, Settings
buy/ sell/ send/ receive/ # Wallet action flows
kyc/ # Multi-step KYC verification
support/ # Ticket system
transaction-history/
src/
components/ # Shared UI components
config/ # Chain configs, environment
hooks/ # Custom React hooks
i18n/ # Localization (DE, EN)
models/ # Domain models / types
services/dfx/ # DFX API client + DTOs
services/hardware-wallet/ # BitBox02 integration
store/ # Zustand stores
theme/ # Colors, typography
| Branch | Purpose | Deploy target |
|---|---|---|
develop |
Default branch, active development | DEV builds |
main |
Production releases | Store builds |
- Every change goes in a feature-specific branch with a pull request against
develop. Never push directly todevelopormain. - Always start from the latest
developbefore creating a new branch:
git checkout develop && git pull origin develop
git checkout -b feat/your-feature # or fix/..., docs/..., chore/...developmoves quickly — multiple PRs land in parallel. Treat any localdevelopolder than a few minutes as stale and pull again before branching.- Never reuse an old branch after its PR was merged or closed — cut a fresh branch from current
develop. - Build on top of existing changes in
develop. If your work overlaps with a recent merge, rebase onto the newdeveloprather than working around it. - Never force-push, never amend published commits.
- Squash-and-merge when merging to
develop— preserve atomic commits on the branch; the squash keeps only the PR title ondevelop. - Release PRs (
develop→main) are created automatically — never open them manually.
- Features:
feat/<scope>-<topic> - Fixes:
fix/<scope>-<topic> - Docs:
docs/<topic> - Chores:
chore/<topic>
Write in English. Imperative mood. No trailing period on the subject. Describe what changed, not how.
# Good
Add BitBox02 BLE transport for Nova
Fix balance refresh after send transaction
Use DfxColors instead of raw hex in dashboard
# Bad
update stuff
WIP
fix
- Strict mode —
strict: truein tsconfig - Functional components with hooks — no class components
- No
any— use explicit types, especially in DTOs - No
console.login committed code - No unused imports
- Named exports for components, default exports only for routed screens
- ESLint:
npm run lint - Prettier: enforced via
npm run format - Run
npm run checkbefore every commit
Order: React → React Native → Expo → third-party → @/ local.
// 1. React
import { useCallback, useState } from 'react';
// 2. React Native
import { View, Text } from 'react-native';
// 3. Expo
import { useRouter } from 'expo-router';
// 4. Third-party
import { create } from 'zustand';
// 5. Internal (absolute paths via @/)
import { useWalletStore } from '@/store/wallet';
import { DfxColors } from '@/theme/colors';Use the @/ path alias for everything under src/.
- Colors: Always use
DfxColors.*fromsrc/theme/colors.ts. NEVER use raw hex values or RN defaults. - Typography: Always use
Typography.*fromsrc/theme/typography.ts. NEVER hardcode font sizes. - Loading: Use RN
ActivityIndicator— no third-party spinners.
- Zustand stores in
src/store/ - No React Context for state — Zustand stores are global singletons
- Secrets in
expo-secure-store, fast KV inreact-native-mmkv
All DFX backend communication goes through src/features/dfx-backend/services/api.ts:
- DTOs in
src/features/dfx-backend/services/dto/with explicit types (noany) - Never call
fetch()directly — always use the typed API client
- Source files:
src/i18n/locales/de.jsonanden.json - Keys are nested by feature:
dashboard.title,buy.title, etc. - Keys MUST be alphabetically sorted within each namespace
- Always update BOTH
deandenfiles in the same PR
- Theme: Light. Soft sky-blue / white surfaces, dark navy text, blue (
#2F7CF7) as the UI accent for icons, links, and active controls. The DFX brand red (#F5516C) is preserved asDfxColors.brandRedfor the logo only. - Dashboard: full-screen mountain-illustration background (
assets/dashboard-bg.png), DFX logo header + hamburger menu, large balance display with eye-toggle, Portfolio + Pay pill buttons, Transactions link, and a bottom Receive | Send pill. Buy and Sell are reached from inside the Receive and Send flows respectively. - Onboarding flow: Welcome → Create/Restore (passkey or seed) → Verify Seed → Legal → PIN → Dashboard
- Settings: reached from the Dashboard hamburger menu (no bottom tab bar). Flat list with sub-pages.
- KYC: Multi-step wizard (Registration → Email → Nationality → Financial Data → 2FA → Ident)
- Bitcoin On-Chain (
wdk-wallet-btc) - Ethereum + L2s: Arbitrum, Polygon, Optimism, Base (
wdk-wallet-evm) - Solana (
wdk-wallet-solana) - TON (
wdk-wallet-ton) - TRON (
wdk-wallet-tron) - Spark / Lightning (
wdk-wallet-spark)
BitBox02 integration is a MUST-HAVE requirement.
Dual-transport architecture:
- USB HID — Standard BitBox02, Android only (Apple blocks USB-HID for 3rd party apps)
- BLE — BitBox02 Nova, Android + iOS
- View-only wallet model: no seed stored locally, signing delegated to hardware
SDK: bitbox-api (npm, v0.12.0, WASM from BitBoxSwiss/bitbox-api-rs)
- Protocol stack (Noise XX handshake, Protobuf, signing) is transport-agnostic
- Only the ReadWrite transport layer needs native implementation
Implementation: src/features/hardware-wallet/
types.ts—HardwareWalletProvider,BitboxTransportinterfacesbitbox.ts—BitboxProvider(scans USB + BLE, auto-selects transport)transport-usb.ts— Android native HID module (pattern:@ledgerhq/react-native-hid)transport-ble.ts— BLE viareact-native-ble-plx(Android + iOS)
Connection flow: Scan (USB+BLE) → Detect → Connect → Noise handshake → Channel Verify → Get Address
Signing: BTC (SegWit, Taproot, PSBT) + ETH (EIP-1559, ERC-20, EIP-712)
RealUnit reference files (Flutter, useful as a reference for the signing flow):
lib/screens/hardware_connect_bitbox/— UI + connection flowlib/packages/hardware_wallet/bitbox_credentials.dart— signing logiclib/packages/hardware_wallet/bitbox.dart— service wrapper
- Unit tests: Jest, co-located with the code they cover
- E2E: Maestro (
npm run e2e:maestro) for cross-platform flows, Detox (npm run e2e:test:ios) for iOS-specific scenarios. See docs/maestro.md and docs/visual-regression.md for the MVP/feature-gated tag split, baseline workflow, and CI runner setup. - Add tests for new business logic in
src/services/andsrc/store/
See SECURITY.md for the responsible disclosure policy and the security model.
- Never commit secrets, seed phrases, or API keys
- Test fixtures must not contain real wallet credentials
- All secrets in
.env.local(gitignored)