View Issue Details

IDProjectCategoryView StatusLast Update
0012065Talerwallet (iOS App)public2026-11-11 01:20
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

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