# /pay Fallback UX Implementation **Date:** 2026-03-29 **Commit:** `2b93236229` on `phase1-dev` **APK:** http://192.168.0.5:8888/ ## What Was Implemented ### Flow B — Fallback When Recipient Has No Linked Wallet When a user types `/pay 10` to a recipient who hasn't linked a Cardano wallet: 1. **Address lookup happens in background** - `CardanoAddressService.lookupAddress(@recipient)` is called 2. **Not found → Show banner card** with: - Warning icon: "⚠️ @recipient hasn't linked a Cardano wallet" - Instructional text: "Enter their Cardano address manually:" - Embedded editable text field with placeholder "addr1... or addr_test1..." 3. **Live validation on manual address input**: - Must start with `addr_test1` (preprod) or `addr1` (mainnet) - Length between 58-108 characters - Shows inline error if invalid - Shows green checkmark "Valid address" when valid 4. **Continue button enables** when: - Amount is valid (≥1 ADA) - Manual address passes validation 5. **Resolved address is passed to confirmation screen** - The `PaymentEntryNode` now passes `state.resolvedAddress` (which comes from either the lookup or manual entry) instead of the raw `recipientInput` ## Files Changed | File | Changes | |------|---------| | `PaymentFlowEvents.kt` | Added `ManualAddressChanged(address: String)` event | | `PaymentEntryState.kt` | Added `manualAddressInput`, `manualAddressError`, `resolvedAddress` fields; added `needsManualAddressEntry` computed property | | `PaymentEntryPresenter.kt` | Handle `ManualAddressChanged` event; `validateManualAddress()` function; updated `canContinue` logic to work with manual entry | | `PaymentEntryView.kt` | Added `ManualAddressEntryCard` composable with embedded `OutlinedTextField`; updated preview states | | `PaymentEntryNode.kt` | Changed to pass `state.resolvedAddress` instead of `state.recipientInput` to confirmation callback | ## Flow A — Happy Path (Already Working) When recipient HAS a linked wallet: 1. User types `/pay 10` 2. Payment form opens with recipient pre-selected from DM context 3. `CardanoAddressService.lookupAddress(@recipient)` returns the address 4. Green card shows: "✓ Address loaded from @recipient's profile" with truncated address 5. Recipient field shows Matrix user ID (read-only context) 6. Amount field pre-filled with "10" 7. Continue button enabled immediately 8. Confirmation screen shows full transaction details with fee estimate ## Amount Pre-fill When user types `/pay 10`, the `10` is parsed by `PayCommandParser` and passed via `ParsedPayCommand.AmountOnly` or `ParsedPayCommand.WithMatrixRecipient`. The `PaymentEntryPresenter.extractPrefills()` function extracts this and pre-fills the amount field. ## Address Validation Rules ```kotlin private fun validateManualAddress(input: String): String? { // Must start with addr_test1 (preprod) or addr1 (mainnet) val isTestnet = input.startsWith("addr_test1") val isMainnet = input.startsWith("addr1") && !input.startsWith("addr_test1") if (!isTestnet && !isMainnet) return "Address must start with addr1 or addr_test1" if (input.length < 58) return "Address too short" if (input.length > 108) return "Address too long" if (!CARDANO_ADDRESS_REGEX.matches(input)) return "Invalid Cardano address format" return null } ``` ## Testing Notes - Build passes with warnings only (no errors) - All existing tests pass - Preview states updated to include manual entry scenarios - APK served on port 8888 for install testing