View Issue Details

IDProjectCategoryView StatusLast Update
0012065Talerwallet (iOS App)public2026-11-11 02:25
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
PrioritynormalSeveritymajorReproducibilityhave not tried
Status newResolutionopen 
Summary0012065: iOS: bank-integrated withdrawal on a fresh wallet skips the ToS step; "Confirm Withdrawal" then fails with WALLET_EXCHANGE_TOS_NOT_ACCEPTED (stale exchange cache)
DescriptionOn a fresh wallet, a bank-integrated withdrawal (taler://withdraw/...) goes straight to "Confirm Withdrawal" without asking for the exchange's terms of service. Confirming then fails with "Internal core error / Error (WALLET_EXCHANGE_TOS_NOT_ACCEPTED)" and the withdrawal cannot continue from that screen.

What the user sees (same URI from bank.demo.taler.net, fresh wallets, origin/master builds of 2026-11-11):
- Android: amount and a "Terms of Service" button (before-android-1.png).
- WebExtension: "Review and accept this exchange's terms of service before withdrawing." with "Review terms"; Withdraw is disabled (before-web-1.png).
- iOS: amounts and "Confirm Withdrawal", no ToS step (before-ios-1.png). Tapping it: "Internal core error / Error (WALLET_EXCHANGE_TOS_NOT_ACCEPTED)" (before-ios-2.png). The same happens for an exchange that is not built in (tripwire.taler.net:8445, KUDOS): before-ios-3.png, before-ios-4.png.
It is a race: in our runs it happened 3 times out of 3 on fresh wallets; the first run after a simulator/driver restart showed the ToS step (before-ios-0-race.png).

Cause:
- wallet-core reports tosStatus "missing-tos" for an exchange whose terms it has not downloaded yet (tosCurrentEtag == null; taler-wallet-core/src/common.ts:353-366). That is the state of a built-in or newly discovered exchange before its first update.
- prepareBankIntegratedWithdrawal updates the bank's exchange (terms now known: "proposed") and emits ExchangeStateTransition notifications on the way. iOS handles each with Controller.updateExchange(for:state:) (Backend/WalletCore.swift:401-406, Controllers/Controller.swift:566-588), which calls exchange(for:); on a cache miss that fetches the entry and caches it (Controller.swift:543-556), possibly while it still says missing-tos.
- WithdrawUriScan.viewDidLoad then takes the exchange from that cache via controller.checkCurrencyInfo(for:) (Views/Sheets/WithdrawBankIntegrated/WithdrawUriScan.swift:130-132; Controller.swift:599-611). If the later notification has not replaced the record yet, the stale missing-tos entry goes to WithdrawAcceptView, whose gate treats missingTos as "nothing to accept" (WithdrawAcceptView.swift:63-71, 119-132), so "Confirm Withdrawal" is offered and acceptBankIntegratedWithdrawal answers 7037.
- Android asks wallet-core again after prepare (withdraw/WithdrawManager.kt:381-404 findExchangeByUrl -> getExchangeEntryByUrl, then TosReviewRequired at :434-438); the WebExtension gates on a live ListExchanges query (routes/App.tsx:3564-3568, routes/withdrawal-review-model.ts:54-65).

This is the "second defect" suspected in the 2026-11-10 note on 0011934 (cached exchange record disagreeing with wallet-core). That note's patch (offer the ToS when 7037 arrives anyway) is complementary; this one prevents the wrong screen.

Patch (ios-bank-withdrawal-tos.patch, against origin/master 674b7915): in WithdrawUriScan.viewDidLoad, keep checkCurrencyInfo (for the currency info) but use wallet-core's current entry, model.getExchangeByUrl(url:viewHandles:true), falling back to the cached one. One statement plus a comment.

Verified: GNU_Taler Release build for the simulator (iOS 27.2); three fresh runs against the demo bank all showed the ToS step (after-ios-1.png); after accepting, "Confirm Withdrawal" (after-ios-2.png) leads to "Authorize at Bank" (after-ios-3.png). Same against tripwire :8445 (after-ios-4.png, after-ios-5.png, confirm -> "Authorize at Bank").

Side observation, not part of this report: on tripwire :8445 the confirm screen shows no amounts at all (before-ios-3.png, after-ios-5.png).

Assisted by LLM
Steps To Reproduce1. Fresh install / empty wallet.
2. Start a withdrawal on https://bank.demo.taler.net/ (any amount) and open the taler://withdraw/... link in the app.
3. Observe "Confirm Withdrawal" without a "Terms of Service" step; tap it.
4. "Internal core error / Error (WALLET_EXCHANGE_TOS_NOT_ACCEPTED)".
(Automated: wc-stack withdraw-uri --amount KUDOS:5; wc-ios start --fresh; wc-ios uri <uri>.)
TagsNo tags attached.
Attached Files
before-android-1.png (37,055 bytes)   
before-android-1.png (37,055 bytes)   
before-web-1.png (111,898 bytes)   
before-web-1.png (111,898 bytes)   
before-ios-0-race.png (183,959 bytes)   
before-ios-0-race.png (183,959 bytes)   
before-ios-1.png (163,532 bytes)   
before-ios-1.png (163,532 bytes)   
before-ios-2.png (122,807 bytes)   
before-ios-2.png (122,807 bytes)   
before-ios-3.png (112,803 bytes)   
before-ios-3.png (112,803 bytes)   
before-ios-4.png (122,807 bytes)   
before-ios-4.png (122,807 bytes)   
after-ios-1.png (185,983 bytes)   
after-ios-1.png (185,983 bytes)   
after-ios-2.png (146,370 bytes)   
after-ios-2.png (146,370 bytes)   
after-ios-3.png (235,025 bytes)   
after-ios-3.png (235,025 bytes)   
after-ios-4.png (153,570 bytes)   
after-ios-4.png (153,570 bytes)   
after-ios-5.png (113,020 bytes)   
after-ios-5.png (113,020 bytes)   
ios-bank-withdrawal-tos.patch (1,225 bytes)   
diff --git a/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawUriScan.swift b/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawUriScan.swift
index 559dad2a..fa3ebc7a 100755
--- a/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawUriScan.swift
+++ b/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawUriScan.swift
@@ -129,7 +129,11 @@ struct WithdrawUriScan: View {
 
             if let baseUrl, let someExchange = try? await controller.checkCurrencyInfo(for: baseUrl) {
                 symLog.log("Info(for: \(baseUrl.trimURL) loaded")
-                exchange = someExchange
+                // checkCurrencyInfo answers from the Controller's exchange cache, which can predate the
+                // exchange update prepareBankIntegratedWithdrawal just did: a built-in exchange whose ToS
+                // were never downloaded is listed as "missing-tos", and WithdrawAcceptView would then offer
+                // "Confirm Withdrawal" without the ToS step. Use wallet-core's current entry instead.
+                exchange = (try? await model.getExchangeByUrl(url: baseUrl, viewHandles: true)) ?? someExchange
             } else {
                 exchangeError = true
             }
ios-bank-withdrawal-tos.patch (1,225 bytes)   

Required notifications

Address or linkRemarkAdded byNotifiedActions

Relationships

related to 0011934 reviewing Withdraw / Loading: expected wallet-core errors are titled "Internal core error" (e.g. 7054 withdrawal limit exceeded), with raw JSON as the body 

Activities

bot-cg [agent]

2026-11-11 02:25

updater   ~0030403

Follow-up: the same stale exchange record also hides the amounts on the confirmation screen, and the patch attached above fixes the ToS step but not that. Combined patch attached (ios-bank-withdrawal-tos-and-amounts.patch).

Observed with a bank withdrawal of KUDOS 3 from tripwire.taler.net:8446 (exchange :8445, not built in), fresh wallets:
- origin/master, race lost: "Withdrawal / tripwire.taler.net / Confirm Withdrawal" and nothing else, no amount, no fee, no "Using payment service" (before-ios-2-race-base.png). Race won: ToS step plus "Chosen amount to withdraw: KUDOS 3.00" (base-ios-amounts-when-race-not-hit.png).
- with only ios-bank-withdrawal-tos.patch: the ToS step is always there, but in 1 of 2 runs the amounts were still missing, also after accepting the ToS (before-ios-3-with-12065-patch-only.png).
- Android and WebExtension, same URI: amount shown (before-android-1.png "Total amount ク3.00", before-web-1.png "Send by bank transfer KUDOS:3 / Receive as digital cash KUDOS:3").

Cause: ThreeAmountsSection (Views/Transactions/ThreeAmountsSection.swift:109-114, 130) shows no rows while the currency info for its scope is unknown, and it only looks the info up in Controller's cache. WithdrawUriScan loads currency info via checkCurrencyInfo for the cached exchange's scope; when that record was stale, the fresh exchange's scope never gets currency info, so the section stays empty.

Extra hunk: ThreeAmountsSection.viewDidLoad loads the currency info (controller.updateInfo2) when the cache has none for the scope. With both changes: 4 of 4 fresh runs showed the ToS step and the amounts (after-ios-1.png), and the amounts stay after accepting the ToS (after-ios-2-after-tos.png). Built as GNU_Taler Release for the simulator.

Assisted by LLM
before-ios-2-race-base.png (112,803 bytes)   
before-ios-2-race-base.png (112,803 bytes)   
before-android-1-2.png (37,290 bytes)   
before-android-1-2.png (37,290 bytes)   
before-web-1-2.png (111,672 bytes)   
before-web-1-2.png (111,672 bytes)   
after-ios-1-2.png (185,754 bytes)   
after-ios-1-2.png (185,754 bytes)   
after-ios-2-after-tos.png (145,992 bytes)   
after-ios-2-after-tos.png (145,992 bytes)   
ios-bank-withdrawal-tos-and-amounts.patch (2,065 bytes)   
diff --git a/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawUriScan.swift b/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawUriScan.swift
index 559dad2a..fa3ebc7a 100755
--- a/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawUriScan.swift
+++ b/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawUriScan.swift
@@ -129,7 +129,11 @@ struct WithdrawUriScan: View {
 
             if let baseUrl, let someExchange = try? await controller.checkCurrencyInfo(for: baseUrl) {
                 symLog.log("Info(for: \(baseUrl.trimURL) loaded")
-                exchange = someExchange
+                // checkCurrencyInfo answers from the Controller's exchange cache, which can predate the
+                // exchange update prepareBankIntegratedWithdrawal just did: a built-in exchange whose ToS
+                // were never downloaded is listed as "missing-tos", and WithdrawAcceptView would then offer
+                // "Confirm Withdrawal" without the ToS step. Use wallet-core's current entry instead.
+                exchange = (try? await model.getExchangeByUrl(url: baseUrl, viewHandles: true)) ?? someExchange
             } else {
                 exchangeError = true
             }
diff --git a/TalerWallet1/Views/Transactions/ThreeAmountsSection.swift b/TalerWallet1/Views/Transactions/ThreeAmountsSection.swift
index ab70daf8..f7f66b40 100644
--- a/TalerWallet1/Views/Transactions/ThreeAmountsSection.swift
+++ b/TalerWallet1/Views/Transactions/ThreeAmountsSection.swift
@@ -109,6 +109,11 @@ struct ThreeAmountsSection: View {
     @MainActor
     private func viewDidLoad() async {
         if let scope {
+            if controller.info(for: scope) == nil {
+                // a scope seen for the first time (e.g. an exchange the wallet just learned
+                // about from a bank withdrawal): without its currency info nothing is shown
+                await controller.updateInfo2(scope)
+            }
             currencyInfo = controller.info(for: scope) ?? CurrencyInfo.zero(UNKNOWN)
         }
     }

Issue History

Date ModifiedUsernameFieldChange
2026-11-11 01:20bot-cgNew Issue
2026-11-11 01:20bot-cgRelationship addedrelated to 0011934
2026-11-11 01:20bot-cgFile Added: before-android-1.png
2026-11-11 01:20bot-cgFile Added: before-web-1.png
2026-11-11 01:20bot-cgFile Added: before-ios-0-race.png
2026-11-11 01:20bot-cgFile Added: before-ios-1.png
2026-11-11 01:20bot-cgFile Added: before-ios-2.png
2026-11-11 01:20bot-cgFile Added: before-ios-3.png
2026-11-11 01:20bot-cgFile Added: before-ios-4.png
2026-11-11 01:20bot-cgFile Added: after-ios-1.png
2026-11-11 01:20bot-cgFile Added: after-ios-2.png
2026-11-11 01:20bot-cgFile Added: after-ios-3.png
2026-11-11 01:20bot-cgFile Added: after-ios-4.png
2026-11-11 01:20bot-cgFile Added: after-ios-5.png
2026-11-11 01:20bot-cgFile Added: ios-bank-withdrawal-tos.patch
2026-11-11 02:25bot-cgNote Added: 0030403
2026-11-11 02:25bot-cgFile Added: before-ios-2-race-base.png
2026-11-11 02:25bot-cgFile Added: base-ios-amounts-when-race-not-hit.png
2026-11-11 02:25bot-cgFile Added: before-ios-3-with-12065-patch-only.png
2026-11-11 02:25bot-cgFile Added: before-android-1-2.png
2026-11-11 02:25bot-cgFile Added: before-web-1-2.png
2026-11-11 02:25bot-cgFile Added: after-ios-1-2.png
2026-11-11 02:25bot-cgFile Added: after-ios-2-after-tos.png
2026-11-11 02:25bot-cgFile Added: ios-bank-withdrawal-tos-and-amounts.patch