Ethos-Protocol mobile apps (iOS + Android) provide a native interface for managing vaults, checking in, and receiving expiry reminders. Both apps share the same REST API contract and feature set.
mobile/
├── shared/
│ └── api-contract.md # Shared API spec (iOS + Android)
├── ios/EthosProtocol/
│ └── Sources/
│ ├── App/ # Entry point, app lifecycle
│ ├── Models/ # Vault, AuthToken, etc.
│ ├── Services/
│ │ ├── APIClient.swift # Ktor-style async HTTP client
│ │ ├── PasskeyService.swift # ASAuthorization / WebAuthn
│ │ ├── KeychainService.swift# Secure token storage
│ │ ├── NotificationService.swift # APNs + local reminders
│ │ └── OfflineSupport.swift # NetworkMonitor + disk cache
│ ├── ViewModels/ # AuthStore, VaultStore (ObservableObject)
│ └── Views/ # SwiftUI screens
└── android/app/src/main/java/com/ethosprotocol/
├── api/
│ ├── ApiClient.kt # Ktor HTTP client
│ └── Infrastructure.kt # NetworkMonitor, OfflineCache, TokenProvider
├── models/ # Kotlinx.serialization data classes
├── services/
│ ├── PasskeyService.kt # CredentialManager / WebAuthn
│ ├── PushService.kt # Firebase Messaging
│ └── NotificationHelper.kt# Local notification display
├── ui/
│ ├── ViewModels.kt # AuthViewModel, VaultViewModel (Hilt)
│ ├── MainActivity.kt # NavHost entry point
│ ├── screens/Screens.kt # Compose screens
│ └── theme/Theme.kt # Material3 dynamic color
└── di/AppModule.kt # Hilt DI bindings
- iOS:
ASAuthorizationPlatformPublicKeyCredentialProvider(iOS 16+) - Android:
CredentialManagerAPI (Android 9+, API 28+) - Flow:
getChallenge()→ device biometric prompt →verifyPasskey()→ JWT stored in Keychain/SharedPreferences - Relying party:
ethos-protocol.app(requires.well-known/assetlinks.json+ Apple App Site Association)
- iOS: APNs via
UNUserNotificationCenter. Device token registered to backend on first launch.- Local reminders scheduled 24h before vault expiry via
UNTimeIntervalNotificationTrigger - Actionable notification category
CHECK_INwith inline "Check In" action
- Local reminders scheduled 24h before vault expiry via
- Android: Firebase Cloud Messaging (FCM). Token refreshed via
onNewToken.- Notification channel
ttl_reminders(IMPORTANCE_HIGH) - Deep-link intent to
MainActivitywithvault_idextra
- Notification channel
NetworkMonitorchecks live connectivity before every requestOfflineCachestores last successful GET responses keyed by URL (SHA-256 filename)- On network unavailable: cached data served transparently; mutations show "offline" error
- iOS:
CryptoKit.SHA256for cache keys; Android:MessageDigest("SHA-256")
- iOS:
@StateObject/ObservableObjectstores (AuthStore,VaultStore) injected via SwiftUI environment - Android: Hilt-injected
ViewModels withStateFlow+collectAsStateWithLifecycle
- Install XcodeGen (
brew install xcodegen) — the.xcodeprojis generated, not checked in - From
ios/EthosProtocol, runmkdir -p Xcode && xcodegen generate --project Xcodeto produceXcode/EthosProtocol.xcodeproj(anEthosProtocolapp target +TTLWidgetwidget extension, perproject.yml) — theXcode/directory must exist beforexcodegen generateruns, or the copy step fails - Open
ios/EthosProtocol/Xcode/EthosProtocol.xcodeprojin Xcode 15+ - Set your Apple Developer Team in signing settings for both the
EthosProtocolandTTLWidgettargets (project.ymlleavesDEVELOPMENT_TEAMblank on purpose — bundle IDscom.ethosprotocol/com.ethosprotocol.TTLWidgetare already set) API_BASE_URLis already set inEthosProtocol/Info.plistandTTLWidget/Info.plist; edit both (they're separate bundles, read independently at runtime) if you need to point at a different environment- Configure Apple App Site Association at
https://ethos-protocol.app/.well-known/apple-app-site-association, listing this app's App ID under bothapplinks(Universal Links) andwebcredentials(platform passkeys) - In the Apple Developer portal, enable Push Notifications, Associated Domains, iCloud (Key-Value storage), and Keychain Sharing capabilities for the
com.ethosprotocolApp ID, and Keychain Sharing forcom.ethosprotocol.TTLWidget— matchingEthosProtocol/EthosProtocol.entitlements/TTLWidget/TTLWidget.entitlements. Set up an APNs key in App Store Connect for push. - Re-run
mkdir -p Xcode && xcodegen generate --project Xcodeany timeproject.ymlchanges; the generatedXcode/directory is disposable and shouldn't be committed
- Open
androidin Android Studio Hedgehog+ - Add
google-services.jsonfrom Firebase Console - Configure
assetlinks.jsonathttps://ethos-protocol.app/.well-known/assetlinks.json - Set
API_BASE_URLinbuild.gradle.ktsbuildConfigField
cd ios/EthosProtocol
swift testCovers: model decoding, Keychain round-trip, offline cache, Base64URL encoding.
Tests run against the SPM package (Package.swift) directly and don't require the
XcodeGen-generated project; CI runs this the same way, via xcodebuild test against
an iOS Simulator destination (swift test alone defaults to macOS, which can't build
the app's iOS-only framework imports).
cd android
./gradlew test # Unit tests (JVM)
./gradlew connectedAndroidTest # Instrumented tests (device/emulator)Covers: ViewModel state transitions, model logic, Compose UI smoke tests.