element-x-ada/BLOCKERS.md

149 lines
6.9 KiB
Markdown

# BLOCKERS.md - Phase 1 Implementation Status
## Task 1: Module Scaffolding ✅ COMPLETE
### Completed
- ✅ Module structure created (api/impl/test)
- ✅ Metro DI setup following Element X patterns
- ✅ WalletEntryPoint and WalletState APIs defined
- ✅ PaymentFlowNode placeholder with Appyx navigation
- ✅ FakeWalletEntryPoint for testing
- ✅ Cardano client library dependencies added
- ✅ ProGuard rules configured
- ✅ Basic unit tests added
- ✅ Pushed to Gitea phase1-dev branch
---
## Task 2: Key Generation + Storage ✅ COMPLETE
### Completed
-**CardanoNetworkConfig.kt** - Single object for testnet/mainnet config swap
- Currently configured for TESTNET (preprod)
- Change `NETWORK` to `CardanoNetwork.MAINNET` for production
- All derived values (Koios URL, explorer URL, address prefix) auto-switch
-**CardanoKeyStorage** (interface + implementation)
- Per-session wallet isolation (key alias: `cardano_wallet_{sessionId}`)
- 24-word BIP-39 mnemonic generation using cardano-client-lib
- AES-GCM-256 encryption with Android Keystore-backed key
- `setUserAuthenticationRequired(true)` - biometric/PIN for every operation
- `setUserAuthenticationValidityDurationSeconds(-1)` - no grace period
- `setInvalidatedByBiometricEnrollment(true)` - invalidate on biometric change
- Methods: `generateWallet`, `importWallet`, `getMnemonic`, `getBaseAddress`, `getStakeAddress`, `deleteWallet`
-**CardanoWalletManager** (interface + implementation)
- Key derivation using CIP-1852 via cardano-client-lib's Account class
- Path `m/1852'/1815'/0'/0/0` for external receiving address
- Path `m/1852'/1815'/0'/2/0` for staking key
- Shelley base address generation (payment + staking key hash)
- Uses CardanoNetworkConfig for network selection
- Exposes: `getAddress(sessionId)`, `getStakeAddress(sessionId)`, `getSpendingKey(sessionId)`
-**SeedPhraseManager** (interface + implementation)
- 24-word mnemonic generation (256-bit entropy)
- Support for 12/15/18/21/24 word counts
- BIP-39 validation (checksum + wordlist)
- Word suggestions for autocomplete
- Normalization (whitespace, case)
- ⚠️ UI must apply `FLAG_SECURE` when displaying seed phrases (documented)
-**FakeCardanoKeyStorage** for testing
- ✅ Unit tests for SeedPhraseManager, CardanoNetworkConfig, CardanoWalletManager
### Decisions Made (per instructions)
- Wallet scope: **PER SESSION** (each Matrix account has its own wallet)
- Biometric change: **INVALIDATE** key + require wallet re-import/creation
- Network: **TESTNET** (preprod) - single config constant for easy mainnet swap
### Not Verified (No Android SDK in build environment)
- ⚠️ Compilation with `./gradlew :features:wallet:impl:assemble`
- ⚠️ Unit tests with `./gradlew :features:wallet:impl:test`
- ⚠️ ktlint compliance
- ⚠️ Actual Android Keystore behavior (requires device/emulator)
- ⚠️ Biometric prompt integration (requires Activity context)
### Security Notes
1. **Mnemonic never stored in plaintext** - Always encrypted with Keystore key
2. **Key material cleared after use** - `ByteArray.fill(0)` called where possible
3. **Per-session isolation** - Different Matrix accounts cannot access each other's wallets
4. **Biometric invalidation** - If user adds/removes fingerprints, wallet key becomes invalid
5. **No screenshots** - UI must apply FLAG_SECURE when showing seed phrase
---
## Task 3: Koios Client ✅ COMPLETE
### Completed
-**CardanoClient.kt** interface in `api/` module:
- `getBalance(address: String): Result<Long>` — balance in lovelace
- `getUtxos(address: String): Result<List<Utxo>>` — unspent outputs
- `submitTx(signedTxCbor: String): Result<String>` — returns tx hash
- `getTxStatus(txHash: String): Result<TxStatus>` — PENDING/CONFIRMED/FAILED
-**Data models** in `api/`:
- `Utxo.kt` — txHash, outputIndex, amount, address
- `TxStatus.kt` — enum PENDING/CONFIRMED/FAILED
- `CardanoException.kt` — typed exceptions (NetworkException, RateLimitException, InvalidAddressException, TransactionNotFoundException, SubmissionFailedException, InsufficientFundsException, ApiException)
-**KoiosCardanoClient.kt** implementation:
- Uses `BackendFactory.getKoiosBackendService()` from cardano-client-lib
- Testnet URL: `https://preprod.koios.rest/api/v1` (via CardanoNetworkConfig)
- Mainnet URL: `https://api.koios.rest/api/v1` (via CardanoNetworkConfig)
- 3 retries with exponential backoff (1s → 2s → 4s, max 10s)
- Basic rate limiting (100ms min between requests for Koios 100 req/10s limit)
- DI: `@ContributesBinding(SessionScope::class)`
- Error parsing: 429 → RateLimitException, 5xx → NetworkException, etc.
-**FakeCardanoClient.kt** for testing:
- Configurable balances, UTxOs, transaction statuses
- Error simulation (network errors, rate limits, submit failures)
- Transaction lifecycle simulation (pending → confirmed → failed)
- Call counters for test verification
- Helper: `setupWallet(address, balance)` creates realistic UTxO set
-**KoiosCardanoClientTest.kt** — 15+ unit tests:
- getBalance success, unknown address, network error, rate limit
- getUtxos success, empty result
- submitTx success, failure
- getTxStatus pending, confirmed, failed
- reset/state management
-**CardanoWalletManager updated** to use CardanoClient:
- `refreshBalance()` now fetches real balance via Koios
- Updates WalletState with lovelace + formatted ADA string
### Design Notes
- **No API key required** — Koios public API is free
- **Network config centralized** — Change `CardanoNetworkConfig.NETWORK` to swap testnet/mainnet
- **Hex CBOR for submitTx** — Accepts hex-encoded signed transaction bytes
- **UTxO pagination** — Limited to first 100 UTxOs (sufficient for typical wallets)
### Potential Issues
- ⚠️ `getTxStatus` returns PENDING for unknown hashes (could be never-submitted or truly pending)
- ⚠️ Koios rate limit (100 req/10s) may need adjustment for heavy usage patterns
- ⚠️ No getProtocolParameters yet (needed for Task 4 fee calculation)
---
## Task 4-8: Pending
See PHASE1-PLAN.md for full task breakdown.
---
## Known Issues
### Issue 1: Biometric Prompt Activity Context
The `CardanoKeyStorageImpl` uses `setUserAuthenticationRequired(true)` which will cause `UserNotAuthenticatedException` when accessing the key. The biometric prompt UI must be triggered from an Activity/Fragment context before calling `getMnemonic()`, `getSpendingKey()`, etc.
**Solution:** Task 6 (Payment Flow UI) must call BiometricPrompt before invoking storage operations.
### Issue 2: KeyPermanentlyInvalidatedException
If user changes biometric enrollment, the Keystore key is invalidated. Current behavior: throws exception, user must delete and recreate wallet.
**Enhancement (future):** Show user-friendly message explaining why wallet became invalid and offer to re-import.
---
*Last updated: 2026-03-27 - Task 2 complete*