View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0011964 | Taler | wallet (iOS App) | public | 2026-11-08 06:39 | 2026-11-08 06:39 |
| Reporter | bot-cg [agent] | Assigned To | Resolution deadline | ||
| Implementer | Reviewer | ||||
| Release signed by | Deployed by | ||||
| Verifier | Communicator | CM_7 | |||
| Data protection authority notification required | No | ||||
| CSIRT notification required | No | ||||
| Estimated effort (hours) | Actual effort (hours) | 0 | |||
| Fast-track | No | ||||
| Work package | none | ||||
| Priority | normal | Severity | minor | Reproducibility | have not tried |
| Status | new | Resolution | open | ||
| Summary | 0011964: Deposit is impossible: getMaxDepositAmount's response is nested, the app decodes it as flat, and the whole flow dies with "Unknown error" | ||||
| Description | A deposit cannot be started at all in the current build. The flow fails before `createDepositGroup` is ever called, and what the user sees is an alert titled "Unknown error" whose message is "The data couldn't be read because it is missing." The cause is a response-schema mismatch on `getMaxDepositAmount`. **What the app expects** -- TalerWallet1/Model/Model+Deposit.swift:144-147: fileprivate struct AmountResponse: Codable { let effectiveAmount: Amount? let rawAmount: Amount // not optional } used by `GetMaxDepositAmount` (:148-159) and read as `response.rawAmount` (:165). **What wallet-core returns** -- captured with taler-wallet-cli against the same core version, `api getMaxDepositAmount {"currency":"KUDOS","restrictScope":{"type":"exchange","url":"https://exchange.demo.taler.net/","currency":"KUDOS"}}`: result.material.instructedAmount = "KUDOS:17.28" result.material.effectiveAmount = "KUDOS:17.28" result.material.rawAmount = "KUDOS:17.28" result.material.fees.{coin,wire,refresh} result.available.instructedAmount = "KUDOS:94" result.available.effectiveAmount = "KUDOS:94" result.available.rawAmount = "KUDOS:94" result.available.fees.{coin,wire,refresh} result.exchangeDiagnostics.<exchange url>.{material,available,reasons} result.reasons = [] There is no `rawAmount` at the top level of `result`, so Swift's decoder throws and the app's os_log shows exactly that: WalletCore | "id":75 getMaxDepositAmount{"restrictScope":{"type":"exchange", "url":"https://exchange.demo.taler.net/","currency":"KUDOS"}, "currency":"KUDOS"} WalletModel | getMaxDepositAmount failed after 196 ms DecodingError.keyNotFound: Key 'rawAmount' not found in keyed decoding container. **The sibling call is still flat, which is probably how this was missed.** Model+P2P.swift:24-27 declares an identical `AmountResponse` for `getMaxPeerPushDebitAmount`, and that one decodes fine, because its result really is flat: result keys: ["exchangeBaseUrl", "effectiveAmount", "rawAmount"] So the two sibling requests have diverged in wallet-core and only the P2P side of the app matches. **Why it takes the whole flow down.** `DepositAmountAction.swift:70` awaits `model.getMaxDepositAmount(scope)` to decide `amountAvailable`; the throw propagates, the error view renders, and `createDepositGroup` (Model+Deposit.swift:235) is never reached. Coverage over 721 instrumented episodes agrees: `createDepositGroup` has never executed. | ||||
| Steps To Reproduce | 1. Install the current GNU_Taler debug build on an iOS 27.2 simulator. 2. Fund the wallet: the empty-wallet screen's "Get demo cash" (25 KUDOS). 3. Settings -> Bank Accounts -> + -> add an IBAN account (any valid IBAN, e.g. CH9300762011623852957, with a label and an account holder). 4. Actions -> Deposit -> select that account -> enter an amount. Observed: the "Deposit <amount>" button is enabled, and tapping it leaves the screen with an alert "Unknown error" / "The data couldn't be read because it is missing." No deposit is created. (The os_log line above appears as soon as the amount screen loads, since that is where getMaxDepositAmount is called.) Expected: the deposit proceeds, or at minimum the failure names what went wrong. The response shape can be checked without the app: taler-wallet-cli --wallet-db=<db> api getMaxDepositAmount \ '{"currency":"KUDOS","restrictScope":{"type":"exchange", "url":"https://exchange.demo.taler.net/","currency":"KUDOS"}}' | ||||
| Additional Information | Suggested fix: decode the nested shape and pick the field the view actually wants. Something like fileprivate struct MaxAmountResponse: Codable { struct Bucket: Codable { let instructedAmount: Amount? let effectiveAmount: Amount? let rawAmount: Amount } let material: Bucket let available: Bucket } and returning `response.available.rawAmount` from `getMaxDepositAmount` -- `available` is the one that reads as "the most that could be deposited" (KUDOS:94, the balance) where `material` was KUDOS:17.28 for the same wallet. Which of the two the UI should show is a judgment call for whoever owns the deposit screen; the decode has to change either way. Two adjacent observations, not filed separately because they are the same screen: * A `DecodingError` reaching the user as "Unknown error" / "The data couldn't be read because it is missing" gives nothing to search for and does not say which request failed. I added a note about the same shape to 11934. * `AmountResponse` is declared twice, `fileprivate` in each of Model+Deposit.swift and Model+P2P.swift. Sharing one type would have made this divergence a compile-time question rather than a runtime one. Found by a deterministic UI scenario in the fuzzing harness (harness/scenarios/deposit.json): it walks Actions -> Deposit -> account -> amount -> Deposit, and asserts both that no "Unknown error" alert appears and that `createDepositGroup` shows a non-zero count in the llvm coverage profile. The step-level trace and the os_log line above come from that run. | ||||
| Tags | No tags attached. | ||||
| Date Modified | Username | Field | Change |
|---|---|---|---|
| 2026-11-08 06:39 | bot-cg | New Issue |