gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-taler-ios] branch master updated (c38068b -> 6b94fe0)


From: gnunet
Subject: [taler-taler-ios] branch master updated (c38068b -> 6b94fe0)
Date: Thu, 11 Apr 2024 23:13:32 +0200

This is an automated email from the git hooks/post-receive script.

marc-stibane pushed a change to branch master
in repository taler-ios.

    from c38068b  details for refresh tx
     new 6413ef7  rename setConfig
     new da472b3  Bump version to 0.9.6 (1)
     new be420ef  updateExchangeEntry (temporarily) back to exchangeBaseUrl 
(instead of scopeInfo)
     new f2b2e74  emitObservabilityEvents
     new aa7472d  symLog after quickjs.sendMessage returned
     new a44efce  clientCancellationId
     new 60c7ee2  Laying the foundations of improved error handling
     new 6cb8d57  Improve errors and move them to WalletModel
     new f7c55f5  Implement error handling all around (+refactoring)
     new fa74633  Improvements to error dialog
     new 7cecef9  Prepare notification error handling
     new 8ba4476  Centralize all error handling in the model
     new 08a200e  Fixes for error handling rebase
     new eb30e3e  catch WalletBackendError.walletCoreError
     new 643226c  cleanup
     new 7587ccd  error handling, logging
     new 6b94fe0  Bump version to 0.9.7 (1)

The 17 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 TalerWallet.xcodeproj/project.pbxproj              |  28 +++--
 TalerWallet1/Backend/WalletBackendError.swift      |  21 ++--
 TalerWallet1/Backend/WalletCore.swift              |  31 +++--
 TalerWallet1/Controllers/Controller.swift          |  36 ++----
 TalerWallet1/Controllers/DebugViewC.swift          |   9 +-
 TalerWallet1/Controllers/PublicConstants.swift     |   3 +
 TalerWallet1/Helper/Encodable+toJSON.swift         |  21 ++++
 TalerWallet1/Model/Model+Balances.swift            |  15 +--
 TalerWallet1/Model/Model+Deposit.swift             |  12 +-
 TalerWallet1/Model/Model+Exchange.swift            |  61 +++++-----
 TalerWallet1/Model/Model+P2P.swift                 |  36 +++---
 TalerWallet1/Model/Model+Payment.swift             |  12 +-
 TalerWallet1/Model/Model+Pending.swift             |  14 +--
 TalerWallet1/Model/Model+Refund.swift              |   8 +-
 TalerWallet1/Model/Model+Settings.swift            |  12 +-
 TalerWallet1/Model/Model+Transactions.swift        |  42 +++----
 TalerWallet1/Model/Model+Withdraw.swift            |  36 +++---
 TalerWallet1/Model/Transaction.swift               |   1 +
 TalerWallet1/Model/WalletModel.swift               |  47 +++++---
 TalerWallet1/Views/Balances/BalancesListView.swift |  19 +--
 .../Views/Balances/BalancesSectionView.swift       |  21 ++--
 TalerWallet1/Views/Banking/DepositAmountV.swift    |  12 +-
 TalerWallet1/Views/Banking/DepositIbanV.swift      |  11 +-
 TalerWallet1/Views/Banking/DepositWithdrawV.swift  |  13 ++-
 TalerWallet1/Views/Banking/ExchangeListView.swift  |  11 +-
 TalerWallet1/Views/Banking/ExchangeRowView.swift   |   5 +-
 TalerWallet1/Views/Banking/ManualWithdraw.swift    |  24 +++-
 .../Views/Banking/ManualWithdrawDone.swift         |  13 +--
 TalerWallet1/Views/HelperViews/AmountInputV.swift  |   5 +-
 TalerWallet1/Views/HelperViews/BarGraph.swift      |   8 +-
 TalerWallet1/Views/HelperViews/SubjectInputV.swift |   5 +-
 .../Views/HelperViews/TransactionButton.swift      |   9 +-
 TalerWallet1/Views/Main/MainView.swift             |  37 +++++-
 TalerWallet1/Views/Main/WalletEmptyView.swift      |   6 +-
 TalerWallet1/Views/Peer2peer/P2PReadyV.swift       |  48 ++++----
 TalerWallet1/Views/Peer2peer/P2PSubjectV.swift     |   5 +-
 TalerWallet1/Views/Peer2peer/RequestPayment.swift  |  16 +--
 TalerWallet1/Views/Peer2peer/SendAmount.swift      |  12 +-
 TalerWallet1/Views/Settings/SettingsView.swift     |  62 +++-------
 TalerWallet1/Views/Sheets/ErrorSheet.swift         | 130 +++++++++++++++++++++
 .../Views/Sheets/P2P_Sheets/P2pAcceptDone.swift    |  16 ++-
 .../Views/Sheets/P2P_Sheets/P2pPayURIView.swift    |   6 +-
 .../Sheets/P2P_Sheets/P2pReceiveURIView.swift      |  11 +-
 .../Views/Sheets/Payment/PayTemplateV.swift        |  14 +--
 .../Views/Sheets/Payment/PaymentDone.swift         |  14 +--
 .../Views/Sheets/Payment/PaymentView.swift         |  19 ++-
 .../Views/Sheets/Refund/RefundURIView.swift        |  11 +-
 TalerWallet1/Views/Sheets/Sheet.swift              |  22 +++-
 .../WithdrawAcceptDone.swift                       |  16 +--
 .../WithdrawBankIntegrated/WithdrawTOSView.swift   |  40 +++----
 .../WithdrawBankIntegrated/WithdrawURIView.swift   |  21 ++--
 TalerWallet1/Views/Sheets/WithdrawExchangeV.swift  |  15 +--
 .../Views/Transactions/TransactionSummaryV.swift   |  18 ++-
 .../Views/Transactions/TransactionsListView.swift  |   4 +-
 TestFlight/WhatToTest.en-US.txt                    |  15 +++
 55 files changed, 643 insertions(+), 516 deletions(-)
 create mode 100644 TalerWallet1/Helper/Encodable+toJSON.swift
 create mode 100644 TalerWallet1/Views/Sheets/ErrorSheet.swift

diff --git a/TalerWallet.xcodeproj/project.pbxproj 
b/TalerWallet.xcodeproj/project.pbxproj
index 9e801c2..417df6e 100644
--- a/TalerWallet.xcodeproj/project.pbxproj
+++ b/TalerWallet.xcodeproj/project.pbxproj
@@ -278,6 +278,10 @@
                E37AA62B2AF197E5003850CF /* Model+Refund.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = E37AA6292AF197E5003850CF /* Model+Refund.swift 
*/; };
                E37AA62E2AF19BE0003850CF /* RefundURIView.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = E37AA62D2AF19BE0003850CF /* RefundURIView.swift 
*/; };
                E37AA62F2AF19BE0003850CF /* RefundURIView.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = E37AA62D2AF19BE0003850CF /* RefundURIView.swift 
*/; };
+               E3E48FB22B9B7B5400898A0F /* ErrorSheet.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = E3E48FB12B9B7B5400898A0F /* ErrorSheet.swift */; 
};
+               E3E48FB32B9B7B5400898A0F /* ErrorSheet.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = E3E48FB12B9B7B5400898A0F /* ErrorSheet.swift */; 
};
+               E3E48FB52B9B7D5000898A0F /* Encodable+toJSON.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = E3E48FB42B9B7D5000898A0F /* 
Encodable+toJSON.swift */; };
+               E3E48FB62B9B7D5000898A0F /* Encodable+toJSON.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = E3E48FB42B9B7D5000898A0F /* 
Encodable+toJSON.swift */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -458,6 +462,8 @@
                D14AFD3E24D232B500C51073 /* TalerUITests.xctest */ = {isa = 
PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path 
= TalerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
                E37AA6292AF197E5003850CF /* Model+Refund.swift */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.swift; path = 
"Model+Refund.swift"; sourceTree = "<group>"; };
                E37AA62D2AF19BE0003850CF /* RefundURIView.swift */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.swift; path = 
RefundURIView.swift; sourceTree = "<group>"; };
+               E3E48FB12B9B7B5400898A0F /* ErrorSheet.swift */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.swift; path = 
ErrorSheet.swift; sourceTree = "<group>"; };
+               E3E48FB42B9B7D5000898A0F /* Encodable+toJSON.swift */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.swift; path = 
"Encodable+toJSON.swift"; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -640,6 +646,7 @@
                                4E8E25322A1CD39700A27BFA /* 
EqualIconWidthDomain.swift */,
                                4EBA563E2A7FD9390084948B /* 
SuperScriptDigits.swift */,
                                4EC4008B2AE5664100DF72C7 /* 
CharacterSet+contains.swift */,
+                               E3E48FB42B9B7D5000898A0F /* 
Encodable+toJSON.swift */,
                        );
                        path = Helper;
                        sourceTree = "<group>";
@@ -830,6 +837,7 @@
                                4EB0952A2989CBFE0043A8A1 /* Payment */,
                                E37AA62C2AF19BA6003850CF /* Refund */,
                                4E3B4BBF2A41E64000CC88B8 /* P2P_Sheets */,
+                               E3E48FB12B9B7B5400898A0F /* ErrorSheet.swift */,
                        );
                        path = Sheets;
                        sourceTree = "<group>";
@@ -1130,6 +1138,7 @@
                                4E3EAE312A990778009F1BE8 /* SendAmount.swift in 
Sources */,
                                4E3EAE332A990778009F1BE8 /* 
EqualIconWidthDomain.swift in Sources */,
                                4EEC11932B83FB7A00146CFF /* SubjectInputV.swift 
in Sources */,
+                               E3E48FB52B9B7D5000898A0F /* 
Encodable+toJSON.swift in Sources */,
                                4E3EAE342A990778009F1BE8 /* 
SuperScriptDigits.swift in Sources */,
                                4E3EAE352A990778009F1BE8 /* P2pPayURIView.swift 
in Sources */,
                                4E3EAE362A990778009F1BE8 /* Model+Payment.swift 
in Sources */,
@@ -1151,6 +1160,7 @@
                                4E3EAE452A990778009F1BE8 /* P2PReadyV.swift in 
Sources */,
                                4E3EAE462A990778009F1BE8 /* 
TextFieldAlert.swift in Sources */,
                                4E3EAE472A990778009F1BE8 /* 
QuiteSomeCoins.swift in Sources */,
+                               E3E48FB22B9B7B5400898A0F /* ErrorSheet.swift in 
Sources */,
                                4E2D8DD52B45822A00234039 /* AmountV.swift in 
Sources */,
                                4E3EAE492A990778009F1BE8 /* 
ManualWithdrawDone.swift in Sources */,
                                4E3EAE4B2A990778009F1BE8 /* ShareSheet.swift in 
Sources */,
@@ -1247,6 +1257,7 @@
                                4E40E0BE29F25ABB00B85369 /* SendAmount.swift in 
Sources */,
                                4E8E25332A1CD39700A27BFA /* 
EqualIconWidthDomain.swift in Sources */,
                                4EEC11942B83FB7A00146CFF /* SubjectInputV.swift 
in Sources */,
+                               E3E48FB62B9B7D5000898A0F /* 
Encodable+toJSON.swift in Sources */,
                                4EBA563F2A7FD9390084948B /* 
SuperScriptDigits.swift in Sources */,
                                4E578E942A4822D500F21F1C /* P2pPayURIView.swift 
in Sources */,
                                4EB095542989CBFE0043A8A1 /* Model+Payment.swift 
in Sources */,
@@ -1268,6 +1279,7 @@
                                4EB3136129FEE79B007D68BC /* P2PReadyV.swift in 
Sources */,
                                4EB0956B2989CBFE0043A8A1 /* 
TextFieldAlert.swift in Sources */,
                                4EBA82AD2A3F580500E5F39A /* 
QuiteSomeCoins.swift in Sources */,
+                               E3E48FB32B9B7B5400898A0F /* ErrorSheet.swift in 
Sources */,
                                4E2D8DD62B45822A00234039 /* AmountV.swift in 
Sources */,
                                4EB431672A1E55C700C5690E /* 
ManualWithdrawDone.swift in Sources */,
                                4E753A082A0B6A5F002D9328 /* ShareSheet.swift in 
Sources */,
@@ -1372,7 +1384,7 @@
                                CODE_SIGN_ENTITLEMENTS = 
"$(TARGET_NAME).entitlements";
                                CODE_SIGN_IDENTITY = "Apple Development";
                                CODE_SIGN_STYLE = Automatic;
-                               CURRENT_PROJECT_VERSION = 3;
+                               CURRENT_PROJECT_VERSION = 1;
                                DEVELOPMENT_TEAM = GUDDQ9428Y;
                                ENABLE_PREVIEWS = YES;
                                GENERATE_INFOPLIST_FILE = YES;
@@ -1390,7 +1402,7 @@
                                        "$(inherited)",
                                        "@executable_path/Frameworks",
                                );
-                               MARKETING_VERSION = 0.9.5;
+                               MARKETING_VERSION = 0.9.7;
                                PRODUCT_BUNDLE_IDENTIFIER = 
"com.taler-systems.talerwallet-1";
                                PRODUCT_NAME = "$(TARGET_NAME)";
                                PROVISIONING_PROFILE_SPECIFIER = "";
@@ -1414,7 +1426,7 @@
                                CODE_SIGN_ENTITLEMENTS = 
"$(TARGET_NAME).entitlements";
                                CODE_SIGN_IDENTITY = "Apple Development";
                                CODE_SIGN_STYLE = Automatic;
-                               CURRENT_PROJECT_VERSION = 3;
+                               CURRENT_PROJECT_VERSION = 1;
                                DEVELOPMENT_TEAM = GUDDQ9428Y;
                                ENABLE_PREVIEWS = YES;
                                GENERATE_INFOPLIST_FILE = YES;
@@ -1432,7 +1444,7 @@
                                        "$(inherited)",
                                        "@executable_path/Frameworks",
                                );
-                               MARKETING_VERSION = 0.9.5;
+                               MARKETING_VERSION = 0.9.7;
                                PRODUCT_BUNDLE_IDENTIFIER = 
"com.taler-systems.talerwallet-1";
                                PRODUCT_NAME = "$(TARGET_NAME)";
                                PROVISIONING_PROFILE_SPECIFIER = "";
@@ -1573,7 +1585,7 @@
                                CODE_SIGN_ENTITLEMENTS = 
"$(TARGET_NAME).entitlements";
                                CODE_SIGN_IDENTITY = "Apple Development";
                                CODE_SIGN_STYLE = Automatic;
-                               CURRENT_PROJECT_VERSION = 3;
+                               CURRENT_PROJECT_VERSION = 1;
                                DEVELOPMENT_TEAM = GUDDQ9428Y;
                                ENABLE_PREVIEWS = YES;
                                GENERATE_INFOPLIST_FILE = YES;
@@ -1591,7 +1603,7 @@
                                        "$(inherited)",
                                        "@executable_path/Frameworks",
                                );
-                               MARKETING_VERSION = 0.9.5;
+                               MARKETING_VERSION = 0.9.7;
                                PRODUCT_BUNDLE_IDENTIFIER = 
"com.taler-systems.talerwallet-2";
                                PRODUCT_NAME = "$(TARGET_NAME)";
                                PROVISIONING_PROFILE_SPECIFIER = "";
@@ -1615,7 +1627,7 @@
                                CODE_SIGN_ENTITLEMENTS = 
"$(TARGET_NAME).entitlements";
                                CODE_SIGN_IDENTITY = "Apple Development";
                                CODE_SIGN_STYLE = Automatic;
-                               CURRENT_PROJECT_VERSION = 3;
+                               CURRENT_PROJECT_VERSION = 1;
                                DEVELOPMENT_TEAM = GUDDQ9428Y;
                                ENABLE_PREVIEWS = YES;
                                GENERATE_INFOPLIST_FILE = YES;
@@ -1633,7 +1645,7 @@
                                        "$(inherited)",
                                        "@executable_path/Frameworks",
                                );
-                               MARKETING_VERSION = 0.9.5;
+                               MARKETING_VERSION = 0.9.7;
                                PRODUCT_BUNDLE_IDENTIFIER = 
"com.taler-systems.talerwallet-2";
                                PRODUCT_NAME = "$(TARGET_NAME)";
                                PROVISIONING_PROFILE_SPECIFIER = "";
diff --git a/TalerWallet1/Backend/WalletBackendError.swift 
b/TalerWallet1/Backend/WalletBackendError.swift
index 9b4cea4..1465988 100644
--- a/TalerWallet1/Backend/WalletBackendError.swift
+++ b/TalerWallet1/Backend/WalletBackendError.swift
@@ -4,6 +4,7 @@
  */
 /**
  * @author Marc Stibane
+ * @author Iván Ávalos
  */
 import Foundation
 
@@ -13,38 +14,38 @@ enum WalletBackendError: Error {
     case initializationError
     case serializationError
     case deserializationError
-    case walletCoreError
+    case walletCoreError(WalletBackendResponseError?)
 }
 
 /// Information supplied by the backend describing an error.
 struct WalletBackendResponseError: Codable {
     /// Numeric error code defined defined in the GANA gnu-taler-error-codes 
registry.
-    var talerErrorCode: Int
+    var code: Int
     
     /// English description of the error code.
-    var talerErrorHint: String
+    var hint: String
     
     /// English diagnostic message that can give details for the instance of 
the error.
-    var message: String
-    
+    var message: String? = nil
+
     /// Error details, type depends on `talerErrorCode`.
-    var details: Data?
+    var details: Data? = nil
 }
 
 extension WalletCore {
     static func serializeRequestError() -> WalletBackendResponseError {
-        return WalletBackendResponseError(talerErrorCode: -1, talerErrorHint: 
"Could not serialize request.", message: "")
+        return WalletBackendResponseError(code: -1, hint: "Could not serialize 
request.", message: "")
     }
     
     static func parseResponseError() -> WalletBackendResponseError {
-        return WalletBackendResponseError(talerErrorCode: -2, talerErrorHint: 
"Could not parse response.", message: "")
+        return WalletBackendResponseError(code: -2, hint: "Could not parse 
response.", message: "")
     }
     
     static func parseFailureError() -> WalletBackendResponseError {
-        return WalletBackendResponseError(talerErrorCode: -3, talerErrorHint: 
"Could not parse error detail.", message: "")
+        return WalletBackendResponseError(code: -3, hint: "Could not parse 
error detail.", message: "")
     }
 
     static func walletError() -> WalletBackendResponseError {
-        return WalletBackendResponseError(talerErrorCode: -4, talerErrorHint: 
"Error detail.", message: "")
+        return WalletBackendResponseError(code: -4, hint: "Error detail.", 
message: "")
     }
 }
diff --git a/TalerWallet1/Backend/WalletCore.swift 
b/TalerWallet1/Backend/WalletCore.swift
index 8952261..47df531 100644
--- a/TalerWallet1/Backend/WalletCore.swift
+++ b/TalerWallet1/Backend/WalletCore.swift
@@ -2,6 +2,10 @@
  * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
  * See LICENSE.md
  */
+/**
+ * @author Marc Stibane
+ * @author Iván Ávalos
+ */
 import SwiftUI              // FOUNDATION has no AppStorage
 import AnyCodable
 import FTalerWalletcore
@@ -62,7 +66,7 @@ class WalletCore: QuickjsMessageHandler {
         let operation: String?
         let id: UInt?
         let result: AnyCodable?
-        let error: AnyCodable?  // should be WalletBackendResponseError?
+        let error: WalletBackendResponseError?
         let payload: AnyCodable?
     }
 
@@ -109,12 +113,10 @@ extension WalletCore {
                 let jsonData = try JSONEncoder().encode(walletError)
                 logger.error("wallet-core sent back an error for request 
\(requestId, privacy: .public)")
                 symLog.log("id:\(requestId)  \(walletError)")
-                // TODO: decode jsonData to WalletBackendResponseError - or 
HTTPError
-                completion(requestId, timeSent, jsonData, 
WalletCore.walletError())
+                completion(requestId, timeSent, jsonData, walletError)
             } catch {        // JSON encoding of response.result failed / 
should never happen
                 symLog.log(decoded)
                 logger.error("cannot encode wallet-core Error")
-                // TODO: show error alert
                 completion(requestId, timeSent, nil, 
WalletCore.parseFailureError())
             }
         } else {             // JSON decoding of error message failed
@@ -195,6 +197,11 @@ extension WalletCore {
                 logger.info("No State change: \(decoded.transactionId, 
privacy: .private(mask: .hash))")
                 return
             }
+
+            if decoded.errorInfo == nil {
+                postNotification(.Error, userInfo: [NOTIFICATIONERROR: 
decoded.errorInfo])
+            }
+
             let components = decoded.transactionId.components(separatedBy: ":")
             if components.count >= 3 {  // txn:$txtype:$uid
                 if let type = TransactionType(rawValue: components[1]) {
@@ -305,6 +312,7 @@ print("\n❗️ WalletCore.swift:251 Notification: ", 
anyPayload, "\n")        /
             }
         } catch let error {
             symLog.log("Error \(error) parsing notification: \(anyPayload)")   
 // TODO: .error
+            postNotification(.Error, userInfo: [NOTIFICATIONERROR: error])
         // TODO: if DevMode then should log into file for user
         }
     }
@@ -386,9 +394,9 @@ print("\n❗️ WalletCore.swift:251 Notification: ", 
anyPayload, "\n")        /
                 self.completions[requestId] = (sendTime, completionHandler)
                 self.requestsMade += 1
                 self.semaphore.signal()         // free requestsMade
-              self.symLog.log(jsonString)
               self.logger.log("sendRequest \(requestId, privacy: .public): 
\(request.operation, privacy: .public)")
                 self.quickjs.sendMessage(message: jsonString)
+              self.symLog.log(jsonString)
             } catch {       // call completion
                 self.semaphore.signal()
               self.symLog.log(error)
@@ -407,9 +415,13 @@ extension WalletCore {
             sendRequest(request: reqData) { requestId, timeSent, result, error 
in
                 let timeUsed = Date.now - timeSent
                 let millisecs = timeUsed.milliseconds
-                self.logger.info("Request \"id\":\(requestId, privacy: 
.public) took \(millisecs, privacy: .public) ms")
+                if let error {
+                    self.logger.error("Request \"id\":\(requestId, privacy: 
.public) failed after \(millisecs, privacy: .public) ms")
+                } else {
+                    self.logger.info("Request \"id\":\(requestId, privacy: 
.public) took \(millisecs, privacy: .public) ms")
+                }
                 var err: Error? = nil
-                if let json = result {
+                if let json = result, error == nil {
                     do {
                         let decoded = try 
JSONDecoder().decode(T.Response.self, from: json)
                         continuation.resume(returning: (decoded, requestId))
@@ -434,12 +446,13 @@ extension WalletCore {
                         err = error     // this will be thrown in 
continuation.resume(throwing:), otherwise keep nil
                     }
                 } else {
-                    if let error = error {
+                    // TODO: WALLET_CORE_REQUEST_CANCELLED
+                    if let error {
                         self.lastError = FullError(type: "error", operation: 
request.operation(), id: requestId, error: error)
                     } else {
                         self.lastError = nil
                     }
-                    err = WalletBackendError.walletCoreError
+                    err = WalletBackendError.walletCoreError(error)
                 }
                 continuation.resume(throwing: err ?? 
TransactionDecodingError.invalidStringValue)
             }
diff --git a/TalerWallet1/Controllers/Controller.swift 
b/TalerWallet1/Controllers/Controller.swift
index 03c6997..a3b79ee 100644
--- a/TalerWallet1/Controllers/Controller.swift
+++ b/TalerWallet1/Controllers/Controller.swift
@@ -87,35 +87,15 @@ class Controller: ObservableObject {
     }
 
     @MainActor
-    func getInfo(from exchangeBaseUrl: String, model: WalletModel) async -> 
CurrencyInfo? {
-        if let exchange = await model.getExchangeByUrl(url: exchangeBaseUrl) {
-//            let scopeInfo = exchange.scopeInfo
-//            if let info = hasInfo(for: scopeInfo.currency) {
-//                return info
-//            }
-//            do {
-//                let info = try await model.getCurrencyInfoM(scope: 
scopeInfo, delay: 0)
-//                await setInfo(info)
-//                return info
-//            } catch {
-//                return nil
-//            }
-
-            let scopeInfo = exchange.scopeInfo
-            if let info = hasInfo(for: scopeInfo.currency) {
-                return info
-            }
-            do {
-                let info = try await model.getCurrencyInfoM(scope: scopeInfo, 
delay: 0)
-                await setInfo(info)
-                return info
-            } catch {
-                return nil
-            }
-        } else {
-            // TODO: Error "Can't get Exchange Info"
+    func getInfo(from exchangeBaseUrl: String, model: WalletModel) async 
throws -> CurrencyInfo? {
+        let exchange = try await model.getExchangeByUrl(url: exchangeBaseUrl)
+        let scopeInfo = exchange.scopeInfo
+        if let info = hasInfo(for: scopeInfo.currency) {
+            return info
         }
-        return nil
+        let info = try await model.getCurrencyInfoM(scope: scopeInfo, delay: 0)
+        await setInfo(info)
+        return info
     }
 
     @MainActor
diff --git a/TalerWallet1/Controllers/DebugViewC.swift 
b/TalerWallet1/Controllers/DebugViewC.swift
index 8cd8108..c7de32f 100644
--- a/TalerWallet1/Controllers/DebugViewC.swift
+++ b/TalerWallet1/Controllers/DebugViewC.swift
@@ -39,15 +39,14 @@ public let VIEW_TRANSACTIONSUMMARY = VIEW_TRANSACTIONLIST + 
1       // 22 Transa
 public let VIEW_TRANSACTIONDETAIL = VIEW_TRANSACTIONSUMMARY + 1     // 23 
TransactionDetail
 
 
-
 // MARK: - Manual Withdrawal (from Banking / ExchangeList)
-// receive coins from bank ==> shows IBAN + Purpose/Subject for manual wire 
transfer
+// receive coins from bank ==> shows payee, IBAN + Purpose/Subject for manual 
wire transfer
 public let VIEW_WITHDRAWAL = VIEW_EMPTY_HISTORY + 10                // 30 
WithdrawAmount
 public let VIEW_WITHDRAW_TOS = VIEW_WITHDRAWAL + 1                  // 31 
WithdrawTOSView
 public let VIEW_WITHDRAW_ACCEPT = VIEW_WITHDRAW_TOS + 1             // 32
 
 // MARK: Manual Deposit (from Banking / ExchangeList)
-// send coins back to bank account ==> orders exchange to make the wire 
transfer
+// send coins back to customer ==> instruct exchange to make the wire transfer 
to the customer's bank account
 public let VIEW_DEPOSIT = VIEW_WITHDRAWAL + 10                      // 40 
Deposit Coins
 //public let VIEW_DEPOSIT_TOS                                       // - user 
already accepted the ToS at withdrawal, invoice or receive
 public let VIEW_DEPOSIT_ACCEPT = VIEW_DEPOSIT + 2                   // 42
@@ -57,7 +56,7 @@ public let VIEW_DEPOSIT_ACCEPT = VIEW_DEPOSIT + 2             
      // 42
 public let VIEW_P2P_SEND = VIEW_DEPOSIT + 10                        // 50 Send 
Coins
 //public let VIEW_SEND_TOS                                          // - user 
already accepted the ToS at withdrawal, invoice or receive
 public let VIEW_P2P_SUBJECT = VIEW_P2P_SEND + 2                     // 52 Send 
/ Request Subject
-public let VIEW_P2P_READY = VIEW_P2P_SEND + 3                       // 53 Send 
/ Request Ready 
+public let VIEW_P2P_READY = VIEW_P2P_SUBJECT + 1                    // 53 Send 
/ Request Ready
 
 // MARK: P2P Private Receive (from Balances)
 // pull credit from another wallet ==> shows QR code to be scanned / link to 
be sent by mail or messenger
@@ -104,6 +103,8 @@ public let SHEET_RCV_P2P_ACCEPT = SHEET_RCV_P2P_TOS + 1     
        // 162 Recei
 // openURL (Link, NFC or scan QR) ==> receive coins from merchant
 public let SHEET_REFUND = SHEET_RCV_P2P + 10                        // 170 
Receive Refunds
 
+
+// MARK: -
 extension UIDevice {
     var hasNotch: Bool {
         let bottom = UIApplication.shared.keyWindow?.safeAreaInsets.bottom ?? 0
diff --git a/TalerWallet1/Controllers/PublicConstants.swift 
b/TalerWallet1/Controllers/PublicConstants.swift
index 2eb2ad8..5b563d0 100644
--- a/TalerWallet1/Controllers/PublicConstants.swift
+++ b/TalerWallet1/Controllers/PublicConstants.swift
@@ -59,6 +59,8 @@ public let TALERURI = "talerUri"
 public let TRANSACTIONTRANSITION = "transactionTransition"
 public let TRANSACTIONID = "transactionID"
 
+public let NOTIFICATIONERROR = "error"
+
 /// Notifications sent by wallet-core
 extension Notification.Name {
     static let BalanceChange = Notification.Name("balance-change")
@@ -79,6 +81,7 @@ extension Notification.Name {
 //    static let PayOperationSuccess = 
Notification.Name("pay-operation-success")
     static let ProposalAccepted = Notification.Name("proposal-accepted")
     static let ProposalDownloaded = Notification.Name("proposal-downloaded")
+    static let Error = Notification.Name("error")
 }
 
 /// Notifications for internal synchronization
diff --git a/TalerWallet1/Helper/Encodable+toJSON.swift 
b/TalerWallet1/Helper/Encodable+toJSON.swift
new file mode 100644
index 0000000..b2ffc74
--- /dev/null
+++ b/TalerWallet1/Helper/Encodable+toJSON.swift
@@ -0,0 +1,21 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+/**
+ * @author Iván Ávalos
+ */
+import Foundation
+
+extension Encodable {
+    func toJSON(_ encoder: JSONEncoder = JSONEncoder()) -> String? {
+        let e = encoder
+        e.outputFormatting = .prettyPrinted
+        if let data = try? e.encode(self) {
+            let result = String(decoding: data, as: UTF8.self)
+            return String(result)
+        }
+
+        return nil
+    }
+}
diff --git a/TalerWallet1/Model/Model+Balances.swift 
b/TalerWallet1/Model/Model+Balances.swift
index 6d4be7f..09c5acd 100644
--- a/TalerWallet1/Model/Model+Balances.swift
+++ b/TalerWallet1/Model/Model+Balances.swift
@@ -54,19 +54,14 @@ fileprivate struct Balances: WalletBackendFormattedRequest {
 // MARK: -
 extension WalletModel {
     /// fetch Balances from Wallet-Core. No networking involved
-    @MainActor func balancesM(_ stack: CallStack)
-      async -> [Balance] {          // M for MainActor
+    @MainActor func balancesM(_ stack: CallStack, viewHandles: Bool = false)
+      async throws -> [Balance] {          // M for MainActor
         await semaphore.wait()
         defer { semaphore.signal() }
         if cachedBalances == nil {
-            do {
-                let request = Balances()
-                let response = try await sendRequest(request, ASYNCDELAY)
-                cachedBalances = response.balances
-            } catch {
-                logger.error("balancesM failed: \(error)")
-                // TODO: show error
-            }
+            let request = Balances()
+            let response = try await sendRequest(request, ASYNCDELAY, 
viewHandles: viewHandles)
+            cachedBalances = response.balances
         } else {
             logger.trace("returning cached Balances")
         }
diff --git a/TalerWallet1/Model/Model+Deposit.swift 
b/TalerWallet1/Model/Model+Deposit.swift
index 8afd67d..2d520ca 100644
--- a/TalerWallet1/Model/Model+Deposit.swift
+++ b/TalerWallet1/Model/Model+Deposit.swift
@@ -74,27 +74,27 @@ fileprivate struct CreateDepositGroup: 
WalletBackendFormattedRequest {
 extension WalletModel {
     /// validate IBAN
     @MainActor
-    func validateIbanM(_ iban: String)       // M for MainActor
+    func validateIbanM(_ iban: String, viewHandles: Bool = false)       // M 
for MainActor
     async throws -> Bool {
         let request = ValidateIban(iban: iban)
-        let response = try await sendRequest(request, ASYNCDELAY)
+        let response = try await sendRequest(request, ASYNCDELAY, viewHandles: 
viewHandles)
         return response.valid
     }
 
     /// deposit coins. Networking involved
     @MainActor
-    func prepareDepositM(_ depositPaytoUri: String, amount: Amount)       // M 
for MainActor
+    func prepareDepositM(_ depositPaytoUri: String, amount: Amount, 
viewHandles: Bool = false)       // M for MainActor
       async throws -> PrepareDepositResult {
           let request = PrepareDeposit(depositPaytoUri: depositPaytoUri, 
amount: amount)
-          let response = try await sendRequest(request, ASYNCDELAY)
+          let response = try await sendRequest(request, ASYNCDELAY, 
viewHandles: viewHandles)
           return response
     }
 
     @MainActor
-    func createDepositGroupM(_ depositPaytoUri: String, amount: Amount)  // M 
for MainActor
+    func createDepositGroupM(_ depositPaytoUri: String, amount: Amount, 
viewHandles: Bool = false)  // M for MainActor
       async throws -> DepositGroupResult {
           let request = CreateDepositGroup(depositPaytoUri: depositPaytoUri, 
amount: amount)
-          let response = try await sendRequest(request, ASYNCDELAY)
+          let response = try await sendRequest(request, ASYNCDELAY, 
viewHandles: viewHandles)
           return response
     }
 }
diff --git a/TalerWallet1/Model/Model+Exchange.swift 
b/TalerWallet1/Model/Model+Exchange.swift
index 2eeeec5..0377ff7 100644
--- a/TalerWallet1/Model/Model+Exchange.swift
+++ b/TalerWallet1/Model/Model+Exchange.swift
@@ -117,12 +117,14 @@ fileprivate struct GetExchangeByUrl: 
WalletBackendFormattedRequest {
 fileprivate struct UpdateExchange: WalletBackendFormattedRequest {
     struct Response: Decodable {}   // no result - getting no error back means 
success
     func operation() -> String { "updateExchangeEntry" }
-    func args() -> Args { Args(scopeInfo: scopeInfo) }
+//    func args() -> Args { Args(scopeInfo: scopeInfo) }
+    func args() -> Args { Args(exchangeBaseUrl: exchangeBaseUrl) }
 
-    var scopeInfo: ScopeInfo
+//    var scopeInfo: ScopeInfo
+    var exchangeBaseUrl: String
 
     struct Args: Encodable {
-        var scopeInfo: ScopeInfo
+        var exchangeBaseUrl: String
     }
 }
 
@@ -154,11 +156,11 @@ fileprivate struct GetCurrencySpecification: 
WalletBackendFormattedRequest {
 // MARK: -
 extension WalletModel {
     /// ask wallet-core for its list of known exchanges
-    @MainActor func listExchangesForScopedCurrencyM(scope: ScopeInfo)
+    @MainActor func listExchangesForScopedCurrencyM(scope: ScopeInfo, 
viewHandles: Bool = false)
       async -> [ExchangeUrlItem] {            // M for MainActor
         do {
             let request = ListExchangesForScopedCurrency(scope: scope)
-            let response = try await sendRequest(request, ASYNCDELAY)
+            let response = try await sendRequest(request, ASYNCDELAY, 
viewHandles: viewHandles)
             return response.exchanges
         } catch {
             return []               // empty, but not nil
@@ -166,52 +168,47 @@ extension WalletModel {
     }
 
     /// ask wallet-core for its list of known exchanges
-    @MainActor func listExchangesM()
-      async -> [Exchange] {   // M for MainActor
-        do {
-            let request = ListExchanges()
-            let response = try await sendRequest(request, ASYNCDELAY)
-            return response.exchanges
-        } catch {
-            // TODO: Error
-            return []               // empty, but not nil
-        }
+    @MainActor func listExchangesM(devMode: Bool = false, viewHandles: Bool = 
false)
+      async throws -> [Exchange] {   // M for MainActor
+        let request = ListExchanges()
+        let response = try await sendRequest(request, ASYNCDELAY, viewHandles: 
viewHandles)
+        return response.exchanges
     }
 
     /// add a new exchange with URL to the wallet's list of known exchanges
-    func getExchangeByUrl(url: String)
-      async -> Exchange? {
-        do {
-            let request = GetExchangeByUrl(exchangeBaseUrl: url)
+    func getExchangeByUrl(url: String, viewHandles: Bool = false)
+      async throws -> Exchange {
+        let request = GetExchangeByUrl(exchangeBaseUrl: url)
 //            logger.info("query for exchange: \(url, privacy: .public)")
-            let response = try await sendRequest(request)
-            return response
-        } catch {
-            return nil
-        }
+        let response = try await sendRequest(request, viewHandles: viewHandles)
+        return response
     }
 
     /// add a new exchange with URL to the wallet's list of known exchanges
-    func addExchange(url: String)
+    func addExchange(url: String, viewHandles: Bool = false)
     async throws {
         let request = AddExchange(exchangeBaseUrl: url)
         logger.info("adding exchange: \(url, privacy: .public)")
-        _ = try await sendRequest(request)
+        _ = try await sendRequest(request, viewHandles: viewHandles)
     }
 
     /// ask wallet-core to update an existing exchange by querying it for 
denominations, fees, and scoped currency info
-    func updateExchange(scopeInfo: ScopeInfo)
+//    func updateExchange(scopeInfo: ScopeInfo)
+//    func updateExchange(scopeInfo: ScopeInfo, viewHandles: Bool = false)
+    func updateExchange(exchangeBaseUrl: String, viewHandles: Bool = false)
       async throws  {
-        let request = UpdateExchange(scopeInfo: scopeInfo)
-        logger.info("updating exchange for: \(scopeInfo.currency, privacy: 
.public)")
-        _ = try await sendRequest(request)
+//        let request = UpdateExchange(scopeInfo: scopeInfo)
+        let request = UpdateExchange(exchangeBaseUrl: exchangeBaseUrl)
+//        logger.info("updating exchange for: \(scopeInfo.currency, privacy: 
.public)")
+        logger.info("updating exchange: \(exchangeBaseUrl, privacy: .public)")
+        _ = try await sendRequest(request, viewHandles: viewHandles)
     }
 
     @MainActor
-    func getCurrencyInfoM(scope: ScopeInfo, delay: UInt = 0)
+    func getCurrencyInfoM(scope: ScopeInfo, delay: UInt = 0, viewHandles: Bool 
= false)
       async throws -> CurrencyInfo {
         let request = GetCurrencySpecification(scope: scope)
-        let response = try await sendRequest(request, ASYNCDELAY + delay)
+        let response = try await sendRequest(request, ASYNCDELAY + delay, 
viewHandles: viewHandles)
         return CurrencyInfo(scope: scope, specs: 
response.currencySpecification,
                         formatter: CurrencyFormatter.formatter(scope: scope,
                                                                specs: 
response.currencySpecification))
diff --git a/TalerWallet1/Model/Model+P2P.swift 
b/TalerWallet1/Model/Model+P2P.swift
index 28ad60d..498eced 100644
--- a/TalerWallet1/Model/Model+P2P.swift
+++ b/TalerWallet1/Model/Model+P2P.swift
@@ -32,10 +32,10 @@ fileprivate struct GetMaxPeerPushAmount: 
WalletBackendFormattedRequest {
 }
 extension WalletModel {
     @MainActor
-    func getMaxPeerPushAmountM(_ currency: String)       // M for MainActor
+    func getMaxPeerPushAmountM(_ currency: String, viewHandles: Bool = false)  
     // M for MainActor
       async throws -> AmountResponse {
         let request = GetMaxPeerPushAmount(currency: currency)
-        let response = try await sendRequest(request, ASYNCDELAY)
+        let response = try await sendRequest(request, ASYNCDELAY, viewHandles: 
viewHandles)
         return response
     }
 } // getMaxPeerPushAmountM
@@ -58,10 +58,10 @@ fileprivate struct CheckPeerPushDebit: 
WalletBackendFormattedRequest {
 }
 extension WalletModel {
     @MainActor
-    func checkPeerPushDebitM(_ amount: Amount)       // M for MainActor
+    func checkPeerPushDebitM(_ amount: Amount, viewHandles: Bool = false)      
 // M for MainActor
       async throws -> CheckPeerPushDebitResponse {
         let request = CheckPeerPushDebit(amount: amount)
-        let response = try await sendRequest(request, ASYNCDELAY)
+        let response = try await sendRequest(request, ASYNCDELAY, viewHandles: 
viewHandles)
         return response
     }
 } // checkPeerPushDebitM
@@ -90,11 +90,11 @@ fileprivate struct InitiatePeerPushDebit: 
WalletBackendFormattedRequest {
 }
 extension WalletModel {
     @MainActor
-    func initiatePeerPushDebitM(_ baseURL: String?, terms: PeerContractTerms)  
     // M for MainActor
+    func initiatePeerPushDebitM(_ baseURL: String?, terms: PeerContractTerms, 
viewHandles: Bool = false)       // M for MainActor
     async throws -> InitiatePeerPushDebitResponse {
         let request = InitiatePeerPushDebit(exchangeBaseUrl: baseURL,
                                             partialContractTerms: terms)
-        let response = try await sendRequest(request, ASYNCDELAY)
+        let response = try await sendRequest(request, ASYNCDELAY, viewHandles: 
viewHandles)
         return response
     }
 } // initiatePeerPushDebitM
@@ -123,10 +123,10 @@ fileprivate struct CheckPeerPullCredit: 
WalletBackendFormattedRequest {
 }
 extension WalletModel {
     @MainActor
-    func checkPeerPullCreditM(_ amount: Amount, exchangeBaseUrl: String?)      
 // M for MainActor
+    func checkPeerPullCreditM(_ amount: Amount, exchangeBaseUrl: String?, 
viewHandles: Bool = false)       // M for MainActor
       async throws -> CheckPeerPullCreditResponse {
         let request = CheckPeerPullCredit(exchangeBaseUrl: exchangeBaseUrl, 
amount: amount)
-        let response = try await sendRequest(request, ASYNCDELAY)
+        let response = try await sendRequest(request, ASYNCDELAY, viewHandles: 
viewHandles)
         return response
     }
 } // checkPeerPullCreditM
@@ -151,11 +151,11 @@ fileprivate struct InitiatePeerPullCredit: 
WalletBackendFormattedRequest {
 }
 extension WalletModel {
     @MainActor
-    func initiatePeerPullCreditM(_ baseURL: String?, terms: PeerContractTerms) 
 // M for MainActor
+    func initiatePeerPullCreditM(_ baseURL: String?, terms: PeerContractTerms, 
viewHandles: Bool = false)  // M for MainActor
       async throws -> InitiatePeerPullCreditResponse {
         let request = InitiatePeerPullCredit(exchangeBaseUrl: baseURL,
                                         partialContractTerms: terms)
-        let response = try await sendRequest(request, ASYNCDELAY)
+        let response = try await sendRequest(request, ASYNCDELAY, viewHandles: 
viewHandles)
         return response
     }
 } // initiatePeerPullCreditM
@@ -181,10 +181,10 @@ fileprivate struct PreparePeerPushCredit: 
WalletBackendFormattedRequest {
 }
 extension WalletModel {
     @MainActor
-    func preparePeerPushCreditM(_ talerUri: String)       // M for MainActor
+    func preparePeerPushCreditM(_ talerUri: String, viewHandles: Bool = false) 
      // M for MainActor
       async throws -> PreparePeerPushCreditResponse {
         let request = PreparePeerPushCredit(talerUri: talerUri)
-        let response = try await sendRequest(request, ASYNCDELAY)
+        let response = try await sendRequest(request, ASYNCDELAY, viewHandles: 
viewHandles)
         return response
     }
 } // preparePeerPushCreditM
@@ -202,10 +202,10 @@ fileprivate struct AcceptPeerPushCredit: 
WalletBackendFormattedRequest {
 }
 extension WalletModel {
     @MainActor
-    func acceptPeerPushCreditM(_ transactionId: String)       // M for 
MainActor
+    func acceptPeerPushCreditM(_ transactionId: String, viewHandles: Bool = 
false)       // M for MainActor
       async throws -> Decodable {
         let request = AcceptPeerPushCredit(transactionId: transactionId)
-        let response = try await sendRequest(request, ASYNCDELAY)
+        let response = try await sendRequest(request, ASYNCDELAY, viewHandles: 
viewHandles)
         return response
     }
 } // acceptPeerPushCreditM
@@ -230,10 +230,10 @@ fileprivate struct PreparePeerPullDebit: 
WalletBackendFormattedRequest {
 }
 extension WalletModel {
     @MainActor
-    func preparePeerPullDebitM(_ talerUri: String)       // M for MainActor
+    func preparePeerPullDebitM(_ talerUri: String, viewHandles: Bool = false)  
     // M for MainActor
       async throws -> PreparePeerPullDebitResponse {
         let request = PreparePeerPullDebit(talerUri: talerUri)
-        let response = try await sendRequest(request, ASYNCDELAY)
+        let response = try await sendRequest(request, ASYNCDELAY, viewHandles: 
viewHandles)
         return response
     }
 } // preparePeerPullDebitM
@@ -251,10 +251,10 @@ fileprivate struct ConfirmPeerPullDebit: 
WalletBackendFormattedRequest {
 }
 extension WalletModel {
     @MainActor
-    func confirmPeerPullDebitM(_ transactionId: String)       // M for 
MainActor
+    func confirmPeerPullDebitM(_ transactionId: String, viewHandles: Bool = 
false)       // M for MainActor
       async throws -> Decodable {
         let request = ConfirmPeerPullDebit(transactionId: transactionId)
-        let response = try await sendRequest(request, ASYNCDELAY)
+        let response = try await sendRequest(request, ASYNCDELAY, viewHandles: 
viewHandles)
         return response
     }
 } // confirmPeerPullDebitM
diff --git a/TalerWallet1/Model/Model+Payment.swift 
b/TalerWallet1/Model/Model+Payment.swift
index f4f1204..3abb316 100644
--- a/TalerWallet1/Model/Model+Payment.swift
+++ b/TalerWallet1/Model/Model+Payment.swift
@@ -197,25 +197,25 @@ fileprivate struct confirmPayForUri: 
WalletBackendFormattedRequest {
 extension WalletModel {
     /// load payment details. Networking involved
     @MainActor
-    func preparePayForUriM(_ talerPayUri: String)       // M for MainActor
+    func preparePayForUriM(_ talerPayUri: String, viewHandles: Bool = false)   
    // M for MainActor
       async throws -> PreparePayResult {
           let request = PreparePayForUri(talerPayUri: talerPayUri)
-          let response = try await sendRequest(request, ASYNCDELAY)
+          let response = try await sendRequest(request, ASYNCDELAY, 
viewHandles: viewHandles)
           return response
     }
     @MainActor
-    func preparePayForTemplateM(_ talerPayTemplateUri: String, amount: 
Amount?, summary: String?)       // M for MainActor
+    func preparePayForTemplateM(_ talerPayTemplateUri: String, amount: 
Amount?, summary: String?, viewHandles: Bool = false)       // M for MainActor
     async throws -> PreparePayResult {
         let templateParams = TemplateParams(amount: amount, summary: summary)
         let request = PreparePayForTemplate(talerPayTemplateUri: 
talerPayTemplateUri, templateParams: templateParams)
-        let response = try await sendRequest(request, ASYNCDELAY)
+        let response = try await sendRequest(request, ASYNCDELAY, viewHandles: 
viewHandles)
         return response
     }
     @MainActor
-    func confirmPayM(_ transactionId: String)              // M for MainActor
+    func confirmPayM(_ transactionId: String, viewHandles: Bool = false)       
       // M for MainActor
       async throws -> ConfirmPayResult {
           let request = confirmPayForUri(transactionId: transactionId)
-          let response = try await sendRequest(request, ASYNCDELAY)
+          let response = try await sendRequest(request, ASYNCDELAY, 
viewHandles: viewHandles)
           return response
     }
 }
diff --git a/TalerWallet1/Model/Model+Pending.swift 
b/TalerWallet1/Model/Model+Pending.swift
index e2dec9f..f316293 100644
--- a/TalerWallet1/Model/Model+Pending.swift
+++ b/TalerWallet1/Model/Model+Pending.swift
@@ -42,14 +42,10 @@ struct PendingOperation: Codable, Hashable {
 }
 // MARK: -
 extension WalletModel {
-    @MainActor func getPendingOperationsM()
-      async -> [PendingOperation] {   // M for MainActor
-        do {
-            let request = GetPendingOperations()
-            let response = try await sendRequest(request, ASYNCDELAY)
-            return response.pendingOperations
-        } catch {
-            return []
-        }
+    @MainActor func getPendingOperationsM(viewHandles: Bool = false)
+      async throws -> [PendingOperation] {   // M for MainActor
+        let request = GetPendingOperations()
+        let response = try await sendRequest(request, ASYNCDELAY, viewHandles: 
viewHandles)
+        return response.pendingOperations
     }
 }
diff --git a/TalerWallet1/Model/Model+Refund.swift 
b/TalerWallet1/Model/Model+Refund.swift
index 4892c61..6ce5407 100644
--- a/TalerWallet1/Model/Model+Refund.swift
+++ b/TalerWallet1/Model/Model+Refund.swift
@@ -40,15 +40,15 @@ struct StartRefundQueryRequest: 
WalletBackendFormattedRequest {
 // MARK: -
 extension WalletModel {
     @MainActor
-    func startRefundForUriM(url: String) async throws -> String {
+    func startRefundForUriM(url: String, viewHandles: Bool = false) async 
throws -> String {
         let request = StartRefundURIRequest(talerRefundUri: url)
-        let response = try await sendRequest(request, ASYNCDELAY)
+        let response = try await sendRequest(request, ASYNCDELAY, viewHandles: 
viewHandles)
         return response.transactionId
     }
 
     @MainActor
-    func startRefundM(transactionId: String) async throws {
+    func startRefundM(transactionId: String, viewHandles: Bool = false) async 
throws {
         let request = StartRefundQueryRequest(transactionId: transactionId)
-        let _ = try await sendRequest(request, ASYNCDELAY)
+        let _ = try await sendRequest(request, ASYNCDELAY, viewHandles: 
viewHandles)
     }
 }
diff --git a/TalerWallet1/Model/Model+Settings.swift 
b/TalerWallet1/Model/Model+Settings.swift
index a727679..0561023 100644
--- a/TalerWallet1/Model/Model+Settings.swift
+++ b/TalerWallet1/Model/Model+Settings.swift
@@ -33,13 +33,13 @@ fileprivate struct WithdrawTestBalanceRequest: 
WalletBackendFormattedRequest {
     }
 }
 extension WalletModel {
-    @MainActor func loadTestKudosM(test: Bool, amount: Amount)
+    @MainActor func loadTestKudosM(test: Bool, amount: Amount, viewHandles: 
Bool = false)
     async throws {          // M for MainActor
         let request = WithdrawTestBalanceRequest(amount: amount,
 //                                             bankBaseUrl: test ? TESTBANK : 
DEMOBANK,
                                      corebankApiBaseUrl: test ? TESTBANK : 
DEMOBANK,
                                         exchangeBaseUrl: test ? TESTEXCHANGE : 
DEMOEXCHANGE)
-        let response = try await sendRequest(request, ASYNCDELAY)
+        let response = try await sendRequest(request, ASYNCDELAY, viewHandles: 
viewHandles)
     }
 } // loadTestKudosM()
 // MARK: -
@@ -77,7 +77,7 @@ fileprivate struct RunIntegrationTest: 
WalletBackendFormattedRequest {
     }
 }
 extension WalletModel {
-    @MainActor func runIntegrationTestM(newVersion: Bool, test: Bool)
+    @MainActor func runIntegrationTestM(newVersion: Bool, test: Bool, 
viewHandles: Bool = false)
     async throws {               // M for MainActor
         let amountW = Amount(currency: test ? TESTCURRENCY : DEMOCURRENCY, 
cent: 300)
         let amountS = Amount(currency: test ? TESTCURRENCY : DEMOCURRENCY, 
cent: 100)
@@ -89,7 +89,7 @@ extension WalletModel {
                                          merchantAuthToken: MERCHANTAUTHTOKEN,
                                          amountToWithdraw: amountW,
                                          amountToSpend: amountS)
-        let _ = try await sendRequest(request, ASYNCDELAY)
+        let _ = try await sendRequest(request, ASYNCDELAY, viewHandles: 
viewHandles)
     }
 } // runIntegrationTestM()
   // MARK: -
@@ -111,9 +111,9 @@ fileprivate struct InfiniteTransactionLoop: 
WalletBackendFormattedRequest {
     }
 }
 extension WalletModel {
-    func testingInfiniteTransaction(delayMs: Int32, shouldFetch: Bool)
+    func testingInfiniteTransaction(delayMs: Int32, shouldFetch: Bool, 
viewHandles: Bool = false)
       async throws {
         let request = InfiniteTransactionLoop(delayMs: delayMs, shouldFetch: 
shouldFetch)
-        let _ = try await sendRequest(request, ASYNCDELAY)
+        let _ = try await sendRequest(request, ASYNCDELAY, viewHandles: 
viewHandles)
     }
 } // runIntegrationTestM()
diff --git a/TalerWallet1/Model/Model+Transactions.swift 
b/TalerWallet1/Model/Model+Transactions.swift
index 26288bf..c8d19df 100644
--- a/TalerWallet1/Model/Model+Transactions.swift
+++ b/TalerWallet1/Model/Model+Transactions.swift
@@ -103,52 +103,48 @@ struct ResumeTransaction: WalletBackendFormattedRequest {
 // MARK: -
 extension WalletModel {
     /// ask wallet-core for its list of transactions filtered by searchString
-    func transactionsT(_ stack: CallStack, scopeInfo: ScopeInfo, searchString: 
String? = nil, 
-                       sort: String = "descending", includeRefreshes: Bool = 
false)
-    async -> [Transaction] {                                          // might 
be called from a background thread itself
-        do {
-            let request = GetTransactions(scopeInfo: scopeInfo, currency: 
scopeInfo.currency, search: searchString, sort: sort, includeRefreshes: 
includeRefreshes)
-            let response = try await sendRequest(request, ASYNCDELAY)
-            return response.transactions
-        } catch {
-            return []
-        }
+    func transactionsT(_ stack: CallStack, scopeInfo: ScopeInfo, searchString: 
String? = nil,
+                       sort: String = "descending", includeRefreshes: Bool = 
false, viewHandles: Bool = false)
+    async throws -> [Transaction] {
+        let request = GetTransactions(scopeInfo: scopeInfo, currency: 
scopeInfo.currency, search: searchString, sort: sort, includeRefreshes: 
includeRefreshes)
+        let response = try await sendRequest(request, ASYNCDELAY, viewHandles: 
viewHandles)
+        return response.transactions
     }
     /// fetch transactions from Wallet-Core. No networking involved
-    @MainActor func transactionsMA(_ stack: CallStack, scopeInfo: ScopeInfo, 
searchString: String? = nil, sort: String = "descending")
-    async -> [Transaction] {    // M for MainActor
-        return await transactionsT(stack.push(), scopeInfo: scopeInfo, 
searchString: searchString, sort: sort)
+    @MainActor func transactionsMA(_ stack: CallStack, scopeInfo: ScopeInfo, 
searchString: String? = nil, sort: String = "descending", viewHandles: Bool = 
false)
+    async throws -> [Transaction] {    // M for MainActor
+        return try await transactionsT(stack.push(), scopeInfo: scopeInfo, 
searchString: searchString, sort: sort, viewHandles: viewHandles)
     }
 
     /// abort the specified transaction from Wallet-Core. No networking 
involved
-    func abortTransaction(transactionId: String) async throws {
+    func abortTransaction(transactionId: String, viewHandles: Bool = false) 
async throws {
         let request = AbortTransaction(transactionId: transactionId)
         logger.notice("abortTransaction: \(transactionId, privacy: 
.private(mask: .hash))")
-        let _ = try await sendRequest(request, ASYNCDELAY)
+        let _ = try await sendRequest(request, ASYNCDELAY, viewHandles: 
viewHandles)
     }
 
     /// delete the specified transaction from Wallet-Core. No networking 
involved
-    func deleteTransaction(transactionId: String) async throws {
+    func deleteTransaction(transactionId: String, viewHandles: Bool = false) 
async throws {
         let request = DeleteTransaction(transactionId: transactionId)
         logger.notice("deleteTransaction: \(transactionId, privacy: 
.private(mask: .hash))")
-        let _ = try await sendRequest(request, ASYNCDELAY)
+        let _ = try await sendRequest(request, ASYNCDELAY, viewHandles: 
viewHandles)
     }
 
-    func failTransaction(transactionId: String) async throws {
+    func failTransaction(transactionId: String, viewHandles: Bool = false) 
async throws {
         let request = FailTransaction(transactionId: transactionId)
         logger.notice("failTransaction: \(transactionId, privacy: 
.private(mask: .hash))")
-        let _ = try await sendRequest(request, ASYNCDELAY)
+        let _ = try await sendRequest(request, ASYNCDELAY, viewHandles: 
viewHandles)
     }
 
-    func suspendTransaction(transactionId: String) async throws {
+    func suspendTransaction(transactionId: String, viewHandles: Bool = false) 
async throws {
         let request = SuspendTransaction(transactionId: transactionId)
         logger.notice("suspendTransaction: \(transactionId, privacy: 
.private(mask: .hash))")
-        let _ = try await sendRequest(request, ASYNCDELAY)
+        let _ = try await sendRequest(request, ASYNCDELAY, viewHandles: 
viewHandles)
     }
 
-    func resumeTransaction(transactionId: String) async throws {
+    func resumeTransaction(transactionId: String, viewHandles: Bool = false) 
async throws {
         let request = ResumeTransaction(transactionId: transactionId)
         logger.notice("resumeTransaction: \(transactionId, privacy: 
.private(mask: .hash))")
-        let _ = try await sendRequest(request, ASYNCDELAY)
+        let _ = try await sendRequest(request, ASYNCDELAY, viewHandles: 
viewHandles)
     }
 }
diff --git a/TalerWallet1/Model/Model+Withdraw.swift 
b/TalerWallet1/Model/Model+Withdraw.swift
index 8ca4725..7aa2488 100644
--- a/TalerWallet1/Model/Model+Withdraw.swift
+++ b/TalerWallet1/Model/Model+Withdraw.swift
@@ -92,13 +92,15 @@ struct WithdrawalAmountDetails: Decodable {
 fileprivate struct GetWithdrawalDetailsForAmount: 
WalletBackendFormattedRequest {
     typealias Response = WithdrawalAmountDetails
     func operation() -> String { "getWithdrawalDetailsForAmount" }
-    func args() -> Args { Args(exchangeBaseUrl: exchangeBaseUrl, amount: 
amount) }
+    func args() -> Args { Args(exchangeBaseUrl: exchangeBaseUrl, amount: 
amount, clientCancellationId: cancellationId) }
 
     var exchangeBaseUrl: String
     var amount: Amount
+    var cancellationId: String?
     struct Args: Encodable {
         var exchangeBaseUrl: String
         var amount: Amount
+        var clientCancellationId: String?
     }
 }
 // MARK: -
@@ -192,56 +194,58 @@ fileprivate struct AcceptManualWithdrawal: 
WalletBackendFormattedRequest {
 extension WalletModel {
     /// load withdraw-exchange details. Networking involved
     @MainActor                    // M for MainActor
-    func loadWithdrawalExchangeForUriM(_ talerUri: String)
+    func loadWithdrawalExchangeForUriM(_ talerUri: String, viewHandles: Bool = 
false)
       async throws -> WithdrawExchangeResponse {
         let request = PrepareWithdrawExchange(talerUri: talerUri)
-        let response = try await sendRequest(request, ASYNCDELAY)
+        let response = try await sendRequest(request, ASYNCDELAY, viewHandles: 
viewHandles)
         return response
     }
     /// load withdrawal details. Networking involved
     @MainActor                  // M for MainActor
-    func getWithdrawalDetailsForUriM(_ talerWithdrawUri: String)
+    func getWithdrawalDetailsForUriM(_ talerWithdrawUri: String, viewHandles: 
Bool = false)
       async throws -> WithdrawUriInfoResponse {
         let request = GetWithdrawalDetailsForURI(talerWithdrawUri: 
talerWithdrawUri)
-        let response = try await sendRequest(request, ASYNCDELAY)
+        let response = try await sendRequest(request, ASYNCDELAY, viewHandles: 
viewHandles)
         return response
     }
     @MainActor                     // M for MainActor
-    func getWithdrawalDetailsForAmountM(_ exchangeBaseUrl: String, amount: 
Amount)
+    func getWithdrawalDetailsForAmountM(_ exchangeBaseUrl: String, amount: 
Amount,
+                                           cancellationId: String? = nil, 
viewHandles: Bool = false)
       async throws -> WithdrawalAmountDetails {
         let request = GetWithdrawalDetailsForAmount(exchangeBaseUrl: 
exchangeBaseUrl,
-                                                             amount: amount)
-        let response = try await sendRequest(request, ASYNCDELAY)
+                                                             amount: amount,
+                                                     cancellationId: 
cancellationId)
+        let response = try await sendRequest(request, ASYNCDELAY, viewHandles: 
viewHandles)
         return response
     }
     @MainActor                  // M for MainActor
-    func loadExchangeTermsOfServiceM(_ exchangeBaseUrl: String, 
acceptedFormat: [String], acceptLanguage: String)
+    func loadExchangeTermsOfServiceM(_ exchangeBaseUrl: String, 
acceptedFormat: [String], acceptLanguage: String, viewHandles: Bool = false)
       async throws -> ExchangeTermsOfService {
         let request = GetExchangeTermsOfService(exchangeBaseUrl: 
exchangeBaseUrl,
                                                  acceptedFormat: 
acceptedFormat,
                                                  acceptLanguage: 
acceptLanguage)
-        let response = try await sendRequest(request, ASYNCDELAY)
+        let response = try await sendRequest(request, ASYNCDELAY, viewHandles: 
viewHandles)
         return response
     }
     @MainActor              // M for MainActor
-    func setExchangeTOSAcceptedM(_ exchangeBaseUrl: String, etag: String)
+    func setExchangeTOSAcceptedM(_ exchangeBaseUrl: String, etag: String, 
viewHandles: Bool = false)
       async throws -> Decodable {
         let request = SetExchangeTOSAccepted(exchangeBaseUrl: exchangeBaseUrl, 
etag: etag)
-        let response = try await sendRequest(request, ASYNCDELAY)
+        let response = try await sendRequest(request, ASYNCDELAY, viewHandles: 
viewHandles)
         return response
     }
     @MainActor               // M for MainActor
-    func sendAcceptIntWithdrawalM(_ exchangeBaseUrl: String, withdrawURL: 
String)
+    func sendAcceptIntWithdrawalM(_ exchangeBaseUrl: String, withdrawURL: 
String, viewHandles: Bool = false)
       async throws -> AcceptWithdrawalResponse? {
         let request = AcceptBankIntegratedWithdrawal(talerWithdrawUri: 
withdrawURL, exchangeBaseUrl: exchangeBaseUrl)
-        let response = try await sendRequest(request, ASYNCDELAY)
+        let response = try await sendRequest(request, ASYNCDELAY, viewHandles: 
viewHandles)
         return response
     }
     @MainActor                  // M for MainActor
-    func sendAcceptManualWithdrawalM(_ exchangeBaseUrl: String, amount: 
Amount, restrictAge: Int?)
+    func sendAcceptManualWithdrawalM(_ exchangeBaseUrl: String, amount: 
Amount, restrictAge: Int?, viewHandles: Bool = false)
       async throws -> AcceptManualWithdrawalResult? {
         let request = AcceptManualWithdrawal(exchangeBaseUrl: exchangeBaseUrl, 
amount: amount, restrictAge: restrictAge)
-        let response = try await sendRequest(request, ASYNCDELAY)
+        let response = try await sendRequest(request, ASYNCDELAY, viewHandles: 
viewHandles)
         return response
     }
 }
diff --git a/TalerWallet1/Model/Transaction.swift 
b/TalerWallet1/Model/Transaction.swift
index dc2d7a6..85ed2d3 100644
--- a/TalerWallet1/Model/Transaction.swift
+++ b/TalerWallet1/Model/Transaction.swift
@@ -142,6 +142,7 @@ struct TransactionTransition: Codable {             // 
Notification
     var newTxState: TransactionState
     var transactionId: String
     var experimentalUserData: String?       // KYC
+    var errorInfo:  WalletBackendResponseError?
 }
 
 enum TxAction: String, Codable {
diff --git a/TalerWallet1/Model/WalletModel.swift 
b/TalerWallet1/Model/WalletModel.swift
index 9c57e90..5463f76 100644
--- a/TalerWallet1/Model/WalletModel.swift
+++ b/TalerWallet1/Model/WalletModel.swift
@@ -27,7 +27,12 @@ class WalletModel: ObservableObject {
     let semaphore = AsyncSemaphore(value: 1)
     var cachedBalances: [Balance]? = nil
 
-    func sendRequest<T: WalletBackendFormattedRequest> (_ request: T, _ delay: 
UInt = 0)
+    @Published var showError: Bool = false
+    @Published var error: ErrorData? = nil {
+        didSet { showError = error != nil }
+    }
+
+    func sendRequest<T: WalletBackendFormattedRequest> (_ request: T, _ delay: 
UInt = 0, viewHandles: Bool = false)
       async throws -> T.Response {    // T for any Thread
 #if !DEBUG
         logger.log("sending: \(request.operation(), privacy: .public)")
@@ -47,20 +52,24 @@ class WalletModel: ObservableObject {
         } catch {       // rethrows
             let timeUsed = Date.now - sendTime
             logger.error("\(request.operation(), privacy: .public) failed 
after \(timeUsed.milliseconds, privacy: .public) ms\n\(error, privacy: 
.public)")
+            if !viewHandles {
+                // TODO: symlog + controller sound
+                self.error = .error(error)
+            }
             throw error
         }
     }
 
-    func getTransactionByIdT(_ transactionId: String)
+    func getTransactionByIdT(_ transactionId: String, viewHandles: Bool = 
false)
       async throws -> Transaction {              // T for any Thread
         // might be called from a background thread itself
         let request = GetTransactionById(transactionId: transactionId)
-        return try await sendRequest(request, ASYNCDELAY)
+        return try await sendRequest(request, ASYNCDELAY, viewHandles: 
viewHandles)
     }
     /// get the specified transaction from Wallet-Core. No networking involved
-    @MainActor func getTransactionByIdM(_ transactionId: String)
+    @MainActor func getTransactionByIdM(_ transactionId: String, viewHandles: 
Bool = false)
     async throws -> Transaction {                // M for MainActor
-        return try await getTransactionByIdT(transactionId)      // call 
GetTransactionById on main thread
+        return try await getTransactionByIdT(transactionId, viewHandles: 
viewHandles)      // call GetTransactionById on main thread
     }
 }
 // MARK: -
@@ -102,7 +111,7 @@ fileprivate struct Testing: Encodable {
         self.insecureTrustExchange = false
         self.preventThrottling = false
         self.skipDefaults = false
-        self.emitObservabilityEvents = false
+        self.emitObservabilityEvents = true
     }
 }
 
@@ -120,7 +129,7 @@ fileprivate struct Config: Encodable {
 fileprivate struct ConfigRequest: WalletBackendFormattedRequest {
     var setTesting: Bool
 
-    func operation() -> String { "setConfig" }
+    func operation() -> String { "setWalletRunConfig" }
     func args() -> Args {
         let testing = Testing(devModeActive: setTesting)
         let builtin = Builtin(exchanges: [])
@@ -177,11 +186,11 @@ fileprivate struct InitRequest: 
WalletBackendFormattedRequest {
 
 extension WalletModel {
     /// initalize Wallet-Core. Will do networking
-    func initWalletCoreT(setTesting: Bool) async throws -> VersionInfo {
+    func initWalletCoreT(setTesting: Bool, viewHandles: Bool = false) async 
throws -> VersionInfo {
                     // T for any Thread
         let docPath = try docPath(sqlite3: true)
         let request = InitRequest(persistentStoragePath: docPath, setTesting: 
setTesting)
-        let response = try await sendRequest(request, 0)    // no Delay
+        let response = try await sendRequest(request, 0, viewHandles: 
viewHandles)    // no Delay
         return response.versionInfo
     }
 
@@ -233,10 +242,10 @@ fileprivate struct ResetRequest: 
WalletBackendFormattedRequest {
 
 extension WalletModel {
     /// reset Wallet-Core
-    func resetWalletCoreT() async throws {
+    func resetWalletCoreT(viewHandles: Bool = false) async throws {
         // T for any Thread
         let request = ResetRequest()
-        _ = try await sendRequest(request, 0)
+        _ = try await sendRequest(request, 0, viewHandles: viewHandles)
     }
 }
 // MARK: -
@@ -255,9 +264,21 @@ fileprivate struct DevExperimentRequest: 
WalletBackendFormattedRequest {
 
 extension WalletModel {
     /// tell wallet-core to mock new transactions
-    func devExperimentT(talerUri: String) async throws {
+    func devExperimentT(talerUri: String, viewHandles: Bool = false) async 
throws {
         // T for any Thread
         let request = DevExperimentRequest(talerUri: talerUri)
-        _ = try await sendRequest(request, 0)
+        _ = try await sendRequest(request, 0, viewHandles: viewHandles)
+    }
+}
+
+extension WalletModel {
+    @MainActor
+    func showError(_ error: ErrorData) {
+        self.error = error
+    }
+
+    @MainActor
+    func cleanError() {
+        self.error = nil
     }
 }
diff --git a/TalerWallet1/Views/Balances/BalancesListView.swift 
b/TalerWallet1/Views/Balances/BalancesListView.swift
index f12483d..804edf2 100644
--- a/TalerWallet1/Views/Balances/BalancesListView.swift
+++ b/TalerWallet1/Views/Balances/BalancesListView.swift
@@ -16,6 +16,7 @@ struct BalancesListView: View {
     @Binding var shouldReloadBalances: Int
 
     @EnvironmentObject private var model: WalletModel
+    @EnvironmentObject private var controller: Controller
 
     @State private var lastReloadedBalances = 0
     @State private var amountToTransfer = Amount.zero(currency: EMPTYSTRING)   
 // Update currency when used
@@ -78,14 +79,18 @@ struct BalancesListView: View {
 
     /// runs on MainActor if called in some Task {}
     @discardableResult
-    private func reloadBalances(_ stack: CallStack, _ invalidateCache: Bool) 
async -> Int {
+    private func reloadBalances(_ stack: CallStack, _ invalidateCache: Bool) 
async -> Int? {
         if invalidateCache {
             model.cachedBalances = nil
         }
-        let reloaded = await model.balancesM(stack.push())
-        let count = reloaded.count
-        balances = reloaded         // redraw
-        return count
+
+        if let reloaded = try? await model.balancesM(stack.push()) {
+            let count = reloaded.count
+            balances = reloaded         // redraw
+            return count
+        }
+
+        return nil
     }
 
     var body: some View {
@@ -126,7 +131,7 @@ extension BalancesListView {
         @Binding var amountToTransfer: Amount
         @Binding var summary: String
         @Binding var shouldReloadBalances: Int
-        var reloadBalances: (_ stack: CallStack, _ invalidateCache: Bool) 
async -> Int
+        var reloadBalances: (_ stack: CallStack, _ invalidateCache: Bool) 
async -> Int?
 
         var body: some View {
 #if PRINT_CHANGES
@@ -155,7 +160,7 @@ extension BalancesListView {
             .refreshable {  // already async
                 symLog?.log("refreshing balances")
                 let count = await reloadBalances(stack.push("refreshing 
balances"), true)
-                if count > 0 {
+                if let count, count > 0 {
                     NotificationCenter.default.post(name: .BalanceReloaded, 
object: nil)
                 }
             }
diff --git a/TalerWallet1/Views/Balances/BalancesSectionView.swift 
b/TalerWallet1/Views/Balances/BalancesSectionView.swift
index 0d1a80a..1fab4db 100644
--- a/TalerWallet1/Views/Balances/BalancesSectionView.swift
+++ b/TalerWallet1/Views/Balances/BalancesSectionView.swift
@@ -40,21 +40,23 @@ struct BalancesSectionView {
     @State private var completedTransactions: [Transaction] = []
     @State private var pendingTransactions: [Transaction] = []
 
-    func reloadOneAction(_ transactionId: String) async throws -> Transaction {
-        return try await model.getTransactionByIdT(transactionId)
+    func reloadOneAction(_ transactionId: String, _ viewHandles: Bool) async 
throws -> Transaction {
+        return try await model.getTransactionByIdT(transactionId, viewHandles: 
viewHandles)
     }
 
     @State private var sectionID = UUID()
     @State private var shownSectionID = UUID()  // guaranteed to be different 
the first time
 
     func reloadCompleted(_ stack: CallStack) async -> () {
-        transactions = await model.transactionsT(stack.push(), scopeInfo: 
balance.scopeInfo, includeRefreshes: developerMode)
-        completedTransactions = WalletModel.completedTransactions(transactions)
+        if let transactions = try? await model.transactionsT(stack.push(), 
scopeInfo: balance.scopeInfo, includeRefreshes: developerMode) {
+            completedTransactions = 
WalletModel.completedTransactions(transactions)
+        }
     }
 
     func reloadPending(_ stack: CallStack) async -> () {
-        transactions = await model.transactionsT(stack.push(), scopeInfo: 
balance.scopeInfo, includeRefreshes: developerMode)
-        pendingTransactions = WalletModel.pendingTransactions(transactions)
+        if let transactions = try? await model.transactionsT(stack.push(), 
scopeInfo: balance.scopeInfo, includeRefreshes: developerMode) {
+            pendingTransactions = WalletModel.pendingTransactions(transactions)
+        }
     }
 }
 
@@ -128,11 +130,12 @@ extension BalancesSectionView: View {
 //            if shownSectionID != sectionID {
                 symLog.log(".task for BalancesSectionView - reload 
Transactions")
             // TODO: only load the MAXRECENT most recent transactions
-            let response = await model.transactionsT(stack.push(".task - 
reload Transactions"), scopeInfo: scopeInfo, includeRefreshes: developerMode)
+            if let response = try? await model.transactionsT(stack.push(".task 
- reload Transactions"), scopeInfo: scopeInfo, includeRefreshes: developerMode) 
{
                 transactions = response
                 pendingTransactions = WalletModel.pendingTransactions(response)
                 completedTransactions = 
WalletModel.completedTransactions(response)
                 shownSectionID = sectionID
+            }
 //            } else {
 //                symLog.log("task for BalancesSectionView \(sectionID) ❗️ 
skip reloading Transactions")
 //            }
@@ -169,7 +172,7 @@ fileprivate struct BalancesPendingRowView: View {
     let balance: Balance                            // this is the currency to 
be used
     @Binding var pendingTransactions: [Transaction]
     let reloadPending: (_ stack: CallStack) async -> ()
-    let reloadOneAction: ((_ transactionId: String) async throws -> 
Transaction)
+    let reloadOneAction: ((_ transactionId: String, _ viewHandles: Bool) async 
throws -> Transaction)
 
     var body: some View {
         let pendingIncoming = balance.pendingIncoming
@@ -229,7 +232,7 @@ fileprivate struct BalancesNavigationLinksView: View {
     @Binding var summary: String
     @Binding var completedTransactions: [Transaction]
     let reloadAllAction: (_ stack: CallStack) async -> ()
-    let reloadOneAction: ((_ transactionId: String) async throws -> 
Transaction)
+    let reloadOneAction: ((_ transactionId: String, _ viewHandles: Bool) async 
throws -> Transaction)
 //    @EnvironmentObject private var model: WalletModel
 
     @State private var buttonSelected: Int? = nil
diff --git a/TalerWallet1/Views/Banking/DepositAmountV.swift 
b/TalerWallet1/Views/Banking/DepositAmountV.swift
index 70c3414..ae90f94 100644
--- a/TalerWallet1/Views/Banking/DepositAmountV.swift
+++ b/TalerWallet1/Views/Banking/DepositAmountV.swift
@@ -122,13 +122,11 @@ struct DepositAmountV: View {
                         depositStarted = true    // don't run twice
                         Task { // runs on MainActor
                             symLog.log("Deposit")
-                            do {
-                                let result = try await 
model.createDepositGroupM(paytoUri, amount: amountToTransfer)
+                            if let result = try? await 
model.createDepositGroupM(paytoUri, amount: amountToTransfer) {
                                 symLog.log(result.transactionId)
                                 ViewState2.shared.popToRootView(stack.push())
                                 NotificationCenter.default.post(name: 
.TransactionDone, object: nil, userInfo: nil)
-                            } catch {    // TODO: show error
-                                symLog.log(error.localizedDescription)
+                            } else {
                                 depositStarted = false
                             }
                         }
@@ -168,15 +166,13 @@ struct DepositAmountV: View {
                         feeStr = EMPTYSTRING
                         prepareDepositResult = nil
                     } else if let paytoUri {
-                        do {
-                            let ppCheck = try await 
model.prepareDepositM(paytoUri, amount: amountToTransfer)
+                        if let ppCheck = try? await 
model.prepareDepositM(paytoUri, amount: amountToTransfer) {
                             prepareDepositResult = ppCheck
                             if let feeAmount = fee(ppCheck: 
prepareDepositResult) {
                                 feeStr = feeAmount.string(currencyInfo)
                             } else { feeStr = EMPTYSTRING }
                             announce(this: "\(amountVoiceOver), \(feeLabel)")
-                        } catch {    // TODO: error
-                            symLog.log(error.localizedDescription)
+                        } else {
                             prepareDepositResult = nil
                         }
                     }
diff --git a/TalerWallet1/Views/Banking/DepositIbanV.swift 
b/TalerWallet1/Views/Banking/DepositIbanV.swift
index 6b6fc9c..6d588bf 100644
--- a/TalerWallet1/Views/Banking/DepositIbanV.swift
+++ b/TalerWallet1/Views/Banking/DepositIbanV.swift
@@ -127,13 +127,10 @@ struct DepositIbanV: View {
 //            print("❗️ P2PSubjectV onDisappear")
         }
         .task(id: depositIBAN) {
-            do {
-                if try await model.validateIbanM(depositIBAN) {
-                    let payto = 
"payto://iban/\(depositIBAN)?receiver-name=\(accountHolder)"
-                    paytoUri = 
payto.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
-                }
-            } catch {    // TODO: error
-                symLog.log(error.localizedDescription)
+            if (try? await model.validateIbanM(depositIBAN)) == true {
+                let payto = 
"payto://iban/\(depositIBAN)?receiver-name=\(accountHolder)"
+                paytoUri = payto.addingPercentEncoding(withAllowedCharacters: 
.urlQueryAllowed)!
+            } else {
                 paytoUri = nil
             }
         }
diff --git a/TalerWallet1/Views/Banking/DepositWithdrawV.swift 
b/TalerWallet1/Views/Banking/DepositWithdrawV.swift
index 6bff91d..d538360 100644
--- a/TalerWallet1/Views/Banking/DepositWithdrawV.swift
+++ b/TalerWallet1/Views/Banking/DepositWithdrawV.swift
@@ -26,11 +26,14 @@ struct DepositWithdrawV: View {
         amountToTransfer.setCurrency(currency)
         buttonSelected = button      // will trigger NavigationLink
         // after user tapped a button, while navigation animation runs, 
contact Exchange to update Fees
-        Task { // runs on MainActor
-            do {
-                try await model.updateExchange(scopeInfo: scopeInfo)
-            } catch {    // TODO: error handling - couldn't updateExchange
-                symLog.log("error: \(error)")
+        if let url = scopeInfo.url {
+            Task { // runs on MainActor
+                do {
+//                    try await model.updateExchange(scopeInfo: scopeInfo)
+                    try await model.updateExchange(exchangeBaseUrl: url)
+                } catch {    // TODO: error handling - couldn't updateExchange
+                    symLog.log("error: \(error)")
+                }
             }
         }
     }
diff --git a/TalerWallet1/Views/Banking/ExchangeListView.swift 
b/TalerWallet1/Views/Banking/ExchangeListView.swift
index 504e077..6191730 100644
--- a/TalerWallet1/Views/Banking/ExchangeListView.swift
+++ b/TalerWallet1/Views/Banking/ExchangeListView.swift
@@ -16,6 +16,7 @@ struct ExchangeListView: View {
     @Binding var balances: [Balance]
     let navTitle: String
     @EnvironmentObject private var model: WalletModel
+    @EnvironmentObject private var controller: Controller
 
     @State var showAlert: Bool = false
     @State var newExchange: String = TESTEXCHANGE
@@ -23,12 +24,9 @@ struct ExchangeListView: View {
     func addExchange(_ exchange: String) -> Void {
         Task { // runs on MainActor
             symLog.log("adding: \(exchange)")
-            do {
-                try await model.addExchange(url: exchange)
+            if let _ = try? await model.addExchange(url: exchange) {
                 symLog.log("added: \(exchange)")
                 announce(this: "added: \(exchange)")
-            } catch {    // TODO: error handling - couldn't add exchangeURL
-                symLog.log("error: \(error)")
             }
         }
     }
@@ -74,6 +72,7 @@ struct ExchangeListCommonV {
     @Binding var balances: [Balance]
 
     @EnvironmentObject private var model: WalletModel
+    @EnvironmentObject private var controller: Controller
     @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
 //    @AppStorage("depositIBAN") var depositIBAN = EMPTYSTRING
 //    @AppStorage("accountHolder") var accountHolder = EMPTYSTRING
@@ -84,7 +83,9 @@ struct ExchangeListCommonV {
     @State private var amountToTransfer = Amount.zero(currency: EMPTYSTRING)   
  // TODO: Hold different values for different currencies?
 
     func reloadExchanges() async -> Void {
-        exchanges = await model.listExchangesM()
+        if let exc = try? await model.listExchangesM() {
+            exchanges = exc
+        }
     }
 }
 // MARK: -
diff --git a/TalerWallet1/Views/Banking/ExchangeRowView.swift 
b/TalerWallet1/Views/Banking/ExchangeRowView.swift
index 58208f9..1792c72 100644
--- a/TalerWallet1/Views/Banking/ExchangeRowView.swift
+++ b/TalerWallet1/Views/Banking/ExchangeRowView.swift
@@ -64,12 +64,9 @@ struct ExchangeRowView: View {
                 // FIXME: remove fake ScopeInfo once the REAL one is in 
exchange.scopeInfo
                 let scopeInfo = exchange.scopeInfo
                              ?? ScopeInfo(type: .global, currency: currency)
-                do {
-                    let info = try await model.getCurrencyInfoM(scope: 
scopeInfo, delay: delay)
+                if let info = try? await model.getCurrencyInfoM(scope: 
scopeInfo, delay: delay) {
 //                    logger.info("got info: \(scope.currency, privacy: 
.public)")
                     await controller.setInfo(info)
-                } catch {    // TODO: error handling - couldn't get 
CurrencyInfo
-//                    logger.error("Couldn't get info for: \(scope.currency, 
privacy: .public)\n\(error)")
                 }
             }
         }
diff --git a/TalerWallet1/Views/Banking/ManualWithdraw.swift 
b/TalerWallet1/Views/Banking/ManualWithdraw.swift
index 6427475..0499fc4 100644
--- a/TalerWallet1/Views/Banking/ManualWithdraw.swift
+++ b/TalerWallet1/Views/Banking/ManualWithdraw.swift
@@ -110,22 +110,34 @@ struct ManualWithdraw: View {
         }
       }
         .task(id: amountToTransfer.value) { // re-run this whenever 
amountToTransfer changes
-            symLog.log("getExchangeByUrl(\(scopeInfo?.url))")
             if let exchangeBaseUrl = scopeInfo?.url {
+                symLog.log("getExchangeByUrl(\(exchangeBaseUrl))")
                 if exchange == nil || exchange?.tosStatus != .accepted {
-                    if let exc = await model.getExchangeByUrl(url: 
exchangeBaseUrl) {
-                        exchange = exc
-                    } else {
+                    do {
+                        exchange = try await model.getExchangeByUrl(url: 
exchangeBaseUrl)
+                    } catch {
                         // TODO: Error "Can't get Exchange / Payment Service 
Provider Info"
+                        symLog.log(error.localizedDescription)
+                        model.showError(.error(error))
+                        controller.playSound(0)
                     }
                 }
                 if !amountToTransfer.isZero {
                     do {
                         let details = try await 
model.getWithdrawalDetailsForAmountM(exchangeBaseUrl,
-                                                                              
amount: amountToTransfer)
+                                                                              
amount: amountToTransfer,
+                                                                      
cancellationId: "cancel",
+                                                                         
viewHandles: true)
                         withdrawalAmountDetails = details
 //                      agePicker.setAges(ages: 
withdrawalAmountDetails?.ageRestrictionOptions)
-                    } catch {    // TODO: error
+                    } catch WalletBackendError.walletCoreError(let 
walletBackendResponseError) {
+                        symLog.log(walletBackendResponseError?.hint)
+                        // TODO: ignore WALLET_CORE_REQUEST_CANCELLED but 
handle all others
+                        // Passing non-nil to clientCancellationId will throw 
WALLET_CORE_REQUEST_CANCELLED
+                        // when calling getWithdrawalDetailsForAmount again 
before the last call returned.
+                        // Since amountToTransfer changed and we don't need 
the old fee anymore, we just
+                        // ignore it and do nothing.
+                    } catch {
                         symLog.log(error.localizedDescription)
                         withdrawalAmountDetails = nil
                     }
diff --git a/TalerWallet1/Views/Banking/ManualWithdrawDone.swift 
b/TalerWallet1/Views/Banking/ManualWithdrawDone.swift
index 3689416..6acb181 100644
--- a/TalerWallet1/Views/Banking/ManualWithdrawDone.swift
+++ b/TalerWallet1/Views/Banking/ManualWithdrawDone.swift
@@ -20,8 +20,8 @@ struct ManualWithdrawDone: View {
     @State private var acceptManualWithdrawalResult: 
AcceptManualWithdrawalResult?
     @State private var transactionId: String?
 
-    func reloadOneAction(_ transactionId: String) async throws -> Transaction {
-        return try await model.getTransactionByIdT(transactionId)
+    func reloadOneAction(_ transactionId: String, viewHandles: Bool) async 
throws -> Transaction {
+        return try await model.getTransactionByIdT(transactionId, viewHandles: 
viewHandles)
     }
     func dismissTopAnimated(_ stack: CallStack) {
         dismissTop()
@@ -55,12 +55,9 @@ struct ManualWithdrawDone: View {
             DebugViewC.shared.setViewID(VIEW_WITHDRAW_ACCEPT, stack: 
stack.push())
         }.task {
             if transactionId == nil {
-                do {
-                    let result = try await 
model.sendAcceptManualWithdrawalM(exchange.exchangeBaseUrl,
-                                                                             
amount: amountToTransfer, restrictAge: 0)
-                    transactionId = result!.transactionId
-                } catch {    // TODO: error
-                    symLog.log(error.localizedDescription)
+                if let result = try? await 
model.sendAcceptManualWithdrawalM(exchange.exchangeBaseUrl,
+                                                                             
amount: amountToTransfer, restrictAge: 0) {
+                    transactionId = result.transactionId
                 }
             }
         }
diff --git a/TalerWallet1/Views/HelperViews/AmountInputV.swift 
b/TalerWallet1/Views/HelperViews/AmountInputV.swift
index 2b542af..4ebb3e8 100644
--- a/TalerWallet1/Views/HelperViews/AmountInputV.swift
+++ b/TalerWallet1/Views/HelperViews/AmountInputV.swift
@@ -33,8 +33,7 @@ struct AmountInputV: View {
 
     func preparePayForTemplate() async {
         if let url {
-            do {
-                let ppCheck = try await 
model.preparePayForTemplateM(url.absoluteString, amount: amountToTransfer, 
summary: summary)
+            if let ppCheck = try? await 
model.preparePayForTemplateM(url.absoluteString, amount: amountToTransfer, 
summary: summary) {
                 let amount = ppCheck.amountRaw
                 let currency = amount.currencyStr
                 let currencyInfo = controller.info(for: currency, 
controller.currencyTicker)
@@ -46,8 +45,6 @@ struct AmountInputV: View {
                 let amountVoiceOver = amount.string(currencyInfo)
                 announce(this: "\(amountVoiceOver), \(feeLabel)")
                 preparePayResult = ppCheck
-            } catch {    // TODO: error
-                symLog.log(error.localizedDescription)
             }
         }
     }
diff --git a/TalerWallet1/Views/HelperViews/BarGraph.swift 
b/TalerWallet1/Views/HelperViews/BarGraph.swift
index 3f64829..ecbd46d 100644
--- a/TalerWallet1/Views/HelperViews/BarGraph.swift
+++ b/TalerWallet1/Views/HelperViews/BarGraph.swift
@@ -15,6 +15,7 @@ struct BarGraphHeader: View {
     @Binding var shouldReloadBalances: Int
 
     @EnvironmentObject private var model: WalletModel
+    @EnvironmentObject private var controller: Controller
     @Environment(\.colorScheme) private var colorScheme
     @Environment(\.colorSchemeContrast) private var colorSchemeContrast
     @AppStorage("minimalistic") var minimalistic: Bool = false
@@ -37,9 +38,10 @@ struct BarGraphHeader: View {
             if let scopeInfo {
                 symLog.log(".task for BarGraphHeader(\(scopeInfo.currency)) - 
reload Transactions")
                 // TODO: only load the 10 most recent transactions
-                let response = await model.transactionsT(stack.push(".task - 
reload Transactions for \(scopeInfo.currency)"),
-                                                         scopeInfo: scopeInfo)
-                completedTransactions = 
WalletModel.completedTransactions(response)
+                if let response = try? await 
model.transactionsT(stack.push(".task - reload Transactions for 
\(scopeInfo.currency)"),
+                                                                 scopeInfo: 
scopeInfo) {
+                    completedTransactions = 
WalletModel.completedTransactions(response)
+                }
             }
         }
     }
diff --git a/TalerWallet1/Views/HelperViews/SubjectInputV.swift 
b/TalerWallet1/Views/HelperViews/SubjectInputV.swift
index 56135f8..df64493 100644
--- a/TalerWallet1/Views/HelperViews/SubjectInputV.swift
+++ b/TalerWallet1/Views/HelperViews/SubjectInputV.swift
@@ -38,8 +38,7 @@ struct SubjectInputV<TargetView: View>: View {
 
     func preparePayForTemplate() async {
         if let url {
-            do {
-                let ppCheck = try await 
model.preparePayForTemplateM(url.absoluteString, amount: amountToTransfer, 
summary: summary)
+            if let ppCheck = try? await 
model.preparePayForTemplateM(url.absoluteString, amount: amountToTransfer, 
summary: summary) {
                 let amount = ppCheck.amountRaw
                 let currency = amount.currencyStr
                 let currencyInfo = controller.info(for: currency, 
controller.currencyTicker)
@@ -51,8 +50,6 @@ struct SubjectInputV<TargetView: View>: View {
                 let amountVoiceOver = amount.string(currencyInfo)
                 announce(this: "\(amountVoiceOver), \(feeLabel)")
                 preparePayResult = ppCheck
-            } catch {    // TODO: error
-                symLog.log(error.localizedDescription)
             }
         }
     }
diff --git a/TalerWallet1/Views/HelperViews/TransactionButton.swift 
b/TalerWallet1/Views/HelperViews/TransactionButton.swift
index cbb60d1..2accb3b 100644
--- a/TalerWallet1/Views/HelperViews/TransactionButton.swift
+++ b/TalerWallet1/Views/HelperViews/TransactionButton.swift
@@ -10,7 +10,7 @@ struct TransactionButton: View {
     let transactionId: String
     let command: TxAction
     let warning: String?
-    let action: (_ transactionId: String) async throws -> Void
+    let action: (_ transactionId: String, _ viewHandles: Bool) async throws -> 
Void
 
     @AppStorage("shouldShowWarning") var shouldShowWarning: Bool = true
 
@@ -21,12 +21,9 @@ struct TransactionButton: View {
     private func doAction() {
         disabled = true     // don't try this more than once
         Task { // runs on MainActor
-            do {
-                try await action(transactionId)
+            if let _ = try? await action(transactionId, false) {
 //                symLog.log("\(executed) \(transactionId)")
                 executed = true
-            } catch {    // TODO: error
-//                symLog.log(error.localizedDescription)
             }
         }
     }
@@ -75,7 +72,7 @@ struct TransactionButton: View {
 #if DEBUG
 struct TransactionButton_Previews: PreviewProvider {
 
-    static func action(_ transactionId: String) async throws {
+    static func action(_ transactionId: String, _ viewHandles: Bool) async 
throws {
         print(transactionId)
     }
 
diff --git a/TalerWallet1/Views/Main/MainView.swift 
b/TalerWallet1/Views/Main/MainView.swift
index 6563daa..9d7effe 100644
--- a/TalerWallet1/Views/Main/MainView.swift
+++ b/TalerWallet1/Views/Main/MainView.swift
@@ -2,6 +2,10 @@
  * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
  * See LICENSE.md
  */
+/**
+ * @author Marc Stibane
+ * @author Iván Ávalos
+ */
 import SwiftUI
 import os.log
 import SymLog
@@ -20,7 +24,14 @@ struct MainView: View {
     let stack: CallStack
     @Binding var soundPlayed: Bool
 
+#if DEBUG
+    @AppStorage("developerMode") var developerMode: Bool = true
+#else
+    @AppStorage("developerMode") var developerMode: Bool = false
+#endif
+
     @EnvironmentObject private var controller: Controller
+    @EnvironmentObject private var model: WalletModel
     @AppStorage("talerFontIndex") var talerFontIndex: Int = 0       // 
extension mustn't define this, so it must be here
     @AppStorage("playSoundsI") var playSoundsI: Int = 1             // 
extension mustn't define this, so it must be here
     @AppStorage("playSoundsB") var playSoundsB: Bool = false
@@ -70,6 +81,15 @@ struct MainView: View {
             let sheet = AnyView(URLSheet(stack: stack.push(), urlToOpen: url))
             Sheet(sheetView: sheet)
         }
+        .sheet(isPresented: $model.showError) {
+            model.cleanError()
+        } content: {
+            if let error = model.error {
+                ErrorSheet(data: error, devMode: developerMode) {
+                    model.cleanError()
+                }.interactiveDismissDisabled()
+            }
+        }
     } // body
 }
 // MARK: - TabBar
@@ -101,6 +121,12 @@ extension MainView {
         @State private var showKycAlert: Bool = false
         @State private var kycURI: URL?
 
+#if DEBUG
+        @AppStorage("developerMode") var developerMode: Bool = true
+#else
+        @AppStorage("developerMode") var developerMode: Bool = false
+#endif
+
         private var openKycButton: some View {
             Button("KYC") {
                 showKycAlert = false
@@ -234,16 +260,19 @@ extension MainView {
                 shouldReloadBalances += 1
                 selectedTab = .balances
             }
+            .onNotification(.Error) { notification in
+                if let error = notification.userInfo?[NOTIFICATIONERROR] as? 
Error {
+                    model.showError(.error(error))
+                    controller.playSound(0)
+                }
+            }
             .onChange(of: balances) { newArray in
                 for balance in newArray {
                     let scope = balance.scopeInfo
                     if controller.hasInfo(for: scope.currency) == nil {
                         Task { // runs on MainActor
-                            do {
-                                let info = try await 
model.getCurrencyInfoM(scope: scope, delay: delay)
+                            if let info = try? await 
model.getCurrencyInfoM(scope: scope, delay: delay) {
                                 await controller.setInfo(info)
-                            } catch {    // TODO: error handling - couldn't 
get CurrencyInfo
-                                logger.error("Couldn't get info for: 
\(scope.currency, privacy: .public)\n\(error)")
                             }
                         }
                     }
diff --git a/TalerWallet1/Views/Main/WalletEmptyView.swift 
b/TalerWallet1/Views/Main/WalletEmptyView.swift
index a99a06f..46cb387 100644
--- a/TalerWallet1/Views/Main/WalletEmptyView.swift
+++ b/TalerWallet1/Views/Main/WalletEmptyView.swift
@@ -44,11 +44,7 @@ struct WalletEmptyView: View {
                     Task { // runs on MainActor
                         let amount = Amount(currency:  DEMOCURRENCY, cent: 
2500)
                         symLog.log("Withdraw KUDOS")
-                        do {
-                            try await model.loadTestKudosM(test: false, 
amount: amount)
-                        } catch {    // TODO: show error
-                            symLog.log(error.localizedDescription)
-                        }
+                        try? await model.loadTestKudosM(test: false, amount: 
amount)
                     }
                 }
                 .buttonStyle(TalerButtonStyle(type: .prominent, narrow: false, 
disabled: withDrawDisabled, aligned: .center))
diff --git a/TalerWallet1/Views/Peer2peer/P2PReadyV.swift 
b/TalerWallet1/Views/Peer2peer/P2PReadyV.swift
index ade1597..d2bf281 100644
--- a/TalerWallet1/Views/Peer2peer/P2PReadyV.swift
+++ b/TalerWallet1/Views/Peer2peer/P2PReadyV.swift
@@ -27,8 +27,8 @@ struct P2PReadyV: View {
     let navTitle = String(localized: "P2P Ready")
     @State private var transactionId: String? = nil
 
-    func reloadOneAction(_ transactionId: String) async throws -> Transaction {
-        return try await model.getTransactionByIdT(transactionId)
+    func reloadOneAction(_ transactionId: String, viewHandles: Bool) async 
throws -> Transaction {
+        return try await model.getTransactionByIdT(transactionId, viewHandles: 
viewHandles)
     }
 
     var body: some View {
@@ -69,35 +69,33 @@ struct P2PReadyV: View {
         }
         .task(id: amountToTransfer.value) {
             symLog.log(".task")
-            do {
-                guard transactionStarted == false else {
+            guard transactionStarted == false else {
 // TODO:               logger.warning("Try to start P2P a second time")
-                    symLog.log("Yikes❗️ Try to start P2P a second time")
-                    return
-                }
-                transactionStarted = true
-                let timestamp = developerMode ? 
Timestamp.inSomeMinutes(expireDays > 20 ? (24*60)
-                                                                      : 
expireDays > 5 ? 60 : 3)
-                                              : 
Timestamp.inSomeDays(expireDays)
-                if amountToSend {
-                    let terms = PeerContractTerms(amount: amountToTransfer,
-                                                 summary: summary,
-                                        purse_expiration: timestamp)
-                    // TODO: let user choose baseURL
-                    let response = try await model.initiatePeerPushDebitM(nil, 
terms: terms)
+                symLog.log("Yikes❗️ Try to start P2P a second time")
+                return
+            }
+            transactionStarted = true
+            let timestamp = developerMode ? Timestamp.inSomeMinutes(expireDays 
> 20 ? (24*60)
+                                                                  : expireDays 
> 5 ? 60 : 3)
+                                          : Timestamp.inSomeDays(expireDays)
+            if amountToSend {
+                let terms = PeerContractTerms(amount: amountToTransfer,
+                                             summary: summary,
+                                    purse_expiration: timestamp)
+                // TODO: let user choose baseURL
+                if let response = try? await model.initiatePeerPushDebitM(nil, 
terms: terms) {
                     // will switch from WithdrawProgressView to 
TransactionSummaryV
                     transactionId = response.transactionId
-                } else {
-                    let terms = PeerContractTerms(amount: amountToTransfer,
-                                                 summary: summary,
-                                        purse_expiration: timestamp)
-                    // TODO: let user choose baseURL
-                    let response = try await 
model.initiatePeerPullCreditM(nil, terms: terms)
+                }
+            } else {
+                let terms = PeerContractTerms(amount: amountToTransfer,
+                                             summary: summary,
+                                    purse_expiration: timestamp)
+                // TODO: let user choose baseURL
+                if let response = try? await 
model.initiatePeerPullCreditM(nil, terms: terms) {
                     // will switch from WithdrawProgressView to 
TransactionSummaryV
                     transactionId = response.transactionId
                 }
-            } catch {    // TODO: error
-                symLog.log(error.localizedDescription)
             }
         } // task
     }
diff --git a/TalerWallet1/Views/Peer2peer/P2PSubjectV.swift 
b/TalerWallet1/Views/Peer2peer/P2PSubjectV.swift
index 6982490..bbb2587 100644
--- a/TalerWallet1/Views/Peer2peer/P2PSubjectV.swift
+++ b/TalerWallet1/Views/Peer2peer/P2PSubjectV.swift
@@ -134,14 +134,11 @@ struct P2PSubjectV: View {
         }
         .task(id: amountToTransfer.value) {
             if amountToSend && feeLabel == nil {
-                do {
-                    let ppCheck = try await 
model.checkPeerPushDebitM(amountToTransfer)
+                if let ppCheck = try? await 
model.checkPeerPushDebitM(amountToTransfer) {
                     if let feeAmount = p2pFee(ppCheck: ppCheck) {
                         let feeStr = feeAmount.string(currencyInfo)
                         myFeeLabel = String(localized: "+ \(feeStr) fee")
                     } else { myFeeLabel = EMPTYSTRING }
-                } catch {    // TODO: error
-                    symLog.log(error.localizedDescription)
                 }
             }
         }
diff --git a/TalerWallet1/Views/Peer2peer/RequestPayment.swift 
b/TalerWallet1/Views/Peer2peer/RequestPayment.swift
index 90a5bc8..84e0a42 100644
--- a/TalerWallet1/Views/Peer2peer/RequestPayment.swift
+++ b/TalerWallet1/Views/Peer2peer/RequestPayment.swift
@@ -105,25 +105,13 @@ struct RequestPayment: View {
         .task(id: amountToTransfer.value) {
             if exchange == nil {
                 if let url = scopeInfo.url {
-                    if let exc = await model.getExchangeByUrl(url: url) {
-                        exchange = exc
-                    } else {
-                        // TODO: Error "Can't get Exchange / Payment Service 
Provider Info"
-                    }
+                    exchange = try? await model.getExchangeByUrl(url: url)
                 }
             }
             if amountToTransfer.isZero {
 //                fee = EMPTYSTRING
             } else {
-                do {
-                    let ppCheck = try await 
model.checkPeerPullCreditM(amountToTransfer, exchangeBaseUrl: nil)
-                    peerPullCheck = ppCheck     // redraw
-                    // TODO: set from exchange
-//                  agePicker.setAges(ages: 
peerPushCheck?.ageRestrictionOptions)
-                } catch {    // TODO: error
-                    symLog.log(error.localizedDescription)
-                    peerPullCheck = nil
-                }
+                peerPullCheck = try? await 
model.checkPeerPullCreditM(amountToTransfer, exchangeBaseUrl: nil)
             }
         }
     }
diff --git a/TalerWallet1/Views/Peer2peer/SendAmount.swift 
b/TalerWallet1/Views/Peer2peer/SendAmount.swift
index ba468e8..0c25cc6 100644
--- a/TalerWallet1/Views/Peer2peer/SendAmount.swift
+++ b/TalerWallet1/Views/Peer2peer/SendAmount.swift
@@ -133,11 +133,7 @@ struct SendAmount: View {
         .task(id: amountToTransfer.value) {
             if exchange == nil {
                 if let url = scopeInfo.url {
-                    if let exc = await model.getExchangeByUrl(url: url) {
-                        exchange = exc
-                    } else {
-                        // TODO: Error "Can't get Exchange / Payment Service 
Provider Info"
-                    }
+                    exchange = try? await model.getExchangeByUrl(url: url)
                 }
             }
             do {
@@ -152,8 +148,7 @@ struct SendAmount: View {
             } else if amountToTransfer.isZero {
                 feeStr = EMPTYSTRING
             } else {
-                do {
-                    let ppCheck = try await 
model.checkPeerPushDebitM(amountToTransfer)
+                if let ppCheck = try? await 
model.checkPeerPushDebitM(amountToTransfer) {
                     peerPushCheck = ppCheck
                 // TODO: set from exchange
 //                agePicker.setAges(ages: peerPushCheck?.ageRestrictionOptions)
@@ -161,8 +156,7 @@ struct SendAmount: View {
                         feeStr = feeAmount.string(currencyInfo)
                     } else { feeStr = EMPTYSTRING }
                     announce(this: "\(amountVoiceOver), \(feeLabel)")
-                } catch {    // TODO: error
-                    symLog.log(error.localizedDescription)
+                } else {
                     peerPushCheck = nil
                 }
             }
diff --git a/TalerWallet1/Views/Settings/SettingsView.swift 
b/TalerWallet1/Views/Settings/SettingsView.swift
index 37ec955..aafe57e 100644
--- a/TalerWallet1/Views/Settings/SettingsView.swift
+++ b/TalerWallet1/Views/Settings/SettingsView.swift
@@ -65,11 +65,7 @@ struct SettingsView: View {
             showResetAlert = false
             Task { // runs on MainActor
                 symLog.log("❗️Reset wallet-core❗️")
-                do {
-                    try await model.resetWalletCoreT()
-                } catch {    // TODO: show error
-                    symLog.log(error.localizedDescription)
-                }
+                try? await model.resetWalletCoreT()
             }
         }
     }
@@ -186,12 +182,8 @@ struct SettingsView: View {
                                 withDrawDisabled = true    // don't run twice
                                 Task { // runs on MainActor
                                     symLog.log("Withdraw KUDOS")
-                                    do {
-                                        let amount = Amount(currency:  
DEMOCURRENCY, cent: 1100)
-                                        try await model.loadTestKudosM(test: 
false, amount: amount)
-                                    } catch {    // TODO: show error
-                                        symLog.log(error.localizedDescription)
-                                    }
+                                    let amount = Amount(currency:  
DEMOCURRENCY, cent: 1100)
+                                    try? await model.loadTestKudosM(test: 
false, amount: amount)
                                 }
                             }
                             .buttonStyle(.bordered)
@@ -204,12 +196,8 @@ struct SettingsView: View {
                                 withDrawDisabled = true    // don't run twice
                                 Task { // runs on MainActor
                                     symLog.log("Withdraw TESTKUDOS")
-                                    do {
-                                        let amount = Amount(currency:  
TESTCURRENCY, cent: 1100)
-                                        try await model.loadTestKudosM(test: 
true, amount: amount)
-                                    } catch {    // TODO: show error
-                                        symLog.log(error.localizedDescription)
-                                    }
+                                    let amount = Amount(currency:  
TESTCURRENCY, cent: 1100)
+                                    try? await model.loadTestKudosM(test: 
true, amount: amount)
                                 }
                             }
                             .buttonStyle(.bordered)
@@ -227,13 +215,9 @@ struct SettingsView: View {
                             Button(title) {
                                 Task { // runs on MainActor
                                     symLog.log("running applyDevExperiment 
Refresh")
-                                    do {
-                                        try await model.setConfigT(setTesting: 
true)
-                                        try await 
model.devExperimentT(talerUri: "taler://dev-experiment/start-block-refresh")
-                                        try await 
model.devExperimentT(talerUri: "taler://dev-experiment/insert-pending-refresh")
-                                    } catch {    // TODO: show error
-                                        symLog.log(error.localizedDescription)
-                                    }
+                                    try? await model.setConfigT(setTesting: 
true)
+                                    try? await model.devExperimentT(talerUri: 
"taler://dev-experiment/start-block-refresh")
+                                    try? await model.devExperimentT(talerUri: 
"taler://dev-experiment/insert-pending-refresh")
                                 }
                             }
                             .buttonStyle(.bordered)
@@ -246,11 +230,7 @@ struct SettingsView: View {
                                 checkDisabled = true    // don't run twice
                                 Task { // runs on MainActor
                                     symLog.log("running integration test on 
demo")
-                                    do {
-                                        try await 
model.runIntegrationTestM(newVersion: false, test: false)
-                                    } catch {    // TODO: show error
-                                        symLog.log(error.localizedDescription)
-                                    }
+                                    try? await 
model.runIntegrationTestM(newVersion: false, test: false)
                                 }
                             }
                             .buttonStyle(.bordered)
@@ -263,11 +243,7 @@ struct SettingsView: View {
                                 checkDisabled = true    // don't run twice
                                 Task { // runs on MainActor
                                     symLog.log("running integration test on 
test")
-                                    do {
-                                        try await 
model.runIntegrationTestM(newVersion: false, test: true)
-                                    } catch {    // TODO: show error
-                                        symLog.log(error.localizedDescription)
-                                    }
+                                    try? await 
model.runIntegrationTestM(newVersion: false, test: true)
                                 }
                             }
                             .buttonStyle(.bordered)
@@ -280,11 +256,7 @@ struct SettingsView: View {
                                 checkDisabled = true    // don't run twice
                                 Task { // runs on MainActor
                                     symLog.log("running integration test V2 on 
demo")
-                                    do {
-                                        try await 
model.runIntegrationTestM(newVersion: true, test: false)
-                                    } catch {    // TODO: show error
-                                        symLog.log(error.localizedDescription)
-                                    }
+                                    try? await 
model.runIntegrationTestM(newVersion: true, test: false)
                                 }
                             }
                             .buttonStyle(.bordered)
@@ -297,11 +269,7 @@ struct SettingsView: View {
                                 checkDisabled = true    // don't run twice
                                 Task { // runs on MainActor
                                     symLog.log("running integration test V2 on 
test")
-                                    do {
-                                        try await 
model.runIntegrationTestM(newVersion: true, test: true)
-                                    } catch {    // TODO: show error
-                                        symLog.log(error.localizedDescription)
-                                    }
+                                    try? await 
model.runIntegrationTestM(newVersion: true, test: true)
                                 }
                             }
                             .buttonStyle(.bordered)
@@ -314,11 +282,7 @@ struct SettingsView: View {
                                 checkDisabled = true    // don't run twice
                                 Task { // runs on MainActor
                                     symLog.log("Running Infinite Transaction 
Loop")
-                                    do {
-                                        try await 
model.testingInfiniteTransaction(delayMs: 10_000, shouldFetch: true)
-                                    } catch {    // TODO: show error
-                                        symLog.log(error.localizedDescription)
-                                    }
+                                    try? await 
model.testingInfiniteTransaction(delayMs: 10_000, shouldFetch: true)
                                 }
                             }
                             .buttonStyle(.bordered)
diff --git a/TalerWallet1/Views/Sheets/ErrorSheet.swift 
b/TalerWallet1/Views/Sheets/ErrorSheet.swift
new file mode 100644
index 0000000..d6d2d1e
--- /dev/null
+++ b/TalerWallet1/Views/Sheets/ErrorSheet.swift
@@ -0,0 +1,130 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+/**
+ * @author Iván Ávalos
+ */
+import SwiftUI
+
+enum ErrorData {
+case message(title: String, message: String)
+case error(Error)
+}
+
+struct ErrorSheet: View {
+    var title: String
+    var message: String? = nil
+    var copyable: Bool
+
+    var onDismiss: () -> Void
+
+    init(title: String, message: String? = nil, copyable: Bool, onDismiss: 
@escaping () -> Void) {
+        self.title = title
+        self.message = message
+        self.copyable = copyable
+        self.onDismiss = onDismiss
+    }
+
+    init(error: Error, devMode: Bool, onDismiss: @escaping () -> Void) {
+        let walletCoreError = String(localized: "Internal core error")
+        let initializationError = String(localized: "Initialization error")
+        let serializationError = String(localized: "Serialization error")
+        let deserializationError = String(localized: "Deserialization error")
+        let unknownError = String(localized: "Unknown error")
+
+        switch error {
+        case let walletError as WalletBackendError:
+            switch walletError {
+            case .walletCoreError(let error):
+                if let json = error?.toJSON(), devMode {
+                    self.init(title: walletCoreError, message: json, copyable: 
true, onDismiss: onDismiss)
+                } else if let hint = error?.hint {
+                    self.init(title: walletCoreError, message: hint, copyable: 
false, onDismiss: onDismiss)
+                } else if let message = error?.message {
+                    self.init(title: walletCoreError, message: message, 
copyable: false, onDismiss: onDismiss)
+                } else {
+                    self.init(title: walletCoreError, copyable: false, 
onDismiss: onDismiss)
+                }
+            case .initializationError:
+                self.init(title: initializationError, copyable: false, 
onDismiss: onDismiss)
+            case .serializationError:
+                self.init(title: serializationError, copyable: false, 
onDismiss: onDismiss)
+            case .deserializationError:
+                self.init(title: deserializationError, copyable: false, 
onDismiss: onDismiss)
+            }
+        default:
+            self.init(title: unknownError, message: 
error.localizedDescription, copyable: false, onDismiss: onDismiss)
+        }
+    }
+
+    init(data: ErrorData, devMode: Bool, onDismiss: @escaping () -> Void) {
+        let unknownError = String(localized: "Unknown error")
+
+        switch data {
+        case .message(let title, let message):
+            self.init(title: title, message: message, copyable: false, 
onDismiss: onDismiss)
+            return
+        case .error(let error):
+            self.init(error: error, devMode: devMode, onDismiss: onDismiss)
+            return
+        }
+
+        self.init(title: unknownError, copyable: false, onDismiss: onDismiss)
+    }
+
+    var body: some View {
+        ScrollView {
+            VStack {
+                Image(systemName: "exclamationmark.circle")
+                    .resizable()
+                    .frame(width: 50, height: 50)
+                    .aspectRatio(contentMode: .fit)
+                    .foregroundStyle(.red)
+                    .padding()
+
+                Text(title)
+                    .talerFont(.title)
+                    .padding(.bottom)
+
+                if let message {
+                    if copyable {
+                        if #available(iOS 16.4, *) {
+                            Text(message).monospaced()
+                        } else {
+                            Text(message).font(.system(.body, design: 
.monospaced))
+                        }
+
+                        CopyButton(textToCopy: message, vertical: false)
+                            .accessibilityLabel("Copy the error JSON")
+                            .padding()
+                    } else {
+                        Text(message)
+                            .multilineTextAlignment(.center)
+                    }
+                }
+            }
+        }
+        .padding()
+        .safeAreaInset(edge: .bottom) {
+            Button("Close", role: .cancel) {
+                onDismiss()
+            }
+            .buttonStyle(TalerButtonStyle(type: .bordered))
+            .padding(.bottom)
+            .padding(.horizontal)
+        }
+    }
+}
+
+struct ErrorSheet_Previews: PreviewProvider {
+    static let error = 
WalletBackendError.walletCoreError(WalletBackendResponseError(
+        code: 7025,
+        hint: "A KYC step is required before withdrawal can proceed",
+        message: "A KYC step is required before withdrawal can proceed"))
+
+    static var previews: some View {
+        ErrorSheet(error: error, devMode: true, onDismiss: {})
+        ErrorSheet(error: error, devMode: false, onDismiss: {})
+    }
+}
diff --git a/TalerWallet1/Views/Sheets/P2P_Sheets/P2pAcceptDone.swift 
b/TalerWallet1/Views/Sheets/P2P_Sheets/P2pAcceptDone.swift
index f0ae796..6edc6eb 100644
--- a/TalerWallet1/Views/Sheets/P2P_Sheets/P2pAcceptDone.swift
+++ b/TalerWallet1/Views/Sheets/P2P_Sheets/P2pAcceptDone.swift
@@ -34,16 +34,14 @@ struct P2pAcceptDone: View {
                                                       : SHEET_PAY_P2P_ACCEPT)
             }
             .task {
-                do {
-                    if incoming {
-                        _ = try await 
model.acceptPeerPushCreditM(transactionId)
-                    } else {
-                        _ = try await 
model.confirmPeerPullDebitM(transactionId)
+                if incoming {
+                    if let _ = try? await 
model.acceptPeerPushCreditM(transactionId) {
+                        dismissTop()
+                    }
+                } else {
+                    if let _ = try? await 
model.confirmPeerPullDebitM(transactionId) {
+                        dismissTop()
                     }
-                    dismissTop()
-                } catch {    // TODO: error
-                    symLog.log(error.localizedDescription)
-                    controller.playSound(0)
                 }
             }
     }
diff --git a/TalerWallet1/Views/Sheets/P2P_Sheets/P2pPayURIView.swift 
b/TalerWallet1/Views/Sheets/P2P_Sheets/P2pPayURIView.swift
index ca2e431..e51f00a 100644
--- a/TalerWallet1/Views/Sheets/P2P_Sheets/P2pPayURIView.swift
+++ b/TalerWallet1/Views/Sheets/P2P_Sheets/P2pPayURIView.swift
@@ -73,11 +73,7 @@ struct P2pPayURIView: View {
                 LoadingView(scopeInfo: nil, message: message)
                     .task { do {
                         symLog.log(".task")
-                        let ppDebitResponse = try await 
model.preparePeerPullDebitM(url.absoluteString)
-                        peerPullDebitResponse = ppDebitResponse
-                    } catch {    // TODO: error
-                        symLog.log(error.localizedDescription)
-                        peerPullDebitResponse = nil
+                        peerPullDebitResponse = try? await 
model.preparePeerPullDebitM(url.absoluteString)
                     } }
             }
         }
diff --git a/TalerWallet1/Views/Sheets/P2P_Sheets/P2pReceiveURIView.swift 
b/TalerWallet1/Views/Sheets/P2P_Sheets/P2pReceiveURIView.swift
index ee63a98..9037682 100644
--- a/TalerWallet1/Views/Sheets/P2P_Sheets/P2pReceiveURIView.swift
+++ b/TalerWallet1/Views/Sheets/P2P_Sheets/P2pReceiveURIView.swift
@@ -17,6 +17,7 @@ struct P2pReceiveURIView: View {
     let url: URL
     
     @EnvironmentObject private var model: WalletModel
+    @EnvironmentObject private var controller: Controller
     @Environment(\.colorScheme) private var colorScheme
     @Environment(\.colorSchemeContrast) private var colorSchemeContrast
     @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
@@ -87,13 +88,11 @@ struct P2pReceiveURIView: View {
             DebugViewC.shared.setSheetID(SHEET_RCV_P2P)
         }
         .task { // must be here and not at LoadingView(), because this needs 
to run a 2nd time after ToS was accepted
-            do { // TODO: cancelled
-                symLog.log(".task")
-                let ppResponse = try await 
model.preparePeerPushCreditM(url.absoluteString)
-                exchange = await model.getExchangeByUrl(url: 
ppResponse.exchangeBaseUrl)
+            symLog.log(".task")
+            if let ppResponse = try? await 
model.preparePeerPushCreditM(url.absoluteString) {
+                exchange = try? await model.getExchangeByUrl(url: 
ppResponse.exchangeBaseUrl)
                 peerPushCreditResponse = ppResponse
-            } catch {    // TODO: error
-                symLog.log(error.localizedDescription)
+            } else {
                 peerPushCreditResponse = nil
             }
         }
diff --git a/TalerWallet1/Views/Sheets/Payment/PayTemplateV.swift 
b/TalerWallet1/Views/Sheets/Payment/PayTemplateV.swift
index 0f6158f..81e7a08 100644
--- a/TalerWallet1/Views/Sheets/Payment/PayTemplateV.swift
+++ b/TalerWallet1/Views/Sheets/Payment/PayTemplateV.swift
@@ -62,17 +62,12 @@ struct PayTemplateV: View {
     }
     func acceptAction(preparePayResult: PreparePayResult) {
         Task { // runs on MainActor
-            do {
-                let confirmPayResult = try await 
model.confirmPayM(preparePayResult.transactionId)
-//                symLog.log(confirmPayResult as Any)
+            if let confirmPayResult = try? await 
model.confirmPayM(preparePayResult.transactionId) {
+                //                symLog.log(confirmPayResult as Any)
                 if confirmPayResult.type != "done" {
                     controller.playSound(0)
                     // TODO: show error
                 }
-            } catch {
-                controller.playSound(0)
-                // TODO: error
-                symLog.log(error.localizedDescription)
             }
             dismissTop()
         }
@@ -94,8 +89,7 @@ struct PayTemplateV: View {
     }
 
     func preparePayForTemplate() async {
-        do {
-            let ppCheck = try await 
model.preparePayForTemplateM(url.absoluteString, amount: amountToTransfer, 
summary: summary)
+        if let ppCheck = try? await 
model.preparePayForTemplateM(url.absoluteString, amount: amountToTransfer, 
summary: summary) {
             let amount = ppCheck.amountRaw
             let currency = amount.currencyStr
             let currencyInfo = controller.info(for: currency, 
controller.currencyTicker)
@@ -107,8 +101,6 @@ struct PayTemplateV: View {
             let amountVoiceOver = amount.string(currencyInfo)
             announce(this: "\(amountVoiceOver), \(feeLabel)")
             preparePayResult = ppCheck
-        } catch {    // TODO: error
-            symLog.log(error.localizedDescription)
         }
     }
 
diff --git a/TalerWallet1/Views/Sheets/Payment/PaymentDone.swift 
b/TalerWallet1/Views/Sheets/Payment/PaymentDone.swift
index 69de946..8ba4e78 100644
--- a/TalerWallet1/Views/Sheets/Payment/PaymentDone.swift
+++ b/TalerWallet1/Views/Sheets/Payment/PaymentDone.swift
@@ -15,8 +15,8 @@ struct PaymentDone: View {
 
     @State var paymentDone: Bool = false
 
-    func reloadOneAction(_ transactionId: String) async throws -> Transaction {
-        return try await model.getTransactionByIdT(transactionId)
+    func reloadOneAction(_ transactionId: String, viewHandles: Bool) async 
throws -> Transaction {
+        return try await model.getTransactionByIdT(transactionId, viewHandles: 
viewHandles)
     }
     func dismissTopAnimated(_ stack: CallStack) {
         dismissTop()
@@ -46,9 +46,8 @@ struct PaymentDone: View {
             } else {
                 LoadingView(scopeInfo: nil, message: "Paying...")
                     .task {
-                        do {
-                            let confirmPayResult = try await 
model.confirmPayM(transactionId)
-//                          symLog.log(confirmPayResult as Any)
+                        if let confirmPayResult = try? await 
model.confirmPayM(transactionId) {
+                            //                          
symLog.log(confirmPayResult as Any)
                             if confirmPayResult.type == "done" {
                                 paymentDone = true
                             } else {
@@ -56,11 +55,6 @@ struct PaymentDone: View {
                                 // TODO: show error
                                 dismissTop()
                             }
-                        } catch {
-                            controller.playSound(0)
-                            // TODO: error
-                            symLog.log(error.localizedDescription)
-                            dismissTop()
                         }
                     }
             }
diff --git a/TalerWallet1/Views/Sheets/Payment/PaymentView.swift 
b/TalerWallet1/Views/Sheets/Payment/PaymentView.swift
index 897bcae..3b7ab98 100644
--- a/TalerWallet1/Views/Sheets/Payment/PaymentView.swift
+++ b/TalerWallet1/Views/Sheets/Payment/PaymentView.swift
@@ -20,6 +20,7 @@ struct PaymentView: View {
     @Binding var summary: String
 
     @EnvironmentObject private var model: WalletModel
+    @EnvironmentObject private var controller: Controller
     @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
 
     @State var preparePayResult: PreparePayResult? = nil
@@ -94,19 +95,17 @@ struct PaymentView: View {
         } else {
             LoadingView(scopeInfo: nil, message: url.host)
             .task { // this runs only once
-                do { // TODO: cancelled
-                    symLog.log(".task")
-                    if template {
-                        let result = try await 
model.preparePayForTemplateM(url.absoluteString,
-                                                                     amount: 
amountToTransfer,
-                                                                    summary: 
summary)
+                symLog.log(".task")
+                if template {
+                    if let result = try? await 
model.preparePayForTemplateM(url.absoluteString,
+                                                                 amount: 
amountToTransfer,
+                                                                            
summary: summary) {
                         preparePayResult = result
-                    } else {
-                        let result = try await 
model.preparePayForUriM(url.absoluteString)
+                    }
+                } else {
+                    if let result = try? await 
model.preparePayForUriM(url.absoluteString) {
                         preparePayResult = result
                     }
-                } catch {    // TODO: error
-                    symLog.log(error.localizedDescription)
                 }
             }
         }
diff --git a/TalerWallet1/Views/Sheets/Refund/RefundURIView.swift 
b/TalerWallet1/Views/Sheets/Refund/RefundURIView.swift
index 284d9e7..ec6ff99 100644
--- a/TalerWallet1/Views/Sheets/Refund/RefundURIView.swift
+++ b/TalerWallet1/Views/Sheets/Refund/RefundURIView.swift
@@ -17,8 +17,8 @@ struct RefundURIView: View {
 
     @State var refundTransactionId: String? = nil
 
-    func reloadOneAction(_ transactionId: String) async throws -> Transaction {
-        return try await model.getTransactionByIdT(transactionId)
+    func reloadOneAction(_ transactionId: String, viewHandles: Bool) async 
throws -> Transaction {
+        return try await model.getTransactionByIdT(transactionId, viewHandles: 
viewHandles)
     }
 
     var body: some View {
@@ -36,12 +36,9 @@ struct RefundURIView: View {
         } else {
             LoadingView(scopeInfo: nil, message: url.host)
                 .task {
-                    do {
-                        symLog.log(".task")
-                        let result = try await model.startRefundForUriM(url: 
url.absoluteString)
+                    symLog.log(".task")
+                    if let result = try? await model.startRefundForUriM(url: 
url.absoluteString) {
                         refundTransactionId = result
-                    } catch {    // TODO: error
-                        symLog.log(error.localizedDescription)
                     }
                 }
         }
diff --git a/TalerWallet1/Views/Sheets/Sheet.swift 
b/TalerWallet1/Views/Sheets/Sheet.swift
index 7c9edb8..a0f2da6 100644
--- a/TalerWallet1/Views/Sheets/Sheet.swift
+++ b/TalerWallet1/Views/Sheets/Sheet.swift
@@ -10,8 +10,15 @@ struct Sheet: View {
     private let symLog = SymLogV(0)
     @Environment(\.dismiss) var dismiss     // call dismiss() to get rid of 
the sheet
     @EnvironmentObject private var debugViewC: DebugViewC
+    @EnvironmentObject private var model: WalletModel
     @AppStorage("talerFontIndex") var talerFontIndex: Int = 0
 
+#if DEBUG
+    @AppStorage("developerMode") var developerMode: Bool = true
+#else
+    @AppStorage("developerMode") var developerMode: Bool = false
+#endif
+
     var sheetView: AnyView
 
     let logger = Logger(subsystem: "net.taler.gnu", category: "Sheet")
@@ -27,8 +34,16 @@ struct Sheet: View {
         let idString = debugViewC.sheetID > 0 ? String(debugViewC.sheetID)
                                               : ""      // show nothing if 0
         NavigationView {
-            sheetView
-                .navigationBarItems(leading: cancelButton)
+            Group {
+                if let error = model.error {
+                    ErrorSheet(data: error, devMode: developerMode) {
+                        dismissTop()
+                    }
+                } else {
+                    sheetView
+                        .navigationBarItems(leading: cancelButton)
+                }
+            }
                 .navigationBarTitleDisplayMode(.automatic)
                 
.background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
         }
@@ -45,5 +60,8 @@ struct Sheet: View {
                 .accessibilityLabel(Text("Sheet.ID.", comment: 
"AccessibilityLabel"))
                 .accessibilityValue(idString)
         }
+        .onDisappear {
+            model.cleanError()
+        }
     }
 }
diff --git 
a/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawAcceptDone.swift 
b/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawAcceptDone.swift
index d71f08c..dfa6a0e 100644
--- a/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawAcceptDone.swift
+++ b/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawAcceptDone.swift
@@ -19,8 +19,8 @@ struct WithdrawAcceptDone: View {
 
     @State private var transactionId: String? = nil
 
-    func reloadOneAction(_ transactionId: String) async throws -> Transaction {
-            return try await model.getTransactionByIdT(transactionId)
+    func reloadOneAction(_ transactionId: String, viewHandles: Bool) async 
throws -> Transaction {
+            return try await model.getTransactionByIdT(transactionId, 
viewHandles: viewHandles)
     }
     func dismissTopAnimated(_ stack: CallStack) {
         dismissTop()
@@ -54,16 +54,12 @@ struct WithdrawAcceptDone: View {
             symLog.log("onAppear")
             DebugViewC.shared.setSheetID(SHEET_WITHDRAW_CONFIRM)
         }.task {
-            do {
-                if let exchangeBaseUrl {
-                    let result = try await 
model.sendAcceptIntWithdrawalM(exchangeBaseUrl, withdrawURL: url.absoluteString)
-                    let confirmTransferUrl = result!.confirmTransferUrl
+            if let exchangeBaseUrl {
+                if let result = try? await 
model.sendAcceptIntWithdrawalM(exchangeBaseUrl, withdrawURL: 
url.absoluteString) {
+                    let confirmTransferUrl = result.confirmTransferUrl
                     symLog.log(confirmTransferUrl)
-                    transactionId = result!.transactionId
+                    transactionId = result.transactionId
                 }
-            } catch {    // TODO: error
-                symLog.log(error.localizedDescription)
-                controller.playSound(0)
             }
         }
     }
diff --git 
a/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawTOSView.swift 
b/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawTOSView.swift
index 82fbb89..f9039d9 100644
--- a/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawTOSView.swift
+++ b/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawTOSView.swift
@@ -23,21 +23,15 @@ struct WithdrawTOSView: View {
     @State var exchangeTOS: ExchangeTermsOfService?
 
     func loadToS(_ language: String) async {
-        do {
-            if let exchangeBaseUrl {
-                let acceptedFormat: [String] = [MARKDOWN, PLAINTEXT]      // 
MARKDOWN, HTML, PLAINTEXT
-                let someTOS = try await 
model.loadExchangeTermsOfServiceM(exchangeBaseUrl,
-                                                           acceptedFormat: 
acceptedFormat,
-                                                           acceptLanguage: 
language)
+        if let exchangeBaseUrl {
+            let acceptedFormat: [String] = [MARKDOWN, PLAINTEXT]      // 
MARKDOWN, HTML, PLAINTEXT
+            if let someTOS = try? await 
model.loadExchangeTermsOfServiceM(exchangeBaseUrl,
+                                                       acceptedFormat: 
acceptedFormat,
+                                                       acceptLanguage: 
language) {
                 exchangeTOS = someTOS
-            } else {
-                // TODO: Yikes! No baseURL
-
-
-
             }
-        } catch {    // TODO: error
-            symLog.log(error.localizedDescription)
+        } else {
+            // TODO: Yikes! No baseURL
         }
     }
 
@@ -48,19 +42,15 @@ struct WithdrawTOSView: View {
             Content(symLog: symLog, tos: exchangeTOS, myListStyle: 
$myListStyle,
                   language: languageCode, languageAction: loadToS) {
                 Task { // runs on MainActor
-                    do {
-                        if let exchangeBaseUrl {
-                            _ = try await 
model.setExchangeTOSAcceptedM(exchangeBaseUrl, etag: exchangeTOS.currentEtag)
-                            if acceptAction != nil {
-                                acceptAction!()
-                            } else { // just go back - caller will reload
-                                self.presentationMode.wrappedValue.dismiss()
-                            }
-                        } else {
-                            // TODO: error
+                    if let exchangeBaseUrl {
+                        _ = try? await 
model.setExchangeTOSAcceptedM(exchangeBaseUrl, etag: exchangeTOS.currentEtag)
+                        if acceptAction != nil {
+                            acceptAction!()
+                        } else { // just go back - caller will reload
+                            self.presentationMode.wrappedValue.dismiss()
                         }
-                    } catch {    // TODO: Show Error
-                        symLog.log(error.localizedDescription)
+                    } else {
+                        // TODO: error
                     }
                 }
             }
diff --git 
a/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawURIView.swift 
b/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawURIView.swift
index 49d45ba..651b502 100644
--- a/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawURIView.swift
+++ b/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawURIView.swift
@@ -10,7 +10,7 @@ import taler_swift
 import SymLog
 
 // Called either when scanning a QR code or tapping the provided link, both 
from the bank's website.
-// We show the user the withdrawal details in a sheet - but first the ToS must 
be accepted.
+// We show the user the bank-integrated withdrawal details in a sheet - but 
first the ToS must be accepted.
 // After the user confirmed the withdrawal, we show a button to return to the 
bank website to confirm there, too
 struct WithdrawURIView: View {
     private let symLog = SymLogV(0)
@@ -92,24 +92,21 @@ struct WithdrawURIView: View {
             DebugViewC.shared.setSheetID(SHEET_WITHDRAWAL)
         }
         .task {
-            do { // TODO: cancelled
-                symLog.log(".task")
-                let withdrawUriInfo = try await 
model.getWithdrawalDetailsForUriM(url.absoluteString)
+            symLog.log(".task")
+            if let withdrawUriInfo = try? await 
model.getWithdrawalDetailsForUriM(url.absoluteString) {
                 let amount = withdrawUriInfo.amount
                 let baseUrl = withdrawUriInfo.defaultExchangeBaseUrl
                            ?? 
withdrawUriInfo.possibleExchanges.first?.exchangeBaseUrl
-                if let baseUrl, let exc = await model.getExchangeByUrl(url: 
baseUrl) {
-                    exchange = exc
-                    let details = try await 
model.getWithdrawalDetailsForAmountM(baseUrl, amount: amount)
-                    withdrawalAmountDetails = details
-//                    agePicker.setAges(ages: details?.ageRestrictionOptions)
+                if let baseUrl {
+                    exchange = try? await model.getExchangeByUrl(url: baseUrl)
+                    if let details = try? await 
model.getWithdrawalDetailsForAmountM(baseUrl, amount: amount) {
+                        withdrawalAmountDetails = details
+                    }
+                    //                    agePicker.setAges(ages: 
details?.ageRestrictionOptions)
                 } else {    // TODO: error
                     symLog.log("no exchangeBaseUrl or no exchange")
                     withdrawalAmountDetails = nil
                 }
-            } catch {    // TODO: error
-                symLog.log(error.localizedDescription)
-                withdrawalAmountDetails = nil
             }
         }
     }
diff --git a/TalerWallet1/Views/Sheets/WithdrawExchangeV.swift 
b/TalerWallet1/Views/Sheets/WithdrawExchangeV.swift
index 3c39c1d..8968849 100644
--- a/TalerWallet1/Views/Sheets/WithdrawExchangeV.swift
+++ b/TalerWallet1/Views/Sheets/WithdrawExchangeV.swift
@@ -25,6 +25,7 @@ struct WithdrawExchangeV: View {
         let _ = Self._printChanges()
         let _ = symLog.vlog()       // just to get the # to compare it with 
.onAppear & onDisappear
 #endif
+
         Group {
             if exchange != nil {
                 ManualWithdraw(stack: stack.push(),
@@ -39,14 +40,13 @@ struct WithdrawExchangeV: View {
         }
         .task {
             if exchange == nil {
-                do { // TODO: cancelled
-                    symLog.log(".task")
-                    let withdrawExchange = try await 
model.loadWithdrawalExchangeForUriM(url.absoluteString)
+                symLog.log(".task")
+                if let withdrawExchange = try? await 
model.loadWithdrawalExchangeForUriM(url.absoluteString) {
                     let baseUrl = withdrawExchange.exchangeBaseUrl
                     symLog.log("getExchangeByUrl(\(baseUrl))")
-                    if let exc = await model.getExchangeByUrl(url: baseUrl) {
+                    if let exc = try? await model.getExchangeByUrl(url: 
baseUrl) {
                         // let the controller collect CurrencyInfo from this 
formerly unknown exchange
-                        let _ = await controller.getInfo(from: baseUrl, model: 
model)
+                        let _ = try? await controller.getInfo(from: baseUrl, 
model: model)
                         if let amount = withdrawExchange.amount {
                             amountToTransfer = amount
                         } else {
@@ -56,11 +56,8 @@ struct WithdrawExchangeV: View {
                         }
                         exchange = exc
                     } else {
-                        // TODO: Error "Can't get Exchange / Payment Service 
Provider Info"
+                        exchange = nil
                     }
-                } catch {    // TODO: error
-                    symLog.log(error.localizedDescription)
-                    exchange = nil
                 }
             }
         }
diff --git a/TalerWallet1/Views/Transactions/TransactionSummaryV.swift 
b/TalerWallet1/Views/Transactions/TransactionSummaryV.swift
index da7ce3c..34bd72c 100644
--- a/TalerWallet1/Views/Transactions/TransactionSummaryV.swift
+++ b/TalerWallet1/Views/Transactions/TransactionSummaryV.swift
@@ -28,14 +28,14 @@ struct TransactionSummaryV: View {
     private let symLog = SymLogV(0)
     let stack: CallStack
     let transactionId: String
-    let reloadAction: ((_ transactionId: String) async throws -> Transaction)
+    let reloadAction: ((_ transactionId: String, _ viewHandles: Bool) async 
throws -> Transaction)
     let navTitle: String?
     let doneAction: ((_ stack: CallStack) -> Void)?
-    let abortAction: ((_ transactionId: String) async throws -> Void)?
-    let deleteAction: ((_ transactionId: String) async throws -> Void)?
-    let failAction: ((_ transactionId: String) async throws -> Void)?
-    let suspendAction: ((_ transactionId: String) async throws -> Void)?
-    let resumeAction: ((_ transactionId: String) async throws -> Void)?
+    let abortAction: ((_ transactionId: String, _ viewHandles: Bool) async 
throws -> Void)?
+    let deleteAction: ((_ transactionId: String, _ viewHandles: Bool) async 
throws -> Void)?
+    let failAction: ((_ transactionId: String, _ viewHandles: Bool) async 
throws -> Void)?
+    let suspendAction: ((_ transactionId: String, _ viewHandles: Bool) async 
throws -> Void)?
+    let resumeAction: ((_ transactionId: String, _ viewHandles: Bool) async 
throws -> Void)?
 
     @Environment(\.colorScheme) private var colorScheme
     @Environment(\.colorSchemeContrast) private var colorSchemeContrast
@@ -50,12 +50,10 @@ struct TransactionSummaryV: View {
     @State var viewId = UUID()
 
     func loadTransaction() async {
-        do {
-            let reloadedTransaction = try await reloadAction(transactionId)
+        if let reloadedTransaction = try? await reloadAction(transactionId, 
false) {
             symLog.log("reloaded transaction: 
\(reloadedTransaction.common.txState.major)")
             withAnimation() { transaction = reloadedTransaction; viewId = 
UUID() }      // redraw
-        } catch {
-            symLog.log(error.localizedDescription)
+        } else {
             withAnimation() { transaction = Transaction(dummyCurrency: 
DEMOCURRENCY); viewId = UUID() }
         }
     }
diff --git a/TalerWallet1/Views/Transactions/TransactionsListView.swift 
b/TalerWallet1/Views/Transactions/TransactionsListView.swift
index 9944327..3f2dbc9 100644
--- a/TalerWallet1/Views/Transactions/TransactionsListView.swift
+++ b/TalerWallet1/Views/Transactions/TransactionsListView.swift
@@ -15,7 +15,7 @@ struct TransactionsListView: View {
     let transactions: [Transaction]
     let showUpDown: Bool
     let reloadAllAction: (_ stack: CallStack) async -> ()
-    let reloadOneAction: ((_ transactionId: String) async throws -> 
Transaction)
+    let reloadOneAction: ((_ transactionId: String, _ viewHandles: Bool) async 
throws -> Transaction)
 
     @State private var viewId = UUID()
     @State private var upAction: () -> Void = {}
@@ -84,7 +84,7 @@ struct TransactionsArraySliceV: View {
     let stack: CallStack
     let scopeInfo: ScopeInfo
     let transactions: [Transaction]
-    let reloadOneAction: ((_ transactionId: String) async throws -> 
Transaction)
+    let reloadOneAction: ((_ transactionId: String, _ viewHandles: Bool) async 
throws -> Transaction)
 
     @EnvironmentObject private var model: WalletModel
     var body: some View {
diff --git a/TestFlight/WhatToTest.en-US.txt b/TestFlight/WhatToTest.en-US.txt
index 4391d6c..0aa1baf 100644
--- a/TestFlight/WhatToTest.en-US.txt
+++ b/TestFlight/WhatToTest.en-US.txt
@@ -1,3 +1,18 @@
+Version 0.9.7 (1)
+
+• better error handling
+- bugfix: wallet-core 0.10.6 cache handling, fee computations
+
+
+Version 0.9.6 (1)
+
+• New feature: Removed 'Banking' tab. Withdraw + deposit are now directly 
available in Balances
+    Added Settings->Payment Services for exchange management (w.i.p.)
+• New feature: LocalConsole also for Taler Wallet
+- bugfix: (pending) refresh transactions are now shown correctly
+- bugfix: wallet-core 0.10.2 speeds up fee computations
+
+
 Version 0.9.5 (3)
 
 - bugfix: wallet-core 0.10.1 fixes exchange handling

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]