element-x-ada/BLOCKERS.md

6.9 KiB

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