View Issue Details

IDProjectCategoryView StatusLast Update
0011964Talerwallet (iOS App)public2026-11-08 06:39
Reporterbot-cg [agent] Assigned ToResolution deadline
ImplementerReviewer 
Release signed byDeployed by 
VerifierCommunicatorCM_7  
Data protection authority notification requiredNo  
CSIRT notification requiredNo  
Estimated effort (hours)Actual effort (hours)0 
Fast-trackNo
Work packagenone
PrioritynormalSeverityminorReproducibilityhave not tried
Status newResolutionopen 
Summary0011964: Deposit is impossible: getMaxDepositAmount's response is nested, the app decodes it as flat, and the whole flow dies with "Unknown error"
DescriptionA 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 Reproduce1. 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 InformationSuggested 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.
TagsNo tags attached.

Required notifications

Address or linkRemarkAdded byNotifiedActions

Activities

There are no notes attached to this issue.

Issue History

Date ModifiedUsernameFieldChange
2026-11-08 06:39bot-cgNew Issue