gnunet-svn
[Top][All Lists]
Advanced

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

[taler-taler-ios] 01/54: Big update after DD37


From: gnunet
Subject: [taler-taler-ios] 01/54: Big update after DD37
Date: Fri, 30 Jun 2023 22:33:33 +0200

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

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

commit c4294a7738cfba17fd1f12c5a08ec28dec8ba9d3
Author: Marc Stibane <marc@taler.net>
AuthorDate: Mon Jun 12 11:30:47 2023 +0200

    Big update after DD37
---
 ...rWalletT.entitlements => GNU Taler.entitlements |   0
 Info.plist                                         |  11 +-
 LICENSE.md                                         |  12 +
 TalerTests/WalletBackendTests.swift                |  15 +-
 TalerUITests/TalerUITests.swift                    |  16 +-
 TalerWallet.xcodeproj/project.pbxproj              | 341 ++++++++++++++-----
 .../taler-logo-2023-red.imageset/Contents.json     |  12 +
 .../taler-logo-2023-red.svg                        |  19 ++
 TalerWallet1/Backend/Transaction.swift             | 365 +++++++++++++++------
 TalerWallet1/Backend/WalletBackendError.swift      |  17 +-
 TalerWallet1/Backend/WalletBackendRequest.swift    | 201 ++++--------
 TalerWallet1/Backend/WalletCore.swift              | 337 ++++++++++++-------
 TalerWallet1/Controllers/Controller.swift          |  61 +---
 TalerWallet1/Controllers/DebugViewC.swift          | 163 +++++++++
 TalerWallet1/Controllers/PublicConstants.swift     |  35 ++
 TalerWallet1/Controllers/TalerWallet1App.swift     |  83 +++--
 TalerWallet1/Helper/AnyTransition+backslide.swift  |  30 ++
 TalerWallet1/Helper/CurrencyFormatter.swift        |  27 ++
 TalerWallet1/Helper/EqualIconWidthDomain.swift     | 141 ++++++++
 TalerWallet1/Helper/KeyboardResponder.swift        |  45 +++
 TalerWallet1/Helper/LocalizedAlertError.swift      |  54 +++
 TalerWallet1/Helper/PublicConstants.swift          |  24 --
 TalerWallet1/Helper/TalerDater.swift               |  37 ++-
 TalerWallet1/Helper/TalerStrings.swift             |  15 +-
 TalerWallet1/Helper/URL+iban.swift                 |  37 +++
 TalerWallet1/Helper/View+dismissTop.swift          |  90 ++++-
 TalerWallet1/Helper/WalletColors.swift             |  56 ++++
 .../{Views/Balances => Model}/BalancesModel.swift  |  52 +--
 .../{Views/Exchange => Model}/ExchangeModel.swift  | 117 ++++---
 .../{Views/Payment => Model}/PaymentURIModel.swift |  52 +--
 TalerWallet1/Model/Peer2peerModel.swift            | 130 ++++++++
 TalerWallet1/Model/PendingModel.swift              |  78 +++++
 ...ExchangeTestModel.swift => SettingsModel.swift} |  75 +++--
 TalerWallet1/Model/TransactionsModel.swift         | 157 +++++++++
 TalerWallet1/Model/WalletInitModel.swift           |  35 +-
 TalerWallet1/Model/WalletModel.swift               |  82 +++--
 .../WithdrawModel.swift}                           | 142 +++++---
 TalerWallet1/Preview Content/transactions.json     | 300 +++++++++++++++++
 TalerWallet1/Quickjs/quickjs.swift                 |  15 +-
 TalerWallet1/Settings.bundle/Root.plist            |  21 ++
 TalerWallet1/Settings.bundle/en.lproj/Root.strings | Bin 0 -> 546 bytes
 TalerWallet1/Views/Balances/BalanceRow.swift       |  46 ---
 TalerWallet1/Views/Balances/BalanceRowView.swift   |  52 +++
 TalerWallet1/Views/Balances/BalancesListView.swift | 133 ++++++++
 .../Views/Balances/BalancesSectionView.swift       | 161 +++++++++
 .../Views/Balances/CurrenciesListView.swift        |  83 -----
 TalerWallet1/Views/Balances/CurrencyView.swift     |  58 ----
 TalerWallet1/Views/Balances/PendingRow.swift       |  61 ----
 TalerWallet1/Views/Balances/PendingRowView.swift   |  48 +++
 .../Views/Balances/UncompletedRowView.swift        |  34 ++
 TalerWallet1/Views/Balances/WalletEmptyView.swift  |  44 ---
 TalerWallet1/Views/Exchange/ExchangeListView.swift | 144 ++++----
 .../Views/Exchange/ExchangeSectionView.swift       | 112 +++++++
 TalerWallet1/Views/Exchange/ManualWithdraw.swift   | 199 +++++++++++
 .../Views/Exchange/ManualWithdrawDone.swift        |  79 +++++
 TalerWallet1/Views/HelperViews/AmountView.swift    |  28 +-
 TalerWallet1/Views/HelperViews/Buttons.swift       | 246 ++++++++++++--
 TalerWallet1/Views/HelperViews/CopyShare.swift     |  85 +++++
 TalerWallet1/Views/HelperViews/CurrencyField.swift | 221 +++++++++++++
 .../Views/HelperViews/CurrencyInputView.swift      |  60 ++++
 .../LaunchAnimationView.swift                      |  18 +-
 TalerWallet1/Views/HelperViews/ListStyle.swift     | 113 +++++++
 TalerWallet1/Views/HelperViews/LoadingView.swift   |  39 +--
 .../Views/HelperViews/QRGeneratorView.swift        |  62 ++++
 TalerWallet1/Views/HelperViews/SelectDays.swift    |  56 ++++
 .../Views/HelperViews/TextFieldAlert.swift         |  15 +-
 TalerWallet1/Views/Main/ContentView.swift          |  92 ------
 TalerWallet1/Views/Main/ErrorView.swift            |  22 +-
 TalerWallet1/Views/Main/MainView.swift             | 100 ++++++
 TalerWallet1/Views/Main/SideBarView.swift          |  71 ++--
 TalerWallet1/Views/Main/WalletEmptyView.swift      |  44 +++
 TalerWallet1/Views/Payment/PaymentAcceptView.swift | 103 +++---
 TalerWallet1/Views/Payment/PaymentURIView.swift    |  47 +--
 TalerWallet1/Views/Peer2peer/ReceivePurpose.swift  | 129 ++++++++
 TalerWallet1/Views/Peer2peer/RequestPayment.swift  |  77 +++++
 TalerWallet1/Views/Peer2peer/SendAmount.swift      |  85 +++++
 TalerWallet1/Views/Peer2peer/SendNow.swift         |  87 +++++
 TalerWallet1/Views/Peer2peer/SendPurpose.swift     | 126 +++++++
 TalerWallet1/Views/Pending/PendingModel.swift      |  82 -----
 .../Views/Pending/PendingOpsListView.swift         |  75 -----
 .../{ => Settings}/Pending/PendingOpView.swift     |  31 +-
 .../Settings/Pending/PendingOpsListView.swift      |  64 ++++
 TalerWallet1/Views/Settings/SettingsItem.swift     |  35 +-
 TalerWallet1/Views/Settings/SettingsView.swift     | 245 ++++++++------
 TalerWallet1/Views/Sheets/QRSheet.swift            |  52 +++
 TalerWallet1/Views/Sheets/ShareSheet.swift         |  40 +++
 TalerWallet1/Views/Sheets/Sheet.swift              |  44 +++
 TalerWallet1/Views/Sheets/URLSheet.swift           |  45 +++
 .../Views/Transactions/ManualDetails.swift         |  69 ++++
 TalerWallet1/Views/Transactions/ThreeAmounts.swift | 109 ++++++
 .../Views/Transactions/TransactionDetail.swift     | 136 --------
 .../Views/Transactions/TransactionDetailView.swift | 255 ++++++++++++++
 .../Views/Transactions/TransactionRow.swift        |  92 ------
 .../Views/Transactions/TransactionRowView.swift    | 137 ++++++++
 .../Views/Transactions/TransactionsEmptyView.swift |  37 +++
 .../Views/Transactions/TransactionsListView.swift  | 153 ++++-----
 .../Views/Transactions/TransactionsModel.swift     |  61 ----
 TalerWallet1/Views/URLSheet.swift                  |  64 ----
 .../Views/Withdraw/WithdrawAcceptView.swift        |  71 ----
 .../Views/Withdraw/WithdrawProgressView.swift      |  45 ---
 TalerWallet1/Views/Withdraw/WithdrawTOSView.swift  |  96 ------
 TalerWallet1/Views/Withdraw/WithdrawURIView.swift  | 103 ------
 .../WithdrawAcceptView.swift                       |  71 ++++
 .../WithdrawProgressView.swift                     |  27 ++
 .../WithdrawBankIntegrated/WithdrawTOSView.swift   |  90 +++++
 .../WithdrawBankIntegrated/WithdrawURIView.swift   |  76 +++++
 taler-swift/Sources/taler-swift/Amount.swift       |  43 ++-
 taler-swift/Sources/taler-swift/Time.swift         |  28 +-
 .../Tests/taler-swiftTests/AmountTests.swift       |  15 +-
 taler-swift/Tests/taler-swiftTests/TimeTests.swift |  15 +-
 110 files changed, 6583 insertions(+), 2603 deletions(-)

diff --git a/TalerWalletT.entitlements b/GNU Taler.entitlements
similarity index 100%
rename from TalerWalletT.entitlements
rename to GNU Taler.entitlements
diff --git a/Info.plist b/Info.plist
index 229b02d..8634a0f 100644
--- a/Info.plist
+++ b/Info.plist
@@ -12,7 +12,7 @@
                        <key>CFBundleTypeRole</key>
                        <string>Viewer</string>
                        <key>CFBundleURLName</key>
-                       <string>com.taler-systems.talerwallet15</string>
+                       <string>com.taler-systems.gnutalerwallet09</string>
                        <key>CFBundleURLSchemes</key>
                        <array>
                                <string>taler</string>
@@ -24,17 +24,12 @@
        </array>
        <key>ITSAppUsesNonExemptEncryption</key>
        <false/>
-       <key>UIApplicationSceneManifest</key>
-       <dict>
-               <key>UIApplicationSupportsMultipleScenes</key>
-               <true/>
-               <key>UISceneConfigurations</key>
-               <dict/>
-       </dict>
        <key>UIBackgroundModes</key>
        <array>
                <string>fetch</string>
                <string>processing</string>
        </array>
+       <key>UIFileSharingEnabled</key>
+       <true/>
 </dict>
 </plist>
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..ed661f4
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,12 @@
+Copyright ©2022-23 Taler Systems S.A.
+
+GNU Taler is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3, or (at your option) any later version.
+
+GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
diff --git a/TalerTests/WalletBackendTests.swift 
b/TalerTests/WalletBackendTests.swift
index 4a09133..20c8788 100644
--- a/TalerTests/WalletBackendTests.swift
+++ b/TalerTests/WalletBackendTests.swift
@@ -1,17 +1,6 @@
 /*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
  */
 import XCTest
 @testable import Taler
diff --git a/TalerUITests/TalerUITests.swift b/TalerUITests/TalerUITests.swift
index 24b772e..9c57fef 100644
--- a/TalerUITests/TalerUITests.swift
+++ b/TalerUITests/TalerUITests.swift
@@ -1,19 +1,7 @@
 /*
- * This file is part of GNU Taler
- * (C) 2021 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
  */
-
 import XCTest
 
 class TalerUITests: XCTestCase {
diff --git a/TalerWallet.xcodeproj/project.pbxproj 
b/TalerWallet.xcodeproj/project.pbxproj
index 2052f9a..66a3b04 100644
--- a/TalerWallet.xcodeproj/project.pbxproj
+++ b/TalerWallet.xcodeproj/project.pbxproj
@@ -3,11 +3,32 @@
        archiveVersion = 1;
        classes = {
        };
-       objectVersion = 52;
+       objectVersion = 54;
        objects = {
 
 /* Begin PBXBuildFile section */
+               4E16E12329F3BB99008B9C86 /* CurrencyFormatter.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4E16E12229F3BB99008B9C86 /* 
CurrencyFormatter.swift */; };
+               4E363CBC2A237E0900D7E98C /* URL+iban.swift in Sources */ = {isa 
= PBXBuildFile; fileRef = 4E363CBB2A237E0900D7E98C /* URL+iban.swift */; };
+               4E363CBE2A23CB2100D7E98C /* AnyTransition+backslide.swift in 
Sources */ = {isa = PBXBuildFile; fileRef = 4E363CBD2A23CB2100D7E98C /* 
AnyTransition+backslide.swift */; };
+               4E363CC02A24754200D7E98C /* Settings.bundle in Resources */ = 
{isa = PBXBuildFile; fileRef = 4E363CBF2A24754200D7E98C /* Settings.bundle */; 
};
+               4E363CC22A2621C200D7E98C /* LocalizedAlertError.swift in 
Sources */ = {isa = PBXBuildFile; fileRef = 4E363CC12A2621C200D7E98C /* 
LocalizedAlertError.swift */; };
+               4E40E0BE29F25ABB00B85369 /* SendAmount.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4E40E0BD29F25ABB00B85369 /* SendAmount.swift */; 
};
+               4E50B3502A1BEE8000F9F01C /* ManualWithdraw.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4E50B34F2A1BEE8000F9F01C /* 
ManualWithdraw.swift */; };
+               4E53A33729F50B7B00830EC2 /* CurrencyField.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4E53A33629F50B7B00830EC2 /* CurrencyField.swift 
*/; };
+               4E6EDD852A3615BE0031D520 /* ManualDetails.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4E6EDD842A3615BE0031D520 /* ManualDetails.swift 
*/; };
+               4E6EDD872A363D8D0031D520 /* ListStyle.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4E6EDD862A363D8D0031D520 /* ListStyle.swift */; 
};
+               4E753A062A0952F8002D9328 /* DebugViewC.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4E753A052A0952F7002D9328 /* DebugViewC.swift */; 
};
+               4E753A082A0B6A5F002D9328 /* ShareSheet.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4E753A072A0B6A5F002D9328 /* ShareSheet.swift */; 
};
+               4E7940DE29FC307C00A9AEA1 /* SendPurpose.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4E7940DD29FC307C00A9AEA1 /* SendPurpose.swift 
*/; };
+               4E87C8732A31CB7F001C6406 /* TransactionsEmptyView.swift in 
Sources */ = {isa = PBXBuildFile; fileRef = 4E87C8722A31CB7F001C6406 /* 
TransactionsEmptyView.swift */; };
+               4E87C8752A34B411001C6406 /* UncompletedRowView.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4E87C8742A34B411001C6406 /* 
UncompletedRowView.swift */; };
+               4E8E25332A1CD39700A27BFA /* EqualIconWidthDomain.swift in 
Sources */ = {isa = PBXBuildFile; fileRef = 4E8E25322A1CD39700A27BFA /* 
EqualIconWidthDomain.swift */; };
+               4E9320432A14F6EA00A87B0E /* WalletColors.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4E9320422A14F6EA00A87B0E /* WalletColors.swift 
*/; };
+               4E9320452A1645B600A87B0E /* RequestPayment.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4E9320442A1645B600A87B0E /* 
RequestPayment.swift */; };
+               4E9320472A164BC700A87B0E /* ReceivePurpose.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4E9320462A164BC700A87B0E /* 
ReceivePurpose.swift */; };
                4EA1ABBE29A3833A008821EA /* PublicConstants.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4EA1ABBD29A3833A008821EA /* 
PublicConstants.swift */; };
+               4EA551252A2C923600FEC9A8 /* CurrencyInputView.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4EA551242A2C923600FEC9A8 /* 
CurrencyInputView.swift */; };
+               4EAD117629F672FA008EDD0B /* KeyboardResponder.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4EAD117529F672FA008EDD0B /* 
KeyboardResponder.swift */; };
                4EB094D629896CD20043A8A1 /* TalerWalletTests.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4EB094D429896CD20043A8A1 /* 
TalerWalletTests.swift */; };
                4EB094D729896CD20043A8A1 /* WalletBackendTests.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4EB094D529896CD20043A8A1 /* 
WalletBackendTests.swift */; };
                4EB094DC29896D030043A8A1 /* TalerWalletUITestsLaunchTests.swift 
in Sources */ = {isa = PBXBuildFile; fileRef = 4EB094D929896D030043A8A1 /* 
TalerWalletUITestsLaunchTests.swift */; };
@@ -23,7 +44,7 @@
                4EB0950A2989CB7C0043A8A1 /* TalerStrings.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EB095072989CB7C0043A8A1 /* TalerStrings.swift 
*/; };
                4EB0950B2989CB7C0043A8A1 /* View+dismissTop.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4EB095082989CB7C0043A8A1 /* 
View+dismissTop.swift */; };
                4EB0950E2989CB9A0043A8A1 /* quickjs.swift in Sources */ = {isa 
= PBXBuildFile; fileRef = 4EB0950D2989CB9A0043A8A1 /* quickjs.swift */; };
-               4EB095152989CBB00043A8A1 /* ExchangeTestModel.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4EB095102989CBB00043A8A1 /* 
ExchangeTestModel.swift */; };
+               4EB095152989CBB00043A8A1 /* SettingsModel.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EB095102989CBB00043A8A1 /* SettingsModel.swift 
*/; };
                4EB095162989CBB00043A8A1 /* WalletModel.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EB095112989CBB00043A8A1 /* WalletModel.swift 
*/; };
                4EB095192989CBB00043A8A1 /* WalletInitModel.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4EB095142989CBB00043A8A1 /* 
WalletInitModel.swift */; };
                4EB0951F2989CBCB0043A8A1 /* WalletBackendRequest.swift in 
Sources */ = {isa = PBXBuildFile; fileRef = 4EB0951B2989CBCB0043A8A1 /* 
WalletBackendRequest.swift */; };
@@ -38,24 +59,24 @@
                4EB095542989CBFE0043A8A1 /* PaymentURIModel.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4EB0952C2989CBFE0043A8A1 /* 
PaymentURIModel.swift */; };
                4EB095552989CBFE0043A8A1 /* PaymentURIView.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4EB0952D2989CBFE0043A8A1 /* 
PaymentURIView.swift */; };
                4EB095562989CBFE0043A8A1 /* TransactionsListView.swift in 
Sources */ = {isa = PBXBuildFile; fileRef = 4EB0952F2989CBFE0043A8A1 /* 
TransactionsListView.swift */; };
-               4EB095572989CBFE0043A8A1 /* TransactionRow.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4EB095302989CBFE0043A8A1 /* 
TransactionRow.swift */; };
-               4EB095582989CBFE0043A8A1 /* TransactionDetail.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4EB095312989CBFE0043A8A1 /* 
TransactionDetail.swift */; };
+               4EB095572989CBFE0043A8A1 /* TransactionRowView.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4EB095302989CBFE0043A8A1 /* 
TransactionRowView.swift */; };
+               4EB095582989CBFE0043A8A1 /* TransactionDetailView.swift in 
Sources */ = {isa = PBXBuildFile; fileRef = 4EB095312989CBFE0043A8A1 /* 
TransactionDetailView.swift */; };
                4EB095592989CBFE0043A8A1 /* TransactionsModel.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4EB095322989CBFE0043A8A1 /* 
TransactionsModel.swift */; };
                4EB0955A2989CBFE0043A8A1 /* URLSheet.swift in Sources */ = {isa 
= PBXBuildFile; fileRef = 4EB095332989CBFE0043A8A1 /* URLSheet.swift */; };
                4EB0955B2989CBFE0043A8A1 /* BalancesModel.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EB095352989CBFE0043A8A1 /* BalancesModel.swift 
*/; };
-               4EB0955C2989CBFE0043A8A1 /* BalanceRow.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EB095362989CBFE0043A8A1 /* BalanceRow.swift */; 
};
-               4EB0955D2989CBFE0043A8A1 /* CurrenciesListView.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4EB095372989CBFE0043A8A1 /* 
CurrenciesListView.swift */; };
-               4EB0955E2989CBFE0043A8A1 /* PendingRow.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EB095382989CBFE0043A8A1 /* PendingRow.swift */; 
};
+               4EB0955C2989CBFE0043A8A1 /* BalanceRowView.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4EB095362989CBFE0043A8A1 /* 
BalanceRowView.swift */; };
+               4EB0955D2989CBFE0043A8A1 /* BalancesListView.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4EB095372989CBFE0043A8A1 /* 
BalancesListView.swift */; };
+               4EB0955E2989CBFE0043A8A1 /* PendingRowView.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4EB095382989CBFE0043A8A1 /* 
PendingRowView.swift */; };
                4EB0955F2989CBFE0043A8A1 /* WalletEmptyView.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4EB095392989CBFE0043A8A1 /* 
WalletEmptyView.swift */; };
-               4EB095602989CBFE0043A8A1 /* CurrencyView.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EB0953A2989CBFE0043A8A1 /* CurrencyView.swift 
*/; };
+               4EB095602989CBFE0043A8A1 /* BalancesSectionView.swift in 
Sources */ = {isa = PBXBuildFile; fileRef = 4EB0953A2989CBFE0043A8A1 /* 
BalancesSectionView.swift */; };
                4EB095612989CBFE0043A8A1 /* WithdrawURIView.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4EB0953C2989CBFE0043A8A1 /* 
WithdrawURIView.swift */; };
-               4EB095622989CBFE0043A8A1 /* WithdrawURIModel.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4EB0953D2989CBFE0043A8A1 /* 
WithdrawURIModel.swift */; };
+               4EB095622989CBFE0043A8A1 /* WithdrawModel.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EB0953D2989CBFE0043A8A1 /* WithdrawModel.swift 
*/; };
                4EB095632989CBFE0043A8A1 /* WithdrawAcceptView.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4EB0953E2989CBFE0043A8A1 /* 
WithdrawAcceptView.swift */; };
                4EB095642989CBFE0043A8A1 /* WithdrawProgressView.swift in 
Sources */ = {isa = PBXBuildFile; fileRef = 4EB0953F2989CBFE0043A8A1 /* 
WithdrawProgressView.swift */; };
                4EB095652989CBFE0043A8A1 /* WithdrawTOSView.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4EB095402989CBFE0043A8A1 /* 
WithdrawTOSView.swift */; };
                4EB095662989CBFE0043A8A1 /* SideBarView.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EB095422989CBFE0043A8A1 /* SideBarView.swift 
*/; };
                4EB095672989CBFE0043A8A1 /* LaunchAnimationView.swift in 
Sources */ = {isa = PBXBuildFile; fileRef = 4EB095432989CBFE0043A8A1 /* 
LaunchAnimationView.swift */; };
-               4EB095682989CBFE0043A8A1 /* ContentView.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EB095442989CBFE0043A8A1 /* ContentView.swift 
*/; };
+               4EB095682989CBFE0043A8A1 /* MainView.swift in Sources */ = {isa 
= PBXBuildFile; fileRef = 4EB095442989CBFE0043A8A1 /* MainView.swift */; };
                4EB095692989CBFE0043A8A1 /* ErrorView.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EB095452989CBFE0043A8A1 /* ErrorView.swift */; 
};
                4EB0956A2989CBFE0043A8A1 /* Buttons.swift in Sources */ = {isa 
= PBXBuildFile; fileRef = 4EB095472989CBFE0043A8A1 /* Buttons.swift */; };
                4EB0956B2989CBFE0043A8A1 /* TextFieldAlert.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4EB095482989CBFE0043A8A1 /* 
TextFieldAlert.swift */; };
@@ -64,6 +85,17 @@
                4EB0956E2989CBFE0043A8A1 /* PendingModel.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EB0954C2989CBFE0043A8A1 /* PendingModel.swift 
*/; };
                4EB0956F2989CBFE0043A8A1 /* PendingOpView.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EB0954D2989CBFE0043A8A1 /* PendingOpView.swift 
*/; };
                4EB095702989CBFE0043A8A1 /* PendingOpsListView.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4EB0954E2989CBFE0043A8A1 /* 
PendingOpsListView.swift */; };
+               4EB3136129FEE79B007D68BC /* SendNow.swift in Sources */ = {isa 
= PBXBuildFile; fileRef = 4EB3136029FEE79B007D68BC /* SendNow.swift */; };
+               4EB431672A1E55C700C5690E /* ManualWithdrawDone.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4EB431662A1E55C700C5690E /* 
ManualWithdrawDone.swift */; };
+               4EC90C782A1B528B0071DC58 /* ExchangeSectionView.swift in 
Sources */ = {isa = PBXBuildFile; fileRef = 4EC90C772A1B528B0071DC58 /* 
ExchangeSectionView.swift */; };
+               4ECB62802A0BA6DF004ABBB7 /* Peer2peerModel.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4ECB627F2A0BA6DF004ABBB7 /* 
Peer2peerModel.swift */; };
+               4ECB62822A0BB01D004ABBB7 /* SelectDays.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4ECB62812A0BB01D004ABBB7 /* SelectDays.swift */; 
};
+               4ED2F94B2A278F5100453B40 /* ThreeAmounts.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4ED2F94A2A278F5100453B40 /* ThreeAmounts.swift 
*/; };
+               4EEC157329F8242800D46A03 /* QRGeneratorView.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4EEC157229F8242800D46A03 /* 
QRGeneratorView.swift */; };
+               4EEC157629F8ECBF00D46A03 /* CodeScanner in Frameworks */ = {isa 
= PBXBuildFile; productRef = 4EEC157529F8ECBF00D46A03 /* CodeScanner */; };
+               4EEC157829F9032900D46A03 /* Sheet.swift in Sources */ = {isa = 
PBXBuildFile; fileRef = 4EEC157729F9032900D46A03 /* Sheet.swift */; };
+               4EEC157A29F9427F00D46A03 /* QRSheet.swift in Sources */ = {isa 
= PBXBuildFile; fileRef = 4EEC157929F9427F00D46A03 /* QRSheet.swift */; };
+               4EF840A72A0B85F400EE0D47 /* CopyShare.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EF840A62A0B85F400EE0D47 /* CopyShare.swift */; 
};
                ABC13AA32859962800D23185 /* taler-swift in Frameworks */ = {isa 
= PBXBuildFile; productRef = ABC13AA22859962800D23185 /* taler-swift */; };
                ABE97B1D286D82BF00580772 /* AnyCodable in Frameworks */ = {isa 
= PBXBuildFile; productRef = ABE97B1C286D82BF00580772 /* AnyCodable */; };
 /* End PBXBuildFile section */
@@ -98,8 +130,30 @@
 /* End PBXCopyFilesBuildPhase section */
 
 /* Begin PBXFileReference section */
-               4E3AE7EF29A7E8F40070BEC4 /* TalerWalletT.entitlements */ = {isa 
= PBXFileReference; lastKnownFileType = text.plist.entitlements; path = 
TalerWalletT.entitlements; sourceTree = "<group>"; };
+               4E16E12229F3BB99008B9C86 /* CurrencyFormatter.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= CurrencyFormatter.swift; sourceTree = "<group>"; };
+               4E363CBB2A237E0900D7E98C /* URL+iban.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= "URL+iban.swift"; sourceTree = "<group>"; };
+               4E363CBD2A23CB2100D7E98C /* AnyTransition+backslide.swift */ = 
{isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = 
sourcecode.swift; path = "AnyTransition+backslide.swift"; sourceTree = 
"<group>"; };
+               4E363CBF2A24754200D7E98C /* Settings.bundle */ = {isa = 
PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = 
Settings.bundle; sourceTree = "<group>"; };
+               4E363CC12A2621C200D7E98C /* LocalizedAlertError.swift */ = {isa 
= PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; 
path = LocalizedAlertError.swift; sourceTree = "<group>"; };
+               4E3AE7EF29A7E8F40070BEC4 /* Taler Wallet.entitlements */ = {isa 
= PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Taler 
Wallet.entitlements"; sourceTree = "<group>"; };
+               4E40E0BD29F25ABB00B85369 /* SendAmount.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name 
= SendAmount.swift; path = TalerWallet1/Views/Peer2peer/SendAmount.swift; 
sourceTree = SOURCE_ROOT; };
+               4E50B34F2A1BEE8000F9F01C /* ManualWithdraw.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= ManualWithdraw.swift; sourceTree = "<group>"; };
+               4E53A33629F50B7B00830EC2 /* CurrencyField.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= CurrencyField.swift; sourceTree = "<group>"; };
+               4E6EDD842A3615BE0031D520 /* ManualDetails.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= ManualDetails.swift; sourceTree = "<group>"; };
+               4E6EDD862A363D8D0031D520 /* ListStyle.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= ListStyle.swift; sourceTree = "<group>"; };
+               4E753A042A08E720002D9328 /* transactions.json */ = {isa = 
PBXFileReference; lastKnownFileType = text.json; path = transactions.json; 
sourceTree = "<group>"; };
+               4E753A052A0952F7002D9328 /* DebugViewC.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= DebugViewC.swift; sourceTree = "<group>"; };
+               4E753A072A0B6A5F002D9328 /* ShareSheet.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= ShareSheet.swift; sourceTree = "<group>"; };
+               4E7940DD29FC307C00A9AEA1 /* SendPurpose.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= SendPurpose.swift; sourceTree = "<group>"; };
+               4E87C8722A31CB7F001C6406 /* TransactionsEmptyView.swift */ = 
{isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = 
sourcecode.swift; path = TransactionsEmptyView.swift; sourceTree = "<group>"; };
+               4E87C8742A34B411001C6406 /* UncompletedRowView.swift */ = {isa 
= PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; 
path = UncompletedRowView.swift; sourceTree = "<group>"; };
+               4E8E25322A1CD39700A27BFA /* EqualIconWidthDomain.swift */ = 
{isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = 
EqualIconWidthDomain.swift; sourceTree = "<group>"; };
+               4E9320422A14F6EA00A87B0E /* WalletColors.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= WalletColors.swift; sourceTree = "<group>"; };
+               4E9320442A1645B600A87B0E /* RequestPayment.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= RequestPayment.swift; sourceTree = "<group>"; };
+               4E9320462A164BC700A87B0E /* ReceivePurpose.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= ReceivePurpose.swift; sourceTree = "<group>"; };
                4EA1ABBD29A3833A008821EA /* PublicConstants.swift */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.swift; path = 
PublicConstants.swift; sourceTree = "<group>"; };
+               4EA551242A2C923600FEC9A8 /* CurrencyInputView.swift */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.swift; path = 
CurrencyInputView.swift; sourceTree = "<group>"; };
+               4EAD117529F672FA008EDD0B /* KeyboardResponder.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= KeyboardResponder.swift; sourceTree = "<group>"; };
                4EB094D429896CD20043A8A1 /* TalerWalletTests.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= TalerWalletTests.swift; sourceTree = "<group>"; };
                4EB094D529896CD20043A8A1 /* WalletBackendTests.swift */ = {isa 
= PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; 
path = WalletBackendTests.swift; sourceTree = "<group>"; };
                4EB094D929896D030043A8A1 /* TalerWalletUITestsLaunchTests.swift 
*/ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = 
sourcecode.swift; path = TalerWalletUITestsLaunchTests.swift; sourceTree = 
"<group>"; };
@@ -115,7 +169,7 @@
                4EB095072989CB7C0043A8A1 /* TalerStrings.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= TalerStrings.swift; sourceTree = "<group>"; };
                4EB095082989CB7C0043A8A1 /* View+dismissTop.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= "View+dismissTop.swift"; sourceTree = "<group>"; };
                4EB0950D2989CB9A0043A8A1 /* quickjs.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= quickjs.swift; sourceTree = "<group>"; };
-               4EB095102989CBB00043A8A1 /* ExchangeTestModel.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= ExchangeTestModel.swift; sourceTree = "<group>"; };
+               4EB095102989CBB00043A8A1 /* SettingsModel.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= SettingsModel.swift; sourceTree = "<group>"; };
                4EB095112989CBB00043A8A1 /* WalletModel.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= WalletModel.swift; sourceTree = "<group>"; };
                4EB095142989CBB00043A8A1 /* WalletInitModel.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= WalletInitModel.swift; sourceTree = "<group>"; };
                4EB0951B2989CBCB0043A8A1 /* WalletBackendRequest.swift */ = 
{isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = 
sourcecode.swift; path = WalletBackendRequest.swift; sourceTree = "<group>"; };
@@ -130,24 +184,24 @@
                4EB0952C2989CBFE0043A8A1 /* PaymentURIModel.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= PaymentURIModel.swift; sourceTree = "<group>"; };
                4EB0952D2989CBFE0043A8A1 /* PaymentURIView.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= PaymentURIView.swift; sourceTree = "<group>"; };
                4EB0952F2989CBFE0043A8A1 /* TransactionsListView.swift */ = 
{isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = 
sourcecode.swift; path = TransactionsListView.swift; sourceTree = "<group>"; };
-               4EB095302989CBFE0043A8A1 /* TransactionRow.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= TransactionRow.swift; sourceTree = "<group>"; };
-               4EB095312989CBFE0043A8A1 /* TransactionDetail.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= TransactionDetail.swift; sourceTree = "<group>"; };
+               4EB095302989CBFE0043A8A1 /* TransactionRowView.swift */ = {isa 
= PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; 
path = TransactionRowView.swift; sourceTree = "<group>"; };
+               4EB095312989CBFE0043A8A1 /* TransactionDetailView.swift */ = 
{isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = 
sourcecode.swift; path = TransactionDetailView.swift; sourceTree = "<group>"; };
                4EB095322989CBFE0043A8A1 /* TransactionsModel.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= TransactionsModel.swift; sourceTree = "<group>"; };
                4EB095332989CBFE0043A8A1 /* URLSheet.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= URLSheet.swift; sourceTree = "<group>"; };
                4EB095352989CBFE0043A8A1 /* BalancesModel.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= BalancesModel.swift; sourceTree = "<group>"; };
-               4EB095362989CBFE0043A8A1 /* BalanceRow.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= BalanceRow.swift; sourceTree = "<group>"; };
-               4EB095372989CBFE0043A8A1 /* CurrenciesListView.swift */ = {isa 
= PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; 
path = CurrenciesListView.swift; sourceTree = "<group>"; };
-               4EB095382989CBFE0043A8A1 /* PendingRow.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= PendingRow.swift; sourceTree = "<group>"; };
+               4EB095362989CBFE0043A8A1 /* BalanceRowView.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= BalanceRowView.swift; sourceTree = "<group>"; };
+               4EB095372989CBFE0043A8A1 /* BalancesListView.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= BalancesListView.swift; sourceTree = "<group>"; };
+               4EB095382989CBFE0043A8A1 /* PendingRowView.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= PendingRowView.swift; sourceTree = "<group>"; };
                4EB095392989CBFE0043A8A1 /* WalletEmptyView.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= WalletEmptyView.swift; sourceTree = "<group>"; };
-               4EB0953A2989CBFE0043A8A1 /* CurrencyView.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= CurrencyView.swift; sourceTree = "<group>"; };
+               4EB0953A2989CBFE0043A8A1 /* BalancesSectionView.swift */ = {isa 
= PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; 
path = BalancesSectionView.swift; sourceTree = "<group>"; };
                4EB0953C2989CBFE0043A8A1 /* WithdrawURIView.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= WithdrawURIView.swift; sourceTree = "<group>"; };
-               4EB0953D2989CBFE0043A8A1 /* WithdrawURIModel.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= WithdrawURIModel.swift; sourceTree = "<group>"; };
+               4EB0953D2989CBFE0043A8A1 /* WithdrawModel.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= WithdrawModel.swift; sourceTree = "<group>"; };
                4EB0953E2989CBFE0043A8A1 /* WithdrawAcceptView.swift */ = {isa 
= PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; 
path = WithdrawAcceptView.swift; sourceTree = "<group>"; };
                4EB0953F2989CBFE0043A8A1 /* WithdrawProgressView.swift */ = 
{isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = 
sourcecode.swift; path = WithdrawProgressView.swift; sourceTree = "<group>"; };
                4EB095402989CBFE0043A8A1 /* WithdrawTOSView.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= WithdrawTOSView.swift; sourceTree = "<group>"; };
                4EB095422989CBFE0043A8A1 /* SideBarView.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= SideBarView.swift; sourceTree = "<group>"; };
                4EB095432989CBFE0043A8A1 /* LaunchAnimationView.swift */ = {isa 
= PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; 
path = LaunchAnimationView.swift; sourceTree = "<group>"; };
-               4EB095442989CBFE0043A8A1 /* ContentView.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= ContentView.swift; sourceTree = "<group>"; };
+               4EB095442989CBFE0043A8A1 /* MainView.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= MainView.swift; sourceTree = "<group>"; };
                4EB095452989CBFE0043A8A1 /* ErrorView.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= ErrorView.swift; sourceTree = "<group>"; };
                4EB095472989CBFE0043A8A1 /* Buttons.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= Buttons.swift; sourceTree = "<group>"; };
                4EB095482989CBFE0043A8A1 /* TextFieldAlert.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= TextFieldAlert.swift; sourceTree = "<group>"; };
@@ -156,8 +210,18 @@
                4EB0954C2989CBFE0043A8A1 /* PendingModel.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= PendingModel.swift; sourceTree = "<group>"; };
                4EB0954D2989CBFE0043A8A1 /* PendingOpView.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= PendingOpView.swift; sourceTree = "<group>"; };
                4EB0954E2989CBFE0043A8A1 /* PendingOpsListView.swift */ = {isa 
= PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; 
path = PendingOpsListView.swift; sourceTree = "<group>"; };
+               4EB3136029FEE79B007D68BC /* SendNow.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= SendNow.swift; sourceTree = "<group>"; };
+               4EB431662A1E55C700C5690E /* ManualWithdrawDone.swift */ = {isa 
= PBXFileReference; lastKnownFileType = sourcecode.swift; path = 
ManualWithdrawDone.swift; sourceTree = "<group>"; };
+               4EC90C772A1B528B0071DC58 /* ExchangeSectionView.swift */ = {isa 
= PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; 
path = ExchangeSectionView.swift; sourceTree = "<group>"; };
+               4ECB627F2A0BA6DF004ABBB7 /* Peer2peerModel.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= Peer2peerModel.swift; sourceTree = "<group>"; };
+               4ECB62812A0BB01D004ABBB7 /* SelectDays.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= SelectDays.swift; sourceTree = "<group>"; };
+               4ED2F94A2A278F5100453B40 /* ThreeAmounts.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= ThreeAmounts.swift; sourceTree = "<group>"; };
+               4EEC157229F8242800D46A03 /* QRGeneratorView.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= QRGeneratorView.swift; sourceTree = "<group>"; };
+               4EEC157729F9032900D46A03 /* Sheet.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= Sheet.swift; sourceTree = "<group>"; };
+               4EEC157929F9427F00D46A03 /* QRSheet.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= QRSheet.swift; sourceTree = "<group>"; };
+               4EF840A62A0B85F400EE0D47 /* CopyShare.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= CopyShare.swift; sourceTree = "<group>"; };
                AB710490285995B6008B04F0 /* taler-swift */ = {isa = 
PBXFileReference; lastKnownFileType = text; path = "taler-swift"; sourceTree = 
SOURCE_ROOT; };
-               D14AFD1D24D232B300C51073 /* TalerWalletT.app */ = {isa = 
PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; 
path = TalerWalletT.app; sourceTree = BUILT_PRODUCTS_DIR; };
+               D14AFD1D24D232B300C51073 /* GNU Taler.app */ = {isa = 
PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; 
path = "GNU Taler.app"; sourceTree = BUILT_PRODUCTS_DIR; };
                D14AFD3324D232B500C51073 /* TalerTests.xctest */ = {isa = 
PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path 
= TalerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
                D14AFD3E24D232B500C51073 /* TalerUITests.xctest */ = {isa = 
PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path 
= TalerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
 /* End PBXFileReference section */
@@ -171,6 +235,7 @@
                                4EB094FD29897D280043A8A1 /* SymLog in 
Frameworks */,
                                4EB094F829897CA20043A8A1 /* 
FTalerWalletcore.framework in Frameworks */,
                                ABC13AA32859962800D23185 /* taler-swift in 
Frameworks */,
+                               4EEC157629F8ECBF00D46A03 /* CodeScanner in 
Frameworks */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
@@ -198,9 +263,10 @@
                                4EB095232989CBFE0043A8A1 /* Views */,
                                4EB0950F2989CBB00043A8A1 /* Model */,
                                4EB0951A2989CBCB0043A8A1 /* Backend */,
-                               4EB0950C2989CB9A0043A8A1 /* Quickjs */,
                                4EB095052989CB7C0043A8A1 /* Helper */,
+                               4EB0950C2989CB9A0043A8A1 /* Quickjs */,
                                4EB094EF298979D30043A8A1 /* Assets.xcassets */,
+                               4E363CBF2A24754200D7E98C /* Settings.bundle */,
                                4EB094F529897A9A0043A8A1 /* Preview Content */,
                        );
                        path = TalerWallet1;
@@ -210,6 +276,7 @@
                        isa = PBXGroup;
                        children = (
                                4EB094F329897A510043A8A1 /* Preview 
Assets.xcassets */,
+                               4E753A042A08E720002D9328 /* transactions.json 
*/,
                        );
                        path = "Preview Content";
                        sourceTree = "<group>";
@@ -227,6 +294,8 @@
                        children = (
                                4EB094EC298979620043A8A1 /* 
TalerWallet1App.swift */,
                                4EB095012989C9BC0043A8A1 /* Controller.swift */,
+                               4EA1ABBD29A3833A008821EA /* 
PublicConstants.swift */,
+                               4E753A052A0952F7002D9328 /* DebugViewC.swift */,
                        );
                        path = Controllers;
                        sourceTree = "<group>";
@@ -234,10 +303,16 @@
                4EB095052989CB7C0043A8A1 /* Helper */ = {
                        isa = PBXGroup;
                        children = (
+                               4E363CBD2A23CB2100D7E98C /* 
AnyTransition+backslide.swift */,
+                               4E16E12229F3BB99008B9C86 /* 
CurrencyFormatter.swift */,
+                               4EAD117529F672FA008EDD0B /* 
KeyboardResponder.swift */,
+                               4E363CC12A2621C200D7E98C /* 
LocalizedAlertError.swift */,
                                4EB095062989CB7C0043A8A1 /* TalerDater.swift */,
                                4EB095072989CB7C0043A8A1 /* TalerStrings.swift 
*/,
                                4EB095082989CB7C0043A8A1 /* 
View+dismissTop.swift */,
-                               4EA1ABBD29A3833A008821EA /* 
PublicConstants.swift */,
+                               4E363CBB2A237E0900D7E98C /* URL+iban.swift */,
+                               4E9320422A14F6EA00A87B0E /* WalletColors.swift 
*/,
+                               4E8E25322A1CD39700A27BFA /* 
EqualIconWidthDomain.swift */,
                        );
                        path = Helper;
                        sourceTree = "<group>";
@@ -253,9 +328,16 @@
                4EB0950F2989CBB00043A8A1 /* Model */ = {
                        isa = PBXGroup;
                        children = (
-                               4EB095102989CBB00043A8A1 /* 
ExchangeTestModel.swift */,
                                4EB095112989CBB00043A8A1 /* WalletModel.swift 
*/,
                                4EB095142989CBB00043A8A1 /* 
WalletInitModel.swift */,
+                               4EB095352989CBFE0043A8A1 /* BalancesModel.swift 
*/,
+                               4EB095282989CBFE0043A8A1 /* ExchangeModel.swift 
*/,
+                               4ECB627F2A0BA6DF004ABBB7 /* 
Peer2peerModel.swift */,
+                               4EB0954C2989CBFE0043A8A1 /* PendingModel.swift 
*/,
+                               4EB0952C2989CBFE0043A8A1 /* 
PaymentURIModel.swift */,
+                               4EB095102989CBB00043A8A1 /* SettingsModel.swift 
*/,
+                               4EB095322989CBFE0043A8A1 /* 
TransactionsModel.swift */,
+                               4EB0953D2989CBFE0043A8A1 /* WithdrawModel.swift 
*/,
                        );
                        path = Model;
                        sourceTree = "<group>";
@@ -275,15 +357,15 @@
                        isa = PBXGroup;
                        children = (
                                4EB095412989CBFE0043A8A1 /* Main */,
-                               4EB095242989CBFE0043A8A1 /* Settings */,
+                               4EB095342989CBFE0043A8A1 /* Balances */,
+                               4EB0952E2989CBFE0043A8A1 /* Transactions */,
                                4EB095272989CBFE0043A8A1 /* Exchange */,
+                               4EB095242989CBFE0043A8A1 /* Settings */,
+                               4ECB627E2A0BA4DA004ABBB7 /* Peer2peer */,
+                               4EEC157129F7188B00D46A03 /* Sheets */,
+                               4EB0953B2989CBFE0043A8A1 /* 
WithdrawBankIntegrated */,
                                4EB0952A2989CBFE0043A8A1 /* Payment */,
-                               4EB0952E2989CBFE0043A8A1 /* Transactions */,
-                               4EB095332989CBFE0043A8A1 /* URLSheet.swift */,
-                               4EB095342989CBFE0043A8A1 /* Balances */,
-                               4EB0953B2989CBFE0043A8A1 /* Withdraw */,
                                4EB095462989CBFE0043A8A1 /* HelperViews */,
-                               4EB0954B2989CBFE0043A8A1 /* Pending */,
                        );
                        path = Views;
                        sourceTree = "<group>";
@@ -293,6 +375,7 @@
                        children = (
                                4EB095252989CBFE0043A8A1 /* SettingsView.swift 
*/,
                                4EB095262989CBFE0043A8A1 /* SettingsItem.swift 
*/,
+                               4EB0954B2989CBFE0043A8A1 /* Pending */,
                        );
                        path = Settings;
                        sourceTree = "<group>";
@@ -300,8 +383,10 @@
                4EB095272989CBFE0043A8A1 /* Exchange */ = {
                        isa = PBXGroup;
                        children = (
-                               4EB095282989CBFE0043A8A1 /* ExchangeModel.swift 
*/,
                                4EB095292989CBFE0043A8A1 /* 
ExchangeListView.swift */,
+                               4EC90C772A1B528B0071DC58 /* 
ExchangeSectionView.swift */,
+                               4E50B34F2A1BEE8000F9F01C /* 
ManualWithdraw.swift */,
+                               4EB431662A1E55C700C5690E /* 
ManualWithdrawDone.swift */,
                        );
                        path = Exchange;
                        sourceTree = "<group>";
@@ -309,9 +394,8 @@
                4EB0952A2989CBFE0043A8A1 /* Payment */ = {
                        isa = PBXGroup;
                        children = (
-                               4EB0952B2989CBFE0043A8A1 /* 
PaymentAcceptView.swift */,
-                               4EB0952C2989CBFE0043A8A1 /* 
PaymentURIModel.swift */,
                                4EB0952D2989CBFE0043A8A1 /* 
PaymentURIView.swift */,
+                               4EB0952B2989CBFE0043A8A1 /* 
PaymentAcceptView.swift */,
                        );
                        path = Payment;
                        sourceTree = "<group>";
@@ -320,9 +404,11 @@
                        isa = PBXGroup;
                        children = (
                                4EB0952F2989CBFE0043A8A1 /* 
TransactionsListView.swift */,
-                               4EB095302989CBFE0043A8A1 /* 
TransactionRow.swift */,
-                               4EB095312989CBFE0043A8A1 /* 
TransactionDetail.swift */,
-                               4EB095322989CBFE0043A8A1 /* 
TransactionsModel.swift */,
+                               4EB095302989CBFE0043A8A1 /* 
TransactionRowView.swift */,
+                               4EB095312989CBFE0043A8A1 /* 
TransactionDetailView.swift */,
+                               4E87C8722A31CB7F001C6406 /* 
TransactionsEmptyView.swift */,
+                               4E6EDD842A3615BE0031D520 /* ManualDetails.swift 
*/,
+                               4ED2F94A2A278F5100453B40 /* ThreeAmounts.swift 
*/,
                        );
                        path = Transactions;
                        sourceTree = "<group>";
@@ -330,35 +416,33 @@
                4EB095342989CBFE0043A8A1 /* Balances */ = {
                        isa = PBXGroup;
                        children = (
-                               4EB095352989CBFE0043A8A1 /* BalancesModel.swift 
*/,
-                               4EB095362989CBFE0043A8A1 /* BalanceRow.swift */,
-                               4EB095372989CBFE0043A8A1 /* 
CurrenciesListView.swift */,
-                               4EB095382989CBFE0043A8A1 /* PendingRow.swift */,
-                               4EB095392989CBFE0043A8A1 /* 
WalletEmptyView.swift */,
-                               4EB0953A2989CBFE0043A8A1 /* CurrencyView.swift 
*/,
+                               4EB095372989CBFE0043A8A1 /* 
BalancesListView.swift */,
+                               4EB0953A2989CBFE0043A8A1 /* 
BalancesSectionView.swift */,
+                               4EB095362989CBFE0043A8A1 /* 
BalanceRowView.swift */,
+                               4EB095382989CBFE0043A8A1 /* 
PendingRowView.swift */,
+                               4E87C8742A34B411001C6406 /* 
UncompletedRowView.swift */,
                        );
                        path = Balances;
                        sourceTree = "<group>";
                };
-               4EB0953B2989CBFE0043A8A1 /* Withdraw */ = {
+               4EB0953B2989CBFE0043A8A1 /* WithdrawBankIntegrated */ = {
                        isa = PBXGroup;
                        children = (
                                4EB0953C2989CBFE0043A8A1 /* 
WithdrawURIView.swift */,
-                               4EB0953D2989CBFE0043A8A1 /* 
WithdrawURIModel.swift */,
                                4EB0953E2989CBFE0043A8A1 /* 
WithdrawAcceptView.swift */,
                                4EB0953F2989CBFE0043A8A1 /* 
WithdrawProgressView.swift */,
                                4EB095402989CBFE0043A8A1 /* 
WithdrawTOSView.swift */,
                        );
-                       path = Withdraw;
+                       path = WithdrawBankIntegrated;
                        sourceTree = "<group>";
                };
                4EB095412989CBFE0043A8A1 /* Main */ = {
                        isa = PBXGroup;
                        children = (
-                               4EB095442989CBFE0043A8A1 /* ContentView.swift 
*/,
+                               4EB095442989CBFE0043A8A1 /* MainView.swift */,
                                4EB095422989CBFE0043A8A1 /* SideBarView.swift 
*/,
-                               4EB095432989CBFE0043A8A1 /* 
LaunchAnimationView.swift */,
                                4EB095452989CBFE0043A8A1 /* ErrorView.swift */,
+                               4EB095392989CBFE0043A8A1 /* 
WalletEmptyView.swift */,
                        );
                        path = Main;
                        sourceTree = "<group>";
@@ -367,9 +451,16 @@
                        isa = PBXGroup;
                        children = (
                                4EB095472989CBFE0043A8A1 /* Buttons.swift */,
+                               4EF840A62A0B85F400EE0D47 /* CopyShare.swift */,
+                               4ECB62812A0BB01D004ABBB7 /* SelectDays.swift */,
+                               4E53A33629F50B7B00830EC2 /* CurrencyField.swift 
*/,
+                               4EA551242A2C923600FEC9A8 /* 
CurrencyInputView.swift */,
+                               4EEC157229F8242800D46A03 /* 
QRGeneratorView.swift */,
+                               4E6EDD862A363D8D0031D520 /* ListStyle.swift */,
                                4EB095482989CBFE0043A8A1 /* 
TextFieldAlert.swift */,
                                4EB095492989CBFE0043A8A1 /* AmountView.swift */,
                                4EB0954A2989CBFE0043A8A1 /* LoadingView.swift 
*/,
+                               4EB095432989CBFE0043A8A1 /* 
LaunchAnimationView.swift */,
                        );
                        path = HelperViews;
                        sourceTree = "<group>";
@@ -377,19 +468,41 @@
                4EB0954B2989CBFE0043A8A1 /* Pending */ = {
                        isa = PBXGroup;
                        children = (
-                               4EB0954C2989CBFE0043A8A1 /* PendingModel.swift 
*/,
-                               4EB0954D2989CBFE0043A8A1 /* PendingOpView.swift 
*/,
                                4EB0954E2989CBFE0043A8A1 /* 
PendingOpsListView.swift */,
+                               4EB0954D2989CBFE0043A8A1 /* PendingOpView.swift 
*/,
                        );
                        path = Pending;
                        sourceTree = "<group>";
                };
+               4ECB627E2A0BA4DA004ABBB7 /* Peer2peer */ = {
+                       isa = PBXGroup;
+                       children = (
+                               4E40E0BD29F25ABB00B85369 /* SendAmount.swift */,
+                               4E7940DD29FC307C00A9AEA1 /* SendPurpose.swift 
*/,
+                               4EB3136029FEE79B007D68BC /* SendNow.swift */,
+                               4E9320442A1645B600A87B0E /* 
RequestPayment.swift */,
+                               4E9320462A164BC700A87B0E /* 
ReceivePurpose.swift */,
+                       );
+                       path = Peer2peer;
+                       sourceTree = "<group>";
+               };
+               4EEC157129F7188B00D46A03 /* Sheets */ = {
+                       isa = PBXGroup;
+                       children = (
+                               4EEC157729F9032900D46A03 /* Sheet.swift */,
+                               4EEC157929F9427F00D46A03 /* QRSheet.swift */,
+                               4E753A072A0B6A5F002D9328 /* ShareSheet.swift */,
+                               4EB095332989CBFE0043A8A1 /* URLSheet.swift */,
+                       );
+                       path = Sheets;
+                       sourceTree = "<group>";
+               };
                D14AFD1424D232B300C51073 = {
                        isa = PBXGroup;
                        children = (
-                               4E3AE7EF29A7E8F40070BEC4 /* 
TalerWalletT.entitlements */,
                                4EB094EE298979840043A8A1 /* TalerWallet1 */,
                                4EB094E129896FED0043A8A1 /* Info.plist */,
+                               4E3AE7EF29A7E8F40070BEC4 /* Taler 
Wallet.entitlements */,
                                AB710490285995B6008B04F0 /* taler-swift */,
                                D14AFD3624D232B500C51073 /* TalerTests */,
                                D14AFD4124D232B500C51073 /* TalerUITests */,
@@ -401,7 +514,7 @@
                D14AFD1E24D232B300C51073 /* Products */ = {
                        isa = PBXGroup;
                        children = (
-                               D14AFD1D24D232B300C51073 /* TalerWalletT.app */,
+                               D14AFD1D24D232B300C51073 /* GNU Taler.app */,
                                D14AFD3324D232B500C51073 /* TalerTests.xctest 
*/,
                                D14AFD3E24D232B500C51073 /* TalerUITests.xctest 
*/,
                        );
@@ -430,9 +543,9 @@
 /* End PBXGroup section */
 
 /* Begin PBXNativeTarget section */
-               D14AFD1C24D232B300C51073 /* TalerWalletT */ = {
+               D14AFD1C24D232B300C51073 /* Taler_Wallet */ = {
                        isa = PBXNativeTarget;
-                       buildConfigurationList = D14AFD4724D232B500C51073 /* 
Build configuration list for PBXNativeTarget "TalerWalletT" */;
+                       buildConfigurationList = D14AFD4724D232B500C51073 /* 
Build configuration list for PBXNativeTarget "Taler_Wallet" */;
                        buildPhases = (
                                D14AFD1924D232B300C51073 /* Sources */,
                                D14AFD1A24D232B300C51073 /* Frameworks */,
@@ -443,14 +556,15 @@
                        );
                        dependencies = (
                        );
-                       name = TalerWalletT;
+                       name = Taler_Wallet;
                        packageProductDependencies = (
                                ABC13AA22859962800D23185 /* taler-swift */,
                                ABE97B1C286D82BF00580772 /* AnyCodable */,
                                4EB094FC29897D280043A8A1 /* SymLog */,
+                               4EEC157529F8ECBF00D46A03 /* CodeScanner */,
                        );
                        productName = Taler;
-                       productReference = D14AFD1D24D232B300C51073 /* 
TalerWalletT.app */;
+                       productReference = D14AFD1D24D232B300C51073 /* GNU 
Taler.app */;
                        productType = "com.apple.product-type.application";
                };
                D14AFD3224D232B500C51073 /* TalerTests */ = {
@@ -527,12 +641,13 @@
                        packageReferences = (
                                ABE97B1B286D82BF00580772 /* 
XCRemoteSwiftPackageReference "AnyCodable" */,
                                4EB094FB29897D280043A8A1 /* 
XCRemoteSwiftPackageReference "SymLog" */,
+                               4EEC157429F8ECBF00D46A03 /* 
XCRemoteSwiftPackageReference "CodeScanner" */,
                        );
                        productRefGroup = D14AFD1E24D232B300C51073 /* Products 
*/;
                        projectDirPath = "";
                        projectRoot = "";
                        targets = (
-                               D14AFD1C24D232B300C51073 /* TalerWalletT */,
+                               D14AFD1C24D232B300C51073 /* Taler_Wallet */,
                                D14AFD3224D232B500C51073 /* TalerTests */,
                                D14AFD3D24D232B500C51073 /* TalerUITests */,
                        );
@@ -545,6 +660,7 @@
                        buildActionMask = 2147483647;
                        files = (
                                4EB094F429897A510043A8A1 /* Preview 
Assets.xcassets in Resources */,
+                               4E363CC02A24754200D7E98C /* Settings.bundle in 
Resources */,
                                4EB094F0298979D30043A8A1 /* Assets.xcassets in 
Resources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
@@ -570,43 +686,65 @@
                        isa = PBXSourcesBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
+                               4ECB62822A0BB01D004ABBB7 /* SelectDays.swift in 
Sources */,
                                4EB095512989CBFE0043A8A1 /* ExchangeModel.swift 
in Sources */,
                                4EB095032989C9BC0043A8A1 /* Controller.swift in 
Sources */,
-                               4EB095682989CBFE0043A8A1 /* ContentView.swift 
in Sources */,
+                               4EB095682989CBFE0043A8A1 /* MainView.swift in 
Sources */,
                                4EB0956A2989CBFE0043A8A1 /* Buttons.swift in 
Sources */,
-                               4EB095602989CBFE0043A8A1 /* CurrencyView.swift 
in Sources */,
+                               4EB095602989CBFE0043A8A1 /* 
BalancesSectionView.swift in Sources */,
+                               4EEC157329F8242800D46A03 /* 
QRGeneratorView.swift in Sources */,
                                4EB095222989CBCB0043A8A1 /* Transaction.swift 
in Sources */,
-                               4EB0955D2989CBFE0043A8A1 /* 
CurrenciesListView.swift in Sources */,
+                               4E9320432A14F6EA00A87B0E /* WalletColors.swift 
in Sources */,
+                               4EB0955D2989CBFE0043A8A1 /* 
BalancesListView.swift in Sources */,
                                4EB095532989CBFE0043A8A1 /* 
PaymentAcceptView.swift in Sources */,
                                4EB095212989CBCB0043A8A1 /* 
WalletBackendError.swift in Sources */,
-                               4EB0955E2989CBFE0043A8A1 /* PendingRow.swift in 
Sources */,
+                               4EB0955E2989CBFE0043A8A1 /* 
PendingRowView.swift in Sources */,
                                4EB0955B2989CBFE0043A8A1 /* BalancesModel.swift 
in Sources */,
                                4EB095632989CBFE0043A8A1 /* 
WithdrawAcceptView.swift in Sources */,
                                4EB0956D2989CBFE0043A8A1 /* LoadingView.swift 
in Sources */,
+                               4E50B3502A1BEE8000F9F01C /* 
ManualWithdraw.swift in Sources */,
+                               4E87C8732A31CB7F001C6406 /* 
TransactionsEmptyView.swift in Sources */,
+                               4E87C8752A34B411001C6406 /* 
UncompletedRowView.swift in Sources */,
+                               4E40E0BE29F25ABB00B85369 /* SendAmount.swift in 
Sources */,
+                               4E8E25332A1CD39700A27BFA /* 
EqualIconWidthDomain.swift in Sources */,
                                4EB095542989CBFE0043A8A1 /* 
PaymentURIModel.swift in Sources */,
                                4EB0954F2989CBFE0043A8A1 /* SettingsView.swift 
in Sources */,
                                4EB095552989CBFE0043A8A1 /* 
PaymentURIView.swift in Sources */,
                                4EB095612989CBFE0043A8A1 /* 
WithdrawURIView.swift in Sources */,
+                               4EF840A72A0B85F400EE0D47 /* CopyShare.swift in 
Sources */,
                                4EB094ED298979620043A8A1 /* 
TalerWallet1App.swift in Sources */,
                                4EB095652989CBFE0043A8A1 /* 
WithdrawTOSView.swift in Sources */,
+                               4EEC157829F9032900D46A03 /* Sheet.swift in 
Sources */,
+                               4E6EDD852A3615BE0031D520 /* ManualDetails.swift 
in Sources */,
                                4EB0950B2989CB7C0043A8A1 /* 
View+dismissTop.swift in Sources */,
                                4EB095562989CBFE0043A8A1 /* 
TransactionsListView.swift in Sources */,
                                4EB0951F2989CBCB0043A8A1 /* 
WalletBackendRequest.swift in Sources */,
-                               4EB095572989CBFE0043A8A1 /* 
TransactionRow.swift in Sources */,
+                               4EAD117629F672FA008EDD0B /* 
KeyboardResponder.swift in Sources */,
+                               4EB095572989CBFE0043A8A1 /* 
TransactionRowView.swift in Sources */,
                                4EA1ABBE29A3833A008821EA /* 
PublicConstants.swift in Sources */,
+                               4EB3136129FEE79B007D68BC /* SendNow.swift in 
Sources */,
                                4EB0956B2989CBFE0043A8A1 /* 
TextFieldAlert.swift in Sources */,
+                               4EB431672A1E55C700C5690E /* 
ManualWithdrawDone.swift in Sources */,
+                               4E9320472A164BC700A87B0E /* 
ReceivePurpose.swift in Sources */,
+                               4E753A082A0B6A5F002D9328 /* ShareSheet.swift in 
Sources */,
                                4EB0956C2989CBFE0043A8A1 /* AmountView.swift in 
Sources */,
+                               4E363CBE2A23CB2100D7E98C /* 
AnyTransition+backslide.swift in Sources */,
                                4EB095592989CBFE0043A8A1 /* 
TransactionsModel.swift in Sources */,
                                4EB0955F2989CBFE0043A8A1 /* 
WalletEmptyView.swift in Sources */,
+                               4E16E12329F3BB99008B9C86 /* 
CurrencyFormatter.swift in Sources */,
                                4EB095192989CBB00043A8A1 /* 
WalletInitModel.swift in Sources */,
                                4EB095092989CB7C0043A8A1 /* TalerDater.swift in 
Sources */,
+                               4E363CC22A2621C200D7E98C /* 
LocalizedAlertError.swift in Sources */,
                                4EB0950E2989CB9A0043A8A1 /* quickjs.swift in 
Sources */,
-                               4EB095152989CBB00043A8A1 /* 
ExchangeTestModel.swift in Sources */,
+                               4E53A33729F50B7B00830EC2 /* CurrencyField.swift 
in Sources */,
+                               4EB095152989CBB00043A8A1 /* SettingsModel.swift 
in Sources */,
                                4EB095692989CBFE0043A8A1 /* ErrorView.swift in 
Sources */,
                                4EB0956E2989CBFE0043A8A1 /* PendingModel.swift 
in Sources */,
                                4EB095522989CBFE0043A8A1 /* 
ExchangeListView.swift in Sources */,
                                4EB095642989CBFE0043A8A1 /* 
WithdrawProgressView.swift in Sources */,
-                               4EB095582989CBFE0043A8A1 /* 
TransactionDetail.swift in Sources */,
+                               4EEC157A29F9427F00D46A03 /* QRSheet.swift in 
Sources */,
+                               4E6EDD872A363D8D0031D520 /* ListStyle.swift in 
Sources */,
+                               4EB095582989CBFE0043A8A1 /* 
TransactionDetailView.swift in Sources */,
                                4EB095202989CBCB0043A8A1 /* WalletCore.swift in 
Sources */,
                                4EB095672989CBFE0043A8A1 /* 
LaunchAnimationView.swift in Sources */,
                                4EB095662989CBFE0043A8A1 /* SideBarView.swift 
in Sources */,
@@ -614,10 +752,18 @@
                                4EB095702989CBFE0043A8A1 /* 
PendingOpsListView.swift in Sources */,
                                4EB095162989CBB00043A8A1 /* WalletModel.swift 
in Sources */,
                                4EB0955A2989CBFE0043A8A1 /* URLSheet.swift in 
Sources */,
-                               4EB095622989CBFE0043A8A1 /* 
WithdrawURIModel.swift in Sources */,
+                               4ED2F94B2A278F5100453B40 /* ThreeAmounts.swift 
in Sources */,
+                               4EB095622989CBFE0043A8A1 /* WithdrawModel.swift 
in Sources */,
+                               4EC90C782A1B528B0071DC58 /* 
ExchangeSectionView.swift in Sources */,
+                               4E7940DE29FC307C00A9AEA1 /* SendPurpose.swift 
in Sources */,
+                               4ECB62802A0BA6DF004ABBB7 /* 
Peer2peerModel.swift in Sources */,
                                4EB0950A2989CB7C0043A8A1 /* TalerStrings.swift 
in Sources */,
+                               4EA551252A2C923600FEC9A8 /* 
CurrencyInputView.swift in Sources */,
+                               4E363CBC2A237E0900D7E98C /* URL+iban.swift in 
Sources */,
+                               4E9320452A1645B600A87B0E /* 
RequestPayment.swift in Sources */,
                                4EB095502989CBFE0043A8A1 /* SettingsItem.swift 
in Sources */,
-                               4EB0955C2989CBFE0043A8A1 /* BalanceRow.swift in 
Sources */,
+                               4EB0955C2989CBFE0043A8A1 /* 
BalanceRowView.swift in Sources */,
+                               4E753A062A0952F8002D9328 /* DebugViewC.swift in 
Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
@@ -645,12 +791,12 @@
 /* Begin PBXTargetDependency section */
                D14AFD3524D232B500C51073 /* PBXTargetDependency */ = {
                        isa = PBXTargetDependency;
-                       target = D14AFD1C24D232B300C51073 /* TalerWalletT */;
+                       target = D14AFD1C24D232B300C51073 /* Taler_Wallet */;
                        targetProxy = D14AFD3424D232B500C51073 /* 
PBXContainerItemProxy */;
                };
                D14AFD4024D232B500C51073 /* PBXTargetDependency */ = {
                        isa = PBXTargetDependency;
-                       target = D14AFD1C24D232B300C51073 /* TalerWalletT */;
+                       target = D14AFD1C24D232B300C51073 /* Taler_Wallet */;
                        targetProxy = D14AFD3F24D232B500C51073 /* 
PBXContainerItemProxy */;
                };
 /* End PBXTargetDependency section */
@@ -776,16 +922,20 @@
                        buildSettings = {
                                ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
                                CLANG_ENABLE_MODULES = YES;
-                               CODE_SIGN_ENTITLEMENTS = 
TalerWalletT.entitlements;
-                               CODE_SIGN_STYLE = Automatic;
-                               CURRENT_PROJECT_VERSION = 2;
-                               DEVELOPMENT_TEAM = GUDDQ9428Y;
+                               CODE_SIGN_ENTITLEMENTS = "GNU 
Taler.entitlements";
+                               "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone 
Distribution";
+                               CODE_SIGN_STYLE = Manual;
+                               CURRENT_PROJECT_VERSION = 6;
+                               DEVELOPMENT_TEAM = "";
+                               "DEVELOPMENT_TEAM[sdk=iphoneos*]" = GUDDQ9428Y;
                                ENABLE_PREVIEWS = YES;
                                GENERATE_INFOPLIST_FILE = YES;
                                INFOPLIST_FILE = Info.plist;
-                               INFOPLIST_KEY_CFBundleDisplayName = "Taler 
Wallet";
+                               INFOPLIST_KEY_CFBundleDisplayName = "GNU Taler";
                                INFOPLIST_KEY_LSApplicationCategoryType = 
"public.app-category.finance";
+                               INFOPLIST_KEY_NSCameraUsageDescription = "Scan 
QR Codes";
                                INFOPLIST_KEY_NSHumanReadableCopyright = "© 
Taler-Systems.com";
+                               
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
                                INFOPLIST_KEY_UILaunchScreen_Generation = YES;
                                INFOPLIST_KEY_UISupportedInterfaceOrientations 
= UIInterfaceOrientationPortrait;
                                
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = 
"UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
@@ -795,8 +945,10 @@
                                        "@executable_path/Frameworks",
                                );
                                MARKETING_VERSION = 0.9.3;
-                               PRODUCT_BUNDLE_IDENTIFIER = 
"com.taler-systems.talerwallet15";
-                               PRODUCT_NAME = "$(TARGET_NAME)";
+                               PRODUCT_BUNDLE_IDENTIFIER = 
"com.taler-systems.talerwallet-1";
+                               PRODUCT_NAME = "GNU Taler";
+                               PROVISIONING_PROFILE_SPECIFIER = "";
+                               "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" 
= iOS_Distribution_230606;
                                SUPPORTED_PLATFORMS = "iphoneos 
iphonesimulator";
                                SUPPORTS_MACCATALYST = NO;
                                SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
@@ -812,16 +964,20 @@
                                ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
                                ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME 
= AccentColor;
                                CLANG_ENABLE_MODULES = YES;
-                               CODE_SIGN_ENTITLEMENTS = 
TalerWalletT.entitlements;
-                               CODE_SIGN_STYLE = Automatic;
-                               CURRENT_PROJECT_VERSION = 2;
-                               DEVELOPMENT_TEAM = GUDDQ9428Y;
+                               CODE_SIGN_ENTITLEMENTS = "GNU 
Taler.entitlements";
+                               "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone 
Distribution";
+                               CODE_SIGN_STYLE = Manual;
+                               CURRENT_PROJECT_VERSION = 6;
+                               DEVELOPMENT_TEAM = "";
+                               "DEVELOPMENT_TEAM[sdk=iphoneos*]" = GUDDQ9428Y;
                                ENABLE_PREVIEWS = YES;
                                GENERATE_INFOPLIST_FILE = YES;
                                INFOPLIST_FILE = Info.plist;
-                               INFOPLIST_KEY_CFBundleDisplayName = "Taler 
Wallet";
+                               INFOPLIST_KEY_CFBundleDisplayName = "GNU Taler";
                                INFOPLIST_KEY_LSApplicationCategoryType = 
"public.app-category.finance";
+                               INFOPLIST_KEY_NSCameraUsageDescription = "Scan 
QR Codes";
                                INFOPLIST_KEY_NSHumanReadableCopyright = "© 
Taler-Systems.com";
+                               
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
                                INFOPLIST_KEY_UILaunchScreen_Generation = YES;
                                INFOPLIST_KEY_UISupportedInterfaceOrientations 
= UIInterfaceOrientationPortrait;
                                
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = 
"UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
@@ -831,8 +987,10 @@
                                        "@executable_path/Frameworks",
                                );
                                MARKETING_VERSION = 0.9.3;
-                               PRODUCT_BUNDLE_IDENTIFIER = 
"com.taler-systems.talerwallet15";
-                               PRODUCT_NAME = "$(TARGET_NAME)";
+                               PRODUCT_BUNDLE_IDENTIFIER = 
"com.taler-systems.talerwallet-1";
+                               PRODUCT_NAME = "GNU Taler";
+                               PROVISIONING_PROFILE_SPECIFIER = "";
+                               "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" 
= iOS_Distribution_230606;
                                SUPPORTED_PLATFORMS = "iphoneos 
iphonesimulator";
                                SUPPORTS_MACCATALYST = NO;
                                SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
@@ -880,7 +1038,7 @@
                                        "@executable_path/Frameworks",
                                        "@loader_path/Frameworks",
                                );
-                               MARKETING_VERSION = 1.0;
+                               MARKETING_VERSION = 0.9.3;
                                PRODUCT_BUNDLE_IDENTIFIER = 
com.taler.TalerTests;
                                PRODUCT_NAME = "$(TARGET_NAME)";
                                SWIFT_EMIT_LOC_STRINGS = NO;
@@ -902,7 +1060,7 @@
                                        "@executable_path/Frameworks",
                                        "@loader_path/Frameworks",
                                );
-                               MARKETING_VERSION = 1.0;
+                               MARKETING_VERSION = 0.9.3;
                                PRODUCT_BUNDLE_IDENTIFIER = 
com.taler.TalerUITests;
                                PRODUCT_NAME = "$(TARGET_NAME)";
                                SWIFT_EMIT_LOC_STRINGS = NO;
@@ -925,7 +1083,7 @@
                                        "@executable_path/Frameworks",
                                        "@loader_path/Frameworks",
                                );
-                               MARKETING_VERSION = 1.0;
+                               MARKETING_VERSION = 0.9.3;
                                PRODUCT_BUNDLE_IDENTIFIER = 
com.taler.TalerUITests;
                                PRODUCT_NAME = "$(TARGET_NAME)";
                                SWIFT_EMIT_LOC_STRINGS = NO;
@@ -947,7 +1105,7 @@
                        defaultConfigurationIsVisible = 0;
                        defaultConfigurationName = Release;
                };
-               D14AFD4724D232B500C51073 /* Build configuration list for 
PBXNativeTarget "TalerWalletT" */ = {
+               D14AFD4724D232B500C51073 /* Build configuration list for 
PBXNativeTarget "Taler_Wallet" */ = {
                        isa = XCConfigurationList;
                        buildConfigurations = (
                                D14AFD4824D232B500C51073 /* Debug */,
@@ -985,6 +1143,14 @@
                                minimumVersion = 0.1.0;
                        };
                };
+               4EEC157429F8ECBF00D46A03 /* XCRemoteSwiftPackageReference 
"CodeScanner" */ = {
+                       isa = XCRemoteSwiftPackageReference;
+                       repositoryURL = 
"https://github.com/twostraws/CodeScanner";;
+                       requirement = {
+                               kind = upToNextMajorVersion;
+                               minimumVersion = 2.0.0;
+                       };
+               };
                ABE97B1B286D82BF00580772 /* XCRemoteSwiftPackageReference 
"AnyCodable" */ = {
                        isa = XCRemoteSwiftPackageReference;
                        repositoryURL = 
"https://github.com/Flight-School/AnyCodable";;
@@ -1001,6 +1167,11 @@
                        package = 4EB094FB29897D280043A8A1 /* 
XCRemoteSwiftPackageReference "SymLog" */;
                        productName = SymLog;
                };
+               4EEC157529F8ECBF00D46A03 /* CodeScanner */ = {
+                       isa = XCSwiftPackageProductDependency;
+                       package = 4EEC157429F8ECBF00D46A03 /* 
XCRemoteSwiftPackageReference "CodeScanner" */;
+                       productName = CodeScanner;
+               };
                ABC13AA22859962800D23185 /* taler-swift */ = {
                        isa = XCSwiftPackageProductDependency;
                        productName = "taler-swift";
diff --git 
a/TalerWallet1/Assets.xcassets/taler-logo-2023-red.imageset/Contents.json 
b/TalerWallet1/Assets.xcassets/taler-logo-2023-red.imageset/Contents.json
new file mode 100644
index 0000000..50ee748
--- /dev/null
+++ b/TalerWallet1/Assets.xcassets/taler-logo-2023-red.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+  "images" : [
+    {
+      "filename" : "taler-logo-2023-red.svg",
+      "idiom" : "universal"
+    },
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}
diff --git 
a/TalerWallet1/Assets.xcassets/taler-logo-2023-red.imageset/taler-logo-2023-red.svg
 
b/TalerWallet1/Assets.xcassets/taler-logo-2023-red.imageset/taler-logo-2023-red.svg
new file mode 100644
index 0000000..3820d36
--- /dev/null
+++ 
b/TalerWallet1/Assets.xcassets/taler-logo-2023-red.imageset/taler-logo-2023-red.svg
@@ -0,0 +1,19 @@
+<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg";>
+<g id="aa" style="fill:red;fill-rule:evenodd">
+<!-- 90% -->
+<path d="M57.6 43.4
+c-25.5 4.3-44.9 28-44.9 56.5 0 31.5 23.9 57.2 53.3 57.2
+s53.3-25.6 53.3-57.2
+c0-15.4-5.7-29.3-14.9-39.6
+c1.6-1.9 6.3-4.8 6.4-4.6 10 11.6 16.1 27.2 16.1 44.2 0 36-27.3 65.3-60.9 
65.3-33.6 0-60.9-29.3-60.9-65.3
+s27.3-65.3 60.9-65.3
+c1.7 0 5.7.3 5.5.4-4.3 2.3-9.7 5.4-13.9 8.5"/>
+<!-- 40% -->
+<path d="M60.8 149.8
+c-13.4-12-22-29.9-22-50 0-36 27.4-65.2 61.1-65.2 1.5 0 3 .1 4.5.2
+a67.6 67.6 0 0 0-13.4 8.6
+c-25.4 4.5-44.7 28.1-44.7 56.4 0 21.3 11 40 27.3 49.8
+a45.9 45.9 0 0 1-12.7.3z"/>
+</g>
+<use transform="translate(200,200) rotate(180)" href="#aa"/>
+</svg>
\ No newline at end of file
diff --git a/TalerWallet1/Backend/Transaction.swift 
b/TalerWallet1/Backend/Transaction.swift
index 873733e..b49d560 100644
--- a/TalerWallet1/Backend/Transaction.swift
+++ b/TalerWallet1/Backend/Transaction.swift
@@ -1,17 +1,6 @@
 /*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
  */
 import Foundation
 import AnyCodable
@@ -26,22 +15,124 @@ enum TransactionDecodingError: Error {
     case invalidStringValue
 }
 
+enum TransactionMinorState: String, Codable {
+      // Placeholder until D37 is fully implemented
+    case unknown
+    case deposit
+    case kyc        // KycRequired
+    case aml        // AmlRequired
+    case mergeKycRequired = "merge-kyc"
+    case track
+    case pay
+    case rebindSession = "rebind-session"
+    case refresh
+    case pickup
+    case autoRefund = "auto-refund"
+    case user
+    case bank
+    case exchange
+    case claimProposal = "claim-proposal"
+    case checkRefund = "check-refund"
+    case createPurse = "create-purse"
+    case deletePurse = "delete-purse"
+    case ready
+    case merge
+    case repurchase
+    case bankRegisterReserve = "bank-register-reserve"
+    case bankConfirmTransfer = "bank-confirm-transfer"
+    case withdrawCoins = "withdraw-coins"
+    case exchangeWaitReserve = "exchange-wait-reserve"
+    case abortingBank = "aborting-bank"
+    case refused
+    case withdraw
+    case merchantOrderProposed = "merchant-order-proposed"
+    case proposed
+    case refundAvailable = "refund-available"
+    case acceptRefund = "accept-refund"
+
+    case submitPayment = "submit-payment"
+}
+
+enum TransactionMajorState: String, Codable {
+      // No state, only used when reporting transitions into the initial state
+    case none
+    case pending
+    case done
+    case aborting
+    case aborted
+    case suspended
+    case dialog
+    case suspendedAborting = "suspended-aborting"
+    case failed
+    case expired
+      // Only used for the notification, never in the transaction history
+    case deleted
+      // Placeholder until D37 is fully implemented
+    case unknown
+}
+
+struct TransactionState: Codable, Hashable {
+    var major: TransactionMajorState
+    var minor: TransactionMinorState?
+}
+
+struct TransactionTransition: Codable {             // Notification
+    enum TransitionType: String, Codable {
+        case transition = "transaction-state-transition"
+    }
+    var type: TransitionType
+    var oldTxState: TransactionState
+    var newTxState: TransactionState
+    var transactionId: String
+}
+
+enum TxAction: String, Codable {
+    case abort      // pending,dialog,suspended -> aborting
+//  case revive     // aborting -> pending ?? maybe post 1.0
+    case fail       // aborting -> failed
+    case delete     // dialog,done,expired,aborted,failed -> ()
+    case suspend    // pending -> suspended; aborting -> ab_suspended
+    case resume     // suspended -> pending; ab_suspended -> aborting
+}
+
 struct TransactionCommon: Decodable {
-    var type: String
+    enum TransactionType: String, Codable {
+        case withdrawal
+        case deposit
+        case payment
+        case refund
+        case refresh
+        case reward
+//        case tip
+        case peerPushDebit = "peer-push-debit"              // send coins to 
peer, show QR
+        case scanPushCredit = "peer-push-credit"            // scan QR, 
receive coins from peer
+        case peerPullCredit = "peer-pull-credit"            // request payment 
from peer, show QR
+        case scanPullDebit = "peer-pull-debit"              // scan QR, pay 
requested
+    }
+    var type: TransactionType
+    var txState: TransactionState
     var amountEffective: Amount
     var amountRaw: Amount
     var transactionId: String
     var timestamp: Timestamp
-    var extendedStatus: String      // TODO: enum with some fixed values?
-    var pending: Bool
-    var frozen: Bool
+    var txActions: [TxAction]
 
     func fee() -> Amount {
         do {
             return try Amount.diff(amountRaw, amountEffective)
-        } catch {
-            return Amount(currency: amountRaw.currencyStr, integer: 0, 
fraction: 0)
-        }
+        } catch {}
+        do {
+            return try Amount.diff(amountEffective, amountRaw)
+        } catch {}
+        return Amount(currency: amountRaw.currencyStr, integer: 0, fraction: 0)
+    }
+
+    func incoming() -> Bool {
+        return type == .withdrawal
+            || type == .refund
+            || type == .reward
+            || type == .peerPullCredit
+            || type == .scanPushCredit
     }
 }
 
@@ -49,12 +140,11 @@ struct WithdrawalDetails: Decodable {
     enum WithdrawalType: String, Decodable {
         case manual = "manual-transfer"
         case bankIntegrated = "taler-bank-integration-api"
-        case peerPullCredit = "peer-pull-credit"
-        case peerPushCredit = "peer-push-credit"
     }
     var type: WithdrawalType
     /// The public key of the reserve.
     var reservePub: String
+    var reserveIsReady: Bool
 
   /// Details for manual withdrawals:
     /// The payto URIs that the exchange supports.
@@ -79,11 +169,11 @@ struct WithdrawalTransaction {
 
 struct PaymentTransactionDetails: Decodable {
     var proposalId: String
-    var status: String          // "paid"
     var totalRefundRaw: Amount
     var totalRefundEffective: Amount
     var refundPending: Amount?
-//    var refunds: []
+    var refundQueryActive: Bool?
+    var refunds: [String]?           // TODO: array type?
     var info: OrderShortInfo
 }
 
@@ -97,7 +187,7 @@ struct RefundTransactionDetails: Decodable {
     var refundPending: Amount?
     /// The amount that couldn't be applied because refund permissions expired.
     var amountInvalid: Amount?
-    var info: OrderShortInfo
+    var info: OrderShortInfo?       // TODO: is this still here?
 }
 
 struct RefundTransaction {
@@ -105,19 +195,44 @@ struct RefundTransaction {
     var details: RefundTransactionDetails
 }
 
-struct TipTransactionDetails: Decodable {
-    /// The exchange that the tip will be withdrawn from
+struct RewardTransactionDetails: Decodable {
+    /// The exchange that the reward will be withdrawn from
     var exchangeBaseUrl: String
 }
 
-struct TipTransaction {
+struct RewardTransaction {
     var common: TransactionCommon
-    var details: TipTransactionDetails
+    var details: RewardTransactionDetails
 }
 
+//struct TipTransactionDetails: Decodable {
+//    /// The exchange that the tip will be withdrawn from
+//    var exchangeBaseUrl: String
+//}
+
+//struct TipTransaction {
+//    var common: TransactionCommon
+//    var details: TipTransactionDetails
+//}
+
+enum RefreshReason: String, Decodable {
+    case manual
+    case payMerchant = "pay-merchant"
+    case payDeposit = "pay-deposit"
+    case payPeerPush = "pay-peer-push"
+    case payPeerPull = "pay-peer-pull"
+    case refund
+    case abortPay = "abort-pay"
+    case abortDeposit = "abort-deposit"
+    case recoup
+    case backupRestored = "backup-restored"
+    case scheduled
+}
 struct RefreshTransactionDetails: Decodable {
-    /// The exchange that the coins are refreshed with.
-    var exchangeBaseUrl: String
+    var refreshReason: RefreshReason
+    var originatingTransactionId: String?
+    var refreshInputAmount: Amount
+    var refreshOutputAmount: Amount
 }
 
 struct RefreshTransaction {
@@ -125,51 +240,126 @@ struct RefreshTransaction {
     var details: RefreshTransactionDetails
 }
 
+struct P2pShortInfo: Codable {
+    var summary: String
+    var expiration: Timestamp
+}
+
+struct P2PTransactionDetails: Codable {
+    var exchangeBaseUrl: String
+    var talerUri: String?       // only if we initiated the transaction
+    var info: P2pShortInfo
+}
+
+struct P2PTransaction {
+    var common: TransactionCommon
+    var details: P2PTransactionDetails
+}
+
 enum Transaction: Decodable, Hashable, Identifiable {
     case withdrawal (WithdrawalTransaction)
     case payment (PaymentTransaction)
     case refund (RefundTransaction)
-    case tip (TipTransaction)
+    case reward (RewardTransaction)
+//    case tip (TipTransaction)
     case refresh (RefreshTransaction)
+    case peer2peer (P2PTransaction)
 
     init(from decoder: Decoder) throws {
-        let common = try TransactionCommon.init(from: decoder)
-
-        switch (common.type) {
-            case WITHDRAWAL:
-                let details = try WithdrawalTransactionDetails.init(from: 
decoder)
-                self = .withdrawal(WithdrawalTransaction(common: common, 
details: details))
-            case PAYMENT:
-                let details = try PaymentTransactionDetails.init(from: decoder)
-                self = .payment(PaymentTransaction(common: common, details: 
details))
-            case REFUND:
-                let details = try RefundTransactionDetails.init(from: decoder)
-                self = .refund(RefundTransaction(common: common, details: 
details))
-            case TIP:
-                let details = try TipTransactionDetails.init(from: decoder)
-                self = .tip(TipTransaction(common: common, details: details))
-            case REFRESH:
-                let details = try RefreshTransactionDetails.init(from: decoder)
-                self = .refresh(RefreshTransaction(common: common, details: 
details))
-            default:
-                let context = DecodingError.Context(
-                    codingPath: decoder.codingPath,
-                    debugDescription: "Invalid transaction type")
-                throw DecodingError.typeMismatch(Transaction.self, context)
+        do {
+            let common = try TransactionCommon.init(from: decoder)
+
+            switch (common.type) {
+                case .withdrawal:
+                    let details = try WithdrawalTransactionDetails.init(from: 
decoder)
+                    self = .withdrawal(WithdrawalTransaction(common: common, 
details: details))
+                case .payment:
+                    let details = try PaymentTransactionDetails.init(from: 
decoder)
+                    self = .payment(PaymentTransaction(common: common, 
details: details))
+                case .refund:
+                    let details = try RefundTransactionDetails.init(from: 
decoder)
+                    self = .refund(RefundTransaction(common: common, details: 
details))
+                case .reward:
+                    let details = try RewardTransactionDetails.init(from: 
decoder)
+                    self = .reward(RewardTransaction(common: common, details: 
details))
+//                case .tip:
+//                    let details = try TipTransactionDetails.init(from: 
decoder)
+//                    self = .tip(TipTransaction(common: common, details: 
details))
+                case .refresh:
+                    let details = try RefreshTransactionDetails.init(from: 
decoder)
+                    self = .refresh(RefreshTransaction(common: common, 
details: details))
+                case .peerPushDebit, .peerPullCredit, .scanPullDebit, 
.scanPushCredit:
+                    let details = try P2PTransactionDetails.init(from: decoder)
+                    self = .peer2peer(P2PTransaction(common: common, details: 
details))
+                default:
+                    let context = DecodingError.Context(
+                        codingPath: decoder.codingPath,
+                        debugDescription: "Invalid transaction type")
+                    throw DecodingError.typeMismatch(Transaction.self, context)
+            }
+            return
+        } catch DecodingError.dataCorrupted(let context) {
+            print(context)
+            throw TransactionDecodingError.invalidStringValue
+        } catch DecodingError.keyNotFound(let key, let context) {
+            print("Key '\(key)' not found:", context.debugDescription)
+            print("codingPath:", context.codingPath)
+            throw TransactionDecodingError.invalidStringValue
+        } catch DecodingError.valueNotFound(let value, let context) {
+            print("Value '\(value)' not found:", context.debugDescription)
+            print("codingPath:", context.codingPath)
+            throw TransactionDecodingError.invalidStringValue
+        } catch DecodingError.typeMismatch(let type, let context) {
+            print("Type '\(type)' mismatch:", context.debugDescription)
+            print("codingPath:", context.codingPath)
+            throw TransactionDecodingError.invalidStringValue
+        } catch {       // TODO: native logging
+            print("error: ", error)
+            throw error
         }
     }
 
-    var id: String { common().transactionId }
+    var id: String { common.transactionId }
 
     static func == (lhs: Transaction, rhs: Transaction) -> Bool {
         return lhs.id == rhs.id
     }
 
     func hash(into hasher: inout Hasher) {
-        id.hash(into: &hasher)
+        hasher.combine(id)
+        hasher.combine(common.txState)        // let SwiftUI redraw if txState 
changes
     }
 
-    func common() -> TransactionCommon {
+    var isWithdrawal : Bool { common.type == .withdrawal }
+    var isDeposit    : Bool { common.type == .deposit }
+    var isPayment    : Bool { common.type == .payment }
+    var isRefund     : Bool { common.type == .refund }
+    var isRefresh    : Bool { common.type == .refresh }
+    var isReward     : Bool { common.type == .reward }
+//    var isTipPayment : Bool { common.type == .tip }
+    var isSendCoins  : Bool { common.type == .peerPushDebit }
+    var isRcvCoins   : Bool { common.type == .scanPushCredit }
+    var isSendInvoice: Bool { common.type == .peerPullCredit }
+    var isPayInvoice : Bool { common.type == .scanPullDebit }
+
+    var isP2pOutgoing: Bool { isSendCoins || isPayInvoice}
+    var isP2pIncoming: Bool { isSendInvoice || isRcvCoins}
+
+    var isDone       : Bool { common.txState.major == .done }
+    var isPending    : Bool { common.txState.major == .pending }
+    var isAborting   : Bool { common.txState.major == .aborting }
+    var isAborted    : Bool { common.txState.major == .aborted }
+    var isFailed     : Bool { common.txState.major == .failed }
+    var isExpired    : Bool { common.txState.major == .expired }
+    var isSpecial    : Bool { isPending || isAborting || isAborted || isFailed 
|| isExpired }
+
+    var isAbortable  : Bool { common.txActions.contains(.abort) }
+    var isFailable   : Bool { common.txActions.contains(.fail) }
+    var isDeleteable : Bool { common.txActions.contains(.delete) }
+    var isResumable  : Bool { common.txActions.contains(.resume) }
+    var isSuspendable: Bool { common.txActions.contains(.suspend) }
+
+    var common: TransactionCommon {
         switch self {
             case .withdrawal(let withdrawalTransaction):
                 return withdrawalTransaction.common
@@ -177,10 +367,14 @@ enum Transaction: Decodable, Hashable, Identifiable {
                 return paymentTransaction.common
             case .refund(let refundTransaction):
                 return refundTransaction.common
-            case .tip(let tipTransaction):
-                return tipTransaction.common
+            case .reward(let rewardTransaction):
+                return rewardTransaction.common
+//            case .tip(let tipTransaction):
+//                return tipTransaction.common
             case .refresh(let refreshTransaction):
                 return refreshTransaction.common
+            case .peer2peer(let p2pTransaction):
+                return p2pTransaction.common
         }
     }
 
@@ -190,46 +384,23 @@ enum Transaction: Decodable, Hashable, Identifiable {
             case .withdrawal(let withdrawalTransaction):
                 result[EXCHANGEBASEURL] = 
withdrawalTransaction.details.exchangeBaseUrl
             case .payment(let paymentTransaction):
-                result["status"] = paymentTransaction.details.status
+                result["summary"] = paymentTransaction.details.info.summary
             case .refund(let refundTransaction):
-                result["summary"] = refundTransaction.details.info.summary
-            case .tip(let tipTransaction):
-                result[EXCHANGEBASEURL] = 
tipTransaction.details.exchangeBaseUrl
+                if let info = refundTransaction.details.info {
+                    result["summary"] = info.summary
+                }
+            case .reward(let rewardTransaction):
+                result[EXCHANGEBASEURL] = 
rewardTransaction.details.exchangeBaseUrl
+//            case .tip(let tipTransaction):
+//                result[EXCHANGEBASEURL] = 
tipTransaction.details.exchangeBaseUrl
             case .refresh(let refreshTransaction):
-                result[EXCHANGEBASEURL] = 
refreshTransaction.details.exchangeBaseUrl
+                result["reason"] = 
refreshTransaction.details.refreshReason.rawValue
+            case .peer2peer(let p2pTransaction):
+                result[EXCHANGEBASEURL] = 
p2pTransaction.details.exchangeBaseUrl
+                result["summary"] = p2pTransaction.details.info.summary
+                result[TALERURI] = p2pTransaction.details.talerUri ?? ""
         }
-        return result
-    }
-}
-
 
-#if DEBUG
-extension Transaction {             // for PreViews
-    init(incoming: Bool, id: String, time: Timestamp) {
-        let effective = incoming ? "Taler:4.8" : "Taler:5.2"
-        let common = TransactionCommon(type: incoming ? WITHDRAWAL : PAYMENT,
-                            amountEffective: try! Amount(fromString: 
effective),
-                                  amountRaw: try! Amount(fromString: 
"Taler:5"),
-                              transactionId: id, timestamp: time,
-                             extendedStatus: "done", pending: false, frozen: 
false)
-        if incoming {
-            let withdrawalDetails = WithdrawalDetails(type: 
WithdrawalDetails.WithdrawalType.bankIntegrated,
-                                                      reservePub: "Public Key 
of the Exchange",
-                                                      confirmed: true)
-            let wDetails = WithdrawalTransactionDetails(exchangeBaseUrl: 
"Exchange.Demo.Taler.net",
-                                                        withdrawalDetails: 
withdrawalDetails)
-            self = .withdrawal(WithdrawalTransaction(common: common, details: 
wDetails))
-        } else {
-            let merchant = Merchant(name: "some random shop")
-            let info = OrderShortInfo(orderId: "some order ID",
-                                      merchant: merchant, summary: "some 
product summary", products: [])
-            let pDetails = PaymentTransactionDetails(proposalId: "some 
proposal ID",
-                                                     status: "paid",
-                                                     totalRefundRaw: try! 
Amount(fromString: "Taler:3.2"),
-                                                     totalRefundEffective: 
try! Amount(fromString: "Taler:3"),
-                                                     info: info)
-            self = .payment(PaymentTransaction(common: common, details: 
pDetails))
-        }
+        return result
     }
 }
-#endif
diff --git a/TalerWallet1/Backend/WalletBackendError.swift 
b/TalerWallet1/Backend/WalletBackendError.swift
index cb0bfeb..802d027 100644
--- a/TalerWallet1/Backend/WalletBackendError.swift
+++ b/TalerWallet1/Backend/WalletBackendError.swift
@@ -1,17 +1,6 @@
 /*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
  */
 import Foundation
 
@@ -25,7 +14,7 @@ enum WalletBackendError: Error {
 }
 
 /// Information supplied by the backend describing an error.
-struct WalletBackendResponseError: Decodable {
+struct WalletBackendResponseError: Codable {
     /// Numeric error code defined defined in the GANA gnu-taler-error-codes 
registry.
     var talerErrorCode: Int
     
diff --git a/TalerWallet1/Backend/WalletBackendRequest.swift 
b/TalerWallet1/Backend/WalletBackendRequest.swift
index ad48879..9bf8d80 100644
--- a/TalerWallet1/Backend/WalletBackendRequest.swift
+++ b/TalerWallet1/Backend/WalletBackendRequest.swift
@@ -1,17 +1,6 @@
 /*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
  */
 import Foundation
 import AnyCodable
@@ -34,8 +23,37 @@ protocol WalletBackendFormattedRequest {
     func args() -> Args
 }
 // MARK: -
-
-
+/// The scope of a currency: either global or a dedicated exchange
+struct ScopeInfo: Codable, Hashable {
+    enum ScopeInfoType: String, Codable {
+        case global
+        case exchange
+    }
+    var type: ScopeInfoType
+    var exchangeBaseUrl: String?    // only for "exchange"
+    var currency: String
+
+    public static func == (lhs: ScopeInfo, rhs: ScopeInfo) -> Bool {
+        if let lhsBaseURL = lhs.exchangeBaseUrl {
+            if let rhsBaseURL = rhs.exchangeBaseUrl {
+                if lhsBaseURL != rhsBaseURL { return false }    // different 
exchanges
+                                                                // else fall 
thru and check type & currency
+            } else { return false }                             // left but 
not right
+        } else if rhs.exchangeBaseUrl != nil {
+            return false                                        // right but 
not left
+        }
+        return lhs.type == rhs.type &&
+        lhs.currency == rhs.currency
+    }
+    public func hash(into hasher: inout Hasher) {
+        hasher.combine(type)
+        if let baseURL = exchangeBaseUrl {
+            hasher.combine(baseURL)
+        }
+        hasher.combine(currency)
+    }
+}
+// MARK: -
 /// A billing or mailing location.
 struct Location: Codable {
     var country: String?
@@ -64,14 +82,15 @@ struct Tax: Codable {
 }
 
 /// A product being purchased from a merchant.
+/// https://docs.taler.net/core/api-merchant.html#the-contract-terms
 struct Product: Codable {
     var product_id: String?
     var description: String
-    // description_i18n?
-    var quantity: Int
-    var unit: String
+//    var description_i18n:   ?
+    var quantity: Int?
+    var unit: String?
     var price: Amount?
-    var image: String // URL to a product image
+    var image: String? // URL to a product image
     var taxes: [Tax]?
     var delivery_date: Timestamp?
 }
@@ -81,33 +100,14 @@ struct OrderShortInfo: Codable {
     var orderId: String
     var merchant: Merchant
     var summary: String
-    // summary_i18n?
+//    var summary_i18n:   ?
     var products: [Product]
     var fulfillmentUrl: String?
     var fulfillmentMessage: String?
-    // fulfillmentMessage_i18n?
+//    var fulfillmentMessage_i18n:    ?
+    var contractTermsHash: String?
 }
-
-
-/// A request to delete a wallet transaction by ID.
-struct WalletBackendDeleteTransactionRequest: WalletBackendFormattedRequest {
-    var transactionId: String
-    
-    struct Args: Encodable {
-        var transactionId: String
-    }
-    
-    struct Response: Decodable {}
-    
-    func operation() -> String {
-        return "deleteTransaction"
-    }
-    
-    func args() -> Args {
-        return Args(transactionId: transactionId)
-    }
-}
-
+// MARK: -
 /// A request to process a refund.
 struct WalletBackendApplyRefundRequest: WalletBackendFormattedRequest {
     var talerRefundUri: String
@@ -154,55 +154,6 @@ struct WalletBackendForceUpdateRequest: 
WalletBackendFormattedRequest {
     }
 }
 
-
-
-
-/// A request to accept a bank-integrated withdrawl.
-struct WalletBackendAcceptBankIntegratedWithdrawalRequest: 
WalletBackendFormattedRequest {
-    var talerWithdrawUri: String
-    var exchangeBaseUrl: String
-    
-    struct Args: Encodable {
-        var talerWithdrawUri: String
-        var exchangeBaseUrl: String
-    }
-    
-    struct Response: Decodable {
-        var bankConfirmationUrl: String?
-    }
-    
-    func operation() -> String {
-        return "acceptWithdrawal"
-    }
-    
-    func args() -> Args {
-        return Args(talerWithdrawUri: talerWithdrawUri, exchangeBaseUrl: 
exchangeBaseUrl)
-    }
-}
-
-/// A request to accept a manual withdrawl.
-struct WalletBackendAcceptManualWithdrawalRequest: 
WalletBackendFormattedRequest {
-    var exchangeBaseUrl: String
-    var amount: Amount
-    
-    struct Args: Encodable {
-        var exchangeBaseUrl: String
-        var amount: Amount
-    }
-    
-    struct Response: Decodable {
-        var exchangePaytoUris: [String]
-    }
-    
-    func operation() -> String {
-        return "acceptManualWithdrawal"
-    }
-    
-    func args() -> Args {
-        return Args(exchangeBaseUrl: exchangeBaseUrl, amount: amount)
-    }
-}
-
 /// A request to deposit funds.
 struct WalletBackendCreateDepositGroupRequest: WalletBackendFormattedRequest {
     var depositePayToUri: String
@@ -264,51 +215,40 @@ struct WalletBackendConfirmPayRequest: 
WalletBackendFormattedRequest {
     }
 }
 
-/// A request to prepare a tip.
-struct WalletBackendPrepareTipRequest: WalletBackendFormattedRequest {
-    var talerTipUri: String
-    
-    struct Args: Encodable {
-        var talerTipUri: String
-    }
-    
-    struct Response: Decodable {
-        var walletTipId: String
-        var accepted: Bool
-        var tipAmountRaw: Amount
-        var tipAmountEffective: Amount
-        var exchangeBaseUrl: String
-        var expirationTimestamp: Timestamp
-    }
-    
-    func operation() -> String {
-        return "prepareTip"
-    }
-    
-    func args() -> Args {
-        return Args(talerTipUri: talerTipUri)
-    }
+// MARK: -
+/// The result from PrepareReward
+struct PrepareRewardResponse: Decodable {
+    var walletRewardId: String
+    var accepted: Bool
+    var rewardAmountRaw: Amount
+    var rewardAmountEffective: Amount
+    var exchangeBaseUrl: String
+    var expirationTimestamp: Timestamp
 }
+/// A request to prepare a reward.
+struct PrepareRewardRequest: WalletBackendFormattedRequest {
+    typealias Response = PrepareRewardResponse
+    func operation() -> String { return "prepareReward" }
+    func args() -> Args { return Args(talerRewardUri: talerRewardUri) }
 
-/// A request to accept a tip.
-struct WalletBackendAcceptTipRequest: WalletBackendFormattedRequest {
-    var walletTipId: String
-    
+    var talerRewardUri: String
     struct Args: Encodable {
-        var walletTipId: String
+        var talerRewardUri: String
     }
-    
+}
+// MARK: -
+/// A request to accept a reward.
+struct AcceptRewardRequest: WalletBackendFormattedRequest {
     struct Response: Decodable {}
-    
-    func operation() -> String {
-        return "acceptTip"
-    }
-    
-    func args() -> Args {
-        return Args(walletTipId: walletTipId)
+    func operation() -> String { return "acceptReward" }
+    func args() -> Args { return Args(walletRewardId: walletRewardId) }
+
+    var walletRewardId: String
+    struct Args: Encodable {
+        var walletRewardId: String
     }
 }
-
+// MARK: -
 /// A request to abort a failed payment.
 struct WalletBackendAbortFailedPaymentRequest: WalletBackendFormattedRequest {
     var proposalId: String
@@ -327,8 +267,7 @@ struct WalletBackendAbortFailedPaymentRequest: 
WalletBackendFormattedRequest {
         return Args(proposalId: proposalId)
     }
 }
-
-
+// MARK: -
 struct IntegrationTestArgs: Codable {
     var exchangeBaseUrl: String
     var bankBaseUrl: String
diff --git a/TalerWallet1/Backend/WalletCore.swift 
b/TalerWallet1/Backend/WalletCore.swift
index be7450d..c7205a7 100644
--- a/TalerWallet1/Backend/WalletCore.swift
+++ b/TalerWallet1/Backend/WalletCore.swift
@@ -1,17 +1,6 @@
 /*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
  */
 import SwiftUI              // FOUNDATION has no AppStorage
 import AnyCodable
@@ -24,13 +13,18 @@ protocol WalletBackendDelegate {
     func walletBackendReceivedUnknownMessage(_ walletCore: WalletCore, 
message: String)
 }
 
+// MARK: -
 /// An interface to the wallet backend.
 class WalletCore: QuickjsMessageHandler {
+    public static let shared = try! WalletCore()          // will (and should) 
crash on failure
     private let symLog = SymLogC()
 
+    private var queue: DispatchQueue
+    private var semaphore: DispatchSemaphore
+
     private var quickjs: Quickjs
     private var requestsMade: UInt          // counter for array of completion 
closures
-    private var completions: [UInt : (UInt, Data?, 
WalletBackendResponseError?) -> Void] = [:]
+    private var completions: [UInt : (Date, (UInt, Date, Data?, 
WalletBackendResponseError?) -> Void)] = [:]
     var delegate: WalletBackendDelegate?
 
     var versionInfo: VersionInfo?           // shown in SettingsView
@@ -41,7 +35,7 @@ class WalletCore: QuickjsMessageHandler {
         let id: UInt
         let args: AnyEncodable
     }
-    
+
     private struct FullResponse: Decodable {
         let type: String
         let operation: String
@@ -58,6 +52,21 @@ class WalletCore: QuickjsMessageHandler {
 
     var lastError: FullError?
 
+    struct ResponseOrNotification: Decodable {
+        let type: String
+        let operation: String?
+        let id: UInt?
+        let result: AnyCodable?
+        let error: AnyCodable?  // should be WalletBackendResponseError?
+        let payload: AnyCodable?
+    }
+
+    struct Payload: Decodable {
+        let type: String
+        let id: String?
+        let reservePub: String?
+    }
+
     deinit {
         symLog.log()
     // TODO: send shutdown message to talerWalletInstance
@@ -65,95 +74,157 @@ class WalletCore: QuickjsMessageHandler {
     }
 
     init() throws {
+        symLog.log("alloc.init wallet-core")
         requestsMade = 0
+        queue = DispatchQueue(label: "net.taler.myQueue", attributes: 
.concurrent)
+        semaphore = DispatchSemaphore(value: 1)
         quickjs = Quickjs()
         quickjs.messageHandler = self
+        symLog.log("wallet-core done")
     }
 }
 // MARK: -  completionHandler functions
 extension WalletCore {
-
-    private func handleResponse(dict responseDict: [String : Any], data 
messageData: Data, isError: Bool = false) throws {
-        guard let id = responseDict["id"] as? UInt else { throw 
WalletBackendError.deserializationError }
-        guard let completion = completions[id] else { throw 
WalletBackendError.deserializationError }
-        completions[id] = nil
-        if isError {
+    private func handleError(_ decoded: ResponseOrNotification) throws {
+        guard let requestId = decoded.id else {
+            symLog.log(decoded)                 // TODO: .error
+            throw WalletBackendError.deserializationError
+        }
+        guard let (timeSent, completion) = completions[requestId] else {
+            symLog.log("requestId \(requestId) not in list")       // TODO: 
.error
+            throw WalletBackendError.deserializationError
+        }
+        completions[requestId] = nil
+        if let walletError = decoded.error {            // wallet-core sent an 
error message
             do {
-                let decoded = try JSONDecoder().decode(FullError.self, from: 
messageData)
-                symLog.log(decoded)
-                completion(id, nil, decoded.error)
-            } catch {
-                symLog.log(responseDict)        // TODO: error
-                completion(id, nil, WalletCore.parseFailureError())
+                let jsonData = try JSONEncoder().encode(walletError)
+            } catch {        // JSON encoding of response.result failed / 
should never happen
+                symLog.log(walletError)                 // TODO: .error
+                completion(requestId, timeSent, nil, 
WalletCore.parseFailureError())
             }
+
+            // TODO: decode jsonData to WalletBackendResponseError - or 
HTTPError
+//            completion(requestId, timeSent, nil, walletError)
+            completion(requestId, timeSent, nil, 
WalletCore.parseFailureError())
+        } else {                                        // JSON decoding of 
error message failed
+            completion(requestId, timeSent, nil, 
WalletCore.parseFailureError())
+        }
+    }
+    private func handleResponse(_ decoded: ResponseOrNotification) throws {
+        guard let requestId = decoded.id else {
+            symLog.log(decoded)                 // TODO: .error
+            throw WalletBackendError.deserializationError
+        }
+        guard let (timeSent, completion) = completions[requestId] else {
+            symLog.log("requestId \(requestId) not in list")       // TODO: 
.error
+            throw WalletBackendError.deserializationError
+        }
+        completions[requestId] = nil
+        guard let result = decoded.result else {
+            symLog.log("requestId \(requestId) got no result")       // TODO: 
.error
+            throw WalletBackendError.deserializationError
+        }
+        do {
+            let jsonData = try JSONEncoder().encode(result)
+            symLog.log(result)                 // TODO: .info
+            completion(requestId, timeSent, jsonData, nil)
+        } catch {        // JSON encoding of response.result failed / should 
never happen
+            symLog.log(result)                 // TODO: .error
+            completion(requestId, timeSent, nil, 
WalletCore.parseResponseError())
+        }
+    }
+
+    @MainActor
+    private func postNotificationM(_ aName: NSNotification.Name,
+                           object anObject: Any? = nil,
+                                  userInfo: [AnyHashable: Any]? = nil) async {
+        symLog.log(aName.rawValue)
+        NotificationCenter.default.post(name: aName, object: anObject, 
userInfo: userInfo)
+    }
+    private func postNotification(_ aName: NSNotification.Name,
+                          object anObject: Any? = nil,
+                                 userInfo: [AnyHashable: Any]? = nil) {
+        Task {
+            await postNotificationM(aName, object: anObject, userInfo: 
userInfo)
+        }
+        if let userInfo { symLog.log(userInfo) } else { symLog.log(aName) }
+    }
+
+    private func handlePendingProcessed(_ payload: Payload) throws {
+        guard let id = payload.id else { throw 
WalletBackendError.deserializationError }
+        let pendingOp = Notification.Name.PendingOperationProcessed.rawValue
+        if id.hasPrefix("exchange-update:") {
+            // Bla Bla Bla
+        } else if id.hasPrefix("refresh:") {
+            // Bla Bla Bla
+        } else if id.hasPrefix("purchase:") {
+            // TODO: handle purchase
+//            symLog.log("\(pendingOp): \(id)")
+        } else if id.hasPrefix("withdraw:") {
+            // TODO: handle withdraw
+//            symLog.log("\(pendingOp): \(id)")
         } else {
-            do {    // pass response.result
-                let decoded = try JSONDecoder().decode(FullResponse.self, 
from: messageData)
-                symLog.log(decoded)
-                let jsonData = try JSONEncoder().encode(decoded.result)
-                completion(id, jsonData, nil)
-            } catch {
-                symLog.log(responseDict)        // TODO: error
-                completion(id, nil, WalletCore.parseResponseError())
-            }
+            // TODO: handle other pending-operation-processed
+print("\n❗️ \(pendingOp): \(id)\n")        // this is a new pendingOp I 
haven't seen before
+            print("\n")
+        }
+    }
+    private func handleStateTransition(_ jsonData: Data) throws {
+        do {
+            let decoded = try JSONDecoder().decode(TransactionTransition.self, 
from: jsonData)
+            postNotification(.TransactionStateTransition,
+                             userInfo: [TRANSACTIONTRANSITION: decoded])
+        } catch {       // rethrows
+            symLog.log(jsonData)       // TODO: .error
+            throw WalletBackendError.deserializationError
         }
     }
 
-    private func handleNotification(dict responseDict: [String : Any]) throws {
+    private func handleNotification(_ anyCodable: AnyCodable?) throws {
+        guard let anyPayload = anyCodable else { throw 
WalletBackendError.deserializationError }
         do {
-            guard let payload = (responseDict["payload"] as? [String : Any]) 
else { throw WalletBackendError.deserializationError }
-            guard let type = (payload["type"] as? String) else { throw 
WalletBackendError.deserializationError }
-            switch type {
-                case "pending-operation-processed":
-                    guard let id = (payload["id"] as? String) else { throw 
WalletBackendError.deserializationError }
-                    if id.hasPrefix("exchange-update:") {
-                        // TODO: handle exchange-update
-                    } else {
-                        symLog.log(id)
-                        // TODO: handle other pending-operation-processed
-                    }
-                case "coin-withdrawn", "withdraw-group-finished", 
"pay-operation-success":
-                    symLog.log(payload)
-                    Task {
-                        do {
-                            // automatically fetch balances after receiving 
these notifications
-                            try await 
Controller.shared.balancesModel.fetchBalances()
-                        } catch {
-                            // TODO: show error
-                            symLog.log(error.localizedDescription)
-                        }
-                    }
-                    break
-                case "proposal-accepted":
-                    symLog.log(payload)
-                    break
-                case "proposal-downloaded":
-                    symLog.log(payload)
-                    break
-                case "waiting-for-retry":
-                    // Bla Bla Bla
-                    break
-                case "exchange-added":
-                    symLog.log(payload)
-                    break
-                case "refresh-started":
-                    symLog.log(payload)
-                    break
-                case "refresh-melted":
-                    symLog.log(payload)
-                    break
-                case "refresh-revealed":
-                    symLog.log(payload)
-                    break
+            let jsonData = try JSONEncoder().encode(anyPayload)
+            let payload = try JSONDecoder().decode(Payload.self, from: 
jsonData)
+
+            switch payload.type {
+                case Notification.Name.TransactionStateTransition.rawValue:
+                    try handleStateTransition(jsonData)
+                case Notification.Name.PendingOperationProcessed.rawValue:
+                    try handlePendingProcessed(payload)
+                case Notification.Name.ExchangeAdded.rawValue:
+                    postNotification(.ExchangeAdded)
+                case Notification.Name.ReserveNotYetFound.rawValue:
+                    if let reservePub = payload.reservePub {
+                        let userInfo = ["reservePub" : reservePub]
+//                        postNotification(.ReserveNotYetFound, userInfo: 
userInfo)   // TODO: remind User to confirm withdrawal
+                    } // else { throw WalletBackendError.deserializationError 
}   shouldn't happen, but if it does just ignore it
+
+                case Notification.Name.ProposalAccepted.rawValue:              
 // "proposal-accepted":
+                    symLog.log(anyPayload)
+                    postNotification(.ProposalAccepted, userInfo: nil)
+                case Notification.Name.ProposalDownloaded.rawValue:            
 // "proposal-downloaded":
+                    symLog.log(anyPayload)
+                    postNotification(.ProposalDownloaded, userInfo: nil)
+
                 case "reserve-registered-with-bank":
-                    symLog.log(payload)
+                    symLog.log(anyPayload)
+
+                // TODO: remove these once wallet-core doesn't send them 
anymore
+                case "withdraw-group-finished",
+                     "pay-operation-success",
+                     "withdrawal-group-bank-confirmed",          // replaced 
by transaction-state-transition
+                     "withdrawal-group-reserve-ready",
+                     "coin-withdrawn",                           // totally 
useless since wallet-core handles coins in bulk
+                     "waiting-for-retry",                        // Bla Bla Bla
+                     "refresh-started", "refresh-melted",
+                     "refresh-revealed", "refresh-unwarranted":
                     break
                 default:
-                    symLog.log(payload)
+print("\n❗️ ", anyPayload, "\n")        // this is a new notification I 
haven't seen before
                     break
             }
         } catch let error {
-            symLog.log("Error \(error) parsing notification: \(responseDict)") 
   // TODO: .error
+            symLog.log("Error \(error) parsing notification: \(anyPayload)")   
 // TODO: .error
         // TODO: if DevMode then should log into file for user
         }
     }
@@ -162,7 +233,7 @@ extension WalletCore {
     func handleMessage(message: String) {
         do {
             var asyncDelay = 0
-            if let delay = developDelay {
+            if let delay: Bool = developDelay {   // Settings: 2 seconds delay
                 if delay {
                     asyncDelay = 2
                 }
@@ -173,42 +244,61 @@ extension WalletCore {
                 sleep(UInt32(asyncDelay))
                 symLog.log("waking up again after \(asyncDelay) seconds, will 
deliver message")
             }
-            guard let messageData = message.data(using: .utf8) else { throw 
WalletBackendError.deserializationError }
-            let jsonDict = try JSONSerialization.jsonObject(with: messageData, 
options: .allowFragments) as? [String : Any]
-            guard let responseDict = jsonDict else { throw 
WalletBackendError.deserializationError }
-            guard let responseType = (responseDict["type"] as? String) else { 
throw WalletBackendError.deserializationError }
-            switch responseType {
+            guard let messageData = message.data(using: .utf8) else {
+                throw WalletBackendError.deserializationError
+            }
+            let decoded = try 
JSONDecoder().decode(ResponseOrNotification.self, from: messageData)
+            switch decoded.type {
                 case "error":
-                    try handleResponse(dict: responseDict, data: messageData, 
isError: true)
+                    symLog.log(decoded)                 // TODO: .error
+                    try handleError(decoded)
                 case "response":
-                    try handleResponse(dict: responseDict, data: messageData)
+                    try handleResponse(decoded)
                 case "notification":
-                    try handleNotification(dict: responseDict)
+                    try handleNotification(decoded.payload)
                 case "tunnelHttp":          // TODO: Handle tunnelHttp
-                    break
+                    symLog.log("Can't handle tunnelHttp: \(decoded)")    // 
TODO: .error
+                    throw WalletBackendError.deserializationError
                 default:
-                    symLog.log("Unknown response type: \(responseDict)")    // 
TODO: .error
+                    symLog.log("Unknown response type: \(decoded)")    // 
TODO: .error
                     throw WalletBackendError.deserializationError
             }
-        } catch {
+        } catch DecodingError.dataCorrupted(let context) {
+            print(context)
+        } catch DecodingError.keyNotFound(let key, let context) {
+            print("Key '\(key)' not found:", context.debugDescription)
+            print("codingPath:", context.codingPath)
+        } catch DecodingError.valueNotFound(let value, let context) {
+            print("Value '\(value)' not found:", context.debugDescription)
+            print("codingPath:", context.codingPath)
+        } catch DecodingError.typeMismatch(let type, let context) {
+            print("Type '\(type)' mismatch:", context.debugDescription)
+            print("codingPath:", context.codingPath)
+        } catch { // TODO: ?
             delegate?.walletBackendReceivedUnknownMessage(self, message: 
message)
         }
     }
     
-    private func sendRequest(request: WalletBackendRequest, completionHandler: 
@escaping (UInt, Data?, WalletBackendResponseError?) -> Void) {
+    private func sendRequest(request: WalletBackendRequest, completionHandler: 
@escaping (UInt, Date, Data?, WalletBackendResponseError?) -> Void) {
         // Encode the request and send it to the backend.
-        let id = requestsMade
-        do {
-            let full = FullRequest(operation: request.operation, id: id, args: 
request.args)
-//            symLog.log(full)
-            let encoded = try JSONEncoder().encode(full)
-            guard let jsonString = String(data: encoded, encoding: .utf8) else 
{ throw WalletBackendError.serializationError }
-            completions[id] = completionHandler
-            requestsMade += 1
-            symLog.log(jsonString)
-            quickjs.sendMessage(message: jsonString)
-        } catch {
-            completionHandler(id, nil, WalletCore.serializeRequestError());
+        queue.async {
+            self.semaphore.wait()               // guard access to requestsMade
+            let requestId = self.requestsMade
+            let now = Date.now
+            do {
+                let full = FullRequest(operation: request.operation, id: 
requestId, args: request.args)
+                //            symLog.log(full)
+                let encoded = try JSONEncoder().encode(full)
+                guard let jsonString = String(data: encoded, encoding: .utf8) 
else { throw WalletBackendError.serializationError }
+                self.completions[requestId] = (now, completionHandler)
+                self.requestsMade += 1
+                self.semaphore.signal()         // free requestsMade
+                self.symLog.log(jsonString)
+                self.quickjs.sendMessage(message: jsonString)
+            } catch {       // call completion
+                self.semaphore.signal()
+                completionHandler(requestId, now, nil, 
WalletCore.serializeRequestError());
+            }
         }
     }
 }
@@ -219,27 +309,44 @@ extension WalletCore {
         let reqData = WalletBackendRequest(operation: request.operation(),
                                            args: AnyEncodable(request.args()))
         return try await withCheckedThrowingContinuation { continuation in
-            sendRequest(request: reqData) { id, result, error in
+            sendRequest(request: reqData) { requestId, timeSent, result, error 
in
+                let timeUsed = Date.now - timeSent
+                let millisecs = timeUsed.milliseconds
+print("Request \(requestId) took \(millisecs) ms")
+                var err: Error? = nil
                 if let json = result {
                     do {
                         let decoded = try 
JSONDecoder().decode(T.Response.self, from: json)
-                        continuation.resume(returning: (decoded, id))
-                    } catch {
+                        continuation.resume(returning: (decoded, requestId))
+                        return
+                    } catch DecodingError.dataCorrupted(let context) {
+                        print(context)
+                    } catch DecodingError.keyNotFound(let key, let context) {
+                        print("Key '\(key)' not found:", 
context.debugDescription)
+                        print("codingPath:", context.codingPath)
+                    } catch DecodingError.valueNotFound(let value, let 
context) {
+                        print("Value '\(value)' not found:", 
context.debugDescription)
+                        print("codingPath:", context.codingPath)
+                    } catch DecodingError.typeMismatch(let type, let context) {
+                        print("Type '\(type)' mismatch:", 
context.debugDescription)
+                        print("codingPath:", context.codingPath)
+                    } catch {       // rethrows
                         if let jsonString = String(data: json, encoding: 
.utf8) {
                             self.symLog.log(jsonString)       // TODO: .error
                         } else {
                             self.symLog.log(json)       // TODO: .error
                         }
-                        continuation.resume(throwing: error)
+                        err = error     // this will be thrown
                     }
                 } else {
                     if let error = error {
-                        self.lastError = FullError(type: "error", operation: 
request.operation(), id: id, error: error)
+                        self.lastError = FullError(type: "error", operation: 
request.operation(), id: requestId, error: error)
                     } else {
                         self.lastError = nil
                     }
-                    continuation.resume(throwing: 
WalletBackendError.walletCoreError)
+                    err = WalletBackendError.walletCoreError
                 }
+                continuation.resume(throwing: err ?? 
TransactionDecodingError.invalidStringValue)
             }
         }
     }
diff --git a/TalerWallet1/Controllers/Controller.swift 
b/TalerWallet1/Controllers/Controller.swift
index 8e10ec6..9a3db72 100644
--- a/TalerWallet1/Controllers/Controller.swift
+++ b/TalerWallet1/Controllers/Controller.swift
@@ -1,19 +1,9 @@
 /*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
  */
 import Foundation
+import SwiftUI
 import SymLog
 
 enum BackendState {
@@ -30,52 +20,35 @@ enum UrlCommand {
     case pay
 }
 
+// MARK: -
 class Controller: ObservableObject {
-    public static var shared = Controller()
+    public static let shared = Controller()
     private let symLog = SymLogC()
 
-    @Published var backendState: BackendState = .none
-
-    var walletCore: WalletCore
-    var exchangeModel: ExchangeModel
-    var balancesModel: BalancesModel
-    var transactionsModel: TransactionsModel
-    var pendingModel: PendingModel
-    var withdrawURIModel: WithdrawURIModel
-    var paymentURIModel: PaymentURIModel
-//    @Published var withdrawTestModel: WithdrawTestModel
+    @Published var backendState: BackendState = .none       // only used for 
launch animation
 
     var messageForSheet: String? = nil
 
     init() {
-        symLog.log("init wallet-core")
-        walletCore = try! WalletCore()          // will (and should) crash on 
failure
-        symLog.log("wallet-core done")
-        exchangeModel = ExchangeModel(walletCore: walletCore)
-        balancesModel = BalancesModel(walletCore: walletCore)
-        transactionsModel = TransactionsModel(walletCore: walletCore)
-        pendingModel = PendingModel(walletCore: walletCore)
-        withdrawURIModel = WithdrawURIModel(walletCore: walletCore)
-        paymentURIModel = PaymentURIModel(walletCore: walletCore)
-//      withdrawTestModel = WithdrawTestModel(walletCore: walletCore)
-        symLog.log("models inited")
         backendState = .instantiated
     }
 
-    @MainActor func initWalletCore() async throws {
+    @MainActor func initWalletCoreM()
+      async throws {           // M for MainActor
         if backendState == .instantiated {
             backendState = .initing
             do {
-                let walletInitModel = WalletInitModel(walletCore: walletCore)
-                let versionInfo = try await walletInitModel.initWallet()
-                walletCore.versionInfo = versionInfo
-                backendState = .ready               // dismiss the launch 
animation
-            } catch {
-                backendState = .error
+                let walletInitModel = WalletInitModel()
+                let versionInfo = try await walletInitModel.initWalletT()
+                WalletCore.shared.versionInfo = versionInfo
+                backendState = .ready                       // dismiss the 
launch animation
+            } catch {       // rethrows
+                symLog.log(error.localizedDescription)
+                backendState = .error                       // TODO: ❗️Yikes 
app cannot continue
                 throw error
             }
         } else {
-            symLog.log("Yikes\(logSymbol(-1)) wallet-core already 
initialized")     // TODO: .warning
+            symLog.log("Yikes❗️ wallet-core already initialized")     // TODO: 
.warning
         }
     }
 }
@@ -112,7 +85,7 @@ extension Controller {
     func talerScheme(_ url:URL,_ uncrypted: Bool = false) -> UrlCommand {
         guard let command = url.host else {return UrlCommand.unknown}
         if uncrypted {
-            print("uncrypted")
+            symLog.log("uncrypted: taler://\(command)")
             // TODO: uncrypted
         }
         switch command {
diff --git a/TalerWallet1/Controllers/DebugViewC.swift 
b/TalerWallet1/Controllers/DebugViewC.swift
new file mode 100644
index 0000000..828ba08
--- /dev/null
+++ b/TalerWallet1/Controllers/DebugViewC.swift
@@ -0,0 +1,163 @@
+/* MIT License
+ * Copyright (c) 2023 Marc Stibane
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to 
deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in 
all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 
THE
+ * SOFTWARE.
+ */
+import SwiftUI
+import SymLog
+
+// Numbering Scheme for Views
+// MARK: - Main View
+public let VIEW_EMPTY = 10                                          // 10 
WalletEmptyView
+public let VIEW_BALANCES = VIEW_EMPTY + 1                           // 11 
BalancesListView
+public let VIEW_SETTINGS = VIEW_BALANCES + 1                        // 12 
SettingsView
+public let VIEW_PENDING = VIEW_SETTINGS + 1                         // 13 
PendingOpsListView
+public let VIEW_EXCHANGES = VIEW_PENDING + 1                        // 14 
ExchangeListView
+
+// MARK: Transactions
+public let VIEW_TRANSACTIONLIST = VIEW_EMPTY + 10                   // 20 
TransactionsListView
+public let VIEW_TRANSACTIONDETAIL = VIEW_TRANSACTIONLIST + 1        // 21 
TransactionDetail
+
+
+
+// MARK: - Manual Withdrawal (from ExchangeList)
+// receive coins from bank ==> shows IBAN + Purpose for manual wire transfer
+public let VIEW_WITHDRAWAL = VIEW_TRANSACTIONLIST + 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 ExchangeList)
+// send coins back to bank account ==> orders exchange to make the wire 
transfer
+public let VIEW_DEPOSIT = VIEW_WITHDRAWAL + 10                      // 40 
Deposit Coins
+//public let VIEW_DEPOSIT_TOS                                       // 41 - 
user already accepted the ToS at withdrawal, invoice or receive
+public let VIEW_DEPOSIT_ACCEPT = VIEW_DEPOSIT + 2                   // 42
+
+// MARK: P2P Invoice (from Balances)
+// pull credit from another wallet ==> shows QR code to be scanned / link to 
be sent by mail or messenger
+public let VIEW_INVOICE_P2P = VIEW_DEPOSIT + 10                     // 50 
Invoice Amount
+public let VIEW_INVOICE_TOS = VIEW_INVOICE_P2P + 1                  // 51 
Invoice ToS
+public let VIEW_INVOICE_PURPOSE = VIEW_INVOICE_TOS + 1              // 52 
Invoice Purpose
+
+// MARK: P2P Send Coins (from Balances)
+// push debit to another wallet ==> shows QR code to be scanned / link to be 
sent by mail or messenger
+public let VIEW_SEND_P2P = VIEW_INVOICE_P2P + 10                    // 60 Send 
Coins
+//public let VIEW_SEND_TOS                                          // 61 - 
user already accepted the ToS at withdrawal, invoice or receive
+public let VIEW_SEND_PURPOSE = VIEW_SEND_P2P + 2                    // 62
+
+
+
+// MARK: - Bank-Integrated Withdrawal
+// openURL (Link or scan QR) ==> obtains coins from bank
+public let SHEET_WITHDRAWAL = VIEW_WITHDRAWAL + 100                 // 130 
WithdrawURIView
+public let SHEET_WITHDRAW_TOS = SHEET_WITHDRAWAL + 1                // 131 
WithdrawTOSView
+public let SHEET_WITHDRAW_ACCEPT = SHEET_WITHDRAW_TOS + 1           // 132 
WithdrawAcceptView
+public let SHEET_WITHDRAW_CONFIRM = SHEET_WITHDRAW_ACCEPT + 1       // 133 
waiting for bank confirmation
+
+// MARK: Merchant Payment
+// openURL (Link, NFC or scan QR) ==> pays merchant
+public let SHEET_PAYMENT = SHEET_WITHDRAWAL + 10                    // 140 Pay 
Merchant
+
+// MARK: Reward - Receive Coins (from merchant)
+// openURL (Link, NFC or scan QR) ==> receive coins from merchant
+public let SHEET_RCV_REWARD = SHEET_PAYMENT + 10                    // 150 
Receive Reward
+
+// MARK: P2P Pay Invoice
+// p2p pull debit - openURL (Link or scan QR)
+public let SHEET_PAY_P2P = SHEET_RCV_REWARD + 10                    // 160 Pay 
P2P Invoice
+
+// MARK: P2P Receive Coins
+// p2p push credit - openURL (Link or scan QR)
+public let SHEET_RCV_P2P = SHEET_PAY_P2P + 10                       // 170 
Receive P2P Coins
+
+//public let SHEET_REFUND =
+
+
+// MARK: -
+struct DebugViewV: View {
+    private let symLog = SymLogV()
+    @EnvironmentObject private var debugViewC: DebugViewC
+
+    var body: some View {
+#if DEBUG
+        let _ = Self._printChanges()
+        let _ = symLog.vlog()       // just to get the # to compare it with 
.onAppear & onDisappear
+#endif
+        let viewIDString = debugViewC.viewID > 0 ? String(debugViewC.viewID)
+                                                 : ""
+        HStack {
+            Spacer()
+            Spacer()
+            Text(viewIDString)
+                .font(.caption2)
+                .foregroundColor(.red)
+            Spacer()
+        }
+        .edgesIgnoringSafeArea(.top)
+    }
+}
+// MARK: -
+class DebugViewC: ObservableObject {
+    private let symLog = SymLogC()                         // 0 to switch off 
viewID change logging
+    public static let shared = DebugViewC()
+    @AppStorage("developerMode") var developerMode: Bool = false
+
+    @Published var viewID: Int = 0
+    @Published var sheetID: Int = 0
+
+    @MainActor func setViewID(_ newID: Int) -> Void {
+        if developerMode {
+            if viewID == 0 {
+                symLog.log("switching ON, \(newID)")
+                viewID = newID                              // publish ON
+            } else if viewID != newID {
+                symLog.log("switching from \(viewID) to \(newID)")
+                viewID = newID                              // publish new 
viewID
+            } else {
+                symLog.log("\(newID) stays")
+                // don't set viewID to the same value, it would just trigger 
an unneccessary redraw
+            }
+        } else if viewID > 0 {
+            symLog.log("switching OFF, will not use \(newID)")
+            viewID = 0                                      // publish OFF
+        } else {
+            symLog.log("off, will not use \(newID)")
+            // don't set viewID from 0 to 0 again, it would just trigger an 
unneccessary redraw
+        }
+    }
+    
+    @MainActor func setSheetID(_ newID: Int) -> Void {
+        if developerMode {
+            if sheetID != newID {
+                symLog.log("switching from \(sheetID) to \(newID)")
+                sheetID = newID                             // publish new 
sheetID
+            } else {
+                symLog.log("\(newID) stays")
+                // don't set sheetID to the same value, it would just trigger 
an unneccessary redraw
+            }
+        } else if sheetID > 0 {
+            // might happen after switching DevMode off, if sheetID still has 
the old value of the last sheet
+            symLog.log("switching OFF, will not use \(newID)")
+            sheetID = 0                                     // publish OFF
+        } else {
+            symLog.log("off, will not use \(newID)")
+            // don't set sheetID from 0 to 0 again, it would just trigger an 
unneccessary redraw
+        }
+    }
+
+}
diff --git a/TalerWallet1/Controllers/PublicConstants.swift 
b/TalerWallet1/Controllers/PublicConstants.swift
new file mode 100644
index 0000000..386f6f6
--- /dev/null
+++ b/TalerWallet1/Controllers/PublicConstants.swift
@@ -0,0 +1,35 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import Foundation
+
+public let DEMOBANK = "https://bAnK.dEmO.tAlEr.nEt";             // should be 
weird to read, but still work
+public let DEMOSHOP = "https://shop.demo.taler.net";
+//public let DEMOEXCHANGE = "https://eXcHaNgE.dEmO.tAlEr.nEt";
+public let DEMOEXCHANGE = "https://exchange.demo.taler.net";
+public let DEMO_AGE_EXCHANGE = "https://exchange-age.taler.ar";
+public let DEMO_EXP_EXCHANGE = "https://exchange-expensive.taler.ar";
+public let DEMOCURRENCY = "KUDOS"
+//public let LONGCURRENCY = "gold-pressed Latinum"                // 20 
characters, with dash and space
+public let LONGCURRENCY = "GOLDLATINUM"                         // 11 
characters, no dash, no space
+
+// MARK: - Keys used in JSON
+
+public let EXCHANGEBASEURL = "exchangeBaseUrl"
+public let TALERURI = "talerUri"
+
+public let TRANSACTIONTRANSITION = "transactionTransition"
+
+extension Notification.Name {
+    static let TransactionStateTransition = 
Notification.Name(TransactionTransition.TransitionType.transition.rawValue)
+    static let ExchangeAdded = Notification.Name("exchange-added")
+    static let PendingOperationProcessed = 
Notification.Name("pending-operation-processed")
+    static let ReserveNotYetFound = Notification.Name("reserve-not-yet-found")
+//    static let WithdrawalGroupBankConfirmed = 
Notification.Name("withdrawal-group-bank-confirmed")
+//    static let WithdrawalGroupReserveReady = 
Notification.Name("withdrawal-group-reserve-ready")
+//    static let WithdrawGroupFinished = 
Notification.Name("withdraw-group-finished")
+//    static let PayOperationSuccess = 
Notification.Name("pay-operation-success")
+    static let ProposalAccepted = Notification.Name("proposal-accepted")
+    static let ProposalDownloaded = Notification.Name("proposal-downloaded")
+}
diff --git a/TalerWallet1/Controllers/TalerWallet1App.swift 
b/TalerWallet1/Controllers/TalerWallet1App.swift
index 1eac830..98593ce 100644
--- a/TalerWallet1/Controllers/TalerWallet1App.swift
+++ b/TalerWallet1/Controllers/TalerWallet1App.swift
@@ -1,17 +1,12 @@
 /*
- * This file is part of GNU Taler
- * (C) 2021 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+/**
+ * Main app entry point
  *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ * @author Marc Stibane
+ * @author Jonathan Buchanan
  */
 import BackgroundTasks
 import SwiftUI
@@ -19,11 +14,15 @@ import SymLog
 
 @main
 struct TalerWallet1App: App {
-    private let symLog = SymLogV(0)
+    private let symLog = SymLogV()
     @Environment(\.scenePhase) private var phase
+    @StateObject private var viewState = ViewState.shared         // 
popToRootView()
+    @State private var isActive = true
 
+    private let walletCore = WalletCore.shared
     // our main controller
-    @StateObject private var controller = Controller.shared
+    private let controller = Controller.shared
+    private let debugViewC = DebugViewC.shared
 
     func scheduleAppRefresh() {
         let request = BGAppRefreshTaskRequest(identifier: "net.taler.refresh")
@@ -33,21 +32,41 @@ struct TalerWallet1App: App {
 
     var body: some Scene {
         WindowGroup {
-            symLog { ContentView()
-                    .environmentObject(controller)
-                        /// external events are taler:// or payto:// URLs 
passed to this app
-                        /// we handle them in .onOpenURL in ContentView.swift
-                    .handlesExternalEvents(preferring: ["*"], allowing: ["*"])
-                    .task {
-                        symLog.log("task -> initWalletCore")
-                        try! await controller.initWalletCore()      // will 
(and should) crash on failure
-                        symLog.log("task done")
-                    }
-            }
+            MainView()
+                .environmentObject(debugViewC)      // change viewID / sheetID
+                .environmentObject(viewState)       // popToRoot
+                .environmentObject(controller)
+                    /// external events are taler:// or payto:// URLs passed 
to this app
+                    /// we handle them in .onOpenURL in MainView.swift
+                .handlesExternalEvents(preferring: ["*"], allowing: ["*"])
+                .task {
+                    symLog.log("task -> initWalletCore")
+                    try! await controller.initWalletCoreM()      // will (and 
should) crash on failure
+                    symLog.log("task -> initWalletCore done")
+                }
+                .onReceive(NotificationCenter.default.publisher(for: 
UIApplication.didBecomeActiveNotification, object: nil)) { _ in
+                    print("❗️App Did Become Active notification")
+                }
+                .onReceive(NotificationCenter.default.publisher(for: 
UIApplication.willResignActiveNotification, object: nil)) { _ in
+                    print("❗️App Will Resign notification")
+                    isActive = false
+                }
+                .onReceive(NotificationCenter.default.publisher(for: 
UIApplication.willEnterForegroundNotification, object: nil)) { _ in
+                    print("❗️App Will Enter Foreground notification")
+                    isActive = true
+                }
+                .onReceive(NotificationCenter.default.publisher(for: 
UIApplication.willTerminateNotification, object: nil)) { _ in
+                    print("❗️App Will Terminate notification")
+                }
+
         }
         .onChange(of: phase) { newPhase in
             switch newPhase {
-                case .background: scheduleAppRefresh()
+                case .active:
+                    print("❗️ .onChange(of: phase) ==> newPhase: Active")
+                case .background:
+                    print("❗️ .onChange(of: phase) ==> newPhase: Background)")
+                    scheduleAppRefresh()
                 default: break
             }
         }
@@ -76,3 +95,15 @@ struct TalerWallet1App: App {
 
     }
 }
+
+final class ViewState : ObservableObject {
+    static let shared = ViewState()
+    @Published var rootViewId = UUID()
+
+    public func popToRootView() -> Void {
+        let _ = symLog()
+        rootViewId = UUID() // setting a new ID will cause tableView 
popToRootView behaviour
+    }
+
+    private init() { }
+}
diff --git a/TalerWallet1/Helper/AnyTransition+backslide.swift 
b/TalerWallet1/Helper/AnyTransition+backslide.swift
new file mode 100644
index 0000000..3cd1d81
--- /dev/null
+++ b/TalerWallet1/Helper/AnyTransition+backslide.swift
@@ -0,0 +1,30 @@
+/* MIT License
+ * Copyright (c) 2021 noranraskin
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to 
deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in 
all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 
THE
+ * SOFTWARE.
+ */
+import SwiftUI
+
+extension AnyTransition {
+    static var backslide: AnyTransition {
+        AnyTransition.asymmetric(
+            insertion: .move(edge: .trailing),
+            removal: .move(edge: .leading))
+    }
+}
diff --git a/TalerWallet1/Helper/CurrencyFormatter.swift 
b/TalerWallet1/Helper/CurrencyFormatter.swift
new file mode 100644
index 0000000..42f391b
--- /dev/null
+++ b/TalerWallet1/Helper/CurrencyFormatter.swift
@@ -0,0 +1,27 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import Foundation
+import taler_swift
+
+public class CurrencyFormatter: NumberFormatter {
+    public static var shared = CurrencyFormatter()
+
+    private override convenience init() {
+        self.init(fractionDigits: 2)
+    }
+
+    public init(fractionDigits: Int) {
+        super.init()
+        self.numberStyle = .decimal // currency could be changed by user
+        self.minimumFractionDigits = fractionDigits
+        self.maximumFractionDigits = fractionDigits
+        self.usesGroupingSeparator = true
+        self.locale = Locale.current
+    }
+
+    required init?(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+}
diff --git a/TalerWallet1/Helper/EqualIconWidthDomain.swift 
b/TalerWallet1/Helper/EqualIconWidthDomain.swift
new file mode 100644
index 0000000..e8c1e74
--- /dev/null
+++ b/TalerWallet1/Helper/EqualIconWidthDomain.swift
@@ -0,0 +1,141 @@
+/* MIT License
+ * Copyright (c) 2021 rob mayoff
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to 
deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in 
all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 
THE
+ * SOFTWARE.
+ */
+import SwiftUI
+
+fileprivate struct IconWidthKey: PreferenceKey {
+    static var defaultValue: CGFloat? { nil }
+
+    static func reduce(value: inout CGFloat?, nextValue: () -> CGFloat?) {
+        switch (value, nextValue()) {
+            case (nil, let next): value = next
+            case (_, nil): break
+            case (.some(let current), .some(let next)): value = max(current, 
next)
+        }
+    }
+}
+
+extension IconWidthKey: EnvironmentKey { }
+
+extension EnvironmentValues {
+    fileprivate var iconWidth: CGFloat? {
+        get { self[IconWidthKey.self] }
+        set { self[IconWidthKey.self] = newValue }
+    }
+}
+
+fileprivate struct IconWidthModifier: ViewModifier {
+    @Environment(\.iconWidth) var width
+
+    func body(content: Content) -> some View {
+        content
+            .background(GeometryReader { proxy in
+                Color.clear
+                    .preference(key: IconWidthKey.self, value: 
proxy.size.width)
+            })
+            .frame(width: width)
+    }
+}
+
+struct EqualIconWidthLabelStyle: LabelStyle {
+    func makeBody(configuration: Configuration) -> some View {
+        HStack {
+            configuration.icon.modifier(IconWidthModifier())
+            configuration.title //(alignment: .leading)
+                .multilineTextAlignment(.leading)
+        }
+    }
+}
+
+struct EqualIconWidthDomain<Content: View>: View {
+    let content: Content
+    @State var iconWidth: CGFloat? = nil
+
+    init(@ViewBuilder _ content: () -> Content) {
+        self.content = content()
+    }
+
+    var body: some View {
+        content
+            .environment(\.iconWidth, iconWidth)
+            .onPreferenceChange(IconWidthKey.self) { self.iconWidth = $0 }
+            .labelStyle(EqualIconWidthLabelStyle())
+    }
+}
+// MARK: -
+#if DEBUG
+struct Demo1View: View {
+    var body: some View {
+        VStack(alignment: .leading) {
+            VStack(alignment: .leading) {
+                Label("People", systemImage: "person.3")
+                Label("Star", systemImage: "star")
+                Label("This is a plane", systemImage: "airplane")
+            }
+            .padding()
+            EqualIconWidthDomain {
+                VStack(alignment: .leading) {
+                    Label("People", systemImage: "person.3")
+                    Label("Star", systemImage: "star")
+                    Label("This is a plane", systemImage: "airplane")
+                }
+            }
+        }
+    }
+}
+
+struct Demo1_Previews: PreviewProvider {
+    static var previews: some View {
+        Demo1View()
+    }
+}
+
+
+struct FancyView: View {
+    var body: some View {
+        EqualIconWidthDomain {
+            VStack {
+                Text("Le Menu")
+                    .font(.caption)
+                Divider()
+                HStack {
+                    VStack(alignment: .leading) {
+                        Label(
+                            title: { Text("Strawberry") },
+                            icon: { Text("🍓") })
+                        Label("Money", systemImage: "banknote")
+                    }
+                    VStack(alignment: .leading) {
+                        Label("People", systemImage: "person.3")
+                        Label("Star", systemImage: "star")
+                    }
+                }
+            }
+        }
+    }
+}
+
+struct Demo2_Previews: PreviewProvider {
+    static var previews: some View {
+        FancyView()
+    }
+}
+#endif
diff --git a/TalerWallet1/Helper/KeyboardResponder.swift 
b/TalerWallet1/Helper/KeyboardResponder.swift
new file mode 100644
index 0000000..c5d9cde
--- /dev/null
+++ b/TalerWallet1/Helper/KeyboardResponder.swift
@@ -0,0 +1,45 @@
+//  MIT License
+//  Copyright © Nicolai Harbo
+//
+//  Permission is hereby granted, free of charge, to any person obtaining a 
copy of this software
+//  and associated documentation files (the "Software"), to deal in the 
Software without restriction,
+//  including without limitation the rights to use, copy, modify, merge, 
publish, distribute,
+//  sublicense, and/or sell copies of the Software, and to permit persons to 
whom the Software is
+//  furnished to do so, subject to the following conditions:
+//
+//  The above copyright notice and this permission notice shall be included in 
all copies or
+//  substantial portions of the Software.
+//
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
IMPLIED, INCLUDING
+//  BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 
PARTICULAR PURPOSE AND
+//  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 
LIABLE FOR ANY CLAIM,
+//  DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 
OTHERWISE, ARISING FROM,
+//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 
THE SOFTWARE.
+//
+import Combine
+import UIKit
+
+public final class KeyboardResponder: ObservableObject {
+
+    @Published public var keyboardHeight: CGFloat = 0
+    var showCancellable: AnyCancellable?
+    var hideCancellable: AnyCancellable?
+
+    public init() {
+        showCancellable = NotificationCenter.default.publisher(for: 
UIResponder.keyboardWillShowNotification)
+            .map { notification in
+                
(notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? 
NSValue)?.cgRectValue.height ?? 0.0
+            }
+            .receive(on: DispatchQueue.main)
+            .sink(receiveValue: { height in
+//                print("keyboard height: \(height)")
+                self.keyboardHeight = height
+            })
+
+        hideCancellable = NotificationCenter.default.publisher(for: 
UIResponder.keyboardWillHideNotification)
+            .receive(on: DispatchQueue.main)
+            .sink(receiveValue: { _ in
+                self.keyboardHeight = 0
+            })
+    }
+}
diff --git a/TalerWallet1/Helper/LocalizedAlertError.swift 
b/TalerWallet1/Helper/LocalizedAlertError.swift
new file mode 100644
index 0000000..00070e5
--- /dev/null
+++ b/TalerWallet1/Helper/LocalizedAlertError.swift
@@ -0,0 +1,54 @@
+//  MIT License
+//  Copyright © Antoine van der Lee
+//
+//  Permission is hereby granted, free of charge, to any person obtaining a 
copy of this software
+//  and associated documentation files (the "Software"), to deal in the 
Software without restriction,
+//  including without limitation the rights to use, copy, modify, merge, 
publish, distribute,
+//  sublicense, and/or sell copies of the Software, and to permit persons to 
whom the Software is
+//  furnished to do so, subject to the following conditions:
+//
+//  The above copyright notice and this permission notice shall be included in 
all copies or
+//  substantial portions of the Software.
+//
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
IMPLIED, INCLUDING
+//  BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 
PARTICULAR PURPOSE AND
+//  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 
LIABLE FOR ANY CLAIM,
+//  DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 
OTHERWISE, ARISING FROM,
+//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 
THE SOFTWARE.
+//
+import SwiftUI
+
+
+struct LocalizedAlertError: LocalizedError {
+    let underlyingError: LocalizedError
+    var errorDescription: String? {
+        underlyingError.errorDescription
+    }
+    var recoverySuggestion: String? {
+        underlyingError.recoverySuggestion
+    }
+
+    init?(error: Error?) {
+        guard let localizedError = error as? LocalizedError else { return nil }
+        underlyingError = localizedError
+    }
+}
+
+extension View {
+
+    @ViewBuilder
+    func errorAlert(error: Binding<Error?>,
+              buttonTitle: LocalizedStringKey = "xloc.generic.ok",
+                   action: (() -> Void)? = nil) -> some View
+    {
+        let localizedAlertError = LocalizedAlertError(error: 
error.wrappedValue)
+        alert(isPresented: .constant(localizedAlertError != nil), error: 
localizedAlertError) { _ in
+            Button(buttonTitle) {
+                action?()
+                error.wrappedValue = nil
+            }
+        } message: { error in
+            Text(error.failureReason ?? error.recoverySuggestion ?? "")
+        }
+    }
+}
diff --git a/TalerWallet1/Helper/PublicConstants.swift 
b/TalerWallet1/Helper/PublicConstants.swift
deleted file mode 100644
index 01680a0..0000000
--- a/TalerWallet1/Helper/PublicConstants.swift
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-import Foundation
-
-public let EXCHANGEBASEURL = "exchangeBaseUrl"
-
-public let WITHDRAWAL = "withdrawal"
-public let PAYMENT = "payment"
-public let REFUND = "refund"
-public let TIP = "tip"
-public let REFRESH = "refresh"
diff --git a/TalerWallet1/Helper/TalerDater.swift 
b/TalerWallet1/Helper/TalerDater.swift
index 5a7abdb..de694a7 100644
--- a/TalerWallet1/Helper/TalerDater.swift
+++ b/TalerWallet1/Helper/TalerDater.swift
@@ -1,23 +1,12 @@
 /*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
  */
 import Foundation
 import taler_swift
 
 public class TalerDater: DateFormatter {
-    public static var shared = TalerDater()
+    public static let shared = TalerDater()
 
     static func relativeDate(from: TimeInterval) -> String? {
         if from > 0 {       // transactions should always be in the past
@@ -42,7 +31,7 @@ public class TalerDater: DateFormatter {
             if day < 14 { return "More than a week ago" }
             // will fall thru...
             return nil
-        } else {            // Yikes! transaction date is in the future
+        } else {            // Yikes❗️ transaction date is in the future
             return nil
         }
     }
@@ -78,7 +67,7 @@ public class TalerDater: DateFormatter {
                 }
             }
             return shared.string(from: date)
-        } catch {
+        } catch {       // Never
             return "Never"
         }
     }
@@ -100,3 +89,19 @@ public class TalerDater: DateFormatter {
         fatalError("init(coder:) has not been implemented")
     }
 }
+
+extension Date {
+    static func - (lhs: Date, rhs: Date) -> TimeInterval {
+        return lhs.timeIntervalSinceReferenceDate - 
rhs.timeIntervalSinceReferenceDate
+    }
+}
+extension TimeInterval {
+
+    var seconds: Int {
+        return Int(self.rounded())
+    }
+
+    var milliseconds: Int {
+        return Int(self * 1000)
+    }
+}
diff --git a/TalerWallet1/Helper/TalerStrings.swift 
b/TalerWallet1/Helper/TalerStrings.swift
index b642798..d1443b4 100644
--- a/TalerWallet1/Helper/TalerStrings.swift
+++ b/TalerWallet1/Helper/TalerStrings.swift
@@ -1,17 +1,6 @@
 /*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
  */
 import Foundation
 
diff --git a/TalerWallet1/Helper/URL+iban.swift 
b/TalerWallet1/Helper/URL+iban.swift
new file mode 100644
index 0000000..ca4689c
--- /dev/null
+++ b/TalerWallet1/Helper/URL+iban.swift
@@ -0,0 +1,37 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+
+extension URL: Identifiable {
+    public var id: URL {self}
+}
+
+extension URL {
+    init(_ string: StaticString) {
+        self.init(string: "\(string)")!
+    }
+
+    var iban: String? {
+        /// https://datatracker.ietf.org/doc/rfc8905/
+        /// payto://iban/DE75512108001245126199?amount=EUR:200.0&message=hello
+        if scheme == "payto" && host == "iban" {
+            return lastPathComponent
+        }
+        return nil
+    }
+
+    /// SwifterSwift: Dictionary of the URL's query parameters.
+    var queryParameters: [String: String]? {
+        guard let components = URLComponents(url: self, 
resolvingAgainstBaseURL: false),
+              let queryItems = components.queryItems else { return nil }
+
+        var items: [String: String] = [:]
+
+        for queryItem in queryItems {
+            items[queryItem.name] = queryItem.value
+        }
+        return items
+    }
+}
diff --git a/TalerWallet1/Helper/View+dismissTop.swift 
b/TalerWallet1/Helper/View+dismissTop.swift
index bf6721e..ffd9c31 100644
--- a/TalerWallet1/Helper/View+dismissTop.swift
+++ b/TalerWallet1/Helper/View+dismissTop.swift
@@ -1,20 +1,80 @@
-/*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
+//  MIT License
+//  Copyright © Nicolai Harbo
+//
+//  Permission is hereby granted, free of charge, to any person obtaining a 
copy of this software
+//  and associated documentation files (the "Software"), to deal in the 
Software without restriction,
+//  including without limitation the rights to use, copy, modify, merge, 
publish, distribute,
+//  sublicense, and/or sell copies of the Software, and to permit persons to 
whom the Software is
+//  furnished to do so, subject to the following conditions:
+//
+//  The above copyright notice and this permission notice shall be included in 
all copies or
+//  substantial portions of the Software.
+//
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
IMPLIED, INCLUDING
+//  BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 
PARTICULAR PURPOSE AND
+//  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 
LIABLE FOR ANY CLAIM,
+//  DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 
OTHERWISE, ARISING FROM,
+//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 
THE SOFTWARE.
+//
 import SwiftUI
 
+// John Sundell
+extension View {
+    func onNotification(
+        _ notificationName: Notification.Name,
+        perform action: @escaping () -> Void
+    ) -> some View {
+        onReceive(NotificationCenter.default
+                    .publisher(for: notificationName)
+        ) { _ in
+            action()
+        }
+    }
+    func onNotification(
+        _ notificationName: Notification.Name,
+        perform action: @escaping (_ notification: Notification) -> Void
+    ) -> some View {
+        onReceive(NotificationCenter.default
+            .publisher(for: notificationName)
+        ) { notification in
+            action(notification)
+        }
+    }
+
+    func onNotificationM(       // M for main thread
+        _ notificationName: Notification.Name,
+        perform action: @escaping () -> Void
+    ) -> some View {
+        onReceive(NotificationCenter.default
+                    .publisher(for: notificationName)
+                    .receive(on: RunLoop.main)
+        ) { _ in
+            action()
+        }
+    }
+
+    func onNotificationM(       // M for main thread
+        _ notificationName: Notification.Name,
+        perform action: @escaping (_ notification: Notification) -> Void
+    ) -> some View {
+        onReceive(NotificationCenter.default
+            .publisher(for: notificationName)
+            .receive(on: RunLoop.main)
+        ) { notification in
+            action(notification)
+        }
+    }
+
+    func onAppEnteredBackground(
+        perform action: @escaping () -> Void
+    ) -> some View {
+        onNotification(
+            UIApplication.didEnterBackgroundNotification,
+            perform: action
+        )
+    }
+}
+
 /// This is just a workaround for a SwiftUI bug
 /// A presented sheet (SwiftUI view) doesn't always close when calling 
"dismiss()" provided by @Environment(\.dismiss),
 /// so we are walking the view stack to find the top presentedViewController 
(UIKit) and dismiss it.
diff --git a/TalerWallet1/Helper/WalletColors.swift 
b/TalerWallet1/Helper/WalletColors.swift
new file mode 100644
index 0000000..1f037cf
--- /dev/null
+++ b/TalerWallet1/Helper/WalletColors.swift
@@ -0,0 +1,56 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+
+public struct WalletColors {
+
+    let tint = Color(.tintColor)
+    let gray1 = Color(.systemGray)
+    let gray2 = Color(.systemGray2)
+    let gray3 = Color(.systemGray3)
+    let gray4 = Color(.systemGray4)
+    let gray5 = Color(.systemGray5)
+    let gray6 = Color(.systemGray6)
+
+    func buttonForeColor(pressed: Bool, disabled: Bool, prominent: Bool = 
false) -> Color {
+            disabled ? gray2
+        : !prominent ? tint
+           : pressed ? gray6 : gray5
+    }
+
+    func buttonBackColor(pressed: Bool, disabled: Bool, prominent: Bool = 
false) -> Color {
+            disabled ? gray5
+         : prominent ? tint
+           : pressed ? gray5 : gray4
+    }
+
+    var backgroundColor: Color {
+        gray6
+    }
+
+    var sideBackground: Color {
+        gray5
+    }
+
+    var fieldForeground: Color {              // text color
+        Color.primary
+    }
+    var fieldBackground: Color {
+        Color(.systemBackground)
+    }
+
+    var uncompletedColor: Color {
+        gray1
+    }
+    func pendingColor(_ incoming: Bool) -> Color {
+        incoming ? Color("PendingIncoming")
+                 : Color("PendingOutgoing")
+    }
+    func transactionColor(_ incoming: Bool) -> Color {
+        incoming ? Color("Incoming")
+                 : Color("Outgoing")
+    }
+
+}
diff --git a/TalerWallet1/Views/Balances/BalancesModel.swift 
b/TalerWallet1/Model/BalancesModel.swift
similarity index 59%
rename from TalerWallet1/Views/Balances/BalancesModel.swift
rename to TalerWallet1/Model/BalancesModel.swift
index 80197d9..525aabf 100644
--- a/TalerWallet1/Views/Balances/BalancesModel.swift
+++ b/TalerWallet1/Model/BalancesModel.swift
@@ -1,25 +1,19 @@
 /*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
  */
 import Foundation
 import taler_swift
-import SymLog
 fileprivate let ASYNCDELAY: UInt = 0   //set e.g to 6 or 9 seconds for 
debugging
 
+// MARK: -
 class BalancesModel: WalletModel {
-    @Published var balances: [Balance]?                 // update view
+    @Published var balances: [Balance]
+
+    override init(_ symbol: Int = -1) {
+        balances = []                        // empty, but not nil
+        super.init(symbol)
+    }
 }
 // MARK: -
 /// A request to get the balances held in the wallet.
@@ -41,6 +35,7 @@ struct Balance: Decodable, Hashable {
     var pendingOutgoing: Amount
     var hasPendingTransactions: Bool
     var requiresUserInput: Bool
+    var scopeInfo: ScopeInfo
 
     public static func == (lhs: Balance, rhs: Balance) -> Bool {
         return lhs.available == rhs.available &&
@@ -61,13 +56,32 @@ struct Balance: Decodable, Hashable {
 // MARK: -
 extension BalancesModel {
     /// fetch Balances from Wallet-Core. No networking involved
-    @MainActor func fetchBalances() async throws {
+    @MainActor func fetchBalancesM()
+      async {          // M for MainActor
         do {
             let request = GetBalances()
-            let response = try await sendRequest(request, ASYNCDELAY)
-            balances = response.balances                // trigger view update 
in CurrenciesListView
+            let response = try await sendRequestM(request, ASYNCDELAY)
+            balances = response.balances                // trigger view update 
in BalancesListView
         } catch {
-            throw error
+            balances = []
+        }
+    }
+}
+
+// MARK: -
+extension BalancesModel {
+    private static var currencies: [String] = []            // names of 
currencies
+    private static var models: [BalancesModel] = []         // one model per 
currency
+
+    static func model(currency: String) -> BalancesModel {
+        if let index = BalancesModel.currencies.firstIndex(of:currency) {
+            let model = BalancesModel.models[index]
+            return model
+        } else {        // new currency
+            let model = BalancesModel()
+            BalancesModel.models.append(model)
+            BalancesModel.currencies.append(currency)
+            return model
         }
     }
 }
diff --git a/TalerWallet1/Views/Exchange/ExchangeModel.swift 
b/TalerWallet1/Model/ExchangeModel.swift
similarity index 63%
rename from TalerWallet1/Views/Exchange/ExchangeModel.swift
rename to TalerWallet1/Model/ExchangeModel.swift
index c16e720..fd2a5d5 100644
--- a/TalerWallet1/Views/Exchange/ExchangeModel.swift
+++ b/TalerWallet1/Model/ExchangeModel.swift
@@ -1,17 +1,6 @@
 /*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
  */
 import Foundation
 import taler_swift
@@ -19,36 +8,16 @@ import SymLog
 fileprivate let ASYNCDELAY: UInt = 0   //set e.g to 6 or 9 seconds for 
debugging
 
 class ExchangeModel: WalletModel {
-    @Published var exchanges: [Exchange]?
-}
-// MARK: -
-/// A request to list exchanges.
-fileprivate struct ListExchanges: WalletBackendFormattedRequest {
-    func operation() -> String { return "listExchanges" }
-    func args() -> Args { return Args() }
-
-    struct Args: Encodable {}           // no arguments needed
+    @Published var exchanges: [Exchange]
 
-    struct Response: Decodable {        // list of known exchanges
-        var exchanges: [Exchange]
+    override init(_ symbol: Int = -1) {
+        exchanges = []                        // empty, but not nil
+        super.init(symbol)
     }
 }
-
-/// A request to add an exchange.
-fileprivate struct AddExchange: WalletBackendFormattedRequest {
-    func operation() -> String { return "addExchange" }
-    func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl) }
-
-    var exchangeBaseUrl: String
-
-    struct Args: Encodable {
-        var exchangeBaseUrl: String
-    }
-
-    struct Response: Decodable {}
-}
 // MARK: -
-struct Exchange: Codable, Hashable {
+/// The result from wallet-core's ListExchanges
+struct Exchange: Codable, Hashable, Identifiable {
     static func == (lhs: Exchange, rhs: Exchange) -> Bool {
         return lhs.exchangeBaseUrl == rhs.exchangeBaseUrl &&
         lhs.exchangeStatus == rhs.exchangeStatus &&
@@ -57,13 +26,16 @@ struct Exchange: Codable, Hashable {
 
     var exchangeBaseUrl: String
     var currency: String?
-    var tosStatus: String
     var paytoUris: [String]
+    var tosStatus: String
     var exchangeStatus: String
-    var permanent: Bool
     var ageRestrictionOptions: [Int]
+    var permanent: Bool
     var lastUpdateErrorInfo: ExchangeError?
 
+    var id: String {
+        exchangeBaseUrl
+    }
     var name: String? {
         if let url = URL(string: exchangeBaseUrl) {
             if let host = url.host {
@@ -73,43 +45,88 @@ struct Exchange: Codable, Hashable {
         return nil
     }
 }
+
 struct ExchangeError: Codable, Hashable {
     var error: HTTPError
 }
 
 struct HTTPError: Codable, Hashable {
     var code: Int
-    var requestUrl: String
+    var requestUrl: String?
     var hint: String
-    var requestMethod: String
+    var requestMethod: String?
     var httpStatusCode: Int?
+    var when: Timestamp?
+    var stack: String?
+}
+// MARK: -
+/// A request to list exchanges.
+fileprivate struct ListExchanges: WalletBackendFormattedRequest {
+    func operation() -> String { return "listExchanges" }
+    func args() -> Args { return Args() }
+
+    struct Args: Encodable {}           // no arguments needed
+
+    struct Response: Decodable {        // list of known exchanges
+        var exchanges: [Exchange]
+    }
 }
 
+/// A request to add an exchange.
+fileprivate struct AddExchange: WalletBackendFormattedRequest {
+    func operation() -> String { return "addExchange" }
+    func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl) }
+
+    var exchangeBaseUrl: String
+
+    struct Args: Encodable {
+        var exchangeBaseUrl: String
+    }
+
+    struct Response: Decodable {}
+}
 // MARK: -
 extension ExchangeModel {
     /// ask wallet-core for its list of known exchanges
-    @MainActor func updateList() async throws {
+    @MainActor func updateListM()
+      async throws {       // M for MainActor
         do {
             let request = ListExchanges()
-            let response = try await sendRequest(request, ASYNCDELAY)
+            let response = try await sendRequestM(request, ASYNCDELAY)
             exchanges = response.exchanges              // trigger view update 
in ExchangeListView
-        } catch { // TODO: Error
+        } catch {       // rethrows
             symLog?.log(error.localizedDescription)
             throw error
         }
     }
 
-    /// add a new exchange with URL to wallet's list of known exchanges
+    /// add a new exchange with URL to the wallet's list of known exchanges
     func add(url: String) async throws  {
         do {
             symLog?.log("adding exchange: \(url)")       // TODO: notice
             let request = AddExchange(exchangeBaseUrl: url)
-            _ = try await sendRequest(request)
+            _ = try await sendRequestT(request)         // TODO: MainActor?
             symLog?.log("added exchange: \(url)")
-            try await updateList()
-        } catch { // TODO: Error
+            try await updateListM()
+        } catch {       // rethrows
             symLog?.log(error.localizedDescription)
             throw error
         }
     }
 }
+
+// MARK: -
+extension ExchangeModel {
+    private static var models: [ExchangeModel] = []     // a list of models 
even though I currently need only one
+
+    static func model() -> ExchangeModel {
+        if ExchangeModel.models.count > 0 {
+            let model = ExchangeModel.models[0]
+            return model
+        } else {        // new model
+            let model = ExchangeModel()
+            ExchangeModel.models.append(model)
+            return model
+        }
+    }
+}
diff --git a/TalerWallet1/Views/Payment/PaymentURIModel.swift 
b/TalerWallet1/Model/PaymentURIModel.swift
similarity index 77%
rename from TalerWallet1/Views/Payment/PaymentURIModel.swift
rename to TalerWallet1/Model/PaymentURIModel.swift
index 8fd4142..dea406c 100644
--- a/TalerWallet1/Views/Payment/PaymentURIModel.swift
+++ b/TalerWallet1/Model/PaymentURIModel.swift
@@ -1,17 +1,6 @@
 /*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
  */
 import Foundation
 import taler_swift
@@ -29,6 +18,9 @@ enum PaymentState {
 
 class PaymentURIModel: WalletModel {
     @Published var paymentState: PaymentState?
+    override init(_ symbol: Int = -1) {     // init with 0 to disable logging 
for this class
+        super.init(symbol)
+    }
 }
 
 
@@ -114,7 +106,7 @@ struct Extra: Codable {
 // MARK: -
 /// The result from PreparePayForUri
 struct PaymentDetailsForUri: Codable {
-    let status: String
+//    let status: String
     let amountRaw: Amount
     let amountEffective: Amount
     let noncePriv: String
@@ -134,7 +126,7 @@ fileprivate struct PreparePayForUri: 
WalletBackendFormattedRequest {
     }
 }
 // MARK: -
-/// The result from getPaymentDetailsForAmount
+/// The result from confirmPayForUri
 struct ConfirmPayResult: Decodable {
     var type: String
     var contractTerms: ContractTerms
@@ -155,29 +147,47 @@ fileprivate struct confirmPayForUri: 
WalletBackendFormattedRequest {
 extension PaymentURIModel {
     /// load payment details. Networking involved
     @MainActor
-    func preparePayForUri(_ talerPayUri: String) async throws -> 
PaymentDetailsForUri {
+    func preparePayForUriM(_ talerPayUri: String)       // M for MainActor
+      async throws -> PaymentDetailsForUri {
         do {
             paymentState = .waitingForUriDetails
             let request = PreparePayForUri(talerPayUri: talerPayUri)
-            let response = try await sendRequest(request, ASYNCDELAY)
+            let response = try await sendRequestM(request, ASYNCDELAY)      // 
TODO: MainActor ?
             paymentState = .receivedUriDetails
             return response
-        } catch {
+        } catch {       // rethrows
             paymentState = .error
             throw error
         }
     }
     @MainActor
-    func confirmPay(_ proposalId: String) async throws -> ConfirmPayResult {
+    func confirmPayM(_ proposalId: String)              // M for MainActor
+      async throws -> ConfirmPayResult {
         do {
             paymentState = .waitingForPaymentAck
             let request = confirmPayForUri(proposalId: proposalId)
-            let response = try await sendRequest(request, ASYNCDELAY)
+            let response = try await sendRequestM(request, ASYNCDELAY)      // 
TODO: MainActor ?
             paymentState = .receivedPaymentAck
             return response
-        } catch {
+        } catch {       // rethrows
             paymentState = .error
             throw error
         }
     }
 }
+
+// MARK: -
+extension PaymentURIModel {
+    private static var models: [PaymentURIModel] = []     // a list of models 
even though I currently need only one
+
+    static func model() -> PaymentURIModel {
+        if PaymentURIModel.models.count > 0 {
+            let model = PaymentURIModel.models[0]
+            return model
+        } else {        // new model
+            let model = PaymentURIModel()
+            PaymentURIModel.models.append(model)
+            return model
+        }
+    }
+}
diff --git a/TalerWallet1/Model/Peer2peerModel.swift 
b/TalerWallet1/Model/Peer2peerModel.swift
new file mode 100644
index 0000000..490915c
--- /dev/null
+++ b/TalerWallet1/Model/Peer2peerModel.swift
@@ -0,0 +1,130 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import Foundation
+import taler_swift
+import AnyCodable
+//import SymLog
+fileprivate let ASYNCDELAY: UInt = 0   //set e.g to 6 or 9 seconds for 
debugging
+
+class Peer2peerModel: WalletModel {
+    override init(_ symbol: Int = -1) {     // init with 0 to disable logging 
for this class
+        super.init(symbol)
+    }
+}
+// MARK: - PeerContractTerms
+struct PeerContractTerms: Codable {
+    let amount: Amount
+    let summary: String
+    let purse_expiration: Timestamp
+}
+// MARK: -
+/// The result from CheckPeerPushDebit
+struct CheckPeerPushDebitResponse: Codable {
+    let amountEffective: Amount
+    let amountRaw: Amount
+//    let maxExpirationDate: Timestamp          // TODO: limit expiration (30 
days or 7 days)
+}
+/// A request to check fees before sending coins to another wallet.
+fileprivate struct CheckPeerPushDebit: WalletBackendFormattedRequest {
+    typealias Response = CheckPeerPushDebitResponse
+    func operation() -> String { return "checkPeerPushDebit" }
+    func args() -> Args { return Args(amount: amount) }
+
+    var amount: Amount
+    struct Args: Encodable {
+        var amount: Amount
+    }
+}
+// MARK: -
+/// The result from CheckPeerPullCredit
+struct CheckPeerPullCreditResponse: Codable {
+    let scopeInfo: ScopeInfo?
+    let exchangeBaseUrl: String?
+    let amountEffective: Amount
+    let amountRaw: Amount
+}
+/// A request to check fees before invoicing another wallet.
+fileprivate struct CheckPeerPullCredit: WalletBackendFormattedRequest {
+    typealias Response = CheckPeerPullCreditResponse
+    func operation() -> String { return "checkPeerPullCredit" }
+    func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl, 
scopeInfo: scopeInfo, amount: amount) }
+
+    var exchangeBaseUrl: String?
+    var scopeInfo: ScopeInfo?
+    var amount: Amount
+    struct Args: Encodable {
+        var exchangeBaseUrl: String?
+        var scopeInfo: ScopeInfo?
+        var amount: Amount
+    }
+}
+// MARK: -
+/// The result from InitiatePeerPushDebit
+struct PeerPushResponse: Codable {
+    let contractPriv: String
+    let mergePriv: String
+    let pursePub: String
+    let exchangeBaseUrl: String
+    let talerUri: String
+    let transactionId: String
+}
+/// A request to send coins to another wallet.
+fileprivate struct InitiatePeerPushDebit: WalletBackendFormattedRequest {
+    typealias Response = PeerPushResponse
+    func operation() -> String { return "initiatePeerPushDebit" }
+    func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl,
+                                 partialContractTerms: partialContractTerms) }
+
+    var exchangeBaseUrl: String?
+    var partialContractTerms: PeerContractTerms
+    struct Args: Encodable {
+        var exchangeBaseUrl: String?
+        var partialContractTerms: PeerContractTerms
+    }
+}
+// MARK: -
+extension Peer2peerModel {
+    /// query exchange for fees (sending coins). Networking involved
+    @MainActor
+    func checkPeerPushDebitM(_ amount: Amount)       // M for MainActor
+      async throws -> CheckPeerPushDebitResponse {
+          let request = CheckPeerPushDebit(amount: amount)
+          let response = try await sendRequestM(request, ASYNCDELAY)
+          return response
+    }
+    /// query exchange for fees (invoice coins). Networking involved
+    @MainActor
+    func checkPeerPullCreditM(_ amount: Amount, exchangeBaseUrl: String?)      
 // M for MainActor
+    async throws -> CheckPeerPullCreditResponse {
+        let request = CheckPeerPullCredit(exchangeBaseUrl: exchangeBaseUrl, 
amount: amount)
+        let response = try await sendRequestM(request, ASYNCDELAY)
+        return response
+    }
+    /// generate peer-push. Networking involved
+    @MainActor
+    func initiatePeerPushDebitM(_ baseURL: String, terms: PeerContractTerms)   
    // M for MainActor
+      async throws -> PeerPushResponse {
+        let request = InitiatePeerPushDebit(exchangeBaseUrl: baseURL,
+                                       partialContractTerms: terms)
+        let response = try await sendRequestM(request, ASYNCDELAY)
+        return response
+    }
+}
+
+// MARK: -
+extension Peer2peerModel {
+    private static var models: [Peer2peerModel] = []     // a list of models 
even though I currently need only one
+
+    static func model() -> Peer2peerModel {
+        if Peer2peerModel.models.count > 0 {
+            let model = Peer2peerModel.models[0]
+            return model
+        } else {        // new model
+            let model = Peer2peerModel()
+            Peer2peerModel.models.append(model)
+            return model
+        }
+    }
+}
diff --git a/TalerWallet1/Model/PendingModel.swift 
b/TalerWallet1/Model/PendingModel.swift
new file mode 100644
index 0000000..764f799
--- /dev/null
+++ b/TalerWallet1/Model/PendingModel.swift
@@ -0,0 +1,78 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import Foundation
+import AnyCodable
+import taler_swift
+import SymLog
+fileprivate let ASYNCDELAY: UInt = 0   //set e.g to 6 or 9 seconds for 
debugging
+
+class PendingModel: WalletModel {
+    @Published var pendingOperations: [PendingOperation]
+
+    override init(_ symbol: Int = -1) {
+        pendingOperations = []                // empty, but not nil
+        super.init(symbol)
+    }
+}
+// MARK: -
+/// A request to list the backend's currently pending operations.
+fileprivate struct GetPendingOperations: WalletBackendFormattedRequest {
+    func operation() -> String { return "getPendingOperations" }
+    func args() -> Args { Args() }
+
+    struct Args: Encodable {}
+
+    struct Response: Decodable {
+        var pendingOperations: [PendingOperation]
+    }
+}
+// MARK: -
+struct PendingOperation: Codable, Hashable {
+    var type: String
+    var exchangeBaseUrl: String?
+    var id: String
+    var isLongpolling: Bool
+    var givesLifeness: Bool
+    var isDue: Bool
+    var timestampDue: Timestamp
+
+    public func hash(into hasher: inout Hasher) {
+        hasher.combine(type)
+        hasher.combine(exchangeBaseUrl)
+        hasher.combine(id)
+        hasher.combine(isLongpolling)
+        hasher.combine(givesLifeness)
+        hasher.combine(isDue)
+        hasher.combine(timestampDue)
+    }
+
+}
+// MARK: -
+extension PendingModel {
+    @MainActor func updateM()
+      async throws {   // M for MainActor
+        do {
+            let request = GetPendingOperations()
+            let response = try await sendRequestM(request, ASYNCDELAY)
+            pendingOperations = response.pendingOperations
+        }
+    }
+}
+
+// MARK: -
+extension PendingModel {
+    private static var models: [PendingModel] = []     // a list of models 
even though I currently need only one
+
+    static func model() -> PendingModel {
+        if PendingModel.models.count > 0 {
+            let model = PendingModel.models[0]
+            return model
+        } else {        // new model
+            let model = PendingModel()
+            PendingModel.models.append(model)
+            return model
+        }
+    }
+}
diff --git a/TalerWallet1/Model/ExchangeTestModel.swift 
b/TalerWallet1/Model/SettingsModel.swift
similarity index 51%
rename from TalerWallet1/Model/ExchangeTestModel.swift
rename to TalerWallet1/Model/SettingsModel.swift
index 98702d5..0172cf6 100644
--- a/TalerWallet1/Model/ExchangeTestModel.swift
+++ b/TalerWallet1/Model/SettingsModel.swift
@@ -1,61 +1,59 @@
 /*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
  */
 import Foundation
 import taler_swift
 import SymLog
 fileprivate let ASYNCDELAY: UInt = 0   //set e.g to 6 or 9 seconds for 
debugging
 
-fileprivate let DEMO_EXCHANGEBASEURL = "https://exchange.demo.taler.net/";
-fileprivate let DEMO_BANKBASEURL = "https://bank.demo.taler.net/";
-fileprivate let DEMO_BANKAPIBASEURL = 
"https://bank.demo.taler.net/demobanks/default/access-api/";
+fileprivate let DEMO_EXCHANGEBASEURL = DEMOEXCHANGE     // 
"https://exchange.demo.taler.net/";
+fileprivate let DEMO_BANKBASEURL     = DEMOBANK         // 
"https://bank.demo.taler.net/";
+fileprivate let DEMO_BANKAPIBASEURL  = DEMOBANK + 
"/demobanks/default/access-api/"
 fileprivate let DEMO_MERCHANTBASEURL = "https://backend.demo.taler.net/";
 fileprivate let DEMO_MERCHANTAUTHTOKEN = "secret-token:sandbox"
 
 // MARK: -
-class ExchangeTestModel: WalletModel {
+class SettingsModel: WalletModel {
+    override init(_ symbol: Int = -1) {     // init with 0 to disable logging 
for this class
+        super.init(symbol)
+    }
 }
 // MARK: -
-extension ExchangeTestModel {
-    @MainActor func loadTestKudos() async throws {
+extension SettingsModel {
+    @MainActor func loadTestKudosM()
+      async throws {          // M for MainActor
         do {
-            let amount = Amount(currency: "KUDOS", integer: 11, fraction: 0)
+            let amount = Amount(currency: DEMOCURRENCY, integer: 11, fraction: 
0)
             let request = WalletBackendWithdrawTestBalance(amount: amount,
                                                       bankBaseUrl: 
DEMO_BANKBASEURL,
                                                   exchangeBaseUrl: 
DEMO_EXCHANGEBASEURL,
                                              bankAccessApiBaseUrl: 
DEMO_BANKAPIBASEURL)
-            let response = try await sendRequest(request, ASYNCDELAY)
+            let response = try await sendRequestM(request, ASYNCDELAY)
             symLog?.log("received: \(response)")
-        } catch {
+        } catch {       // rethrows
+            symLog?.log(error.localizedDescription)
             throw error
         }
     }
 
-    @MainActor func runIntegrationTest() async throws {
+    @MainActor func runIntegrationTestM(newVersion: Bool)
+      async throws {               // M for MainActor
         do {
-            let amountW = Amount(currency: "KUDOS", integer: 3, fraction: 0)
-            let amountS = Amount(currency: "KUDOS", integer: 1, fraction: 0)
-            let request = WalletBackendRunIntegration(amountToWithdraw: 
amountW,
-                                                         amountToSpend: 
amountS,
-                                                           bankBaseUrl: 
DEMO_BANKAPIBASEURL,
-                                                       exchangeBaseUrl: 
DEMO_EXCHANGEBASEURL,
-                                                       merchantBaseUrl: 
DEMO_MERCHANTBASEURL,
-                                                     merchantAuthToken: 
DEMO_MERCHANTAUTHTOKEN)
-            let response = try await sendRequest(request, ASYNCDELAY)
-            symLog?.log("received: \(response)")
-        } catch {
+            let amountW = Amount(currency: DEMOCURRENCY, integer: 3, fraction: 
0)
+            let amountS = Amount(currency: DEMOCURRENCY, integer: 1, fraction: 
0)
+            let request = WalletBackendRunIntegration(newVersion: newVersion,
+                                                amountToWithdraw: amountW,
+                                                   amountToSpend: amountS,
+                                                     bankBaseUrl: 
DEMO_BANKAPIBASEURL,
+                                            bankAccessApiBaseUrl: 
DEMO_BANKAPIBASEURL,
+                                                 exchangeBaseUrl: 
DEMO_EXCHANGEBASEURL,
+                                                 merchantBaseUrl: 
DEMO_MERCHANTBASEURL,
+                                               merchantAuthToken: 
DEMO_MERCHANTAUTHTOKEN)
+            let _ = try await sendRequestT(request, ASYNCDELAY)
+            symLog?.log("runIntegrationTest finished")
+        } catch {       // rethrows
+            symLog?.log(error.localizedDescription)
             throw error
         }
     }
@@ -85,21 +83,25 @@ fileprivate struct WalletBackendWithdrawTestBalance: 
WalletBackendFormattedReque
 
 /// A request to add a test balance to the wallet.
 fileprivate struct WalletBackendRunIntegration: WalletBackendFormattedRequest {
-    typealias Response = String
-    func operation() -> String { return "runIntegrationTest" }
+    struct Response: Decodable {}
+    func operation() -> String { return newVersion ? "runIntegrationTestV2" : 
"runIntegrationTest" }
     func args() -> Args {
         return Args(amountToWithdraw: amountToWithdraw,
                     amountToSpend: amountToSpend,
                     bankBaseUrl: bankBaseUrl,
+                    bankAccessApiBaseUrl: bankAccessApiBaseUrl,
                     exchangeBaseUrl: exchangeBaseUrl,
                     merchantBaseUrl: merchantBaseUrl,
                     merchantAuthToken: merchantAuthToken
         )
     }
 
+    let newVersion: Bool
+
     var amountToWithdraw: Amount
     var amountToSpend: Amount
     var bankBaseUrl: String
+    var bankAccessApiBaseUrl: String
     var exchangeBaseUrl: String
     var merchantBaseUrl: String
     var merchantAuthToken: String
@@ -108,6 +110,7 @@ fileprivate struct WalletBackendRunIntegration: 
WalletBackendFormattedRequest {
         var amountToWithdraw: Amount
         var amountToSpend: Amount
         var bankBaseUrl: String
+        var bankAccessApiBaseUrl: String
         var exchangeBaseUrl: String
         var merchantBaseUrl: String
         var merchantAuthToken: String
diff --git a/TalerWallet1/Model/TransactionsModel.swift 
b/TalerWallet1/Model/TransactionsModel.swift
new file mode 100644
index 0000000..668f3c3
--- /dev/null
+++ b/TalerWallet1/Model/TransactionsModel.swift
@@ -0,0 +1,157 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import Foundation
+import taler_swift
+import SymLog
+fileprivate let ASYNCDELAY: UInt = 0   //set e.g to 6 or 9 seconds for 
debugging
+
+// MARK: -
+class TransactionsModel: WalletModel {
+    @Published var transactions: [Transaction]
+
+    static func specialTransactions(_ transactions: [Transaction]) -> 
[Transaction] {
+        transactions.filter { transaction in
+            transaction.isSpecial
+        }
+    }
+    var specialTransactions: [Transaction] {
+        transactions.filter { transaction in
+            transaction.isSpecial
+        }
+    }
+
+    static func completedTransactions(_ transactions: [Transaction]) -> 
[Transaction] {
+        transactions.filter { transaction in
+            transaction.isDone
+        }
+    }
+    static func pendingTransactions(_ transactions: [Transaction]) -> 
[Transaction] {
+        transactions.filter { transaction in
+            transaction.isPending
+        }
+    }
+    static func uncompletedTransactions(_ transactions: [Transaction]) -> 
[Transaction] {
+        transactions.filter { transaction in
+            !transaction.isDone && !transaction.isPending
+        }
+    }
+
+    override init(_ symbol: Int = -1) {
+        transactions = []                // empty, but not nil
+        super.init(symbol)
+    }
+}
+
+// MARK: -
+/// A request to get the transactions in the wallet's history.
+fileprivate struct GetTransactions: WalletBackendFormattedRequest {
+    func operation() -> String { return "getTransactions" }
+//    func operation() -> String { return "testingGetSampleTransactions" }
+    func args() -> Args { return Args(currency: currency, search: search) }
+
+    var currency: String?
+    var search: String?
+
+    struct Args: Encodable {
+        var currency: String?
+        var search: String?
+    }
+
+    struct Response: Decodable {                    // list of transactions
+        var transactions: [Transaction]
+    }
+}
+/// A request to abort a wallet transaction by ID.
+struct AbortTransaction: WalletBackendFormattedRequest {
+    func operation() -> String { return "abortTransaction" }
+    func args() -> Args { return Args(transactionId: transactionId) }
+
+    var transactionId: String
+
+    struct Args: Encodable {
+        var transactionId: String
+    }
+
+    struct Response: Decodable {}
+}
+/// A request to delete a wallet transaction by ID.
+struct DeleteTransaction: WalletBackendFormattedRequest {
+    struct Response: Decodable {}
+    func operation() -> String { return "deleteTransaction" }
+    func args() -> Args { return Args(transactionId: transactionId) }
+
+    var transactionId: String
+
+    struct Args: Encodable {
+        var transactionId: String
+    }
+}
+
+// MARK: -
+extension TransactionsModel {
+    /// ask wallet-core for its list of transactions filtered by searchString
+    func fetchTransactions(currency: String?) async {             // might be 
called from a background thread itself
+        await fetchTransactionsM(currency: currency, searchString: nil)
+    }
+    /// fetch transactions from Wallet-Core. No networking involved
+    @MainActor func fetchTransactionsM(currency: String? = nil, searchString: 
String? = nil)
+      async {              // M for MainActor
+        do {
+            let request = GetTransactions(currency: currency, search: 
searchString)
+            let response = try await sendRequestM(request, ASYNCDELAY)
+            transactions = response.transactions        // trigger view update 
in TransactionsListView
+        } catch {
+            transactions = []
+        }
+    }
+
+    func abortTransaction(transactionId: String) async throws {        // 
might be called from a background thread itself
+        try await abortTransactionM(transactionId: transactionId)      // call 
deleteTransactionM on main thread
+    }
+    /// delete the specified transaction from Wallet-Core. No networking 
involved
+    @MainActor func abortTransactionM(transactionId: String)
+    async throws {                // M for MainActor
+        do {
+            let request = AbortTransaction(transactionId: transactionId)
+            let _ = try await sendRequestT(request, ASYNCDELAY)
+        } catch {       // rethrows
+            symLog?.log(error.localizedDescription)
+            throw error
+        }
+    }
+
+    func deleteTransaction(transactionId: String) async throws {        // 
might be called from a background thread itself
+        try await deleteTransactionM(transactionId: transactionId)      // 
call deleteTransactionM on main thread
+    }
+    /// delete the specified transaction from Wallet-Core. No networking 
involved
+    @MainActor func deleteTransactionM(transactionId: String)
+    async throws {                // M for MainActor
+        do {
+            let request = DeleteTransaction(transactionId: transactionId)
+            let _ = try await sendRequestT(request, ASYNCDELAY)
+        } catch {       // rethrows
+            symLog?.log(error.localizedDescription)
+            throw error
+        }
+    }
+}
+
+// MARK: -
+extension TransactionsModel {
+    private static var currencies: [String] = []            // names of 
currencies
+    private static var models: [TransactionsModel] = []     // one model per 
currency
+
+    static func model(currency: String) -> TransactionsModel {
+        if let index = TransactionsModel.currencies.firstIndex(of:currency) {
+            let model = TransactionsModel.models[index]
+            return model
+        } else {        // new currency
+            let model = TransactionsModel()
+            TransactionsModel.models.append(model)
+            TransactionsModel.currencies.append(currency)
+            return model
+        }
+    }
+}
diff --git a/TalerWallet1/Model/WalletInitModel.swift 
b/TalerWallet1/Model/WalletInitModel.swift
index 7be0dff..d977890 100644
--- a/TalerWallet1/Model/WalletInitModel.swift
+++ b/TalerWallet1/Model/WalletInitModel.swift
@@ -1,25 +1,16 @@
 /*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
  */
 import Foundation
 import SymLog
 
-let DATABASE = "talerwalletdb-v30"
+private let DATABASE = "talerwalletdb-v30"
 
 class WalletInitModel: WalletModel {
-
+    override init(_ symbol: Int = -1) {     // init with 0 to disable logging 
for this class
+        super.init(symbol)
+    }
 }
 // MARK: -
 ///  A request to initialize Wallet-core
@@ -39,11 +30,8 @@ fileprivate struct WalletBackendInitRequest: 
WalletBackendFormattedRequest {
 
     var persistentStoragePath: String
 
-    struct Response: Decodable {        // versioninfo
+    struct Response: Decodable {
         var versionInfo: VersionInfo
-        enum CodingKeys: String, CodingKey {
-            case versionInfo = "versionInfo"
-        }
     }
 }
 // MARK: -
@@ -59,14 +47,15 @@ struct VersionInfo: Decodable {
 // MARK: -
 extension WalletInitModel {
     /// initalize Wallet-Core. Will do networking
-    func initWallet() async throws -> VersionInfo? {
+    func initWalletT()              // T for any Thread
+      async throws -> VersionInfo? {
         do {
             let docPath = try docPath()
             let request = WalletBackendInitRequest(persistentStoragePath: 
docPath)
             symLog?.log("info: not main thread")
-            let response = try await sendRequest(request, 0)    // no Delay
+            let response = try await sendRequestT(request, 0)    // no Delay
             return response.versionInfo
-        } catch {
+        } catch {       // rethrows
             symLog?.log("error: \(error)")
             throw error
         }
@@ -80,7 +69,7 @@ extension WalletInitModel {
             storageDir.appendPathExtension("json")
             return storageDir.path
         } else {    // should never happen
-            symLog?.log("Yikes! documentURLs empty")     // TODO: symLog.error
+            symLog?.log("Yikes❗️ documentURLs empty")     // TODO: symLog.error
             throw WalletBackendError.initializationError
         }
     }
diff --git a/TalerWallet1/Model/WalletModel.swift 
b/TalerWallet1/Model/WalletModel.swift
index d340278..9fb0622 100644
--- a/TalerWallet1/Model/WalletModel.swift
+++ b/TalerWallet1/Model/WalletModel.swift
@@ -1,55 +1,87 @@
 /*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
  */
 import Foundation
 import SymLog
 
 fileprivate let ASYNCDELAY: UInt = 0   //set e.g to 6 or 9 seconds for 
debugging
 
+
+// MARK: -
 /// The "virtual" base class for all models
 class WalletModel: ObservableObject {
     static func className() -> String {"\(self)"}
     var symLog: SymLogC?
-    var walletCore: WalletCore
 
     @Published var loading: Bool = false                // update view
 
-    init(walletCore: WalletCore) {
-        self.symLog = SymLogC(funcName: Self.className())
-        self.walletCore = walletCore
+    init(_ symbol: Int) {                               // init with 0 to 
disable logging for this class
+        self.symLog = SymLogC(symbol == 0 ? 0 : -1, funcName: Self.className())
     }
 
-    @MainActor func sendRequest<T: WalletBackendFormattedRequest> (_ request: 
T, _ delay: UInt = 0)
-      async throws -> T.Response {
+    @MainActor func sendRequestM<T: WalletBackendFormattedRequest> (_ request: 
T, _ delay: UInt = 0)
+      async throws -> T.Response {  // M for MainActor
         loading = true                                  // enter progressView
+        do {
+            let response = try await sendRequestT(request, delay)
+            loading = false                             // exit progressView
+            return response
+        } catch {       // rethrows
+            loading = false                             // exit progressView
+            throw error
+        }
+    }
+
+    func sendRequestT<T: WalletBackendFormattedRequest> (_ request: T, _ 
delay: UInt = 0)
+    async throws -> T.Response {    // T for any Thread
+        let sendTime = Date.now
         do {
             symLog?.log("sending: \(request)")
-            let (response, id) = try await 
walletCore.sendFormattedRequest(request: request)
+            let (response, id) = try await 
WalletCore.shared.sendFormattedRequest(request: request)
+            let timeUsed = Date.now - sendTime
             let asyncDelay: UInt = delay > 0 ? delay : UInt(ASYNCDELAY)
             if asyncDelay > 0 {     // test LoadingView, sleep some seconds
-                symLog?.log("received: (\(id)), going to sleep for 
\(asyncDelay) seconds...")
+                symLog?.log("received: (\(id)) after \(timeUsed.milliseconds) 
ms, going to sleep for \(asyncDelay) seconds...")
                 try? await Task.sleep(nanoseconds: 1_000_000_000 * 
UInt64(asyncDelay))
-                symLog?.log("waking up again after \(asyncDelay) seconds, will 
deliver \(response)")
+                symLog?.log("(\(id)) waking up again after \(asyncDelay) 
seconds, will deliver \(response)")
             } else {
-                symLog?.log("received: \(response)")
+                symLog?.log("received: (\(id)) after \(timeUsed.milliseconds) 
ms, \(response)")
             }
-            loading = false                             // exit progressView
             return response
-        } catch {
+        } catch {       // rethrows
+            let timeUsed = Date.now - sendTime
+            symLog?.log("Yikes❗️ \(request.operation()) failed after 
\(timeUsed.milliseconds) ms")
             throw error
         }
     }
 
+    func getTransactionById(transactionId: String) async throws -> Transaction 
{        // might be called from a background thread itself
+        return try await getTransactionByIdM(transactionId: transactionId)     
 // call deleteTransactionM on main thread
+    }
+    /// get the specified transaction from Wallet-Core. No networking involved
+    @MainActor func getTransactionByIdM(transactionId: String)
+    async throws -> Transaction {                // M for MainActor
+        do {
+            let request = GetTransactionById(transactionId: transactionId)
+            let response = try await sendRequestT(request, ASYNCDELAY)
+            return response
+        } catch {       // rethrows
+            throw error
+        }
+    }
 }
+// MARK: -
+/// A request to get a wallet transaction by ID.
+struct GetTransactionById: WalletBackendFormattedRequest {
+    typealias Response = Transaction
+    func operation() -> String { return "getTransactionById" }
+    func args() -> Args { return Args(transactionId: transactionId) }
+
+    var transactionId: String
+
+    struct Args: Encodable {
+        var transactionId: String
+    }
+}
+
diff --git a/TalerWallet1/Views/Withdraw/WithdrawURIModel.swift 
b/TalerWallet1/Model/WithdrawModel.swift
similarity index 54%
rename from TalerWallet1/Views/Withdraw/WithdrawURIModel.swift
rename to TalerWallet1/Model/WithdrawModel.swift
index f375a1d..b3a2161 100644
--- a/TalerWallet1/Views/Withdraw/WithdrawURIModel.swift
+++ b/TalerWallet1/Model/WithdrawModel.swift
@@ -1,17 +1,6 @@
 /*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
  */
 import Foundation
 import taler_swift
@@ -32,16 +21,19 @@ enum WithdrawState {
     case receivedWithdrAck
 }
 
-class WithdrawURIModel: WalletModel {
+class WithdrawModel: WalletModel {
     @Published var withdrawState: WithdrawState?
+    override init(_ symbol: Int = -1) {     // init with 0 to disable logging 
for this class
+        super.init(symbol)
+    }
 }
 
 // MARK: -
 /// The result from getWithdrawalDetailsForUri
-struct WithdrawalDetailsForUri: Decodable {
+struct WithdrawUriInfoResponse: Decodable {
     var amount: Amount
-    var defaultExchangeBaseUrl: String?
-    var possibleExchanges: [ExchangeListItem]
+    var defaultExchangeBaseUrl: String?             // TODO: might be nil 
❗️Yikes
+    var possibleExchanges: [ExchangeListItem]       // TODO: query these for 
fees?
 }
 struct ExchangeListItem: Codable, Hashable {
     var exchangeBaseUrl: String
@@ -62,7 +54,7 @@ struct ExchangeListItem: Codable, Hashable {
 }
 /// A request to get an exchange's withdrawal details.
 fileprivate struct GetWithdrawalDetailsForURI: WalletBackendFormattedRequest {
-    typealias Response = WithdrawalDetailsForUri
+    typealias Response = WithdrawUriInfoResponse
     func operation() -> String { return "getWithdrawalDetailsForUri" }
     func args() -> Args { return Args(talerWithdrawUri: talerWithdrawUri) }
 
@@ -73,14 +65,17 @@ fileprivate struct GetWithdrawalDetailsForURI: 
WalletBackendFormattedRequest {
 }
 // MARK: -
 /// The result from getWithdrawalDetailsForAmount
-struct WithdrawalDetailsForAmount: Decodable {
-    var tosAccepted: Bool
+struct ManualWithdrawalDetails: Decodable {
     var amountRaw: Amount
     var amountEffective: Amount
+    var paytoUris: [String]
+    var tosAccepted: Bool
+    var ageRestrictionOptions: [Int]?
+    var numCoins: Int?
 }
 /// A request to get an exchange's withdrawal details.
 fileprivate struct GetWithdrawalDetailsForAmount: 
WalletBackendFormattedRequest {
-    typealias Response = WithdrawalDetailsForAmount
+    typealias Response = ManualWithdrawalDetails
     func operation() -> String { return "getWithdrawalDetailsForAmount" }
     func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl, 
amount: amount) }
 
@@ -124,12 +119,14 @@ fileprivate struct SetExchangeTOSAccepted: 
WalletBackendFormattedRequest {
     }
 }
 // MARK: -
-struct BankConfirmation: Decodable {
-    var bankConfirmationUrl: String?
+struct AcceptWithdrawalResponse: Decodable {
+    var reservePub: String
+    var confirmTransferUrl: String?
+    var transactionId: String
 }
 /// A request to accept a bank-integrated withdrawl.
 fileprivate struct AcceptBankIntegratedWithdrawal: 
WalletBackendFormattedRequest {
-    typealias Response = BankConfirmation
+    typealias Response = AcceptWithdrawalResponse
     func operation() -> String { return "acceptBankIntegratedWithdrawal" }
     func args() -> Args { return Args(talerWithdrawUri: talerWithdrawUri, 
exchangeBaseUrl: exchangeBaseUrl) }
 
@@ -142,72 +139,131 @@ fileprivate struct AcceptBankIntegratedWithdrawal: 
WalletBackendFormattedRequest
     }
 }
 // MARK: -
-extension WithdrawURIModel {
+struct AcceptManualWithdrawalResult: Decodable {
+    var reservePub: String
+    var exchangePaytoUris: [String]
+    var transactionId: String
+}
+/// A request to accept a manual withdrawl.
+fileprivate struct AcceptManualWithdrawal: WalletBackendFormattedRequest {
+    typealias Response = AcceptManualWithdrawalResult
+    func operation() -> String { return "acceptManualWithdrawal" }
+    func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl, 
amount: amount, restrictAge: restrictAge) }
+
+    var exchangeBaseUrl: String
+    var amount: Amount
+    var restrictAge: Int?
+
+    struct Args: Encodable {
+        var exchangeBaseUrl: String
+        var amount: Amount
+        var restrictAge: Int?
+    }
+}
+// MARK: -
+extension WithdrawModel {
     /// load withdrawal details. Networking involved
     @MainActor
-    func loadWithdrawalDetailsForURI(_ talerWithdrawUri: String) async throws 
-> WithdrawalDetailsForUri {
+    func loadWithdrawalDetailsForUriM(_ talerWithdrawUri: String)              
 // M for MainActor
+      async throws -> WithdrawUriInfoResponse {
         do {
             withdrawState = .waitingForUriDetails
             let request = GetWithdrawalDetailsForURI(talerWithdrawUri: 
talerWithdrawUri)
-            let response = try await sendRequest(request, ASYNCDELAY)
+            let response = try await sendRequestM(request, ASYNCDELAY)         
 // TODO: MainActor ?
             withdrawState = .receivedUriDetails
             return response
-        } catch {
+        } catch {       // rethrows
             withdrawState = .error
             throw error
         }
     }
     @MainActor
-    func loadWithdrawalDetailsForAmount(_ detailsForUri: 
WithdrawalDetailsForUri) async throws -> WithdrawalDetailsForAmount {
+    func loadWithdrawalDetailsForAmountM(_ exchangeBaseUrl: String, amount: 
Amount)  // M for MainActor
+      async throws -> ManualWithdrawalDetails {
         do {
             withdrawState = .waitingForAmountDetails
-            let baseURL = detailsForUri.defaultExchangeBaseUrl!
-            let request = GetWithdrawalDetailsForAmount(exchangeBaseUrl: 
baseURL, amount: detailsForUri.amount)
-            let response = try await sendRequest(request, ASYNCDELAY)
+            let request = GetWithdrawalDetailsForAmount(exchangeBaseUrl: 
exchangeBaseUrl,
+                                                                 amount: 
amount)
+            let response = try await sendRequestM(request, ASYNCDELAY)         
 // TODO: MainActor ?
             withdrawState = .receivedAmountDetails
             return response
-        } catch {
+        } catch {       // rethrows
             withdrawState = .error
             throw error
         }
     }
     @MainActor
-    func loadExchangeTermsOfService(_ exchangeBaseUrl: String) async throws -> 
ExchangeTermsOfService {
+    func loadExchangeTermsOfServiceM(_ exchangeBaseUrl: String)             // 
M for MainActor
+      async throws -> ExchangeTermsOfService {
         do {
             withdrawState = .waitingForTOS
             let request = GetExchangeTermsOfService(exchangeBaseUrl: 
exchangeBaseUrl)
-            let response = try await sendRequest(request, ASYNCDELAY)
+            let response = try await sendRequestM(request, ASYNCDELAY)         
 // TODO: MainActor ?
             withdrawState = .receivedTOS
             return response
-        } catch {
+        } catch {       // rethrows
             withdrawState = .error
             throw error
         }
     }
     @MainActor
-    func setExchangeTOSAccepted(_ exchangeBaseUrl: String, etag: String) async 
throws -> Decodable {
+    func setExchangeTOSAcceptedM(_ exchangeBaseUrl: String, etag: String)      
 // M for MainActor
+      async throws -> Decodable {
         do {
             withdrawState = .waitingForTOSAck
             let request = SetExchangeTOSAccepted(exchangeBaseUrl: 
exchangeBaseUrl, etag: etag)
-            let response = try await sendRequest(request, ASYNCDELAY)
+            let response = try await sendRequestM(request, ASYNCDELAY)         
 // TODO: MainActor ?
             withdrawState = .receivedTOSAck
             return response
-        } catch {
+        } catch {       // rethrows
             withdrawState = .error
             throw error
         }
     }
     @MainActor
-    func sendAcceptIntWithdrawal(_ exchangeBaseUrl: String, withdrawURL: 
String) async throws -> String? {
+    func sendAcceptIntWithdrawalM(_ exchangeBaseUrl: String, withdrawURL: 
String)   // M for MainActor
+      async throws -> String? {
         do {
             withdrawState = .waitingForWithdrAck
             let request = AcceptBankIntegratedWithdrawal(talerWithdrawUri: 
withdrawURL, exchangeBaseUrl: exchangeBaseUrl)
-            let response = try await sendRequest(request, ASYNCDELAY)
+            let response = try await sendRequestM(request, ASYNCDELAY)         
 // TODO: MainActor ?
             withdrawState = .receivedWithdrAck
-            return response.bankConfirmationUrl
-        } catch {
+            return response.confirmTransferUrl
+        } catch {       // rethrows
             withdrawState = .error
             throw error
         }
     }
+    @MainActor
+    func sendAcceptManualWithdrawalM(_ exchangeBaseUrl: String, amount: 
Amount, restrictAge: Int?)   // M for MainActor
+    async throws -> AcceptManualWithdrawalResult? {
+        do {
+            withdrawState = .waitingForWithdrAck
+            let request = AcceptManualWithdrawal(exchangeBaseUrl: 
exchangeBaseUrl, amount: amount, restrictAge: restrictAge)
+            let response = try await sendRequestM(request, ASYNCDELAY)         
 // TODO: MainActor ?
+            withdrawState = .receivedWithdrAck
+            return response
+        } catch {       // rethrows
+            withdrawState = .error
+            throw error
+        }
+    }
+}
+
+// MARK: -
+extension WithdrawModel {
+    private static var exchanges: [String] = []            // names of 
exchanges
+    private static var models: [WithdrawModel] = []     // one model per 
exchange
+
+    static func model(baseURL: String) -> WithdrawModel {
+        if let index = WithdrawModel.exchanges.firstIndex(of:baseURL) {
+            let model = WithdrawModel.models[index]
+            return model
+        } else {        // new model
+            let model = WithdrawModel()
+            WithdrawModel.models.append(model)
+            WithdrawModel.exchanges.append(baseURL)
+            return model
+        }
+    }
 }
diff --git a/TalerWallet1/Preview Content/transactions.json 
b/TalerWallet1/Preview Content/transactions.json
new file mode 100644
index 0000000..b2db39c
--- /dev/null
+++ b/TalerWallet1/Preview Content/transactions.json    
@@ -0,0 +1,300 @@
+{
+    "transactions":[
+        {   "type": "withdrawal",
+            "amountEffective": "KUDOS:2.9",
+            "amountRaw": "KUDOS:3",
+            "transactionId": 
"txn:withdrawal:QQWXZ908YYWFPH9QV2GB72Z29C83FFK612WJARXBX6YSNRGPP660",
+            "timestamp": ["t_s": 1683531967]
+            "txState": {
+                "major": "done"
+            },
+            "pending": false,
+
+            "exchangeBaseUrl": "https://exchange.demo.taler.net/";,
+            "withdrawalDetails": {
+                "type": "taler-bank-integration-api",
+                "reservePub": 
"R4G8E58Q97M6HQP69V3J3Q6XX1DXQ06FARWNSNRAWYX446WB2R8G"
+                "bankConfirmationUrl": "https://bank.demo.taler.net/";,
+                "confirmed": true,
+                "reserveIsReady": true,
+            },
+        },
+        {   "type": "payment",
+            "amountEffective": "KUDOS:1.1",
+            "amountRaw": "KUDOS:1",
+            "transactionId": 
"txn:payment:43JV9JTB270X1EBH5T59HV9JF2CQW8ZY7M23PS8444BGY4F2Z110",
+            "timestamp": ["t_s": 1683531978],,
+            "txState": {
+                "major": "done"
+            },
+
+            "proposalId": 
"43JV9JTB270X1EBH5T59HV9JF2CQW8ZY7M23PS8444BGY4F2Z110",
+            "status": "paid",
+            "totalRefundRaw": "KUDOS:0",
+            "totalRefundEffective": "KUDOS:0"
+            "refundQueryActive": false
+            "refunds": [],
+            "info": {
+                "orderId": "2023.128-03A25VEED68QC",
+                "merchant": {
+                    "name": "GNU Taler",
+                    "jurisdiction": [:],
+                    "address": [:]
+                },
+                "summary": "hello world",
+                "products": []
+                "fulfillmentUrl": "taler://fulfillment-success/thx",
+                "contractTermsHash": 
"X0QJGEPSDKARFPBNTSF5HMXJYDGQ4WRX789PAB2G3EER9J7B5Z8WHHJ0W8KX3Z175A2FGVBKPZ85H8X0Y6H4MNHARJCA9ACW43QS10R",
+            },
+        },
+        {   "type": "withdrawal",
+            "amountEffective": "KUDOS:17.8",
+            "amountRaw": "KUDOS:18",
+            "transactionId": 
"txn:withdrawal:N9CR5EH8BSPB2NVEDW1HAXX94C9DCKSAWF097M4S9AHJ5KW5R01G",
+            "timestamp": ["t_s": 1683531980],
+            "txState": {
+                "major": "done"
+            },
+            "pending": false,
+
+            "exchangeBaseUrl": "https://exchange.demo.taler.net/";,
+            "withdrawalDetails": {
+                "type": "taler-bank-integration-api",
+                "reservePub": 
"6P2Y5N7H1NY8938H60S9WJ1HFS11F90P0RR1H51WCV7MHYYFNS80",
+                "reserveIsReady": true,
+                "confirmed": true,
+                "bankConfirmationUrl": "https://bank.demo.taler.net/";,
+            },
+        },
+        {   "type": "payment",
+            "amountEffective": "KUDOS:7.2",
+            "amountRaw": "KUDOS:7",
+            "transactionId": 
"txn:payment:3F9DWB6DNAT8WDPRHQ160AZ1G8WNJH23069Q39HDK8Z3EAR804M0",
+            "timestamp": ["t_s": 1683531996],
+            "txState": {
+                "major": "done"
+            },
+
+            "proposalId": 
"3F9DWB6DNAT8WDPRHQ160AZ1G8WNJH23069Q39HDK8Z3EAR804M0",
+            "status": "paid",
+            "totalRefundRaw": "KUDOS:0",
+            "totalRefundEffective": "KUDOS:0",
+              "refundPending": "KUDOS:6",
+            "refundQueryActive": false,
+            "refunds": [],
+            "info": {
+                "orderId": "2023.128-0388C075CX1R0",
+                "merchant": {
+                    "name": "GNU Taler",
+                    "jurisdiction": [:],
+                    "address": [:]
+                },
+                "summary": "order that will be refunded",
+                "products": [],
+                "fulfillmentUrl": "taler://fulfillment-success/thx",
+                "contractTermsHash": 
"SPD5XJTFE8N73FXQT0JRVZ2ABSGKWVHFWV1GAZGDAWZT7CK0WSHCJSWV1FMCFEGTHT786ZPFVFHJWWB3V0ADCNPBT0YFS78Z2P3KYH0",
+            },
+        },
+        {   "type": "refund",
+            "amountEffective": "KUDOS:5.8",
+            "amountRaw": "KUDOS:6",
+            "transactionId": 
"txn:refund:PFW6JNX6QVKMF5HHER8KHPS9ADJTPXBQ1JK6C7W55F4X6CK59Q20",
+            "timestamp": {"t_s": 1683531997},
+            "txState": {
+                "major": "done"
+            },
+
+            "refundedTransactionId": 
"txn:payment:3F9DWB6DNAT8WDPRHQ160AZ1G8WNJH23069Q39HDK8Z3EAR804M0",
+        },
+        {   "type": "payment",
+            "amountEffective": "KUDOS:3.2",
+            "amountRaw": "KUDOS:3",
+            "transactionId": 
"txn:payment:8AG5GVQ7E2FGREEH8V5ADNW1D6NPVD4YCF26763A9JYEQ76AVBX0",
+            "timestamp": ["t_s": 1683531998],
+            "txState": {
+                "major": "done"
+            },
+
+            "proposalId": 
"8AG5GVQ7E2FGREEH8V5ADNW1D6NPVD4YCF26763A9JYEQ76AVBX0"
+            "status": "paid",
+            "totalRefundRaw": "KUDOS:0",
+            "totalRefundEffective": "KUDOS:0",
+            "refundQueryActive": false,
+            "refunds": [],
+            "info": {
+                "orderId": "2023.128-00G672RZHTRWA",
+                "merchant": [
+                    "name": "GNU Taler",
+                    "address": [:],
+                    "jurisdiction": [:]
+                ],
+                "summary": "payment after refund",
+                "products": []
+                "fulfillmentUrl": "taler://fulfillment-success/thx",
+                "contractTermsHash": 
"GZZ75P8G5H7PEV93E6M7TNNT1RNCSP0MJT7K7K4VXJAZGK5FAFC2YJYDGX3CT077VPQFZJR3QV5M9ZNH2T86ZWBA8BDDREM12RZYWN0",
+            },
+        }
+    ]
+}
+
+
+"transactions":[
+    {
+        "type":"withdrawal",
+        "txState":{
+            "major":"done"
+        },
+        "amountEffective":"KUDOS:2.9",
+        "amountRaw":"KUDOS:3",
+        "withdrawalDetails":{
+            "type":"taler-bank-integration-api",
+            "confirmed":true,
+            
"reservePub":"R4G8E58Q97M6HQP69V3J3Q6XX1DXQ06FARWNSNRAWYX446WB2R8G",
+            "bankConfirmationUrl":"https://bank.demo.taler.net/";,
+            "reserveIsReady":true
+        },
+        "exchangeBaseUrl":"https://exchange.demo.taler.net/";,
+        "pending":false,
+        "timestamp":{ "t_s":1683531967 },
+        
"transactionId":"txn:withdrawal:QQWXZ908YYWFPH9QV2GB72Z29C83FFK612WJARXBX6YSNRGPP660",
+    },
+    {
+        "type":"payment",
+        "txState":{
+            "major":"done"
+        },
+        "amountRaw":"KUDOS:1",
+        "amountEffective":"KUDOS:1.1",
+        "totalRefundRaw":"KUDOS:0",
+        "totalRefundEffective":"KUDOS:0",
+        "status":"paid",
+        "extendedStatus":"done",
+        "pending":false,
+        "refunds":[],
+        "timestamp":{
+            "t_s":1683531978
+        },
+        
"transactionId":"txn:payment:43JV9JTB270X1EBH5T59HV9JF2CQW8ZY7M23PS8444BGY4F2Z110",
+        "proposalId":"43JV9JTB270X1EBH5T59HV9JF2CQW8ZY7M23PS8444BGY4F2Z110",
+        "info":{
+            "merchant":{
+                "name":"GNU Taler",
+                "address":{
+
+                },
+                "jurisdiction":{
+                }
+            },
+            "orderId":"2023.128-03A25VEED68QC",
+            "products":[],
+            "summary":"hello world",
+            
"contractTermsHash":"X0QJGEPSDKARFPBNTSF5HMXJYDGQ4WRX789PAB2G3EER9J7B5Z8WHHJ0W8KX3Z175A2FGVBKPZ85H8X0Y6H4MNHARJCA9ACW43QS10R",
+            "fulfillmentUrl":"taler://fulfillment-success/thx"
+        },
+        "refundQueryActive":false,
+    },
+    {
+        "type":"withdrawal",
+        "txState":{
+            "major":"done"
+        },
+        "amountEffective":"KUDOS:17.8",
+        "amountRaw":"KUDOS:18",
+        "withdrawalDetails":{
+            "type":"taler-bank-integration-api",
+            "confirmed":true,
+            
"reservePub":"6P2Y5N7H1NY8938H60S9WJ1HFS11F90P0RR1H51WCV7MHYYFNS80",
+            "bankConfirmationUrl":"https://bank.demo.taler.net/";,
+            "reserveIsReady":true
+        },
+        "exchangeBaseUrl":"https://exchange.demo.taler.net/";,
+        "extendedStatus":"done",
+        "pending":false,
+        "timestamp":{ "t_s":1683531980 },
+        
"transactionId":"txn:withdrawal:N9CR5EH8BSPB2NVEDW1HAXX94C9DCKSAWF097M4S9AHJ5KW5R01G",
+    },
+    {
+        "type":"payment",
+        "txState":{
+            "major":"done"
+        },
+        "amountRaw":"KUDOS:7",
+        "amountEffective":"KUDOS:7.2",
+        "totalRefundRaw":"KUDOS:0",
+        "totalRefundEffective":"KUDOS:0",
+        "refundPending":"KUDOS:6",
+        "status":"paid",
+        "extendedStatus":"done",
+        "pending":false,
+        "refunds":[],
+        "timestamp":{ "t_s":1683531996 },
+        
"transactionId":"txn:payment:3F9DWB6DNAT8WDPRHQ160AZ1G8WNJH23069Q39HDK8Z3EAR804M0",
+        "proposalId":"3F9DWB6DNAT8WDPRHQ160AZ1G8WNJH23069Q39HDK8Z3EAR804M0",
+        "info":{
+            "merchant":{
+                "name":"GNU Taler",
+                "address":{
+
+                },
+                "jurisdiction":{
+                }
+            },
+            "orderId":"2023.128-0388C075CX1R0",
+            "products":[],
+            "summary":"order that will be refunded",
+            
"contractTermsHash":"SPD5XJTFE8N73FXQT0JRVZ2ABSGKWVHFWV1GAZGDAWZT7CK0WSHCJSWV1FMCFEGTHT786ZPFVFHJWWB3V0ADCNPBT0YFS78Z2P3KYH0",
+            "fulfillmentUrl":"taler://fulfillment-success/thx"
+        },
+        "refundQueryActive":false,
+    },
+    {
+        "type":"refund",
+        "amountEffective":"KUDOS:5.8",
+        "amountRaw":"KUDOS:6",
+        
"refundedTransactionId":"txn:payment:3F9DWB6DNAT8WDPRHQ160AZ1G8WNJH23069Q39HDK8Z3EAR804M0",
+        "timestamp":{
+            "t_s":1683531997
+        },
+        
"transactionId":"txn:refund:PFW6JNX6QVKMF5HHER8KHPS9ADJTPXBQ1JK6C7W55F4X6CK59Q20",
+        "txState":{
+            "major":"done"
+        },
+        "extendedStatus":"done",
+        "pending":false
+    },
+    {
+        "type":"payment",
+        "txState":{
+            "major":"done"
+        },
+        "amountRaw":"KUDOS:3",
+        "amountEffective":"KUDOS:3.2",
+        "totalRefundRaw":"KUDOS:0",
+        "totalRefundEffective":"KUDOS:0",
+        "status":"paid",
+        "extendedStatus":"done",
+        "pending":false,
+        "refunds":[],
+        "timestamp":{
+            "t_s":1683531998
+        },
+        
"transactionId":"txn:payment:8AG5GVQ7E2FGREEH8V5ADNW1D6NPVD4YCF26763A9JYEQ76AVBX0",
+        "proposalId":"8AG5GVQ7E2FGREEH8V5ADNW1D6NPVD4YCF26763A9JYEQ76AVBX0",
+        "info":{
+            "merchant":{
+                "name":"GNU Taler",
+                "address":{
+
+                },
+                "jurisdiction":{
+                }
+            },
+            "orderId":"2023.128-00G672RZHTRWA",
+            "products":[],
+            "summary":"payment after refund",
+            
"contractTermsHash":"GZZ75P8G5H7PEV93E6M7TNNT1RNCSP0MJT7K7K4VXJAZGK5FAFC2YJYDGX3CT077VPQFZJR3QV5M9ZNH2T86ZWBA8BDDREM12RZYWN0",
+            "fulfillmentUrl":"taler://fulfillment-success/thx"
+        },
+        "refundQueryActive":false,
+    }
+]
diff --git a/TalerWallet1/Quickjs/quickjs.swift 
b/TalerWallet1/Quickjs/quickjs.swift
index 040bfcb..b301190 100644
--- a/TalerWallet1/Quickjs/quickjs.swift
+++ b/TalerWallet1/Quickjs/quickjs.swift
@@ -1,17 +1,6 @@
 /*
- * This file is part of GNU Taler
- * (C) 2021 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
  */
 import Foundation
 
diff --git a/TalerWallet1/Settings.bundle/Root.plist 
b/TalerWallet1/Settings.bundle/Root.plist
new file mode 100644
index 0000000..89aaf6b
--- /dev/null
+++ b/TalerWallet1/Settings.bundle/Root.plist
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" 
"http://www.apple.com/DTDs/PropertyList-1.0.dtd";>
+<plist version="1.0">
+<dict>
+       <key>StringsTable</key>
+       <string>Root</string>
+       <key>PreferenceSpecifiers</key>
+       <array>
+               <dict>
+                       <key>Type</key>
+                       <string>PSToggleSwitchSpecifier</string>
+                       <key>Title</key>
+                       <string>Diagnostic Mode</string>
+                       <key>Key</key>
+                       <string>diagnostic_mode_enabled</string>
+                       <key>DefaultValue</key>
+                       <false/>
+               </dict>
+       </array>
+</dict>
+</plist>
diff --git a/TalerWallet1/Settings.bundle/en.lproj/Root.strings 
b/TalerWallet1/Settings.bundle/en.lproj/Root.strings
new file mode 100644
index 0000000..8cd87b9
Binary files /dev/null and b/TalerWallet1/Settings.bundle/en.lproj/Root.strings 
differ
diff --git a/TalerWallet1/Views/Balances/BalanceRow.swift 
b/TalerWallet1/Views/Balances/BalanceRow.swift
deleted file mode 100644
index 9c8aeee..0000000
--- a/TalerWallet1/Views/Balances/BalanceRow.swift
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-import SwiftUI
-import taler_swift
-
-struct BalanceRow: View {
-    let amount: Amount
-    let sendAction: () -> Void
-    let recvAction: () -> Void
-    var body: some View {
-        HStack {
-            Button("Send\nFunds", action: sendAction)
-                .lineLimit(nil)
-                .buttonStyle(.bordered)
-                .padding(.trailing)
-            Button("Receive\nFunds", action: recvAction)
-                .buttonStyle(.bordered)
-            Spacer()
-            VStack(alignment: .trailing) {
-                Text("Balance")
-                    .font(.footnote)
-                Text("\(amount.valueStr)")
-                    .font(.title)
-            }
-        }
-    }
-}
-
-struct Balance_Previews: PreviewProvider {
-    static var previews: some View {
-        BalanceRow(amount: try! Amount(fromString: "Taler:0.1"), sendAction: 
{}, recvAction: {})
-    }
-}
diff --git a/TalerWallet1/Views/Balances/BalanceRowView.swift 
b/TalerWallet1/Views/Balances/BalanceRowView.swift
new file mode 100644
index 0000000..cb847ec
--- /dev/null
+++ b/TalerWallet1/Views/Balances/BalanceRowView.swift
@@ -0,0 +1,52 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+
+/// This view shows the currency row in a currency section
+/// [Send Coins]  [Receive Coins]     Balance
+
+struct BalanceRowView: View {
+    let amount: Amount
+    let sendAction: () -> Void
+    let recvAction: () -> Void
+    let rowAction: () -> Void
+    var body: some View {
+        HStack {
+            Button("Request\nPayment", action: recvAction)
+                .lineLimit(2)
+                .disabled(false)
+                .buttonStyle(TalerButtonStyle(type: .bordered, narrow: true, 
aligned: .leading))
+            Button("Send\nCoins", action: sendAction)
+                .lineLimit(2)
+                .disabled(amount.isZero)
+                .buttonStyle(TalerButtonStyle(type: .bordered, narrow: true, 
aligned: .leading))
+            Button(action: rowAction) {
+                VStack(alignment: .trailing) {
+                    Text("Balance")
+                        .font(.footnote)
+                        .accessibility(sortPriority: 2)
+                    Text("\(amount.valueStr)")  // TODO: CurrencyFormatter
+                        .font(.title)
+                        .accessibility(sortPriority: 1)
+                }
+            }   .disabled(false)
+                .buttonStyle(TalerButtonStyle(type: .plain, aligned: 
.trailing))
+        }
+        .fixedSize(horizontal: false, vertical: true)   // should make all 
buttons equal height - but doesn't
+        .accessibilityElement(children: .combine)
+    }
+}
+// MARK: -
+#if DEBUG
+struct BalanceRowView_Previews: PreviewProvider {
+    static var previews: some View {
+        List {
+            BalanceRowView(amount: try! Amount(fromString: LONGCURRENCY + 
":0.1"),
+                           sendAction: {}, recvAction: {}, rowAction: {})
+        }
+    }
+}
+#endif
diff --git a/TalerWallet1/Views/Balances/BalancesListView.swift 
b/TalerWallet1/Views/Balances/BalancesListView.swift
new file mode 100644
index 0000000..15d0f1c
--- /dev/null
+++ b/TalerWallet1/Views/Balances/BalancesListView.swift
@@ -0,0 +1,133 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+import SymLog
+import AVFoundation
+
+/// This view shows the list of balances / currencies, each in its own section
+
+struct BalancesListView: View {
+    private let symLog = SymLogV()
+    let navTitle = String(localized: "GNU Taler")       // + Wallet
+    @AppStorage("listStyle") var myListStyle = MyListStyle.automatic
+
+    @ObservedObject var model: BalancesModel
+    var hamburgerAction: () -> Void
+
+    @State private var centsToTransfer: UInt64 = 0        // TODO: maybe 
Decimal?
+    @State private var showQRScanner: Bool = false
+    @State private var showCameraAlert: Bool = false
+
+    var ClosingAnnouncement = AttributedString(localized: "Closing Camera")
+    private var openSettingsButton: some View {
+        Button("Open Settings") {
+            showCameraAlert = false
+            UIApplication.shared.open(URL(string: 
UIApplication.openSettingsURLString)!)
+        }
+    }
+    private var dismissAlertButton: some View {
+        Button("Cancel", role: .cancel) {
+//            
AccessibilityNotification.Announcement(ClosingAnnouncement).post()
+            showCameraAlert = false
+        }
+    }
+
+    var defaultPriorityAnnouncement = AttributedString(localized: "Opening 
Camera")
+    var lowPriorityAnnouncement: AttributedString {
+        var lowPriorityString = AttributedString ("Camera Loading")
+        //        lowPriorityString.accessibilitySpeechAnnouncementPriority = 
.low
+        return lowPriorityString
+    }
+    var highPriorityAnnouncement: AttributedString {
+        var highPriorityString = AttributedString("Camera Active")
+        //        highPriorityString.accessibilitySpeechAnnouncementPriority = 
.high
+        return highPriorityString
+    }
+    private func checkCameraAvailable() -> Void {
+        /// Open Camera Code
+//        
AccessibilityNotification.Announcement(defaultPriorityAnnouncement).post()
+        AVCaptureDevice.requestAccess(for: .video, completionHandler: { 
(granted: Bool) -> Void in
+            if granted {
+                showQRScanner = true
+            } else {
+                // Camera Loaded Code
+//                
AccessibilityNotification.Announcement(highPriorityAnnouncement).post()
+                showCameraAlert = true
+            }
+        })
+    }
+
+    var body: some View {
+#if DEBUG
+        let _ = Self._printChanges()
+        let _ = symLog.vlog()       // just to get the # to compare it with 
.onAppear & onDisappear
+#endif
+        let reloadAction = model.fetchBalancesM
+        Content(symLog: symLog, model: model, centsToTransfer: 
$centsToTransfer,
+                reloadAction: reloadAction, myListStyle: $myListStyle)
+            .navigationTitle(navTitle)
+            .navigationBarItems(leading: HamburgerButton(action: 
hamburgerAction),
+                                trailing: QRButton(action: 
checkCameraAvailable))
+            .alert("Scanning QR-codes requires access to the camera",
+                   isPresented: $showCameraAlert,
+                       actions: {   openSettingsButton
+                                    dismissAlertButton },
+                       message: {   Text("Please allow camera access in 
settings.") })
+            .sheet(isPresented: $showQRScanner) {
+                let sheet = AnyView(QRSheet())
+                Sheet(sheetView: sheet)
+            } // sheet
+            .task {
+                symLog.log(".task fetchBalancesM")
+                await reloadAction()
+            } // task
+    }
+}
+// MARK: -
+extension BalancesListView {
+    struct Content: View {
+        let symLog: SymLogV?
+        @ObservedObject var model: BalancesModel
+        @Binding var centsToTransfer: UInt64
+        var reloadAction: () async -> ()
+        @Binding var myListStyle: MyListStyle
+
+        var body: some View {
+#if DEBUG
+            let _ = Self._printChanges()
+            let _ = symLog?.vlog()       // just to get the # to compare it 
with .onAppear & onDisappear
+#endif
+            Group {
+                List (model.balances, id: \.self) { balance in
+                    let model = TransactionsModel.model(currency: 
balance.available.currencyStr)
+                    BalancesSectionView(balance: balance, centsToTransfer: 
$centsToTransfer, model: model)
+                }
+                .refreshable {
+                    symLog?.log("refreshing")
+                    await reloadAction()      // this closure is already 
async, no need for a Task
+                }
+                .listStyle(myListStyle.style)
+                .anyView
+            }
+            .navigationBarTitleDisplayMode(.automatic)
+            .onAppear() {
+                DebugViewC.shared.setViewID(VIEW_BALANCES)
+            }
+            .overlay {
+                if model.balances.isEmpty {
+                    WalletEmptyView()
+                        
.background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
+                }
+            }
+            // automatically fetch balances after receiving 
transaction-state-transition ...
+            .onNotification(.TransactionStateTransition) { notification in
+                // doesn't need to be received on main thread because we just 
reload in the background anyway
+                symLog?.log(".onNotification(.TransactionStateTransition) ==> 
reloading balances")
+                Task { await reloadAction() }
+            }
+        } // body
+    } // Content
+}
diff --git a/TalerWallet1/Views/Balances/BalancesSectionView.swift 
b/TalerWallet1/Views/Balances/BalancesSectionView.swift
new file mode 100644
index 0000000..d9535c1
--- /dev/null
+++ b/TalerWallet1/Views/Balances/BalancesSectionView.swift
@@ -0,0 +1,161 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+import SymLog
+
+/// This view shows a currency section
+///     Currency Name
+/// [Send Coins]  [Receive Coins]     Balance
+///      tapping on Balance leads to completed Transactions (.done)
+/// optional:   Pending Incoming
+/// optional:   Pending Outgoing
+/// optional:   Suspended / Aborting / Aborted / Expired
+
+struct BalancesSectionView: View {
+    private let symLog = SymLogV()
+    var balance:Balance
+    @Binding var centsToTransfer: UInt64
+    @ObservedObject var model: TransactionsModel
+
+    @State private var isShowingDetailView = false
+    @State private var buttonSelected: Int? = nil
+    @State private var completedTransactions: [Transaction] = []
+    @State private var pendingTransactions: [Transaction] = []
+    @State private var uncompletedTransactions: [Transaction] = []
+
+    var body: some View {
+        let currency = balance.available.currencyStr
+        let reloadCompleted = {
+            await model.fetchTransactions(currency: currency)
+            completedTransactions = 
TransactionsModel.completedTransactions(model.transactions)
+        }
+        let reloadPending = {
+            await model.fetchTransactions(currency: currency)
+            pendingTransactions = 
TransactionsModel.pendingTransactions(model.transactions)
+        }
+        let reloadUncompleted = {
+            await model.fetchTransactions(currency: currency)
+            uncompletedTransactions = 
TransactionsModel.uncompletedTransactions(model.transactions)
+        }
+        let deleteAction = model.deleteTransaction
+        let abortAction = model.abortTransaction
+
+
+        Section {
+            if "KUDOS" == currency && !balance.available.isZero {
+                Text("You can spend these KUDOS in the [Demo 
Shop](https://shop.demo.taler.net), or send coins to another wallet.")
+                    .multilineTextAlignment(.leading)
+            }
+            HStack(spacing: 0) {
+                NavigationLink(destination: LazyView {
+                    SendAmount(amountAvailable: balance.available)
+                  }, tag: 1, selection: $buttonSelected
+                ) { EmptyView() }.frame(width: 0).opacity(0).hidden()
+
+                NavigationLink(destination: LazyView {
+                    RequestPayment(model: Peer2peerModel.model(),
+                               scopeInfo: balance.scopeInfo,
+                         centsToTransfer: $centsToTransfer)
+                        
.background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
+                  }, tag: 2, selection: $buttonSelected
+                ) { EmptyView() }.frame(width: 0).opacity(0).hidden()
+
+                NavigationLink(destination: LazyView {
+                    TransactionsListView(navTitle: String(localized: 
"Transactions"), currency: currency,
+                                     transactions: completedTransactions,
+                                     reloadAction: reloadCompleted,
+                                     deleteAction: deleteAction,
+                                      abortAction: abortAction)
+                  }, tag: 3, selection: $buttonSelected
+                ) { EmptyView() }.frame(width: 0).opacity(0).hidden()
+
+                BalanceRowView(amount: balance.available, sendAction: {
+print("button: Send Coins: \(currency)")
+                        buttonSelected = 1      // will trigger SendAmount 
NavigationLink
+                    }, recvAction: {
+print("button: Request Payment: \(currency)")
+                        buttonSelected = 2      // will trigger RequestPayment 
NavigationLink
+                    }, rowAction: {
+print("button: Transactions: \(currency)")
+                        buttonSelected = 3      // will trigger 
TransactionList NavigationLink
+                    })
+            }
+            let hasPending = pendingTransactions.count > 0
+            if hasPending {
+                let hasPendingIn = !balance.pendingIncoming.isZero
+                let hasPendingOut = !balance.pendingOutgoing.isZero
+                NavigationLink {
+let _ = print("button: Pending Transactions: \(currency)")
+                    LazyView {
+                        TransactionsListView(navTitle: String(localized: 
"Pending"), currency: currency,
+                                         transactions: pendingTransactions,
+                                         reloadAction: reloadPending,
+                                         deleteAction: deleteAction,
+                                          abortAction: abortAction)
+                    }
+                } label: {
+                    VStack(spacing: 6) {
+                        if hasPendingIn {
+                            PendingRowView(amount: balance.pendingIncoming, 
incoming: true)
+                        }
+                        if hasPendingOut {
+                            PendingRowView(amount: balance.pendingOutgoing, 
incoming: false)
+                        }
+                    }
+                }
+            }
+            let hasUncompleted = uncompletedTransactions.count > 0
+            if hasUncompleted {
+                NavigationLink {
+let _ = print("button: Uncompleted Transactions: \(currency)")
+                    LazyView {
+                        TransactionsListView(navTitle: String(localized: 
"Uncompleted"), currency: currency,
+                                             transactions: 
uncompletedTransactions,
+                                             reloadAction: reloadUncompleted,
+                                             deleteAction: deleteAction,
+                                             abortAction: abortAction)
+                    }
+                } label: {
+                    UncompletedRowView(uncompletedTransactions: 
uncompletedTransactions)
+                }
+
+            }
+        } header: {
+            Text(currency)
+                .font(.title)
+        } .task {
+            await model.fetchTransactions(currency: currency)
+            pendingTransactions = 
TransactionsModel.pendingTransactions(model.transactions)
+            uncompletedTransactions = 
TransactionsModel.uncompletedTransactions(model.transactions)
+        }
+    } // body
+}
+// MARK: -
+#if DEBUG
+fileprivate struct BindingViewContainer : View {
+    @State var centsToTransfer: UInt64 = 333
+    let model = TransactionsModel.model(currency: LONGCURRENCY)
+
+    var body: some View {
+        let scopeInfo = ScopeInfo(type: ScopeInfo.ScopeInfoType.exchange, 
exchangeBaseUrl: DEMOEXCHANGE, currency: LONGCURRENCY)
+        let balance = Balance(available: try! Amount(fromString: LONGCURRENCY 
+ ":0.1"),
+                        pendingIncoming: try! Amount(fromString: LONGCURRENCY 
+ ":4.8"),
+                        pendingOutgoing: try! Amount(fromString: LONGCURRENCY 
+ ":3.25"),
+                 hasPendingTransactions: true,
+                      requiresUserInput: false,
+                              scopeInfo: scopeInfo)
+        List {
+            BalancesSectionView(balance: balance, centsToTransfer: 
$centsToTransfer, model: model)
+        }
+    }
+}
+
+struct BalancesSectionView_Previews: PreviewProvider {
+    static var previews: some View {
+        BindingViewContainer()
+    }
+}
+#endif
diff --git a/TalerWallet1/Views/Balances/CurrenciesListView.swift 
b/TalerWallet1/Views/Balances/CurrenciesListView.swift
deleted file mode 100644
index 192d531..0000000
--- a/TalerWallet1/Views/Balances/CurrenciesListView.swift
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-import SwiftUI
-import SymLog
-
-struct CurrenciesListView: View {
-    private let symLog = SymLogV()
-    let navTitle = "GNU Taler Wallet"
-
-    @ObservedObject var viewModel: BalancesModel
-    var hamburgerAction: () -> Void
-
-    var body: some View {
-        let reloadAction = viewModel.fetchBalances
-        VStack {
-            if viewModel.balances == nil {
-                symLog { LoadingView(backButtonHidden: true) }
-            } else {
-                symLog { NavigationView {
-                    Content(symLog: symLog, viewModel: viewModel, 
reloadAction: reloadAction)
-                        .navigationBarItems(leading: HamburgerButton(action: 
hamburgerAction))
-                        .navigationTitle(navTitle)
-                } }
-            }
-        }.task {
-            symLog.log(".task")
-            do {
-                try await reloadAction()
-            } catch {
-                // TODO: show error
-                symLog.log(error.localizedDescription)
-            }
-        }
-    }
-}
-// MARK: -
-extension CurrenciesListView {
-    struct Content: View {
-        let symLog: SymLogV?
-        @ObservedObject var viewModel: BalancesModel
-        @EnvironmentObject var controller : Controller
-        var reloadAction: () async throws -> ()
-
-        var body: some View {
-            if viewModel.balances!.isEmpty {        // TODO: all spent?
-                WalletEmptyView()
-                    .navigationBarTitleDisplayMode(.large)
-            } else {
-                List (viewModel.balances!, id: \.self) { balance in
-                    NavigationLink {
-                        TransactionsListView(viewModel: 
controller.transactionsModel)
-                    } label: {
-                        // TODO: sendAction, recvAction
-                        CurrencyView(balance: balance, sendAction: {}, 
recvAction: {})
-                    }
-                }
-                    .navigationBarTitleDisplayMode(.large)      // .inline
-                    .refreshable {
-                        do {
-                            symLog?.log("refreshing")
-                            try await reloadAction()
-                        } catch {
-                            // TODO: error
-                            symLog?.log(error.localizedDescription)
-                        }
-                    }
-            }
-        }
-    }
-}
diff --git a/TalerWallet1/Views/Balances/CurrencyView.swift 
b/TalerWallet1/Views/Balances/CurrencyView.swift
deleted file mode 100644
index 0978de0..0000000
--- a/TalerWallet1/Views/Balances/CurrencyView.swift
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-import SwiftUI
-import taler_swift
-
-/// This view shows a currency
-/// Header: Currency Name (e.g. Kudos)
-/// [Send Funds]  [Receive Funds]     Balance
-/// Pending Incoming
-/// Pending Outgoing
-
-struct CurrencyView: View {
-    var balance:Balance
-    let sendAction: () -> Void
-    let recvAction: () -> Void
-    var body: some View {
-        VStack {
-            Text(balance.available.currencyStr)
-                .font(.title)
-            BalanceRow(amount: balance.available, sendAction: sendAction, 
recvAction: recvAction)
-
-            let inAmount = balance.pendingIncoming
-            if !inAmount.isZero {
-                PendingRow(amount: inAmount, incoming: true, counterparty: 
"exchange.demo.taler.net")
-            }
-            let outAmount = balance.pendingOutgoing
-            if !outAmount.isZero {
-                PendingRow(amount: outAmount, incoming: false, counterparty: 
"merchant")
-            }
-        }
-//            .padding()
-    }
-}
-
-struct CurrencyView_Previews: PreviewProvider {
-    static var balance = Balance(available: try! Amount(fromString: 
"Taler:0.1"),
-                                 pendingIncoming: try! Amount(fromString: 
"Taler:4.8"),
-                                 pendingOutgoing: try! Amount(fromString: 
"Taler:3.25"),
-                                 hasPendingTransactions: true,
-                                 requiresUserInput: false)
-    
-    static var previews: some View {
-        CurrencyView(balance: balance, sendAction: {}, recvAction: {})
-    }
-}
diff --git a/TalerWallet1/Views/Balances/PendingRow.swift 
b/TalerWallet1/Views/Balances/PendingRow.swift
deleted file mode 100644
index d6321d2..0000000
--- a/TalerWallet1/Views/Balances/PendingRow.swift
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-import SwiftUI
-import taler_swift
-
-struct PendingRow: View {
-    let amount: Amount
-    let incoming: Bool
-    let counterparty: String
-    var body: some View {
-        HStack {
-            Image(systemName: incoming ? "text.badge.plus" : 
"text.badge.minus")
-                .padding(.trailing)
-                .font(.largeTitle)
-                .foregroundColor(incoming ? Color("PendingIncoming")  : 
Color("PendingOutgoing"))
-
-            VStack(alignment: .leading) {
-                Text("\(counterparty)")
-                    .font(.headline)
-                    .fontWeight(.medium)
-                Text("Waiting for confirmation")
-                    .font(.callout)
-                    .padding(.vertical, -2.0)
-                Text("some time ago")       // TODO: show time-interval
-                    .font(.callout)
-            }
-            Spacer()
-            VStack(alignment: .trailing) {
-                let sign = incoming ? "+" : "-"
-                Text(sign + "\(amount.valueStr)")
-                    .font(.title)
-                    .foregroundColor(Color.gray)
-                Text("PENDING")
-                    .font(.callout)
-            }
-        }
-        .padding(.top)
-    }
-}
-
-struct PendingRow_Previews: PreviewProvider {
-    static var previews: some View {
-        Group {
-            PendingRow(amount: try! Amount(fromString: "Taler:4.8"), incoming: 
true, counterparty: "exchange.demo.taler.net")
-            PendingRow(amount: try! Amount(fromString: "Taler:3.25"), 
incoming: false, counterparty: "merchant")
-        }
-    }
-}
diff --git a/TalerWallet1/Views/Balances/PendingRowView.swift 
b/TalerWallet1/Views/Balances/PendingRowView.swift
new file mode 100644
index 0000000..95345f0
--- /dev/null
+++ b/TalerWallet1/Views/Balances/PendingRowView.swift
@@ -0,0 +1,48 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+
+/// This view shows a pending transaction row in a currency section
+struct PendingRowView: View {
+    let amount: Amount
+    let incoming: Bool
+
+    var body: some View {
+        HStack {
+            Image(systemName: incoming ? "text.badge.plus"
+                                       : "text.badge.minus")
+                .font(.largeTitle)
+//                .foregroundColor(WalletColors().pendingColor)    // pending 
is always gray
+                .foregroundColor(WalletColors().pendingColor(incoming))
+                .accessibility(hidden: true)
+
+            Spacer()
+            Text("pending\n" + (incoming ? "incoming" : "outgoing"))
+            Spacer()
+            VStack(alignment: .trailing) {
+                let sign = incoming ? "+" : "-"
+                Text(sign + "\(amount.valueStr)")
+                    .font(.title)
+                    .foregroundColor(WalletColors().pendingColor(incoming))
+//                Text("PENDING")
+//                    .font(.callout)
+//                    .foregroundColor(WalletColors().pendingColor(incoming))
+            }
+        }
+        .accessibilityElement(children: .combine)
+    }
+}
+// MARK: -
+#if DEBUG
+struct PendingRowView_Previews: PreviewProvider {
+    static var previews: some View {
+        List {
+            PendingRowView(amount: try! Amount(fromString: LONGCURRENCY + 
":4.8"), incoming: true)
+            PendingRowView(amount: try! Amount(fromString: LONGCURRENCY + 
":3.25"), incoming: false)
+        }
+    }
+}
+#endif
diff --git a/TalerWallet1/Views/Balances/UncompletedRowView.swift 
b/TalerWallet1/Views/Balances/UncompletedRowView.swift
new file mode 100644
index 0000000..b7642dc
--- /dev/null
+++ b/TalerWallet1/Views/Balances/UncompletedRowView.swift
@@ -0,0 +1,34 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+
+/// This view shows an uncompleted transaction row in a currency section
+struct UncompletedRowView: View {
+    var uncompletedTransactions: [Transaction]
+
+    var body: some View {
+        let count = uncompletedTransactions.count
+        HStack {
+            Spacer()
+            Text("\(count) uncompleted transactions")
+                .font(.title2)
+                .foregroundColor(WalletColors().uncompletedColor)
+            Spacer()
+        }
+        .accessibilityElement(children: .combine)
+    }
+}
+// MARK: -
+#if DEBUG
+struct UncompletedRowView_Previews: PreviewProvider {
+    static var previews: some View {
+        let uncompletedTransactions: [Transaction] = []
+        List {
+            UncompletedRowView(uncompletedTransactions: 
uncompletedTransactions)
+        }
+    }
+}
+#endif
diff --git a/TalerWallet1/Views/Balances/WalletEmptyView.swift 
b/TalerWallet1/Views/Balances/WalletEmptyView.swift
deleted file mode 100644
index 72d5192..0000000
--- a/TalerWallet1/Views/Balances/WalletEmptyView.swift
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-import SwiftUI
-
-struct WalletEmptyView: View {
-
-    var body: some View {
-        Form {
-            Section {
-                Text("There is no digital cash in your wallet.")
-                    .padding()
-            }
-            Section {
-                Text("You can get test money from the demo bank:")
-                    .padding()
-            }
-            Section {
-                Text("https://bank.demo.taler.net";)
-                    .padding()
-            }
-        }
-//            .multilineTextAlignment(.center)
-            .font(.title2)
-    }
-}
-
-struct EmptyView_Previews: PreviewProvider {
-    static var previews: some View {
-        WalletEmptyView()
-    }
-}
diff --git a/TalerWallet1/Views/Exchange/ExchangeListView.swift 
b/TalerWallet1/Views/Exchange/ExchangeListView.swift
index fbd52e4..e7fd4fe 100644
--- a/TalerWallet1/Views/Exchange/ExchangeListView.swift
+++ b/TalerWallet1/Views/Exchange/ExchangeListView.swift
@@ -1,103 +1,129 @@
 /*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
  */
 import SwiftUI
+import taler_swift
 import SymLog
 
+/// This view shows the list of exchanges
 struct ExchangeListView: View {
     private let symLog = SymLogV()
-    let navTitle = "Exchanges"
+    let navTitle = String(localized: "Exchanges")
 
-    @ObservedObject var viewModel: ExchangeModel
+    @ObservedObject var model: ExchangeModel
+    var hamburgerAction: () -> Void
+
+
+    // source of truth for the value the user enters in currencyField
+    @State private var centsToTransfer: UInt64 = 0        // TODO: different 
values for different currencies
 
     var body: some View {
-        let reloadAction = viewModel.updateList
-        VStack {
-            if viewModel.exchanges == nil {
-                symLog { LoadingView(backButtonHidden: false) }
-            } else {
-                Content(symLog: symLog, viewModel: viewModel, reloadAction: 
reloadAction)
-                    .navigationTitle(navTitle)
-            }
-        }.task {
-            symLog.log(".task")
-            do {
-                try await reloadAction()
-            } catch {
-                // TODO: show error
-                symLog.log(error.localizedDescription)
+#if DEBUG
+        let _ = Self._printChanges()
+        let _ = symLog.vlog()       // just to get the # to compare it with 
.onAppear & onDisappear
+#endif
+        let reloadAction = model.updateListM
+        Content(symLog: symLog, model: model,
+                centsToTransfer: $centsToTransfer,
+                reloadAction: reloadAction)
+            .navigationBarItems(leading: HamburgerButton(action: 
hamburgerAction))
+            .navigationTitle(navTitle)
+            .task {
+                symLog.log(".task")
+                do {
+                    try await reloadAction()
+                } catch {    // TODO: show error
+                    symLog.log(error.localizedDescription)
+                }
             }
-        }
+    }
+}
+// MARK: -
+struct ExchangeAmount: Identifiable {
+    let exchange: Exchange
+    let amountAvailable: Amount
+
+    var id: String {    // needed for Identifiable
+        exchange.exchangeBaseUrl
     }
 }
 // MARK: -
 extension ExchangeListView {
     struct Content: View {
-        let symLog: SymLogV
-        @ObservedObject var viewModel: ExchangeModel
+        let symLog: SymLogV?
+        @AppStorage("listStyle") var myListStyle = MyListStyle.automatic
+        @ObservedObject var model: ExchangeModel
+        @Binding var centsToTransfer: UInt64
         var reloadAction: () async throws -> ()
+
         @State var showAlert: Bool = false
         @State var newExchange: String = "https://exchange-age.taler.ar/";
 
         func addExchange(_ exUrl: String) -> Void {
             Task {
                 do {
-                    symLog.log("adding: \(exUrl)")
-                    try await viewModel.add(url: exUrl)
-                    symLog.log("added: \(exUrl)")
-                } catch {
-                    symLog.log("error: \(error)")
-                    // TODO: error handling - couldn't add exchangeURL
+                    symLog?.log("adding: \(exUrl)")
+                    try await model.add(url: exUrl)
+                    symLog?.log("added: \(exUrl)")
+                } catch {    // TODO: error handling - couldn't add exchangeURL
+                    symLog?.log("error: \(error)")
                 }
             }
         }
 
+        func currenciesDict(_ exchanges: [Exchange]) -> [String : [Exchange]] {
+            var currencies: [String : [Exchange]] = [:]
+
+            for exchange in exchanges {
+                let currency = exchange.currency ?? "Unknown"
+                if currencies[currency] != nil {
+                    currencies[currency]!.append(exchange)
+                } else {
+                    currencies[currency] = [exchange]
+                }
+            }
+            return currencies
+        }
+
+        @State private var exchangeAmount: ExchangeAmount? = nil
+
         var body: some View {
             let plusAction: () -> Void = {
-//                withAnimation { showPopup = true }
+//                withAnimation { showAlert = true }
                 showAlert = true
             }
             VStack {
-                if viewModel.exchanges!.isEmpty {
+                if model.exchanges.isEmpty {
                     Text("No Exchanges yet...")
                 } else {
-                    List(viewModel.exchanges!, id: \.self) { exchange in
-                        VStack {
-                            Text(exchange.exchangeBaseUrl)
-                                .frame(maxWidth: .infinity)
-                                .padding()
-                            Text("Currency: " + (exchange.currency ?? "?"))
-                                .frame(maxWidth: .infinity)
-                                .padding()
+                    ScrollViewReader { scrollView in    // 
+                        let dict = currenciesDict(model.exchanges)
+                        let sortedDict = dict.sorted{ $0.key < $1.key}
+                        List {
+                            ForEach(sortedDict, id: \.key) { key, value in
+                                ExchangeSectionView(currency: key, exchanges: 
value, centsToTransfer: $centsToTransfer)
+                            }
                         }
-                    }
-                        .navigationBarTitleDisplayMode(.large)      // .inline
                         .refreshable {
                             do {
-                                symLog.log("refreshing")
+                                symLog?.log("refreshing")
                                 try await reloadAction()
-                            } catch {
-                                // TODO: error
-                                symLog.log(error.localizedDescription)
+                            } catch {    // TODO: error
+                                symLog?.log(error.localizedDescription)
                             }
                         }
-                }
+                        .listStyle(myListStyle.style)
+                        .anyView
+                    }
+                } // else
+            }.onAppear() {
+                DebugViewC.shared.setViewID(VIEW_EXCHANGES)
             }
-                .navigationBarItems(trailing: PlusButton(action: plusAction))
-                .textFieldAlert(isPresented: $showAlert, title: "Add Exchange",
-                                doneText: "Add", text: $newExchange, action: 
addExchange)
+            .navigationBarTitleDisplayMode(.automatic)
+            .navigationBarItems(trailing: PlusButton(action: plusAction))
+            .textFieldAlert(isPresented: $showAlert, title: "Add Exchange",
+                               doneText: "Add", text: $newExchange, action: 
addExchange)
         } // body
     }
 }
diff --git a/TalerWallet1/Views/Exchange/ExchangeSectionView.swift 
b/TalerWallet1/Views/Exchange/ExchangeSectionView.swift
new file mode 100644
index 0000000..cdcacbe
--- /dev/null
+++ b/TalerWallet1/Views/Exchange/ExchangeSectionView.swift
@@ -0,0 +1,112 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+
+struct ExchangeRowView: View {
+    let exchange: Exchange
+    @Binding var centsToTransfer: UInt64
+
+    @State private var buttonSelected: Int? = nil
+    var body: some View {
+        let baseURL = exchange.exchangeBaseUrl
+        let model = WithdrawModel.model(baseURL: baseURL)
+
+        HStack(spacing: 0) {    // can't use the built in Label because it 
adds the accessory arrow
+            Text(baseURL.trimURL())
+
+            NavigationLink(destination: LazyView {
+                EmptyView()
+            }, tag: 1, selection: $buttonSelected
+            ) {
+                EmptyView()
+            }   .frame(width: 0)
+                .opacity(0)
+            NavigationLink(destination: LazyView {
+                ManualWithdraw(exchange: exchange,
+                               model: model,
+                               centsToTransfer: $centsToTransfer)
+                
.background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
+            }, tag: 2, selection: $buttonSelected
+            ) {
+                EmptyView()
+            }   .frame(width: 0)
+                .opacity(0)
+        }.listRowSeparator(.hidden)
+
+        HStack {        // buttons just set "buttonSelected" so the 
NavigationLink will trigger
+            Button("Deposit\nCoins") { buttonSelected = 1 }
+                .multilineTextAlignment(.center)
+                .lineLimit(2)
+                .buttonStyle(TalerButtonStyle(type: .bordered))
+                .disabled(true)     // TODO: after implementing Deposit check 
available
+
+            Button("Withdraw\nCoins") { buttonSelected = 2 }
+                .multilineTextAlignment(.center)
+                .lineLimit(2)
+                .buttonStyle(TalerButtonStyle(type: .bordered))
+        }.listRowSeparator(.visible)
+//        .listRowSeparatorTint(.red)
+        .fixedSize(horizontal: false, vertical: true)
+    }
+}
+/// This view shows the currency name in an exchange section
+///         currency
+/// [Deposit Coins]  [Withdraw Coins]
+struct ExchangeSectionView: View {
+    let currency: String
+    let exchanges: [Exchange]
+
+    @Binding var centsToTransfer: UInt64
+
+    var body: some View {
+#if DEBUG
+        let _ = Self._printChanges()
+//        let _ = symLog.vlog()       // just to get the # to compare it with 
.onAppear & onDisappear
+#endif
+        Section {
+            ForEach(exchanges) { exchange in
+                ExchangeRowView(exchange: exchange, centsToTransfer: 
$centsToTransfer)
+            }
+            .accessibilityElement(children: .combine)
+        } header: {
+            Text(currency)
+                .font(.title)
+        }
+    }
+}
+// MARK: -
+#if DEBUG
+struct ExchangeRow_Container : View {
+    @State private var centsToTransfer: UInt64 = 100        // TODO: maybe 
Decimal?
+
+    var body: some View {
+        let exchange1 = Exchange(exchangeBaseUrl: DEMO_AGE_EXCHANGE,
+                                        currency: LONGCURRENCY,
+                                       paytoUris: [],
+                                       tosStatus: "tosStatus",
+                                  exchangeStatus: "exchangeStatus",
+                           ageRestrictionOptions: [12,16],
+                                       permanent: true)
+        let exchange2 = Exchange(exchangeBaseUrl: DEMO_EXP_EXCHANGE,
+                                        currency: LONGCURRENCY,
+                                       paytoUris: [],
+                                       tosStatus: "tosStatus",
+                                  exchangeStatus: "exchangeStatus",
+                           ageRestrictionOptions: [],
+                                       permanent: false)
+        List {
+            ExchangeSectionView(currency: LONGCURRENCY, exchanges: [exchange1, 
exchange2],
+                                centsToTransfer: $centsToTransfer)
+        }
+    }
+}
+
+struct ExchangeRow_Previews: PreviewProvider {
+    static var previews: some View {
+        ExchangeRow_Container()
+    }
+}
+#endif
diff --git a/TalerWallet1/Views/Exchange/ManualWithdraw.swift 
b/TalerWallet1/Views/Exchange/ManualWithdraw.swift
new file mode 100644
index 0000000..db32716
--- /dev/null
+++ b/TalerWallet1/Views/Exchange/ManualWithdraw.swift
@@ -0,0 +1,199 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+import SymLog
+
+struct ManualWithdraw: View {
+    private let symLog = SymLogV()
+    let navTitle = String(localized: "Withdraw Coins")
+
+    var exchange: Exchange
+    @ObservedObject var model: WithdrawModel
+    @Binding var centsToTransfer: UInt64
+
+    @State var manualWithdrawalDetails: ManualWithdrawalDetails? = nil
+//    @State var numCoins: Int = 0
+
+    // returns numCoins, 0 if invalid, -1 if unknown
+    //         either fees or empty string
+    private func numAndFee(detailsForAmount: ManualWithdrawalDetails?) -> 
(Int, String) {
+        do {
+            if let details = detailsForAmount {
+                let fee = try details.amountRaw - details.amountEffective
+                return (details.numCoins ?? -1,    // either the number of 
coins, or unknown
+                        fee.isZero ? "" : fee.readableDescription)
+            }
+        } catch {}
+        symLog.log("invalid")
+        return (0, "")      // invalid
+    }
+    @State var ageMenuList: [Int] = []
+    @State var selectedAge = 0
+
+
+    var body: some View {
+#if DEBUG
+        let _ = Self._printChanges()
+        let _ = symLog.vlog()       // just to get the # to compare it with 
.onAppear & onDisappear
+#endif
+        let currency = exchange.currency!
+        let currencyField = CurrencyField(value: $centsToTransfer, currency: 
currency) // becomeFirstResponder
+
+        ScrollView {
+            Text("from \(exchange.exchangeBaseUrl.trimURL())")
+                .padding(.top)
+                .font(.title3)
+            CurrencyInputView(currencyField: currencyField, title: 
String(localized: "Amount to withdraw:"))
+
+            let (numCoins, fee) = numAndFee(detailsForAmount: 
manualWithdrawalDetails)
+            let unknown   = (numCoins < 0)
+            let invalid   = (numCoins == 0)
+            let manyCoins = (numCoins > 99)
+            let quiteSome = (numCoins > 199)
+            let tooMany   = (numCoins > 999)
+            let hasFee    = (fee.count > 0)
+            let shownFee = hasFee ? String(localized: "- \(fee)") : 
String(localized: "No")
+            Text(invalid ? "invalid amount"
+               : tooMany ? "too many coins for a single withdrawal"
+                         : "\(shownFee) withdrawal fee")
+                .foregroundColor((invalid || tooMany || hasFee) ? .red : 
.primary)
+                .padding()
+
+            if !invalid {
+                HStack {
+                    Text(unknown ? "Some" : "\(numCoins)")
+                        .foregroundColor(quiteSome ? .red : .primary)
+                    Text(tooMany ? "coins" : "coins to obtain:")
+                        .foregroundColor(tooMany ? .red : .primary)
+
+                    Spacer()
+                    if !tooMany {
+                        let effective = 
manualWithdrawalDetails?.amountEffective ?? Amount(currency: currency, value: 0)
+                        Text(effective.readableDescription)
+                    }
+                } // xx coins to obtain:    YYY currency
+                .padding(.top)
+//                .font(.title3)
+
+                if !tooMany {
+                    if manyCoins {
+                        Text(quiteSome ? "Warning: It will take quite some 
time\nto generate this many coins!"
+                                       : "Warning: It will take some time\nto 
generate this many coins.")
+                        .multilineTextAlignment(.leading)
+                        .padding(.top, 6)
+                        .foregroundColor(quiteSome ? .red : .primary)
+                    } // warnings
+
+                    if ageMenuList.count > 1 {
+                        HStack {
+                            Text("If this wallet belongs to a child or 
teenager, the generated coins should be age-restricted:")
+                                .multilineTextAlignment(.leading)
+                                .font(.footnote)
+                            Spacer()
+                        }.padding(.top)
+                        Picker("Select age", selection: $selectedAge) {
+                            ForEach($ageMenuList, id: \.self) { item in
+                                let index = item.wrappedValue
+                                Text((index == 0) ? "unlimited"
+                                                  : "\(index) 
years").tag(index)
+                            }
+                        }
+                    }
+                    if let tosAcc = manualWithdrawalDetails?.tosAccepted {
+                        if tosAcc {
+                            let restrictAge: Int? = (selectedAge == 0) ? nil
+                                                                       : 
selectedAge
+let _ = print(selectedAge, restrictAge)
+                            NavigationLink(destination: LazyView {
+                                ManualWithdrawDone(exchange: exchange,
+                                                      model: model,
+                                            centsToTransfer: centsToTransfer,
+                                                restrictAge: restrictAge)
+                            }) {
+                                Text("Confirm Withdrawal")      // 
VIEW_WITHDRAW_ACCEPT
+                            }.buttonStyle(TalerButtonStyle(type: .prominent))
+                        } else {
+                            NavigationLink(destination: LazyView {
+                                WithdrawTOSView(exchangeBaseUrl: 
exchange.exchangeBaseUrl,
+                                                          model: model,
+                                                         viewID: 
VIEW_WITHDRAW_TOS,
+                                                   acceptAction: nil)         
// pop back to here
+                            }) {
+                                Text("Check Terms of Service")  // 
VIEW_WITHDRAW_TOS
+                            }.buttonStyle(TalerButtonStyle(type: .prominent))
+                        }
+                    }
+                } // tooMany
+            } // invalid
+            Spacer()
+        }
+        .frame(maxWidth: .infinity, alignment: .leading)
+        .navigationTitle(navTitle)
+        .padding(.horizontal)
+        .onAppear {
+            symLog.log("onAppear")
+            DebugViewC.shared.setViewID(VIEW_WITHDRAWAL)
+        }
+        .task(id: centsToTransfer) {
+            let amount = Amount.amountFromCents(currency, centsToTransfer)
+            do {
+                manualWithdrawalDetails = try await 
model.loadWithdrawalDetailsForAmountM(exchange.exchangeBaseUrl, amount: amount)
+                if let ageRestrictions = 
manualWithdrawalDetails?.ageRestrictionOptions {
+                    var ages = ageRestrictions
+                    let nonzero = ages.count > 0        // need at least 1 
value from exchange which is not 0
+                    if nonzero {
+                        if ages[0] != 0 {               // ensure that the 
first age is "0"
+                            ages.insert(0, at: 0)       // if not, insert "0" 
at position 0
+                        }
+                        if selectedAge >= ages.count {  // check for out of 
bounds
+                            selectedAge = 0
+                        }
+                    } else {
+                        selectedAge = 0             // first ensure that 
selected is not out of bounds
+                    }
+print(ages)
+                    ageMenuList = ages              // set State (will update 
view)
+                }
+            } catch {    // TODO: error
+                symLog.log(error.localizedDescription)
+                manualWithdrawalDetails = nil
+            }
+        }
+    }
+}
+// MARK: -
+#if DEBUG
+struct ManualWithdraw_Container : View {
+    @State private var centsToTransfer: UInt64 = 510
+    @State private var manualWithdrawalDetails = 
ManualWithdrawalDetails(amountRaw: try! Amount(fromString: LONGCURRENCY + 
":5.1"),
+                                                                   
amountEffective: try! Amount(fromString: LONGCURRENCY + ":5.0"),
+                                                                         
paytoUris: [],
+                                                                       
tosAccepted: false,
+                                                             
ageRestrictionOptions: [],
+                                                                          
numCoins: 6)
+
+    var body: some View {
+        let model = WithdrawModel.model(baseURL: DEMOEXCHANGE)
+        let exchange = Exchange(exchangeBaseUrl: DEMOEXCHANGE,
+                                       currency: LONGCURRENCY,
+                                      paytoUris: [],
+                                      tosStatus: "tosStatus",
+                                 exchangeStatus: "exchangeStatus",
+                          ageRestrictionOptions: [],
+                                      permanent: false)
+        ManualWithdraw(exchange: exchange,
+                          model: model,
+                centsToTransfer: $centsToTransfer,
+        manualWithdrawalDetails: manualWithdrawalDetails)
+    }
+}
+
+struct ManualWithdraw_Previews: PreviewProvider {
+    static var previews: some View {
+        ManualWithdraw_Container()
+    }
+}
+#endif
diff --git a/TalerWallet1/Views/Exchange/ManualWithdrawDone.swift 
b/TalerWallet1/Views/Exchange/ManualWithdrawDone.swift
new file mode 100644
index 0000000..3d698bf
--- /dev/null
+++ b/TalerWallet1/Views/Exchange/ManualWithdrawDone.swift
@@ -0,0 +1,79 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+import SymLog
+
+struct ManualWithdrawDone: View {
+    private let symLog = SymLogV()
+    let navTitle = String(localized: "Wire Transfer")
+
+    var exchange: Exchange
+    @ObservedObject var model: WithdrawModel
+    var centsToTransfer: UInt64
+    var restrictAge: Int?
+    @State var acceptManualWithdrawalResult: AcceptManualWithdrawalResult?
+    @State var withdrawalTransaction: Transaction?
+
+    var body: some View {
+#if DEBUG
+        let _ = Self._printChanges()
+        let _ = symLog.vlog()       // just to get the # to compare it with 
.onAppear & onDisappear
+#endif
+        VStack {
+            if let transaction = withdrawalTransaction {
+                TransactionDetailView(transaction: transaction,
+                                       doneAction: 
{ViewState.shared.popToRootView()})
+                .navigationBarBackButtonHidden(true)            // exit only 
by Done-Button
+                .navigationTitle(navTitle)
+            } else {
+                WithdrawProgressView(message: 
exchange.exchangeBaseUrl.trimURL())
+                    .navigationTitle("Loading " + navTitle)
+            }
+        }.onAppear() {
+            symLog.log("onAppear")
+            DebugViewC.shared.setViewID(VIEW_WITHDRAW_ACCEPT)
+        }.task {
+            do {
+                let amount = Amount.amountFromCents(exchange.currency!, 
centsToTransfer)
+                let result = try await 
model.sendAcceptManualWithdrawalM(exchange.exchangeBaseUrl,
+                                                                          
amount: amount, restrictAge: restrictAge)
+print(result as Any)
+                let transaction = try await 
model.getTransactionById(transactionId: result!.transactionId)
+                withdrawalTransaction = transaction
+//                acceptManualWithdrawalResult = result
+            } catch {    // TODO: error
+                symLog.log(error.localizedDescription)
+            }
+        }
+    }
+}
+
+// MARK: -
+#if DEBUG
+struct ManualWithdrawDone_Container : View {
+    @State private var centsToTransfer: UInt64 = 510
+
+    var body: some View {
+        let model = WithdrawModel.model(baseURL: DEMOEXCHANGE)
+        let exchange = Exchange(exchangeBaseUrl: DEMOEXCHANGE,
+                                       currency: LONGCURRENCY,
+                                      paytoUris: [],
+                                      tosStatus: "tosStatus",
+                                 exchangeStatus: "exchangeStatus",
+                          ageRestrictionOptions: [],
+                                      permanent: false)
+        ManualWithdrawDone(exchange: exchange,
+                              model: model,
+                    centsToTransfer: centsToTransfer)
+    }
+}
+
+struct ManualWithdrawDone_Previews: PreviewProvider {
+    static var previews: some View {
+        ManualWithdrawDone_Container()
+    }
+}
+#endif
diff --git a/TalerWallet1/Views/HelperViews/AmountView.swift 
b/TalerWallet1/Views/HelperViews/AmountView.swift
index 4249876..bf2b32f 100644
--- a/TalerWallet1/Views/HelperViews/AmountView.swift
+++ b/TalerWallet1/Views/HelperViews/AmountView.swift
@@ -1,17 +1,6 @@
 /*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
  */
 import SwiftUI
 
@@ -19,13 +8,14 @@ struct AmountView: View {
     let title: String
     let value: String
     let color: Color
+    let large: Bool      // set to false for QR or IBAN
     var body: some View {
         VStack {
             Text(title)
                 .font(.title3)
             Text(value)
-                .font(.largeTitle)
-                .fontWeight(.medium)
+                .font(large ? .largeTitle : .title)
+                .fontWeight(large ? .medium : .regular)
                 .foregroundColor(color)
         }
             .frame(maxWidth: .infinity, alignment: .center)
@@ -35,9 +25,11 @@ struct AmountView: View {
 
 struct AmountView_Previews: PreviewProvider {
     static var previews: some View {
-        Form {
-            AmountView(title: "Fee", value: "- 0,2 Taler", color: 
Color("Outgoing"))
-            AmountView(title: "Coins", value: "4,8 Taler", color: 
Color("Incoming"))
+        List {
+            AmountView(title: "Fee", value: "- 0,2 Taler",
+                       color: Color("Outgoing"), large: true)
+            AmountView(title: "Coins", value: "4,8 Taler",
+                       color: Color("Incoming"), large: false)
         }
     }
 }
diff --git a/TalerWallet1/Views/HelperViews/Buttons.swift 
b/TalerWallet1/Views/HelperViews/Buttons.swift
index fd298cb..68d17eb 100644
--- a/TalerWallet1/Views/HelperViews/Buttons.swift
+++ b/TalerWallet1/Views/HelperViews/Buttons.swift
@@ -1,90 +1,248 @@
 /*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
  */
 import SwiftUI
+import Foundation
+import AVFoundation
+
+
+
+
+extension ShapeStyle where Self == Color {
+    static var random: Color {
+        Color(
+            red: .random(in: 0...1),
+            green: .random(in: 0...1),
+            blue: .random(in: 0...1)
+        )
+    }
+}
 
 struct HamburgerButton : View  {
+    var font: Font?
     let action: () -> Void
 
     var body: some View {
         Button(action: action) {
             Image(systemName: "line.3.horizontal")
         }
-            .font(.title)
+            .font(font ?? .title)
+    }
+}
+
+struct QRButton : View  {
+    var font: Font?
+    let action: () -> Void
+
+    var body: some View {
+        Button(action: action) {
+            Image(systemName: "qrcode.viewfinder")
+        }
+        .font(font ?? .title)
     }
 }
 
 struct PlusButton : View  {
+    var font: Font?
     let action: () -> Void
 
     var body: some View {
         Button(action: action) {
             Image(systemName: "plus")
         }
-            .font(.title)
+            .font(font ?? .title)
     }
 }
 
 struct ArrowUpButton : View  {
+    var font: Font?
     let action: () -> Void
 
     var body: some View {
         Button(action: action) {
             Image(systemName: "arrow.up.to.line")
         }
-        .font(.title3)
+            .font(font ?? .title3)
     }
 }
 
 struct ArrowDownButton : View  {
+    var font: Font?
     let action: () -> Void
 
     var body: some View {
         Button(action: action) {
             Image(systemName: "arrow.down.to.line")
         }
-        .font(.title3)
+            .font(font ?? .title3)
     }
 }
 
 struct ReloadButton : View  {
     let disabled: Bool
+    var font: Font?
     let action: () -> Void
 
     var body: some View {
         Button(action: action) {
             Image(systemName: "arrow.clockwise")
         }
-            .font(.title)
+            .font(font ?? .title)
             .disabled(disabled)
     }
 }
 
-struct AwesomeButton: View {
+struct BigBlueButton: View {
     let title: String
+    var font: Font?
+    let disabled: Bool
     let action: () -> Void
     var body: some View {
         Button(action: action) {
+            let colors: [Color] = disabled ? [Color.gray, Color.blue]
+                                           : [Color.red, Color.blue]
+            let back = LinearGradient(gradient: Gradient(colors: colors),
+                                      startPoint: .leading, endPoint: 
.trailing)
             Text(title)
-                .frame(minWidth: 0, maxWidth: 300)
+                .frame(minWidth: 50, maxWidth: 500)
                 .padding()
                 .foregroundColor(.white)
-                .background(LinearGradient(gradient: Gradient(colors: 
[Color.red, Color.blue]), startPoint: .leading, endPoint: .trailing))
-                .cornerRadius(40)
-                .font(.title)
+                .background(disabled ? .gray : .blue)   // back
+                .cornerRadius(20)
+                .font(font ?? .title)
+        }
+            .disabled(disabled)
+    }
+}
+struct BorderedButton: View {
+    let title: String
+    var font: Font?
+    let action: () -> Void
+
+    var body: some View {
+        Button(action: action) {
+            Text(title)
+                .frame(minWidth: 50, maxWidth: 500)
+                .padding()
+                .font(font ?? .title)
+        }
+        .buttonStyle(.bordered)
+    }
+}
+struct ProminentButton: View {
+    let title: String
+    var font: Font?
+    let action: () -> Void
+
+    var body: some View {
+        Button(action: action) {
+            Text(title)
+                .frame(minWidth: 50, maxWidth: 500)
+                .padding()
+                .font(font ?? .title)
         }
+        .buttonStyle(.borderedProminent)
+    }
+}
+
+struct TalerButtonStyle: ButtonStyle {
+    @Environment(\.isEnabled) private var isEnabled: Bool
+    func disabled() -> Bool { !isEnabled }
+
+    enum TalerButtonStyleType {
+        case plain
+        case bordered
+        case prominent
+    }
+    var type: TalerButtonStyleType = .plain
+    var dimmed: Bool = false
+    var narrow: Bool = false
+    var aligned: TextAlignment = .center
+
+    public func makeBody(configuration: ButtonStyle.Configuration) -> some 
View {
+            MyBigButton(type: type,
+                   foreColor: foreColor(type: type, pressed: 
configuration.isPressed),
+                   backColor: backColor(type: type, pressed: 
configuration.isPressed),
+                      dimmed: dimmed,
+               configuration: configuration,
+                    disabled: disabled(),
+                      narrow: narrow,
+                     aligned: aligned)
+    }
+
+    func foreColor(type: TalerButtonStyleType, pressed: Bool) -> Color {
+        return if type == .plain {
+            WalletColors().fieldForeground
+        } else {
+            WalletColors().buttonForeColor(pressed: pressed,
+                                          disabled: disabled(),
+                                         prominent: type == .prominent)
+        }
+    }
+    func backColor(type: TalerButtonStyleType, pressed: Bool) -> Color {
+        return if type == .plain {
+            Color.clear
+        } else {
+            WalletColors().buttonBackColor(pressed: pressed,
+                                           disabled: disabled(),
+                                           prominent: type == .prominent)
+        }
+    }
+    struct BackgroundView: View {
+        let color: Color
+        let dimmed: Bool
+        var body: some View {
+            RoundedRectangle(
+                cornerRadius: 15,
+                style: .continuous
+            )
+            .fill(color)
+            .opacity(dimmed ? 0.6 : 1.0)
+        }
+    }
+
+    struct MyBigButton: View {
+        var type: TalerButtonStyleType
+        let foreColor: Color
+        let backColor: Color
+        let dimmed: Bool
+        let configuration: ButtonStyle.Configuration
+        let disabled: Bool
+        let narrow: Bool
+        let aligned: TextAlignment
+
+        var body: some View {
+            let aligned2: Alignment = (aligned == .center) ? Alignment.center
+                                    : (aligned == .leading) ? Alignment.leading
+                                    : Alignment.trailing
+            configuration.label
+                .multilineTextAlignment(aligned)
+                .font(.title2)
+                .frame(minWidth: 0, maxWidth: narrow ? nil : .infinity, 
alignment: aligned2)
+                .padding(.vertical, 10)
+                .padding(.horizontal, 6)
+                .foregroundColor(foreColor)
+                .background(BackgroundView(color: backColor, dimmed: dimmed))
+                .contentShape(Rectangle())      // make sure the button can be 
pressed even if backgroundColor == clear
+                .scaleEffect(configuration.isPressed ? 0.95 : 1)
+                .animation(.spring(response: 0.1), value: 
configuration.isPressed)
+                .disabled(disabled)
+        }
+    }
+}
+
+
+
+struct GrowingButton: ButtonStyle {
+    func makeBody(configuration: Configuration) -> some View {
+        configuration.label
+            .padding()
+            .background(.blue)
+            .foregroundColor(.white)
+            .clipShape(Capsule())
+            .scaleEffect(configuration.isPressed ? 1.2 : 1)
+            .animation(.easeOut(duration: 0.2), value: configuration.isPressed)
     }
 }
 
@@ -93,6 +251,8 @@ struct Buttons_Previews: PreviewProvider {
         VStack {
             HamburgerButton() {}
                 .padding()
+            QRButton() {}
+                .padding()
             PlusButton() {}
                 .padding()
             HStack {
@@ -101,8 +261,46 @@ struct Buttons_Previews: PreviewProvider {
                 ReloadButton(disabled: true) {}
                     .padding()
             }
-            AwesomeButton(title: "AwesomeButton") {}
+            BigBlueButton(title: "DisabledButton", disabled: true) { 
AudioServicesPlaySystemSound(1015) }
+                .padding()
+            BigBlueButton(title: "BigBlueButton", disabled: false) { 
AudioServicesPlaySystemSound(1000) }
+                .padding()
+        }
+    }
+}
+
+#if DEBUG
+struct ContentView: View {
+    @State var isOn = false
+    //The better route is to have a separate variable to control the animations
+    // This prevents unpleasant side-effects.
+    @State private var animate = false
+
+    var body: some View {
+        VStack {
+            Text("I don't change.")
                 .padding()
+            Button("Press me, I do change") {
+                isOn.toggle()
+                animate = false
+                // Because .opacity is animated, we need to switch it
+                // back so the button shows.
+                DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
+                    animate = true
+                }
+            }
+            // In this case I chose to animate .opacity
+            .opacity(animate ? 1 : 0)
+            .animation(.easeIn, value: animate)
+            .frame(width: 300, height: 400)
+            // If you want the button to animate when the view appears, you 
need to change the value
+            .onAppear { animate = true }
         }
     }
 }
+struct ContentView_Previews: PreviewProvider {
+    static var previews: some View {
+        ContentView()
+    }
+}
+#endif
diff --git a/TalerWallet1/Views/HelperViews/CopyShare.swift 
b/TalerWallet1/Views/HelperViews/CopyShare.swift
new file mode 100644
index 0000000..19e06a1
--- /dev/null
+++ b/TalerWallet1/Views/HelperViews/CopyShare.swift
@@ -0,0 +1,85 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import UniformTypeIdentifiers
+import SwiftUI
+import SymLog
+
+struct CopyButton: View {
+    private let symLog = SymLogV(0)
+    @Environment(\.isEnabled) private var isEnabled: Bool
+    let textToCopy: String
+    let vertical: Bool
+
+    func copyAction() -> Void {
+        symLog.log(textToCopy)
+        UIPasteboard.general.setValue(textToCopy,
+                                      forPasteboardType: 
UTType.plainText.identifier)
+    }
+
+    var body: some View {
+        Button(action: copyAction) {
+            if vertical {
+                VStack {
+                    Image(systemName: "doc.on.doc")
+                    Text("Copy", comment: "5 letters max, else abbreviate")
+                }
+            } else {
+                HStack {
+                    Image(systemName: "doc.on.doc")
+                    Text("Copy", comment: "may be a bit longer")
+                }
+            }
+        }
+        .disabled(!isEnabled)
+    }
+}
+// MARK: -
+struct ShareButton: View {
+    private let symLog = SymLogV(0)
+    @Environment(\.isEnabled) private var isEnabled: Bool
+
+    let textToShare: String
+    let dismissFirst: Bool
+
+    func shareAction() -> Void {
+        if dismissFirst {
+            dismissTop()        // cannot open another sheet from within a 
sheet
+        }
+        symLog.log(textToShare)
+        ShareSheet.shareSheet(url: textToShare)
+    }
+
+    var body: some View {
+        Button(action: shareAction) {
+            HStack {
+                Image(systemName: "square.and.arrow.up")
+                Text("Share")
+            }
+        }
+        .disabled(!isEnabled)
+    }
+}
+// MARK: -
+struct CopyShare: View {
+    @Environment(\.isEnabled) private var isEnabled: Bool
+
+    let textToCopy: String
+    let dismissFirst: Bool
+
+    var body: some View {
+        HStack {
+            CopyButton(textToCopy: textToCopy, vertical: false)
+                .buttonStyle(TalerButtonStyle(type: .bordered))
+            ShareButton(textToShare: textToCopy, dismissFirst: dismissFirst)
+                .buttonStyle(TalerButtonStyle(type: .bordered))
+        } // two buttons
+    }
+}
+// MARK: -
+struct CopyShare_Previews: PreviewProvider {
+    static var previews: some View {
+        CopyShare(textToCopy: "Hallö", dismissFirst: false)
+    }
+}
diff --git a/TalerWallet1/Views/HelperViews/CurrencyField.swift 
b/TalerWallet1/Views/HelperViews/CurrencyField.swift
new file mode 100644
index 0000000..8f2e525
--- /dev/null
+++ b/TalerWallet1/Views/HelperViews/CurrencyField.swift
@@ -0,0 +1,221 @@
+/* MIT License
+ * Copyright (c) 2022 Javier Trinchero
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to 
deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in 
all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 
THE
+ * SOFTWARE.
+ */
+import SwiftUI
+import UIKit
+
+public struct CurrencyField: View {
+    @Binding var value: UInt64
+    var currency: String
+    var formatter: NumberFormatter
+    private var currencyInputField: CurrencyInputField! = nil
+
+    public func becomeFirstResponder() -> Void {
+        currencyInputField.becomeFirstResponder()
+    }
+
+    public func resignFirstResponder() -> Void {
+        currencyInputField.resignFirstResponder()
+    }
+
+    private var label: String {
+        let mag = pow(10, formatter.maximumFractionDigits)
+        return formatter.string(for: Decimal(value) / mag) ?? ""
+    }
+
+    public init(value: Binding<UInt64>, currency: String, formatter: 
NumberFormatter) {
+        self._value = value
+        self.currency = currency
+        self.formatter = formatter
+        self.currencyInputField = CurrencyInputField(value: $value, formatter: 
formatter)
+    }
+
+    public init(value: Binding<UInt64>, currency: String) {
+        let formatter = NumberFormatter()
+        formatter.locale = .current
+        formatter.numberStyle = .currency
+        formatter.currencySymbol = currency
+        formatter.minimumFractionDigits = 2
+        formatter.maximumFractionDigits = 2
+
+        self.init(value: value, currency: currency, formatter: formatter)
+    }
+
+    public var body: some View {
+        ZStack {
+            // Text view to display the formatted currency
+            // Set as priority so CurrencyInputField size doesn't affect parent
+            Text(label)
+                .layoutPriority(1)
+                
+            // Input text field to handle UI
+            currencyInputField
+        }
+    }
+}
+
+// Sub-class UITextField to remove selection and caret
+class NoCaretTextField: UITextField {
+    override func canPerformAction(_ action: Selector, withSender sender: 
Any?) -> Bool {
+        false
+    }
+
+    override func selectionRects(for range: UITextRange) -> 
[UITextSelectionRect] {
+        []
+    }
+
+    override func caretRect(for position: UITextPosition) -> CGRect {
+        .null
+    }
+}
+
+struct CurrencyInputField: UIViewRepresentable {
+    @Binding var value: UInt64
+    var formatter: NumberFormatter
+    private let textField = NoCaretTextField(frame: .zero)
+
+    func makeCoordinator() -> Coordinator {
+        Coordinator(self)
+    }
+
+    public func becomeFirstResponder() -> Void {
+        textField.becomeFirstResponder()
+    }
+
+    public func resignFirstResponder() -> Void {
+        textField.resignFirstResponder()
+    }
+
+    func makeUIView(context: Context) -> NoCaretTextField {
+        // Assign delegate
+        textField.delegate = context.coordinator
+
+        // Set keyboard type
+        textField.keyboardType = .numberPad
+
+        // Make visual components invisible
+        textField.tintColor = .clear
+        textField.textColor = .clear
+        textField.backgroundColor = .clear
+
+        // Add editingChanged event handler
+        textField.addTarget(
+            context.coordinator,
+            action: #selector(Coordinator.editingChanged(textField:)),
+            for: .editingChanged
+        )
+
+        // Set initial textfield text
+        context.coordinator.updateText(value, textField: textField)
+
+        return textField
+    }
+
+    func updateUIView(_ uiView: NoCaretTextField, context: Context) {}
+
+    class Coordinator: NSObject, UITextFieldDelegate {
+        // Reference to currency input field
+        private var input: CurrencyInputField
+
+        // Last valid text input string to be displayed
+        private var lastValidInput: String? = ""
+
+        init(_ currencyTextField: CurrencyInputField) {
+            self.input = currencyTextField
+        }
+
+        func setValue(_ value: UInt64, textField: UITextField) {
+            // Update input value
+            input.value = value
+
+            // Update textfield text
+            updateText(value, textField: textField)
+        }
+
+        func updateText(_ value: UInt64, textField: UITextField) {
+            // Update field text and last valid input text
+            textField.text = String(value)
+            lastValidInput = String(value)
+        }
+
+        func textField(_ textField: UITextField, shouldChangeCharactersIn 
range: NSRange, replacementString string: String) -> Bool {
+            // If replacement string is empty, we can assume the backspace key 
was hit
+            if string.isEmpty {
+                // Resign first responder when delete is hit when value is 0
+                if input.value == 0 {
+                    textField.resignFirstResponder()
+                }
+
+                // Remove trailing digit
+                setValue(UInt64(input.value / 10), textField: textField)
+            }
+            return true
+        }
+
+        @objc func editingChanged(textField: NoCaretTextField) {
+            // Get a mutable copy of last text
+            guard var oldText = lastValidInput else {
+                return
+            }
+
+            // Iterate through each char of the new string and compare LTR 
with old string
+            let char = (textField.text ?? "").first { next in
+                // If old text is empty or its next character doesn't match new
+                if oldText.isEmpty || next != oldText.removeFirst() {
+                    // Found the mismatching character
+                    return true
+                }
+                return false
+            }
+
+            // Find new character and try to get an Int value from it
+            guard let char = char, let digit = Int(String(char)) else {
+                // New character could not be converted to Int
+                // Revert to last valid text
+                textField.text = lastValidInput
+                return
+            }
+
+            // Multiply by 10 to shift numbers one position to the left, 
revert if an overflow occurs
+            let (multValue, multOverflow) = 
input.value.multipliedReportingOverflow(by: 10)
+            if multOverflow {
+                textField.text = lastValidInput
+                return
+            }
+
+            // Add the new trailing digit, revert if an overflow occurs
+            let (addValue, addOverflow) = 
multValue.addingReportingOverflow(UInt64(digit))
+            if addOverflow {
+                textField.text = lastValidInput
+                return
+            }
+
+            // If new value has more digits than allowed by formatter, revert
+            if input.formatter.maximumFractionDigits + 
input.formatter.maximumIntegerDigits < String(addValue).count {
+                textField.text = lastValidInput
+                return
+            }
+
+            // Update new value
+            setValue(addValue, textField: textField)
+        }
+    }
+}
diff --git a/TalerWallet1/Views/HelperViews/CurrencyInputView.swift 
b/TalerWallet1/Views/HelperViews/CurrencyInputView.swift
new file mode 100644
index 0000000..da84250
--- /dev/null
+++ b/TalerWallet1/Views/HelperViews/CurrencyInputView.swift
@@ -0,0 +1,60 @@
+//
+//  CurrencyInputView.swift
+//  TalerWalletT
+//
+//  Created by Marc Stibane on 2023-06-04.
+//  Copyright © 2023 Taler. All rights reserved.
+//
+
+import SwiftUI
+
+struct CurrencyInputView: View {
+    let currencyField: CurrencyField
+    let title: String
+
+    @State var hasBeenShown = false
+    var body: some View {
+        VStack (alignment: .leading) {
+            Text(title)
+                .padding(.top)
+                .font(.title3)
+            currencyField
+                .frame(maxWidth: .infinity, alignment: .trailing)
+                .foregroundColor(WalletColors().fieldForeground)     // text 
color
+                .background(WalletColors().fieldBackground)
+                .font(.title)
+                .border(.primary)
+        }.onAppear {   // make CurrencyField show the keyboard after 0.4 
seconds
+            if hasBeenShown {
+                print("❗️Yikes: CurrencyInputView hasBeenShown")
+            } else {
+                print("❗️Yikes: First CurrencyInputView❗️")
+                DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
+                    hasBeenShown = true
+                    currencyField.becomeFirstResponder()
+                }
+            }
+        }.onDisappear {
+            currencyField.resignFirstResponder()
+        }
+    }
+}
+#if DEBUG
+fileprivate struct BindingViewContainer : View {
+    @State var centsToTransfer: UInt64 = 0
+
+    var body: some View {
+        let currencyField = CurrencyField(value: $centsToTransfer, currency: 
LONGCURRENCY)
+        CurrencyInputView(currencyField: currencyField,
+                                  title: "Amount to withdraw:")
+    }
+}
+
+struct CurrencyInputView_Previews: PreviewProvider {
+    static var previews: some View {
+        List {
+            BindingViewContainer()
+        }
+    }
+}
+#endif
diff --git a/TalerWallet1/Views/Main/LaunchAnimationView.swift 
b/TalerWallet1/Views/HelperViews/LaunchAnimationView.swift
similarity index 67%
rename from TalerWallet1/Views/Main/LaunchAnimationView.swift
rename to TalerWallet1/Views/HelperViews/LaunchAnimationView.swift
index cc20fa4..03a9eac 100644
--- a/TalerWallet1/Views/Main/LaunchAnimationView.swift
+++ b/TalerWallet1/Views/HelperViews/LaunchAnimationView.swift
@@ -1,23 +1,24 @@
-
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
 import SwiftUI
-import SymLog
 
 struct LaunchAnimationView: View {
-    private let symLog = SymLogV(0)
     @State private var rotationDirection = false
 
     private let animationTimer = Timer
-        .publish(every: 1.4, on: .current, in: .common)
+        .publish(every: 1.6, on: .current, in: .common)
         .autoconnect()
 
     var body: some View {
         ZStack {
-            Color.teal.ignoresSafeArea()
-            Image(systemName: "hurricane")
+            Color(.systemGray6).ignoresSafeArea()
+            Image("taler-logo-2023-red")
                 .resizable()
                 .scaledToFit()
-                .frame(width: 200, height: 200)
-                .rotationEffect(rotationDirection ? Angle(degrees: 0) : 
Angle(degrees: 1080))
+                .frame(width: 250, height: 250)
+                .rotationEffect(rotationDirection ? Angle(degrees: 0) : 
Angle(degrees: 900))
         }
             .onReceive(animationTimer) { timerValue in
                 withAnimation(.easeInOut(duration: 1.9)) {
@@ -26,6 +27,7 @@ struct LaunchAnimationView: View {
             }
     }
 }
+// MARK: -
 struct LaunchAnimationView_Previews: PreviewProvider {
     static var previews: some View {
         LaunchAnimationView()
diff --git a/TalerWallet1/Views/HelperViews/ListStyle.swift 
b/TalerWallet1/Views/HelperViews/ListStyle.swift
new file mode 100644
index 0000000..409e275
--- /dev/null
+++ b/TalerWallet1/Views/HelperViews/ListStyle.swift
@@ -0,0 +1,113 @@
+/* MIT License
+ * Copyright (c) 2022 young rtSwift
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to 
deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in 
all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 
THE
+ * SOFTWARE.
+ */
+import SwiftUI
+
+public extension View {
+    var anyView: AnyView {
+        AnyView(self)
+    }
+}
+// MARK: -
+// Our ListStyle each case corresponds to a SwiftUI.ListStyle
+// Here we make it CaseIterable for SwiftUI.ForEach
+// and the UI Display name
+public enum MyListStyle: String, CaseIterable, Hashable {
+    case automatic    = "Automatic"
+    case grouped      = "Grouped"
+    case inset        = "Inset"
+    case insetGrouped = "InsetGrouped"
+    case plain        = "Plain"
+    case sidebar      = "Sidebar"
+
+    // map to SwiftUI ListStyle
+    var style: any SwiftUI.ListStyle {
+        switch self {
+            case .automatic:    return .automatic
+            case .grouped:      return .grouped
+            case .inset:        return .inset
+            case .insetGrouped: return .insetGrouped
+            case .plain:        return .plain
+            case .sidebar:      return .sidebar
+        }
+    }
+
+    var displayName: String {
+        String(self.rawValue)
+    }
+}
+// MARK: -
+#if DEBUG
+struct AnyViewDemo: View {
+    @State private var selectedStyle = MyListStyle.automatic
+
+    let sections = ["Breakfast", "Lunch", "Dinner"]
+    let breakfast = ["pancakes", "bacon", "orange juice"]
+    var body: some View {
+        VStack {
+            Picker("List Style", selection: $selectedStyle) {
+                ForEach(MyListStyle.allCases, id: \.self) {
+                    Text($0.displayName).tag($0)
+                }
+            }
+
+            List(sections, id: \.self) { section in
+                Section(section) {
+                    if "Breakfast" == section {
+                        ForEach(breakfast, id: \.self) { item in
+                            Text(item)
+                        }
+                    } else {
+                        Text("row")
+                    }
+                }
+//                Section("Breakfast") {
+//                    Text("pancakes")
+//                    Text("bacon")
+//                    Text("orange juice")
+//                }
+//                Section("Lunch") {
+//                    Text("sandwich")
+//                    Text("chips")
+//                    Text("lemonade")
+//                }
+//                Section("Dinner") {
+//                    Text("spaghetti")
+//                    Text("bread")
+//                    Text("water")
+//                }
+            }
+            .refreshable {
+                print("refreshing")
+                // this closure is already async, no need for a Task
+            }
+            .listStyle(selectedStyle.style)
+            .anyView
+        }
+    }
+}
+
+struct AnyViewDemo_Previews: PreviewProvider {
+    static var previews: some View {
+        AnyViewDemo()
+    }
+}
+#endif
diff --git a/TalerWallet1/Views/HelperViews/LoadingView.swift 
b/TalerWallet1/Views/HelperViews/LoadingView.swift
index 10c245e..0601ae0 100644
--- a/TalerWallet1/Views/HelperViews/LoadingView.swift
+++ b/TalerWallet1/Views/HelperViews/LoadingView.swift
@@ -1,45 +1,28 @@
 /*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
  */
 import SwiftUI
 import SymLog
 
 struct LoadingView: View {
     private let symLog = SymLogV(0)
+    let navTitle = String(localized: "Loading...")
     let backButtonHidden: Bool
 
     var body: some View {
-        symLog { NavigationView {
-            VStack {
-                Spacer()
-                ProgressView()
-                Spacer()
-                Spacer()
-                Spacer()
-            }
-                .navigationBarBackButtonHidden(backButtonHidden)
-                .navigationTitle("Loading...")
-        }
+        LaunchAnimationView()
+            .navigationBarBackButtonHidden(backButtonHidden)
+            .navigationTitle(navTitle)
             .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: 
.center)
-            .background(Color(.systemGray6))
-        }
+            .background(WalletColors().backgroundColor)
     }
 }
-
+// MARK: -
 struct LoadingView_Previews: PreviewProvider {
     static var previews: some View {
-        LoadingView(backButtonHidden: true)
+        NavigationView {
+            LoadingView(backButtonHidden: true)
+        }
     }
 }
diff --git a/TalerWallet1/Views/HelperViews/QRGeneratorView.swift 
b/TalerWallet1/Views/HelperViews/QRGeneratorView.swift
new file mode 100644
index 0000000..9f51b37
--- /dev/null
+++ b/TalerWallet1/Views/HelperViews/QRGeneratorView.swift
@@ -0,0 +1,62 @@
+/* MIT License
+ * Copyright (c) 2020 Jeeva Tamilselvan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to 
deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in 
all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 
THE
+ * SOFTWARE.
+ */
+import SwiftUI
+
+struct QRGeneratorView: View {
+    var text: String
+
+    var body: some View {
+        if let data = getQRCodeData(text: text) {
+            if let uiImage = UIImage(data: data)  {
+                Image(uiImage: uiImage)
+                    .interpolation(.none)
+                    .resizable()
+                    .scaledToFit()
+                    .frame(width: 200, height: 200)
+            } else {
+                EmptyView()
+            }
+        } else {
+            EmptyView()
+        }
+    }
+
+    func getQRCodeData(text: String) -> Data? {
+        guard let filter = CIFilter(name: "CIQRCodeGenerator") else { return 
nil }
+        let data = text.data(using: .ascii, allowLossyConversion: false)
+        filter.setValue(data, forKey: "inputMessage")
+        guard let ciimage = filter.outputImage else { return nil }
+        let transform = CGAffineTransform(scaleX: 10, y: 10)
+        let scaledCIImage = ciimage.transformed(by: transform)
+        let uiimage = UIImage(ciImage: scaledCIImage)
+        return uiimage.pngData()!
+    }
+}
+
+struct QRGeneratorView_Previews: PreviewProvider {
+    static var previews: some View {
+        VStack {
+            QRGeneratorView(text: "Hello World!")
+            QRGeneratorView(text: 
"taler://pay-pull/exchange.demo.taler.net/7J7SNHYMCCAZ1ARY9YCB5Z9FTY0YZP8F2KDRXV94KZCQ6WAVMTX0")
+        }
+    }
+}
diff --git a/TalerWallet1/Views/HelperViews/SelectDays.swift 
b/TalerWallet1/Views/HelperViews/SelectDays.swift
new file mode 100644
index 0000000..8fb1905
--- /dev/null
+++ b/TalerWallet1/Views/HelperViews/SelectDays.swift
@@ -0,0 +1,56 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+import SymLog
+
+struct SelectDays: View {
+    private let symLog = SymLogV(0)
+    @Environment(\.isEnabled) private var isEnabled: Bool
+
+    @Binding var selected: UInt
+    let maxExpiration: UInt
+
+    func oneDayAction() -> Void {
+        selected = 1
+        symLog.log(selected)
+    }
+
+    func sevenDayAction() -> Void {
+        selected = 7
+        symLog.log(selected)
+    }
+
+    func thirtyDayAction() -> Void {
+        selected = 30
+        symLog.log(selected)
+    }
+
+    var body: some View {
+        HStack {
+            Button(action: oneDayAction) {
+                Text("1 Day")
+            }.buttonStyle(TalerButtonStyle(type: (selected == 1) ? .prominent 
: .bordered, dimmed: true))
+                .disabled(!isEnabled)
+
+            Button(action: sevenDayAction) {
+                Text("7 Days")
+            }.buttonStyle(TalerButtonStyle(type: (selected == 7) ? .prominent 
: .bordered, dimmed: true))
+                .disabled(!isEnabled || maxExpiration < 7)
+
+            Button(action: thirtyDayAction) {
+                Text("30 Days")
+            }.buttonStyle(TalerButtonStyle(type: (selected == 30) ? .prominent 
: .bordered, dimmed: true))
+                .disabled(!isEnabled || maxExpiration < 30)
+        } // 3 buttons
+    }
+}
+
+struct SelectDays_Previews: PreviewProvider {
+    static var previews: some View {
+        @State var expireDays: UInt = 1
+        SelectDays(selected: $expireDays, maxExpiration: 20)
+    }
+}
diff --git a/TalerWallet1/Views/HelperViews/TextFieldAlert.swift 
b/TalerWallet1/Views/HelperViews/TextFieldAlert.swift
index de2eaf6..e307ff1 100644
--- a/TalerWallet1/Views/HelperViews/TextFieldAlert.swift
+++ b/TalerWallet1/Views/HelperViews/TextFieldAlert.swift
@@ -1,17 +1,6 @@
 /*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
  */
 import SwiftUI
 
diff --git a/TalerWallet1/Views/Main/ContentView.swift 
b/TalerWallet1/Views/Main/ContentView.swift
deleted file mode 100644
index 1609351..0000000
--- a/TalerWallet1/Views/Main/ContentView.swift
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-import SwiftUI
-import SymLog
-
-extension URL: Identifiable {
-    public var id: URL {self}
-}
-
-struct ContentView: View {
-    private let symLog = SymLogV()
-    @EnvironmentObject private var controller: Controller
-    @State private var sheetPresented = false
-    @State private var urlToOpen: URL? = nil
-
-    var body: some View {
-        if controller.backendState == .ready {
-            symLog {
-                Content(symLog: symLog, controller: controller)
-                    .onAppear {     // called e.g. after coming to foreground
-                        symLog.log(".onAppear")
-                    }
-                    .onOpenURL { url in
-    // TODO: check if this is called when launching the app from the camera 
scanning a QR code
-                        symLog.log(".onOpenURL: \(url)")
-                        urlToOpen = url
-                    }
-                    .sheet(item: $urlToOpen, onDismiss: {}) { url in
-                        URLSheet(urlToOpen: url)
-                    }
-            } // symLog
-        } else if controller.backendState == .error {
-            ErrorView()            // TODO: show Error View
-        } else {
-            LaunchAnimationView()
-        }
-    }
-}
-// MARK: - Content
-extension ContentView {
-    struct Content: View {
-        let symLog: SymLogV?
-        var controller: Controller
-
-        @State var sidebarVisible: Bool = false
-        @State var currentView: Int = 0
-        var views: [SidebarItem] {[
-            SidebarItem(name: "Balances",
-                        sysImage: "creditcard.fill",        // TODO: Wallet 
Icon
-                        view: AnyView(CurrenciesListView(viewModel: 
controller.balancesModel)
-                                      { sidebarVisible = true }
-                                     )),
-            SidebarItem(name: "Settings",
-                        sysImage: "gearshape.fill",
-                        view: AnyView(SettingsView()
-                                      { sidebarVisible = true }
-                                     )),
-            SidebarItem(name: "Pending Operations",
-                        sysImage: "arrow.triangle.2.circlepath",
-                        view: AnyView(PendingOpsListView(viewModel: 
controller.pendingModel)
-                                      { sidebarVisible = true }
-                                     ))
-        ]}
-        var body: some View {
-            ZStack(alignment: .leading) {
-                views[currentView].view
-                    .frame(maxWidth: .infinity, maxHeight: .infinity, 
alignment: .center)
-                SideBarView(views: views, currentView: $currentView, 
sidebarVisible: $sidebarVisible)
-            }
-                .background(Color(.systemGray6))
-        }
-    }
-}
-// MARK: -
-//struct ContentView_Previews: PreviewProvider {
-//    static var previews: some View {
-//        ContentView.Content(symLog: nil, controller: controller)
-//    }
-//}
diff --git a/TalerWallet1/Views/Main/ErrorView.swift 
b/TalerWallet1/Views/Main/ErrorView.swift
index 25fc2f0..50e0515 100644
--- a/TalerWallet1/Views/Main/ErrorView.swift
+++ b/TalerWallet1/Views/Main/ErrorView.swift
@@ -1,31 +1,23 @@
 /*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
  */
 import SwiftUI
 import SymLog
 
 struct ErrorView: View {
     private let symLog = SymLogV()
+
+    let errortext: String?
+
     var body: some View {
         
-        Text("Couldn't load Wallet-Core!")
+        Text(errortext ?? "Couldn't load Wallet-Core!")
     }
 }
 
 struct ErrorView_Previews: PreviewProvider {
     static var previews: some View {
-        ErrorView()
+        ErrorView(errortext: "")
     }
 }
diff --git a/TalerWallet1/Views/Main/MainView.swift 
b/TalerWallet1/Views/Main/MainView.swift
new file mode 100644
index 0000000..e6ce3f1
--- /dev/null
+++ b/TalerWallet1/Views/Main/MainView.swift
@@ -0,0 +1,100 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import SymLog
+
+// Use this to delay instantiation when using `NavigationLink`, etc...
+struct LazyView<Content: View>: View {
+    var content: () -> Content
+    var body: some View {
+        self.content()
+    }
+}
+
+struct MainView: View {
+    private let symLog = SymLogV()
+    @EnvironmentObject private var viewState: ViewState         // 
popToRootView()
+    @EnvironmentObject private var controller: Controller
+    @State private var sheetPresented = false
+    @State private var urlToOpen: URL? = nil
+
+    func sheetDismiss() -> Void {
+        symLog.log("sheet dismiss")
+    }
+    var body: some View {
+#if DEBUG
+        let _ = Self._printChanges()
+        let _ = symLog.vlog()       // just to get the # to compare it with 
.onAppear & onDisappear
+#endif
+            NavigationView {   // the one and only for all non-sheet views
+                if controller.backendState == .ready {
+                    Content(symLog: symLog)
+                        // any change to rootViewId triggers popToRootView 
behaviour
+                        .id(viewState.rootViewId)
+                        .onOpenURL { url in
+                            symLog.log(".onOpenURL: \(url)")
+                            // will be called on a taler:// scheme either
+                            // by user tapping such link in a browser (bank 
website)
+                            // or when launching the app from iOS Camera.app 
scanning a QR code
+                            urlToOpen = url     // raise sheet
+                        }
+                } else if controller.backendState == .error {
+                    ErrorView(errortext: nil)            // TODO: show Error 
View
+                } else {
+                    LaunchAnimationView()
+                }
+            }.navigationViewStyle(.stack)
+            .overlay(alignment: .top) {
+                // Show the viewID on top of the app's NavigationView
+                DebugViewV()
+                    .id("ViewID")
+            }
+            .sheet(item: $urlToOpen, onDismiss: sheetDismiss) { url in
+                let sheet = AnyView(URLSheet(urlToOpen: url))
+                Sheet(sheetView: sheet)
+            }
+    } // body
+}
+// MARK: - Content
+extension MainView {
+    struct Content: View {
+        let symLog: SymLogV?
+
+        @State var sidebarVisible: Bool = false
+        @State var currentView: Int = 0
+
+        var views: [SidebarItem] {[
+            SidebarItem(name: String(localized: "Balances"),
+                    sysImage: "creditcard.fill",        // TODO: Wallet Icon
+                        view: AnyView(BalancesListView(model: 
BalancesModel.model(currency: "*"))
+                                      { sidebarVisible = !sidebarVisible }     
// hamburgerAction
+                                     )),
+            SidebarItem(name: String(localized: "Exchanges"),
+                    sysImage: "building.columns",
+                        view: AnyView(ExchangeListView(model: 
ExchangeModel.model())
+                                      { sidebarVisible = !sidebarVisible }     
// hamburgerAction
+                                     )),
+            SidebarItem(name: String(localized: "Settings"),    // TODO: 
"About"?
+                    sysImage: "gearshape.fill",
+                        view: AnyView(SettingsView()
+                                      { sidebarVisible = !sidebarVisible }     
// hamburgerAction
+                                     ))
+        ]}
+        var body: some View {
+#if DEBUG
+            let _ = Self._printChanges()
+            let _ = symLog?.vlog()       // just to get the # to compare it 
with .onAppear & onDisappear
+#endif
+            ZStack(alignment: .leading) {
+                views[currentView].view
+                    .frame(maxWidth: .infinity, maxHeight: .infinity, 
alignment: .center)
+                    .id(views[currentView].name)
+                    .transition(.backslide)
+                SideBarView(views: views, currentView: $currentView, 
sidebarVisible: $sidebarVisible)
+            }
+                .background(WalletColors().backgroundColor)
+        }
+    }
+}
diff --git a/TalerWallet1/Views/Main/SideBarView.swift 
b/TalerWallet1/Views/Main/SideBarView.swift
index bec2b2c..db50a2c 100644
--- a/TalerWallet1/Views/Main/SideBarView.swift
+++ b/TalerWallet1/Views/Main/SideBarView.swift
@@ -1,22 +1,11 @@
 /*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
  */
 import SwiftUI
 import SymLog
 
-fileprivate let sidebarWidth = 250.0
+fileprivate let sidebarWidth = 220.0
 
 struct SidebarItem {
     var name: String
@@ -31,15 +20,21 @@ struct SideBarView: View {
     @Binding var sidebarVisible: Bool
 
     var body: some View {
-        symLog {
-            HStack {
+        HStack {    // sideView left, clear dismiss target right
+            EqualIconWidthDomain {
                 VStack {
                     Spacer()
+                    Image("taler-logo-2023-red")
+                        .resizable()
+                        .scaledToFit()
+                        .frame(width: 100, height: 100)
+                        .padding(.top, 30)
                     ForEach(0..<views.count, id: \.self) { i in
                         Button {
                             symLog.log("sidebar item \"\(views[i].name)\" 
selected")
                             sidebarVisible = false      // slide sidebar to 
the left
-                            currentView = i             // switch to the view 
the user selected
+                            withAnimation(.easeInOut) {currentView = i}        
             // switch to the view the user selected
+//                            withAnimation(.easeInOut(duration: 0.5)) 
{currentView = i}      // switch to the view the user selected
                         } label: {
                             if let sysImage = views[i].sysImage {
                                 Label(views[i].name, systemImage: sysImage)
@@ -51,14 +46,24 @@ struct SideBarView: View {
                         }
                         .buttonStyle(.bordered)
                         .font(.title)
-//                        .padding(.vertical)
-
-//                        Divider()
+                        //                        .padding(.vertical)
                     }
                     Spacer()
                     Spacer()
+                    Spacer()
+//                    Button {
+//                        symLog.log("Scan QR selected")
+//                        sidebarVisible = false      // slide sidebar to the 
left
+                        // TODO: show scan sheet
+//                    } label: {
+//                        Label("Scan QR", systemImage: "qrcode.viewfinder")
+//                            .frame(maxWidth: sidebarWidth, alignment: 
.leading)
+//                    }
+//                    .buttonStyle(.bordered)
+//                    .font(.title)
+//                    .padding(.bottom, 26.0)
                 }
-                .background(Color(.systemGray5))
+                .background(WalletColors().sideBackground)
                 .frame(width: sidebarWidth, alignment: .center)
                 // TODO: use leading instead of sidebarWidth for right-to-left
                 .offset(x: sidebarVisible ? 0 : -sidebarWidth)
@@ -67,23 +72,23 @@ struct SideBarView: View {
                 //  .onAppear can NOT be used here, because we don't show or 
dismiss this view,
                 //   but only slide it left or right - so it is always there.
                 //   .onAppear {}    would be called once even before 
LaunchScreen is dismissed and then never again
-
+                
                 // this is just a target for a tap gesture outside the sidebar 
to dismiss it
-                Color.clear
-                    .frame(maxWidth: sidebarVisible ? .infinity : 0, 
maxHeight: .infinity, alignment: .leading)
-                    // TODO: right-to-left ?
-                    .offset(x: sidebarVisible ? sidebarWidth : 0)
-                    .contentShape(Rectangle())
-                    .onTapGesture {
-                        sidebarVisible = false
-                    }
             }
+            Color.clear
+                .frame(maxWidth: sidebarVisible ? .infinity : 0, maxHeight: 
.infinity, alignment: .leading)
+                // TODO: right-to-left ?
+                .offset(x: sidebarVisible ? sidebarWidth : 0)
+                .contentShape(Rectangle())
+                .onTapGesture {
+                    sidebarVisible = false
+                }
         }
     }
 }
-
+// MARK: -
 #if DEBUG
-struct BindingViewContainer : View {
+fileprivate struct BindingViewContainer : View {
     @State var currentView: Int = 0
     @State var sidebarVisible: Bool = true
     var views: [SidebarItem]
@@ -99,8 +104,10 @@ struct BindingViewContainer : View {
 struct SideBarView_Previews: PreviewProvider {
     static var views: [SidebarItem] {[
         SidebarItem(name: "Balances",
+                sysImage: "creditcard.fill",        // TODO: Wallet Icon
                     view: AnyView(WalletEmptyView())),
         SidebarItem(name: "Settings",
+                sysImage: "gearshape.fill",
                     view: AnyView(WalletEmptyView()))
     ]}
     static var previews: some View {
diff --git a/TalerWallet1/Views/Main/WalletEmptyView.swift 
b/TalerWallet1/Views/Main/WalletEmptyView.swift
new file mode 100644
index 0000000..a6a810a
--- /dev/null
+++ b/TalerWallet1/Views/Main/WalletEmptyView.swift
@@ -0,0 +1,44 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import SymLog
+
+/// This view shows hints if a wallet is empty
+/// It is the very first thing the user sees after installing the app
+
+struct WalletEmptyView: View {
+    private let symLog = SymLogV()
+    @AppStorage("listStyle") var myListStyle = MyListStyle.automatic
+
+    var body: some View {
+        List {
+            Section {
+                Text("There is no digital cash in your wallet.")
+            }
+            Section {
+                Text("You can get some test money from the demo bank:")
+            }
+            Section {
+                Link(DEMOBANK, destination: URL(string: DEMOBANK)!)
+            }
+            Section {
+                Text("Just register a test account, then withdraw some coins.")
+            }
+        }
+        .padding(.vertical)
+        .font(.title2)
+        .listStyle(myListStyle.style)
+        .anyView
+        .onAppear() {
+            DebugViewC.shared.setViewID(VIEW_EMPTY)     // 10
+        }
+    }
+}
+
+struct WalletEmptyView_Previews: PreviewProvider {
+    static var previews: some View {
+        WalletEmptyView()
+    }
+}
diff --git a/TalerWallet1/Views/Payment/PaymentAcceptView.swift 
b/TalerWallet1/Views/Payment/PaymentAcceptView.swift
index 26eb916..3b15583 100644
--- a/TalerWallet1/Views/Payment/PaymentAcceptView.swift
+++ b/TalerWallet1/Views/Payment/PaymentAcceptView.swift
@@ -1,71 +1,74 @@
 /*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
  */
 import SwiftUI
 import taler_swift
+import AVFoundation
 import SymLog
 
 struct PaymentAcceptView: View {
     private let symLog = SymLogV()
-    @ObservedObject var viewModel: PaymentURIModel
+    @ObservedObject var model: PaymentURIModel
 
-    var detailsForAmount: PaymentDetailsForUri
+    let detailsForAmount: PaymentDetailsForUri
 
-//    @Environment(\.dismiss) var dismiss     // call dismiss() to get rid of 
the sheet
-    let navTitle = "Accept Payment"
-    var cancelButton: some View {
-        Button("Cancel6") { dismissTop() }
-    }
+    let navTitle = String(localized: "Accept Payment")
+    @State private var confirmPayResult: ConfirmPayResult?
 
-    @State var confirmPayResult: ConfirmPayResult?
+    func playSound(success: Bool) {
+//        let url = URL(fileURLWithPath: 
"/System/Library/Audio/UISounds/PaymentReceived.caf")
+        let url = URL(fileURLWithPath: 
"/System/Library/Audio/UISounds/payment_" + (success ? "success.caf"
+                                                                               
             : "failure.caf"))
+        var soundID: SystemSoundID = 0
+        AudioServicesCreateSystemSoundID(url as CFURL, &soundID)
+        print(soundID)
+        AudioServicesPlaySystemSound(soundID);
+    }
+    func acceptAction() {
+        Task {
+            do {
+                confirmPayResult = try await 
model.confirmPayM(detailsForAmount.proposalId)
+                symLog.log(confirmPayResult as Any)
+                if confirmPayResult?.type == "done" {
+                    // TODO: Show Hints that Payment was successfull
+                    playSound(success: true)
+                } else {
+                    // TODO: show error
+                    playSound(success: false)
+                }
+            } catch {    // TODO: error
+                symLog.log(error.localizedDescription)
+            }
+            dismissTop()
+        }
+    }
 
+    @State private var disabled = false
     var body: some View {
-        symLog { Group {
+        Group {
             let raw = detailsForAmount.amountRaw
             let effective = detailsForAmount.amountEffective
             let fee = try! Amount.diff(raw, effective)      // TODO: different 
currencies
-            Form {
-                AmountView(title: "Amount to pay:",
-                           value: raw.readableDescription, color: 
Color(UIColor.label))
-                .padding(.bottom)
-                AmountView(title: "Exchange fee:",
-                           value: fee.readableDescription, color: 
Color("Outgoing"))
-                .padding(.bottom)
-                AmountView(title: "Coins to be spent:",
-                           value: effective.readableDescription, color: 
Color("Outgoing"))
-            }
-            AwesomeButton(title: "Accept") {
-                Task {
-                    do {
-                        confirmPayResult = try await 
viewModel.confirmPay(detailsForAmount.proposalId)
-                        symLog.log(confirmPayResult as Any)
-                        if confirmPayResult?.type == "done" {
-                            // TODO: Show Hints that Payment was successfull
-                            // success
-                        } else {
-                            // TODO: show error
-                        }
-                    } catch {
-                        symLog.log(error.localizedDescription)                
// TODO: error
-                    }
-                    dismissTop()
-                }
+            ThreeAmountsView(topTitle: "Amount to pay:",
+                             topAmount: raw, fee: fee,
+                             bottomTitle: "Coins to be spent:",
+                             bottomAmount: effective,
+                             large: true, pending: false, incoming: false,
+                             baseURL: 
detailsForAmount.contractTerms.exchanges.first?.url)
+                // TODO: payment: popup with all possible exchanges, check fees
+            .safeAreaInset(edge: .bottom) {
+                ProminentButton(title: "Accept", action: acceptAction)
+                    .padding(.horizontal)
             }
         }
-            .navigationBarItems(leading: cancelButton)
             .navigationTitle(navTitle)
-        }
     }
 }
+
+//struct PaymentAccept_Previews: PreviewProvider {
+//    static var previews: some View {
+//        let model: PaymentURIModel =
+//        PaymentAcceptView(model: <#PaymentURIModel#>, detailsForAmount: 
<#PaymentDetailsForUri#>)
+//    }
+//}
diff --git a/TalerWallet1/Views/Payment/PaymentURIView.swift 
b/TalerWallet1/Views/Payment/PaymentURIView.swift
index 38ad04c..8815be7 100644
--- a/TalerWallet1/Views/Payment/PaymentURIView.swift
+++ b/TalerWallet1/Views/Payment/PaymentURIView.swift
@@ -1,17 +1,6 @@
 /*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
  */
 import SwiftUI
 import SymLog
@@ -19,46 +8,38 @@ import SymLog
 struct PaymentURIView: View {
     private let symLog = SymLogV()
     var url: URL
-    @ObservedObject var viewModel: PaymentURIModel
-
-//    @Environment(\.dismiss) var dismiss     // call dismiss() to get rid of 
the sheet
-    let navTitle = "Payment"
-    var cancelButton: some View {
-        Button("Cancel5") { dismissTop() }
-    }
-
+    @ObservedObject var model: PaymentURIModel
     @State var detailsForUri: PaymentDetailsForUri?
 
+    let navTitle = String(localized: "Payment")
+
     var body: some View {
         let badURL = "Error in URL: \(url)"
         VStack {
-            if viewModel.paymentState == nil {
+            if model.paymentState == nil {
                 LoadingView(backButtonHidden: false)
-            } else { switch viewModel.paymentState {
+            } else { switch model.paymentState {
                 case .waitingForUriDetails:
                     let _ = symLog.vlog("waitingForUriDetails")
-                    WithdrawProgressView(message: url.host ?? badURL) { 
dismissTop() }
+                    WithdrawProgressView(message: url.host ?? badURL)
                         .navigationTitle("Contacting Exchange")
                 case .receivedUriDetails:
                     let _ = symLog.vlog("waitingForUser")
-                    PaymentAcceptView(viewModel: viewModel, detailsForAmount: 
detailsForUri!)
+                    PaymentAcceptView(model: model, detailsForAmount: 
detailsForUri!)
                 default:
-                    symLog {
-                        Text("Payment")
-                            .navigationBarItems(leading: cancelButton)
-                            .navigationTitle(navTitle)
-                    }
+                    Text("Payment")
+                        .navigationTitle(navTitle)
             } }
         }.task {
             do { // TODO: cancelled
                 symLog.log(".task")
-                detailsForUri = try await 
viewModel.preparePayForUri(url.absoluteString)
+                detailsForUri = try await 
model.preparePayForUriM(url.absoluteString)
 //                print(detailsForUri?.status)
 //                print(detailsForUri?.amountRaw.description)
 //                print(detailsForUri?.amountEffective.description)
 //                print(detailsForUri?.proposalId)
-            } catch {
-                symLog.log(error.localizedDescription)                // TODO: 
error
+            } catch {    // TODO: error
+                symLog.log(error.localizedDescription)
             }
         }
     }
diff --git a/TalerWallet1/Views/Peer2peer/ReceivePurpose.swift 
b/TalerWallet1/Views/Peer2peer/ReceivePurpose.swift
new file mode 100644
index 0000000..38be62b
--- /dev/null
+++ b/TalerWallet1/Views/Peer2peer/ReceivePurpose.swift
@@ -0,0 +1,129 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+import SymLog
+
+struct ReceivePurpose: View {
+    private let symLog = SymLogV()
+    @FocusState private var isFocused: Bool
+    @StateObject var model = Peer2peerModel.model()
+    @State var peerPullCheck: CheckPeerPullCreditResponse?
+
+    var scopeInfo: ScopeInfo
+    var centsToTransfer: UInt64
+    @Binding var purpose: String
+    @Binding var expireDays: UInt
+    var deactivateAction: () -> Void
+
+    let formatter = CurrencyFormatter.shared    // TODO: based on currency
+
+    let buttonFont: Font = .title2
+    private var label: String {
+        let mag = pow(10, formatter.maximumFractionDigits)
+        return formatter.string(for: Decimal(centsToTransfer) / mag) ?? ""
+    }
+
+    func pullFee(ppCheck: CheckPeerPullCreditResponse?) -> String {
+        do {
+            if let p2pcheck = ppCheck {
+                let fee = try p2pcheck.amountRaw - p2pcheck.amountEffective
+                return fee.readableDescription
+            }
+        } catch {}
+        return ""
+    }
+
+    var body: some View {
+        let amount = Amount.amountFromCents(scopeInfo.currency, 
centsToTransfer)
+
+        let fee = pullFee(ppCheck: peerPullCheck)
+        VStack (spacing: 6) {
+            Text(amount.readableDescription)
+            Text("+ \(fee) payment fee")
+                .foregroundColor(.red)
+            VStack(alignment: .leading, spacing: 6) {
+                Text("Purpose:")
+                    .padding(.top)
+                    .font(.title3)
+
+                TextField("Purpose", text: $purpose)
+                    .font(.title)
+                    .foregroundColor(WalletColors().fieldForeground)     // 
text color
+                    .background(WalletColors().fieldBackground)
+                    .border(.primary)
+                    .focused($isFocused)
+                    .onAppear {
+                        DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
+                            isFocused = true        // make first responder - 
raise keybord
+                        }
+                    }
+
+                HStack{
+                    Spacer()
+                    Text("\(purpose.count)/100")
+                } // maximum 100 characters
+
+                Text("Expires in:")
+                    .font(.title3)
+
+                SelectDays(selected: $expireDays, maxExpiration: 35)
+                    .disabled(false)
+                    .padding(.bottom)
+
+                let buttonTitle = "Invoice \(label) \(scopeInfo.currency)"
+                let disabled = (expireDays == 0) || (purpose.count < 1)
+
+                NavigationLink(destination: LazyView {
+                    SendNow(amountToSend: amount, purpose: purpose, 
expireDays: expireDays, model: model)
+                    
.background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
+                }) {
+                    Text(buttonTitle)
+                        .font(buttonFont)
+                }
+                .buttonStyle(TalerButtonStyle(type: .prominent))
+                .disabled(disabled)
+
+                Spacer()
+            }
+            .frame(maxWidth: .infinity, alignment: .leading)
+            .padding(.horizontal)
+        }
+        .navigationTitle("Invoice another Wallet")
+        .onAppear {
+            DebugViewC.shared.setSheetID(VIEW_INVOICE_PURPOSE)
+            print("❗️ ReceivePurpose onAppear")
+        }
+        .onDisappear {
+            print("❗️ ReceivePurpose onDisappear")
+            deactivateAction()
+        }
+        .task {
+            symLog.log(".task")
+            do {
+                peerPullCheck = try await model.checkPeerPullCreditM(amount, 
exchangeBaseUrl: scopeInfo.exchangeBaseUrl ?? nil)   // TODO: exchangeBaseUrl!
+            } catch {    // TODO: error
+                symLog.log(error.localizedDescription)
+            }
+        }
+    }
+
+}
+// MARK: -
+#if DEBUG
+struct ReceivePurpose_Previews: PreviewProvider {
+    static var previews: some View {
+        let scopeInfo = ScopeInfo(type: ScopeInfo.ScopeInfoType.exchange, 
exchangeBaseUrl: DEMOEXCHANGE, currency: LONGCURRENCY)
+        @State var purpose: String = "pUrPoSe"
+        @State var expireDays: UInt = 0
+        ReceivePurpose(scopeInfo: scopeInfo,
+                 centsToTransfer: 5,
+                         purpose: $purpose,
+                      expireDays: $expireDays) {
+            print("deactivateAction")
+        }
+    }
+}
+#endif
diff --git a/TalerWallet1/Views/Peer2peer/RequestPayment.swift 
b/TalerWallet1/Views/Peer2peer/RequestPayment.swift
new file mode 100644
index 0000000..0c37649
--- /dev/null
+++ b/TalerWallet1/Views/Peer2peer/RequestPayment.swift
@@ -0,0 +1,77 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+import SymLog
+
+struct RequestPayment: View {
+    private let symLog = SymLogV()
+    let navTitle = String(localized: "Request Payment")
+
+    @ObservedObject var model: Peer2peerModel
+    var scopeInfo: ScopeInfo
+    @Binding var centsToTransfer: UInt64
+
+    @State private var peerPullCheck: CheckPeerPullCreditResponse? = nil
+    @State private var purpose: String = ""
+    @State private var expireDays: UInt = 0
+
+    var body: some View {
+#if DEBUG
+        let _ = Self._printChanges()
+        let _ = symLog.vlog()       // just to get the # to compare it with 
.onAppear & onDisappear
+#endif
+        let currencyField = CurrencyField(value: $centsToTransfer, currency: 
scopeInfo.currency)
+
+        VStack(alignment: .leading, spacing: 6) {
+            CurrencyInputView(currencyField: currencyField, title: "Amount to 
receive:")
+
+            HStack {
+                let disabled = centsToTransfer == 0
+
+                let deactivateAction = {
+                    print("❗️ deactivateAction")
+                }
+
+                NavigationLink(destination: LazyView {
+                    ReceivePurpose(scopeInfo: scopeInfo,
+                             centsToTransfer: centsToTransfer,
+                                     purpose: $purpose,
+                                  expireDays: $expireDays
+                    ) {
+                        deactivateAction()
+                    }
+                    
.background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
+                }) {
+                    Text("title2")
+                }
+                .buttonStyle(TalerButtonStyle(type: .prominent))
+                .disabled(disabled)
+            }
+            
+            Spacer()
+        }
+        .frame(maxWidth: .infinity, alignment: .leading)
+        .navigationTitle(navTitle)
+        .padding(.horizontal)
+        .onAppear {   // make CurrencyField show the keyboard
+            DebugViewC.shared.setViewID(VIEW_INVOICE_P2P)
+            print("❗️Yikes \(navTitle) onAppear")
+        }
+        .onDisappear {
+            print("❗️Yikes \(navTitle) onDisappear")
+        }
+    }
+}
+// MARK: -
+#if DEBUG
+//struct ReceiveAmount_Previews: PreviewProvider {
+//    static var scopeInfo = ScopeInfo(type: ScopeInfo.ScopeInfoType.exchange, 
exchangeBaseUrl: DEMOEXCHANGE, currency: LONGCURRENCY)
+//    static var previews: some View {
+//        let amount = Amount(currency: LONGCURRENCY, integer: 10, fraction: 0)
+//        RequestPayment(exchangeBaseUrl: DEMOEXCHANGE, currency: LONGCURRENCY)
+//    }
+//}
+#endif
diff --git a/TalerWallet1/Views/Peer2peer/SendAmount.swift 
b/TalerWallet1/Views/Peer2peer/SendAmount.swift
new file mode 100644
index 0000000..e95aab9
--- /dev/null
+++ b/TalerWallet1/Views/Peer2peer/SendAmount.swift
@@ -0,0 +1,85 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+import SymLog
+
+struct SendAmount: View {
+    private let symLog = SymLogV()
+    let navTitle = String(localized: "Send Coins")
+//    @ObservedObject private var keyboardResponder = KeyboardResponder()
+//    @FocusState private var isFocused: Bool
+
+    let amountAvailable: Amount
+    let buttonFont: Font = .title2
+
+    @State private var centsToTransfer: UInt64 = 0        // TODO: maybe 
Decimal?
+    @State private var purpose: String = ""
+    @State private var expireDays: UInt = 0
+
+    var body: some View {
+        let currencyField = CurrencyField(value: $centsToTransfer, currency: 
amountAvailable.currencyStr)
+
+        VStack(alignment: .leading, spacing: 6) {
+            CurrencyInputView(currencyField: currencyField, title: "Amount to 
send:")
+
+            let available = amountAvailable.readableDescription
+            Text("Available: \(available)")
+
+            Text("Choose where to send to:")
+                .padding(.top)
+                .font(.title3)
+            HStack {
+                let kbdShown: Bool = false  // 
keyboardResponder.keyboardHeight > 0
+                let title1 = kbdShown ? "To bank" : "To a bank\naccount"
+                let title2 = kbdShown ? "To wallet" : "To another\nwallet"
+                let disabled = centsToTransfer == 0    // TODO: check 
amountAvailable
+
+                // Left button: To bank
+                NavigationLink(destination: LazyView {
+                    Text("Bank pressed")
+                }) {
+                    Text(title1)
+                }
+                .buttonStyle(TalerButtonStyle(type: .bordered))
+                .disabled(disabled)
+
+                NavigationLink(destination: LazyView {
+                    SendPurpose(amountAvailable: amountAvailable,
+                                    centsToSend: centsToTransfer,
+                                        purpose: $purpose,
+                                     expireDays: $expireDays,
+                               deactivateAction: {})
+                    
.background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
+                }) {
+                    Text(title2)
+                }
+                .buttonStyle(TalerButtonStyle(type: .prominent))
+                .disabled(disabled)
+            }
+            
+            Spacer()
+        }
+        .frame(maxWidth: .infinity, alignment: .leading)
+        .navigationTitle(navTitle)
+        .padding(.horizontal)
+        .onAppear {   // make CurrencyField show the keyboard
+            DebugViewC.shared.setSheetID(SHEET_PAY_P2P)
+            print("❗️Yikes SendAmount onAppear")
+        }
+        .onDisappear {
+            print("❗️Yikes SendAmount onDisappear")
+        }
+    }
+}
+// MARK: -
+#if DEBUG
+struct SendAmount_Previews: PreviewProvider {
+    static var previews: some View {
+        let amount = Amount(currency: "TaLeR", integer: 10, fraction: 0)
+        SendAmount(amountAvailable: amount)
+    }
+}
+#endif
diff --git a/TalerWallet1/Views/Peer2peer/SendNow.swift 
b/TalerWallet1/Views/Peer2peer/SendNow.swift
new file mode 100644
index 0000000..8c81271
--- /dev/null
+++ b/TalerWallet1/Views/Peer2peer/SendNow.swift
@@ -0,0 +1,87 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+import SymLog
+
+
+struct SendNow: View {
+    private let symLog = SymLogV()
+
+    var amountToSend: Amount
+    var purpose: String
+    var expireDays: UInt
+
+    @ObservedObject var model: Peer2peerModel
+    @State var peerPushResponse: PeerPushResponse?
+
+    @State var talerURI: String? = nil
+
+    var body: some View {
+        ScrollView {
+            if talerURI == nil {
+                LoadingView(backButtonHidden: true)
+            } else {
+                VStack() {
+                    Text("Let the payee scan this QR code to receive:")
+                        .fixedSize(horizontal: false, vertical: true)
+                        .padding(.top, 30)
+                        .font(.title3)
+
+                    QRGeneratorView(text: talerURI!)
+
+                    Text("Alternatively, copy and send this URI:")
+                        .fixedSize(horizontal: false, vertical: true)
+                        .font(.title3)
+                        .padding(.vertical)
+
+                    Text(talerURI!)
+                        .padding(.bottom)
+
+                    CopyShare(textToCopy: talerURI!, dismissFirst: true)
+                        .disabled(false)
+
+                    Text("The QR code can also be copied and shared from 
Transactions later")
+                        .fixedSize(horizontal: false, vertical: true)
+                        .font(.subheadline)
+                        .padding(.vertical, 20)
+
+                    Button("Done") {
+                        dismissTop()
+                    }
+                    .buttonStyle(TalerButtonStyle(type: .prominent))
+                    .padding(.vertical)
+
+                }
+                .interactiveDismissDisabled()       // can only use "Done" 
button to dismiss
+                .navigationBarHidden(true)          // no back button, no title
+                .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: 
.leading)
+                .padding(.horizontal)
+            }
+        }
+        .task {
+            symLog.log(".task")
+            do {
+                // generate talerURI
+                let timestamp = Timestamp.inSomeDays(expireDays)
+                let terms = PeerContractTerms(amount: amountToSend, summary: 
purpose, purse_expiration: timestamp)
+                let baseURL = DEMOEXCHANGE  // TODO: use correct baseURL
+                peerPushResponse = try await 
model.initiatePeerPushDebitM(baseURL, terms: terms)
+                talerURI = peerPushResponse?.talerUri
+            } catch {    // TODO: error
+                symLog.log(error.localizedDescription)
+            }
+        } // task
+    }
+}
+// MARK: -
+//struct SendNow_Previews: PreviewProvider {
+//    static var previews: some View {
+//        Group {
+//            SendNow()
+//            SendNow(talerURI: "taler://pay-push/exchange.demo.taler.net")
+//        }
+//    }
+//}
diff --git a/TalerWallet1/Views/Peer2peer/SendPurpose.swift 
b/TalerWallet1/Views/Peer2peer/SendPurpose.swift
new file mode 100644
index 0000000..cabeea8
--- /dev/null
+++ b/TalerWallet1/Views/Peer2peer/SendPurpose.swift
@@ -0,0 +1,126 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+import SymLog
+
+struct SendPurpose: View {
+    private let symLog = SymLogV()
+    @FocusState private var isFocused: Bool
+    @StateObject var model = Peer2peerModel.model()
+    @State var peerPushCheck: CheckPeerPushDebitResponse?
+
+    var amountAvailable: Amount
+    var centsToSend: UInt64
+    @Binding var purpose: String
+    @Binding var expireDays: UInt
+    var deactivateAction: () -> Void
+
+    let formatter = CurrencyFormatter.shared    // TODO: based on currency
+
+    let buttonFont: Font = .title2
+    private var label: String {
+        let mag = pow(10, formatter.maximumFractionDigits)
+        return formatter.string(for: Decimal(centsToSend) / mag) ?? ""
+    }
+
+    private func fee(ppCheck: CheckPeerPushDebitResponse?) -> String {
+        do {
+            if let p2pcheck = ppCheck {
+                let fee = try p2pcheck.amountEffective - p2pcheck.amountRaw
+                return fee.readableDescription
+            }
+        } catch {}
+        return ""
+    }
+
+    var body: some View {
+        let amount = Amount.amountFromCents(amountAvailable.currencyStr, 
centsToSend)
+
+        let fee = fee(ppCheck: peerPushCheck)
+        VStack (spacing: 6) {
+            Text(amount.readableDescription)
+            Text("+ \(fee) payment fee")
+                .foregroundColor(.red)
+            VStack(alignment: .leading, spacing: 6) {
+                Text("Purpose:")
+                    .padding(.top)
+                    .font(.title3)
+
+                TextField("Purpose", text: $purpose)
+                    .font(.title)
+                    .foregroundColor(WalletColors().fieldForeground)     // 
text color
+                    .background(WalletColors().fieldBackground)
+                    .border(.primary)
+                    .focused($isFocused)
+                    .onAppear {
+                        DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
+                            isFocused = true        // make first responder - 
raise keybord
+                        }
+                    }
+
+                HStack{
+                    Spacer()
+                    Text("\(purpose.count)/100")
+                } // maximum 100 characters
+
+                Text("Expires in:")
+                    .font(.title3)
+
+                // TODO: compute max Expiration day from peerPushCheck to 
disable 30 (and even 7)
+                SelectDays(selected: $expireDays, maxExpiration: 35)
+                    .disabled(false)
+                    .padding(.bottom)
+
+                let buttonTitle = "Send \(label) 
\(amountAvailable.currencyStr) now"
+                let disabled = (expireDays == 0) || (purpose.count < 1)    // 
TODO: check amountAvailable
+
+                NavigationLink(destination: LazyView {
+                    SendNow(amountToSend: amount, purpose: purpose, 
expireDays: expireDays, model: model)
+                        
.background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
+                }) {
+                    Text(buttonTitle)
+                        .font(buttonFont)
+                }
+                .buttonStyle(TalerButtonStyle(type: .prominent))
+                .disabled(disabled)
+
+                Spacer()
+            }
+            .frame(maxWidth: .infinity, alignment: .leading)
+            .padding(.horizontal)
+        }
+        .navigationTitle("To another Wallet")
+        .onAppear {
+            DebugViewC.shared.setSheetID(VIEW_SEND_PURPOSE)
+            print("❗️ SendPurpose onAppear")
+        }
+        .onDisappear {
+            print("❗️ SendPurpose onDisappear")
+            deactivateAction()
+        }
+        .task {
+            symLog.log(".task")
+            do {
+                peerPushCheck = try await model.checkPeerPushDebitM(amount)
+            } catch {    // TODO: error
+                symLog.log(error.localizedDescription)
+            }
+        }
+    }
+
+}
+// MARK: -
+struct SendPurpose_Previews: PreviewProvider {
+    static var previews: some View {
+        @State var purpose: String = ""
+        @State var expireDays: UInt = 0
+        let amount = Amount(currency: "TaLeR", integer: 10, fraction: 0)
+        SendPurpose(amountAvailable: amount, centsToSend: 5,
+                    purpose: $purpose, expireDays: $expireDays) {
+            print("deactivateAction")
+        }
+    }
+}
diff --git a/TalerWallet1/Views/Pending/PendingModel.swift 
b/TalerWallet1/Views/Pending/PendingModel.swift
deleted file mode 100644
index 6b0e38e..0000000
--- a/TalerWallet1/Views/Pending/PendingModel.swift
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-import Foundation
-import AnyCodable
-import taler_swift
-import SymLog
-fileprivate let ASYNCDELAY: UInt = 0   //set e.g to 6 or 9 seconds for 
debugging
-
-class PendingModel: WalletModel {
-    @Published var pendingOperations: [PendingOperation]?
-}
-// MARK: -
-/// A request to list the backend's currently pending operations.
-fileprivate struct GetPendingOperations: WalletBackendFormattedRequest {
-    func operation() -> String { return "getPendingOperations" }
-    func args() -> Args { Args() }
-
-    struct Args: Encodable {}
-
-    struct Response: Decodable {
-        var pendingOperations: [PendingOperation]
-    }
-}
-// MARK: -
-struct PendingOperation: Codable, Hashable {
-    var type: String
-    var exchangeBaseUrl: String
-    var id: String
-    var isLongpolling: Bool
-    var givesLifeness: Bool
-    var isDue: Bool
-    var timestampDue: Timestamp
-
-    public func hash(into hasher: inout Hasher) {
-        hasher.combine(type)
-        hasher.combine(exchangeBaseUrl)
-        hasher.combine(id)
-        hasher.combine(isLongpolling)
-        hasher.combine(givesLifeness)
-        hasher.combine(isDue)
-        hasher.combine(timestampDue)
-    }
-
-}
-//let pending1 = ["type": "exchange-update",
-//                EXCHANGEBASEURL: "https://exchange.demo.taler.net/";,
-//                "id": "exchange-update:https://exchange.demo.taler.net/";,
-//                "timestampDue": ["t_ms": 1669931055000],
-//                "isDue": false,
-//                "isLongpolling": false,
-//                "givesLifeness": false] as [String : Any]
-//
-//let pending2 = ["type": "exchange-check-refresh",
-//                EXCHANGEBASEURL: "https://exchange.demo.taler.net/";,
-//                "id": "exchange-update:https://exchange.demo.taler.net/";,
-//                "timestampDue": ["t_ms": 1670013862000],
-//                "isDue": false,
-//                "isLongpolling": false,
-//                "givesLifeness": false] as [String : Any]
-// MARK: -
-extension PendingModel {
-    @MainActor func update() async throws {
-        do {
-            let request = GetPendingOperations()
-            let response = try await sendRequest(request, ASYNCDELAY)
-            pendingOperations = response.pendingOperations
-        }
-    }
-}
diff --git a/TalerWallet1/Views/Pending/PendingOpsListView.swift 
b/TalerWallet1/Views/Pending/PendingOpsListView.swift
deleted file mode 100644
index a4d9e61..0000000
--- a/TalerWallet1/Views/Pending/PendingOpsListView.swift
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-import SwiftUI
-import SymLog
-
-struct PendingOpsListView: View {
-    private let symLog = SymLogV()
-    let navTitle = "Pending"
-
-    @ObservedObject var viewModel: PendingModel
-    var hamburgerAction: () -> Void
-
-    var body: some View {
-        let reloadAction = viewModel.update
-        VStack {
-            if viewModel.pendingOperations == nil {
-                symLog { LoadingView(backButtonHidden: true) }
-            } else {
-                symLog { NavigationView {
-                    Content(symLog: symLog, viewModel: viewModel, 
reloadAction: reloadAction)
-                        .navigationBarItems(leading: HamburgerButton(action: 
hamburgerAction))
-                } }
-                .navigationTitle(navTitle)
-            }
-        }.task {
-            symLog.log(".task")
-            do {
-                try await reloadAction()
-            } catch {
-                // TODO: show error
-                symLog.log(error.localizedDescription)
-            }
-        }
-    }
-}
-// MARK: -
-extension PendingOpsListView {
-    struct Content: View {
-        let symLog: SymLogV?
-        @ObservedObject var viewModel: PendingModel
-//        @EnvironmentObject var controller : Controller
-        var reloadAction: () async throws -> ()
-
-        var body: some View {
-            Group {
-                List(viewModel.pendingOperations!, id: \.self) { pendingOp in
-                    PendingOpView(pendingOp: pendingOp)
-                }
-                .navigationBarTitleDisplayMode(.large)      // .inline
-                .refreshable {
-                    do {
-                        symLog?.log("refreshing")
-                        try await reloadAction()
-                    } catch {
-                        // TODO: error
-                        symLog?.log(error.localizedDescription)
-                    }
-                }
-            }
-        }
-    }
-}
diff --git a/TalerWallet1/Views/Pending/PendingOpView.swift 
b/TalerWallet1/Views/Settings/Pending/PendingOpView.swift
similarity index 62%
rename from TalerWallet1/Views/Pending/PendingOpView.swift
rename to TalerWallet1/Views/Settings/Pending/PendingOpView.swift
index 12c2924..16a9aab 100644
--- a/TalerWallet1/Views/Pending/PendingOpView.swift
+++ b/TalerWallet1/Views/Settings/Pending/PendingOpView.swift
@@ -1,17 +1,6 @@
 /*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
  */
 import SwiftUI
 import taler_swift
@@ -24,7 +13,9 @@ struct PendingOpView: View {
 
     var body: some View {
         Section {
-            Text(pendingOp.exchangeBaseUrl)
+            if let baseURL = pendingOp.exchangeBaseUrl {
+                Text(baseURL)
+            }
             Text(pendingOp.id)
                 .font(.caption)
             Toggle("isLongPolling", isOn: $polling)
@@ -47,18 +38,20 @@ struct PendingOpView: View {
         }
     }
 }
-
+// MARK: -
+#if DEBUG
 struct PendingOpView_Previews: PreviewProvider {
     static var pending1 = PendingOperation(type: "exchange-check-refresh",
-                                exchangeBaseUrl: 
"https://exchange.demo.taler.net/";,
-                                             id: 
"exchange-update:https://exchange.demo.taler.net/";,
+                                exchangeBaseUrl: DEMOEXCHANGE,
+                                             id: "exchange-update:" + 
DEMOEXCHANGE,
                                   isLongpolling: false,
                                   givesLifeness: true,
                                           isDue: false,
-                                   timestampDue: Timestamp(from:1669931055000))
+                                   timestampDue: Timestamp(from:1700000000000))
     static var previews: some View {
-        Form {
+        List {
             PendingOpView(pendingOp: pending1)
         }
     }
 }
+#endif
diff --git a/TalerWallet1/Views/Settings/Pending/PendingOpsListView.swift 
b/TalerWallet1/Views/Settings/Pending/PendingOpsListView.swift
new file mode 100644
index 0000000..3a35398
--- /dev/null
+++ b/TalerWallet1/Views/Settings/Pending/PendingOpsListView.swift
@@ -0,0 +1,64 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import SymLog
+
+struct PendingOpsListView: View {
+    private let symLog = SymLogV(0)
+    let navTitle = String(localized: "Pending")
+
+    @ObservedObject var model: PendingModel
+
+    var body: some View {
+#if DEBUG
+        let _ = Self._printChanges()
+        let _ = symLog.vlog()       // just to get the # to compare it with 
.onAppear & onDisappear
+#endif
+        let reloadAction = model.updateM
+        Content(symLog: symLog, model: model, reloadAction: reloadAction)
+            .navigationTitle(navTitle)
+            .task {
+                symLog.log(".task")
+                do {
+                    try await reloadAction()
+                } catch {    // TODO: show error
+                    symLog.log(error.localizedDescription)
+                }
+            }
+    }
+}
+// MARK: -
+extension PendingOpsListView {
+    struct Content: View {
+        let symLog: SymLogV?
+        @ObservedObject var model: PendingModel
+        var reloadAction: () async throws -> ()
+
+        var body: some View {
+#if DEBUG
+            let _ = Self._printChanges()
+            let _ = symLog?.vlog()       // just to get the # to compare it 
with .onAppear & onDisappear
+#endif
+            ScrollViewReader { scrollView in
+                List(model.pendingOperations, id: \.self) { pendingOp in
+                    PendingOpView(pendingOp: pendingOp)
+                }
+                .listStyle(SidebarListStyle())
+                .navigationBarTitleDisplayMode(.large)
+                .onAppear() {
+                    DebugViewC.shared.setViewID(VIEW_PENDING)
+                }
+                .refreshable {
+                    do {
+                        symLog?.log("refreshing")
+                        try await reloadAction()
+                    } catch {    // TODO: error
+                        symLog?.log(error.localizedDescription)
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/TalerWallet1/Views/Settings/SettingsItem.swift 
b/TalerWallet1/Views/Settings/SettingsItem.swift
index 7735c97..b8443fe 100644
--- a/TalerWallet1/Views/Settings/SettingsItem.swift
+++ b/TalerWallet1/Views/Settings/SettingsItem.swift
@@ -1,19 +1,7 @@
 /*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
  */
-
 import SwiftUI
 
 struct SettingsItem<Content: View>: View {
@@ -44,16 +32,21 @@ struct SettingsItem<Content: View>: View {
         }.padding([.bottom], 4)
     }
 }
-
+// MARK: -
 struct SettingsToggle: View {
     var name: String
     @Binding var value: Bool
     var description: String?
+    var action: () -> Void = {}
 
     var body: some View {
         VStack {
-            Toggle(name, isOn: $value.animation(.spring()))
+            Toggle(name, isOn: $value.animation())
                 .font(.title2)
+                .onChange(of: value) { value in
+                    action()
+                }
+
             if let desc = description {
                 Text(desc)
                     .frame(maxWidth: .infinity, alignment: .leading)
@@ -62,9 +55,8 @@ struct SettingsToggle: View {
         }.padding([.bottom], 4)
     }
 }
-
-
-
+// MARK: -
+#if DEBUG
 struct SettingsItemPreview : View {
     @State var developerMode: Bool = false
 
@@ -78,9 +70,7 @@ struct SettingsItemPreview : View {
 struct SettingsItem_Previews: PreviewProvider {
     static var previews: some View {
         List {
-            NavigationLink { } label: {
-                SettingsItem (name: "Exchanges", description: "Manage list of 
exchanges known to this wallet") {}
-            }
+            SettingsItem (name: "Exchanges", description: "Manage list of 
exchanges known to this wallet") {}
             SettingsItemPreview()
             SettingsItem(name: "Save Logfile", description: "Help debugging 
wallet-core") {
                 Button("Save") {
@@ -91,3 +81,4 @@ struct SettingsItem_Previews: PreviewProvider {
         }
     }
 }
+#endif
diff --git a/TalerWallet1/Views/Settings/SettingsView.swift 
b/TalerWallet1/Views/Settings/SettingsView.swift
index b84b242..db63b5a 100644
--- a/TalerWallet1/Views/Settings/SettingsView.swift
+++ b/TalerWallet1/Views/Settings/SettingsView.swift
@@ -1,17 +1,6 @@
 /*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
  */
 import SwiftUI
 import taler_swift
@@ -21,115 +10,173 @@ import SymLog
  * Backup
  * Last backup: 5 hr. ago
  *
- *
  * Debug log
  * View/send internal log
  *
- *
  * Reset Wallet (dangerous!)
  * Throws away your money
  */
 
 struct SettingsView: View {
-    private let symLog = SymLogV(0)
-    
-    @EnvironmentObject var controller: Controller
+    private let symLog = SymLogV()
+    let navTitle = String(localized: "Settings")
+
     @AppStorage("developerMode") var developerMode: Bool = false
     @AppStorage("developDelay") var developDelay: Bool = false
+    @AppStorage("listStyle") var myListStyle = MyListStyle.automatic
 
-    var showSidebar: () -> Void
-    init(showSidebar: @escaping () -> Void) {
-        self.showSidebar = showSidebar
-    }
+    var hamburgerAction: () -> Void
 
     @State private var checkDisabled = false
     @State private var withDrawDisabled = false
+#if DEBUG
+    @State private var diagnosticModeEnabled = true
+#else
+    @State private var diagnosticModeEnabled = 
UserDefaults.standard.bool(forKey: "diagnostic_mode_enabled")
+#endif
+    @State private var showDevelopItems = false
 
     var body: some View {
-        symLog { NavigationView {
+#if DEBUG
+        let _ = Self._printChanges()
+        let _ = symLog.vlog()       // just to get the # to compare it with 
.onAppear & onDisappear
+#endif
+        let walletCore = WalletCore.shared
+        Group {
             List {
-                NavigationLink {
-                    ExchangeListView(viewModel: controller.exchangeModel)
-                } label: {
-                    SettingsItem(name: "Exchanges", description: "Manage list 
of exchanges known to this wallet") {}
+                HStack {
+                    Text("Liststyle:")
+                        .font(.title2)
+                    Spacer()
+                    Picker(selection: $myListStyle) {
+                        ForEach(MyListStyle.allCases, id: \.self) {
+                            Text($0.displayName).tag($0)
+                                .font(.title2)
+                        }
+                    } label: {}
+                        .pickerStyle(.menu)
+//                        .frame(alignment: .trailing)
+//                        .background(WalletColors().buttonBackColor(pressed: 
false, disabled: false))  TODO: RoundRect
                 }
-                SettingsToggle(name: "Developer Mode", value: $developerMode,
-                               description: "More information intended for 
debugging")
-                if developerMode {  // show or hide the following items
-                    let walletCore = controller.walletCore
-                    SettingsToggle(name: "Set 2 seconds delay", value: 
$developDelay,
-                                   description: "After each wallet-core 
action")
-                    .onChange(of: developDelay, perform: { developDelay in
-                        walletCore.developDelay = developDelay
-                    })
+                if diagnosticModeEnabled {
+                    SettingsToggle(name: String(localized: "Developer Mode"), 
value: $developerMode,
+                                   description: String(localized: "More 
information intended for debugging")) {
+                        DebugViewC.shared.setViewID(VIEW_SETTINGS)
+                        withAnimation { showDevelopItems = developerMode }
+                    }
+                    if showDevelopItems {  // show or hide the following items
+                        NavigationLink {        // whole row like in a 
tableView
+                            LazyView { PendingOpsListView(model: 
PendingModel.model()) }
+                        } label: {
+                            SettingsItem(name: String(localized: "Pending 
Operations"), description: String(localized: "Exchange not yet ready...")) {}
+                        }
+                        SettingsToggle(name: String(localized: "Set 2 seconds 
delay"), value: $developDelay,
+                                       description: String(localized: "After 
each wallet-core action"))
+                        .onChange(of: developDelay, perform: { developDelay in
+                            walletCore.developDelay = developDelay
+                        })
 
-                    SettingsItem(name: "Withdraw KUDOS", description: "Get 
money for testing") {
-                        Button("Withdraw") {
-                            withDrawDisabled = true    // don't run twice
-                            Task {
-                                let testModel: ExchangeTestModel = 
ExchangeTestModel(walletCore: walletCore)
-                                symLog.log("Withdrawing")
-                                do {
-                                    try await testModel.loadTestKudos()
-                                } catch {
-                                    // TODO: show error
-                                    symLog.log(error.localizedDescription)
+                        SettingsItem(name: String(localized: "Withdraw 
\(DEMOCURRENCY)"), description: String(localized: "Get money for testing")) {
+                            Button("Withdraw") {
+                                withDrawDisabled = true    // don't run twice
+                                Task {
+                                    let model: SettingsModel = SettingsModel()
+                                    symLog.log("Withdraw TestKUDOS")
+                                    do {
+                                        try await model.loadTestKudosM()
+                                    } catch {    // TODO: show error
+                                        symLog.log(error.localizedDescription)
+                                    }
                                 }
                             }
+                            .buttonStyle(.bordered)
+                            .disabled(withDrawDisabled)
                         }
-                        .buttonStyle(.bordered)
-                        .disabled(withDrawDisabled)
-                    }
-                    SettingsItem(name: "Run Integration Test", description: 
"Check if wallet-core works") {
-                        Button("Check") {
-                            checkDisabled = true    // don't run twice
-                            Task {
-                                let testModel: ExchangeTestModel = 
ExchangeTestModel(walletCore: walletCore)
-                                symLog.log("running integration test")
-                                do {
-                                    try await testModel.runIntegrationTest()
-                                } catch {
-                                    // TODO: show error
-                                    symLog.log(error.localizedDescription)
+                        SettingsItem(name: String(localized: "Run Integration 
Test"),
+                                     description: String(localized: "Perform 
basic test transactions")) {
+                            Button("Test 1") {
+                                checkDisabled = true    // don't run twice
+                                Task {
+                                    let model: SettingsModel = SettingsModel()
+                                    symLog.log("running integration test")
+                                    do {
+                                        try await 
model.runIntegrationTestM(newVersion: false)
+                                    } catch {    // TODO: show error
+                                        symLog.log(error.localizedDescription)
+                                    }
                                 }
                             }
+                            .buttonStyle(.bordered)
+                            .disabled(checkDisabled)
                         }
-                        .buttonStyle(.bordered)
-                        .disabled(checkDisabled)
-                    }
-                    SettingsItem(name: "Save Logfile", description: "Help 
debugging wallet-core") {
-                        Button("Save") {
-                            symLog.log("Saving Log")
-                            // FIXME: Save Logfile
-                        }
-                        .buttonStyle(.bordered)
-                        .disabled(true)
-                    }
-                    VStack {
-                        SettingsItem(name: "App Version") {
-                            Text("\(Bundle.main.releaseVersionNumberPretty)")
-                        }
-                        SettingsItem(name: "Wallet Core Version") {
-                            Text("\(walletCore.versionInfo!.version)")
-                        }
-                        SettingsItem(name: "Wallet Core DevMode") {
-                            Text("\(walletCore.versionInfo!.devMode ? "YES" : 
"NO")")
-                        }
-                        SettingsItem(name: "Supported Exchange Versions") {
-                            Text("\(walletCore.versionInfo!.exchange)")
-                        }
-                        SettingsItem(name: "Supported Merchant Versions") {
-                            Text("\(walletCore.versionInfo!.merchant)")
+                        SettingsItem(name: String(localized: "Run Integration 
Test V2"),
+                                     description: String(localized: "Perform 
more test transactions")) {
+                            Button("Test 2") {
+                                checkDisabled = true    // don't run twice
+                                Task {
+                                    let model: SettingsModel = SettingsModel()
+                                    symLog.log("running integration test V2")
+                                    do {
+                                        try await 
model.runIntegrationTestM(newVersion: true)
+                                    } catch {    // TODO: show error
+                                        symLog.log(error.localizedDescription)
+                                    }
+                                }
+                            }
+                            .buttonStyle(.bordered)
+                            .disabled(checkDisabled)
                         }
-                        SettingsItem(name: "Used Bank") {
-                            Text("\(walletCore.versionInfo!.bank)")
+                        SettingsItem(name: String(localized: "Save Logfile"),
+                                     description: String(localized: "Help 
debugging wallet-core")) {
+                            Button("Save") {
+                                symLog.log("Saving Log")
+                                // FIXME: Save Logfile
+                            }
+                            .buttonStyle(.bordered)
+                            .disabled(true)
                         }
                     }
                 }
+
+                VStack {
+                    SettingsItem(name: String(localized: "App Version")) {
+                        Text("\(Bundle.main.releaseVersionNumberPretty)")
+                    }
+                    SettingsItem(name: String(localized: "Wallet Core 
Version")) {
+                        Text("\(walletCore.versionInfo!.version)")
+                    }
+                    SettingsItem(name: String(localized: "Wallet Core 
DevMode")) {
+                        Text("\(walletCore.versionInfo!.devMode ? "YES" : 
"NO")")
+                    }
+                    SettingsItem(name: String(localized: "Supported Exchange 
Versions")) {
+                        Text("\(walletCore.versionInfo!.exchange)")
+                    }
+                    SettingsItem(name: String(localized: "Supported Merchant 
Versions")) {
+                        Text("\(walletCore.versionInfo!.merchant)")
+                    }
+                    SettingsItem(name: String(localized: "Used Bank")) {
+                        Text("\(walletCore.versionInfo!.bank)")
+                    }
+                }
             }
-                .navigationTitle("Settings")
-                .navigationBarItems(leading: HamburgerButton(action: 
showSidebar))
-        } } // symLog
+            .listStyle(myListStyle.style)
+            .anyView
+        }
+        .navigationTitle(navTitle)
+        .navigationBarItems(leading: HamburgerButton(action: hamburgerAction))
+        .onAppear() {
+            showDevelopItems = developerMode
+            DebugViewC.shared.setViewID(VIEW_SETTINGS)
+        }
+#if !DEBUG
+        .onReceive(
+            NotificationCenter.default
+                .publisher(for: UserDefaults.didChangeNotification)
+                .receive(on: RunLoop.main)
+        ) { _ in            // user changed Diagnostic Mode in iOS Settings.app
+            withAnimation { diagnosticModeEnabled = 
UserDefaults.standard.bool(forKey: "diagnostic_mode_enabled") }
+        }
+#endif
     }
 }
 extension Bundle {
@@ -144,10 +191,10 @@ extension Bundle {
     }
 }
 
-struct SettingsView_Previews: PreviewProvider {
-    static var previews: some View {
-        SettingsView {
-            
-        }
-    }
-}
+//struct SettingsView_Previews: PreviewProvider {
+//    static var previews: some View {
+//        SettingsView {
+//
+//        }
+//    }
+//}
diff --git a/TalerWallet1/Views/Sheets/QRSheet.swift 
b/TalerWallet1/Views/Sheets/QRSheet.swift
new file mode 100644
index 0000000..6385ec4
--- /dev/null
+++ b/TalerWallet1/Views/Sheets/QRSheet.swift
@@ -0,0 +1,52 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import CodeScanner
+import SymLog
+import AVFoundation
+
+struct QRSheet: View {
+    private let symLog = SymLogV()
+    @State private var scannedCode: String?
+
+    var body: some View {
+        Group {
+            if scannedCode != nil {
+                let _ = print(scannedCode as Any)       // TODO: logging
+
+                if let scannedURL = URL(string: scannedCode!) {
+                    let scheme = scannedURL.scheme
+                    if scheme == "taler" {
+                        URLSheet(urlToOpen: scannedURL)
+                    } else {
+                        let _ = print(scannedURL)       // TODO: logging
+                        ErrorView(errortext: scannedURL.absoluteString)
+                    }
+                } else {
+                    ErrorView(errortext: scannedCode)
+                }
+            } else {
+                CodeScannerView(codeTypes: [AVMetadataObject.ObjectType.qr], 
showViewfinder: true) { response in
+                    switch response {
+                        case .success(let result):
+                            symLog.log("Found code: \(result.string)")
+                            scannedCode = result.string
+                        case .failure(let error):
+                            ErrorView(errortext: error.localizedDescription)
+                    }
+                }
+                // TODO: errorAlert
+            }
+        }
+    }
+
+}
+// MARK: -
+//struct PaySheet_Previews: PreviewProvider {
+//    static var previews: some View {
+            // needs BackendManager
+//        URLSheet(urlToOpen: URL(string: "ftp://this.URL.is.invalid";)!)
+//    }
+//}
diff --git a/TalerWallet1/Views/Sheets/ShareSheet.swift 
b/TalerWallet1/Views/Sheets/ShareSheet.swift
new file mode 100644
index 0000000..e56148f
--- /dev/null
+++ b/TalerWallet1/Views/Sheets/ShareSheet.swift
@@ -0,0 +1,40 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import Foundation
+import UIKit
+import SymLog
+
+// You can control the appearance of the link by providing view content.
+// For example, you can use a Label to display a link with a custom icon:
+//    ShareLink(item: URL(string: 
"https://developer.apple.com/xcode/swiftui/";)!) {
+//        Label("Share", image: "MyCustomShareIcon")
+//    }
+// If you only wish to customize the link’s title, you can use one of the 
convenience
+// initializers that takes a string and creates a Label for you:
+//    ShareLink("Share URL", item: URL(string: 
"https://developer.apple.com/xcode/swiftui/";)!)
+// The link can share any content that is Transferable.
+// Many framework types, like URL, already conform to this protocol.
+
+
+public class ShareSheet: ObservableObject {
+    private let symLog = SymLogC()
+
+    static func shareSheet(url: String) {
+        let url = URL(string: url)
+        let activityView = UIActivityViewController(activityItems: [url!], 
applicationActivities: nil)
+
+        let allScenes = UIApplication.shared.connectedScenes
+        let scene = allScenes.first { $0.activationState == .foregroundActive }
+
+        if let windowScene = scene as? UIWindowScene {
+            windowScene.keyWindow?.rootViewController?.present(activityView, 
animated: true, completion: nil)
+        }
+    }
+
+    init() {
+        symLog.log("init")
+
+    }
+}
diff --git a/TalerWallet1/Views/Sheets/Sheet.swift 
b/TalerWallet1/Views/Sheets/Sheet.swift
new file mode 100644
index 0000000..e2e8a9a
--- /dev/null
+++ b/TalerWallet1/Views/Sheets/Sheet.swift
@@ -0,0 +1,44 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import SymLog
+
+struct Sheet: View {
+    private let symLog = SymLogV()
+    @Environment(\.dismiss) var dismiss     // call dismiss() to get rid of 
the sheet
+    @EnvironmentObject private var debugViewC: DebugViewC
+
+    var sheetView: AnyView
+
+    var cancelButton: some View {
+        Button("Cancel") {
+            print(dismiss)  // TODO: delete this line
+            dismissTop()
+        }
+    }
+
+    var body: some View {
+        let idString = debugViewC.sheetID > 0 ? String(debugViewC.sheetID) : ""
+        NavigationView {
+            sheetView
+                .navigationBarItems(leading: cancelButton)
+                
.background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
+        }
+        .overlay(alignment: .top) {
+            // Show the viewID on top of the sheet's NavigationView
+            Text(idString)
+                .font(.caption2)
+                .foregroundColor(.purple)
+                .edgesIgnoringSafeArea(.top)
+                .id("sheetID")
+        }
+    }
+}
+// MARK: -
+//struct Sheet_Previews: PreviewProvider {
+//    static var previews: some View {
+//
+//    }
+//}
diff --git a/TalerWallet1/Views/Sheets/URLSheet.swift 
b/TalerWallet1/Views/Sheets/URLSheet.swift
new file mode 100644
index 0000000..342447b
--- /dev/null
+++ b/TalerWallet1/Views/Sheets/URLSheet.swift
@@ -0,0 +1,45 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import SymLog
+
+struct URLSheet: View {
+    private let symLog = SymLogV()
+    let navTitle = String(localized: "Invalid URL")
+    var urlToOpen: URL
+    @EnvironmentObject private var controller: Controller
+
+    @State private var urlCommand: UrlCommand? = nil
+
+    var body: some View {
+        Group {
+            if urlCommand == UrlCommand.withdraw {
+                let model = WithdrawModel.model(baseURL: "global")      // 
TODO: get baseURL from command
+                WithdrawURIView(url: urlToOpen, model: model)
+            } else if urlCommand == UrlCommand.pay {
+                let model = PaymentURIModel.model()
+                PaymentURIView(url: urlToOpen, model: model)
+            } else {
+                VStack {        // Error view
+                    Spacer()
+                    Text(controller.messageForSheet ?? 
urlToOpen.absoluteString)
+                        .font(.title)
+                    Spacer()
+                    Spacer()
+                }
+                .navigationTitle(navTitle)
+            }
+        }.task {
+            urlCommand = controller.openURL(urlToOpen)
+        }
+    }
+}
+// MARK: -
+//struct PaySheet_Previews: PreviewProvider {
+//    static var previews: some View {
+            // needs BackendManager
+//        URLSheet(urlToOpen: URL(string: "ftp://this.URL.is.invalid";)!)
+//    }
+//}
diff --git a/TalerWallet1/Views/Transactions/ManualDetails.swift 
b/TalerWallet1/Views/Transactions/ManualDetails.swift
new file mode 100644
index 0000000..4940e18
--- /dev/null
+++ b/TalerWallet1/Views/Transactions/ManualDetails.swift
@@ -0,0 +1,69 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+
+struct ManualDetails: View {
+    var common : TransactionCommon
+    var details : WithdrawalDetails
+    var body: some View {
+        if let paytoUris = details.exchangePaytoUris {
+            let payto = paytoUris[0]
+            let payURL = URL(string: payto)
+            let iban = payURL?.iban ?? "unknown IBAN"
+            let amount = common.amountRaw.readableDescription
+            Text("Make a wire transfer of \(amount) to:")
+                .listRowSeparator(.hidden)
+            HStack {
+                Text(iban)
+                Spacer()
+                CopyButton(textToCopy: iban, vertical: true)
+                    .disabled(false)
+            }   .padding(.leading)
+                .padding(.vertical, -8)
+                .listRowSeparator(.hidden)
+            Text("and use the transaction subject:")
+                .listRowSeparator(.hidden)
+            HStack {
+                Text(details.reservePub)
+                Spacer()
+                CopyButton(textToCopy: details.reservePub, vertical: true)
+                    .disabled(false)
+            }   .padding(.leading)
+                .padding(.vertical, -8)
+                .listRowSeparator(.hidden)
+            HStack {
+                Spacer()
+                ShareButton(textToShare: payto, dismissFirst: false)
+                    .disabled(false)
+                Spacer()
+            }   .listRowSeparator(.hidden)
+            Text("Payto URL")
+                .font(.footnote)
+                .foregroundColor(Color.yellow)   // clear
+                .padding(.vertical, -8)
+                .listRowSeparator(.automatic)
+        }
+    }
+}
+// MARK: -
+#if DEBUG
+struct ManualDetails_Previews: PreviewProvider {
+    static var previews: some View {
+        let common = TransactionCommon(type: .withdrawal,
+                                    txState: TransactionState(major: .done),
+                            amountEffective: try! Amount(fromString: 
LONGCURRENCY + ":1.1"),
+                                  amountRaw: try! Amount(fromString: 
LONGCURRENCY + ":2.2"),
+                              transactionId: "someTxID",
+                                  timestamp: Timestamp(from: 
1_666_666_000_000),
+                                  txActions: [])
+        let details = WithdrawalDetails(type: .manual, reservePub: 
"ReSeRvEpUbLiC_KeY_FoR_WiThDrAwAl", reserveIsReady: false,
+                                        
exchangePaytoUris:["payto://iban/SANDBOXX/DE159593?receiver-name=Exchange+Company"])
+        List {
+            ManualDetails(common: common, details: details)
+        }
+    }
+}
+#endif
diff --git a/TalerWallet1/Views/Transactions/ThreeAmounts.swift 
b/TalerWallet1/Views/Transactions/ThreeAmounts.swift
new file mode 100644
index 0000000..d016089
--- /dev/null
+++ b/TalerWallet1/Views/Transactions/ThreeAmounts.swift
@@ -0,0 +1,109 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+
+struct ThreeAmounts: View {
+    var common: TransactionCommon
+    var topTitle: String
+    var bottomTitle: String?
+    let baseURL: String?
+    let large: Bool               // set to false for QR or IBAN
+    var body: some View {
+        let raw = common.amountRaw
+        let effective = common.amountEffective
+        let fee = common.fee()
+        let incoming = common.incoming()
+        let pending = (common.txState.major == TransactionMajorState.pending)
+
+        let defaultBottomTitle = incoming ? (pending ? "Pending coins to 
obtain:" : "Obtained coins:")
+                                                     : "Paid coins:"
+        ThreeAmountsView(topTitle: topTitle, topAmount: raw, fee: fee,
+                      bottomTitle: bottomTitle ?? defaultBottomTitle, 
bottomAmount: effective,
+                            large: large, pending: pending, incoming: incoming,
+                          baseURL: baseURL,
+                           status: common.txState.major.rawValue)
+    }
+}
+// MARK: -
+struct ThreeAmountsView: View {
+    var topTitle: String
+    var topAmount: Amount
+    var fee: Amount
+    var bottomTitle: String
+    var bottomAmount: Amount
+    let large: Bool
+    let pending: Bool
+    let incoming: Bool
+    let baseURL: String?
+    var status: String?
+
+    var body: some View {
+        let labelColor = Color(UIColor.label)
+        let foreColor = pending ? WalletColors().pendingColor(incoming)
+                                : WalletColors().transactionColor(incoming)
+        let feeColor = WalletColors().transactionColor(false)
+        let feeSign = incoming ? "-" : "+"
+
+        VStack {
+            AmountView(title: topTitle,
+                       value: topAmount.readableDescription,
+                       color: labelColor,
+                       large: large)
+                .padding(.bottom, 4)
+            AmountView(title: "Exchange fee:",
+                       value: feeSign + fee.readableDescription,
+                       color: fee.isZero ? labelColor : feeColor,
+                       large: false)
+                .padding(.bottom, 4)
+            AmountView(title: bottomTitle,
+                       value: bottomAmount.readableDescription,
+                       color: foreColor,
+                       large: large)
+            if let baseURL {
+                VStack {
+                    Text(incoming ? "from Exchange" : "to Exchange")
+                        .font(.title3)
+                    Text(baseURL.trimURL())
+                        .font(large ? .title : .title2)
+                        .fontWeight(large ? .medium : .regular)
+                        .foregroundColor(labelColor)
+                }
+                .padding(.top, 4)
+                .frame(maxWidth: .infinity, alignment: .center)
+                .listRowSeparator(.hidden)
+
+            }
+            if let status {
+                HStack {
+                    Spacer()
+                    Text("Status: \(status)")       // TODO: localize
+                        .font(.title2)
+                }.padding()
+            }
+        }
+    }
+}
+// MARK: -
+struct ThreeAmounts_Previews: PreviewProvider {
+    static var previews: some View {
+        let common = TransactionCommon(type: .withdrawal,
+                                    txState: TransactionState(major: .done),
+                            amountEffective: try! Amount(fromString: 
LONGCURRENCY + ":0.1"),
+                                  amountRaw: try! Amount(fromString: 
LONGCURRENCY + ":0.2"),
+                              transactionId: "someTxID",
+                                  timestamp: Timestamp(from: 
1_666_666_000_000),
+                                  txActions: [])
+        Group {
+            List {
+                ThreeAmounts(common: common, topTitle: "Withdrawal", baseURL: 
DEMOEXCHANGE, large: 1==1)
+                    .safeAreaInset(edge: .bottom) {
+                        ProminentButton(title: "Accept", action: {})
+                            .padding(.horizontal)
+                    }
+            }
+        }
+    }
+}
diff --git a/TalerWallet1/Views/Transactions/TransactionDetail.swift 
b/TalerWallet1/Views/Transactions/TransactionDetail.swift
deleted file mode 100644
index 6e74f0e..0000000
--- a/TalerWallet1/Views/Transactions/TransactionDetail.swift
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-import SwiftUI
-import taler_swift
-
-struct TransactionDetail: View {
-    var transaction : Transaction
-
-    var body: some View {
-        let common = transaction.common()
-        let dateString = TalerDater.dateString(from: common.timestamp)
-
-        VStack() {
-            Spacer()
-            Text("\(common.type)")      // TODO: translation
-                .font(.title)
-                .fontWeight(.medium)
-                .padding(.bottom)
-            Spacer()
-            Text("\(dateString)")
-                .font(.title2)
-                .padding(.vertical)
-            switch transaction {
-                case .withdrawal(let withdrawalTransaction):
-                    details(transaction: transaction)
-                    threeAmounts(common: common, topTitle: "Chosen amount to 
withdraw:", bottomTitle: "Obtained coins:", incoming: true)
-                case .payment(let paymentTransaction):
-                    let details = paymentTransaction.details
-                    let info = details.info
-                    Text("Status: \(details.status)")
-                        .font(.title2)
-                        .padding(.bottom)
-                    Text(info.summary)
-                        .font(.title)
-                        .lineLimit(4)
-                        .padding(.bottom)
-                    threeAmounts(common: common, topTitle: "Sum to be paid:", 
bottomTitle: "Paid coins:", incoming: false)
-                case .refund(let refundTransaction):
-                    threeAmounts(common: common, topTitle: "Refunded amount:", 
bottomTitle: "Obtained coins:", incoming: true)
-                case .tip(let tipTransaction):
-                    threeAmounts(common: common, topTitle: "Tip to be paid:", 
bottomTitle: "Paid coins:", incoming: false)
-                case .refresh(let refreshTransaction):
-                    threeAmounts(common: common, topTitle: "Refreshed 
amount:", bottomTitle: "Paid coins:", incoming: false)
-            }
-            Spacer()
-            Button(role: .destructive, action: {
-                // TODO: delete from wallet-core
-                print("Should delete \(common.transactionId)")
-            }, label: {
-                HStack {
-                    Text("Delete from list" + "        ")
-                    Image(systemName: "trash")
-                }
-                    .font(.title)
-                    .frame(maxWidth: .infinity)
-            })
-                .buttonStyle(.bordered)
-                .controlSize(.large)
-        }
-    }
-}
-
-extension TransactionDetail {
-    struct threeAmounts: View {
-        var common: TransactionCommon
-        var topTitle: String
-        var bottomTitle: String
-        var incoming: Bool
-
-        var body: some View {
-            let raw = common.amountRaw
-            let effective = common.amountEffective
-            let fee = common.fee()
-            let labelColor = Color(UIColor.label)
-            let outColor = Color("Outgoing")
-            let inColor = Color("Incoming")
-
-            AmountView(title: topTitle,
-                       value: raw.readableDescription,
-                       color: labelColor)
-                .padding(.bottom)
-            AmountView(title: "Exchange fee:",
-                       value: fee.readableDescription,
-                       color: fee.isZero ? labelColor : outColor)
-                .padding(.bottom)
-            AmountView(title: bottomTitle,
-                       value: effective.readableDescription,
-                       color: incoming ? inColor : outColor)
-                .padding(.bottom)
-        }
-    }
-    struct details: View {
-        var transaction : Transaction
-        var body: some View {
-            let details = transaction.detailsToShow()
-            let keys = details.keys
-            if keys.contains(EXCHANGEBASEURL) {
-                if let baseURL = details[EXCHANGEBASEURL] {
-                    Text("from \(baseURL.trimURL())")
-                        .font(.title2)
-                        .padding(.bottom)
-                }
-            }
-        }
-    }
-}
-
-#if DEBUG
-struct TransactionDetail_Previews: PreviewProvider {
-    static var withdrawal = Transaction(incoming: true,
-                                        id: "some withdrawal ID",
-                                        time: Timestamp(from: 
1_666_000_000_000))
-    static var payment = Transaction(incoming: false,
-                                     id: "some payment ID",
-                                     time: Timestamp(from: 1_666_666_000_000))
-    static var previews: some View {
-        Group {
-            TransactionDetail(transaction: withdrawal)
-            TransactionDetail(transaction: payment)
-        }
-    }
-}
-#endif
diff --git a/TalerWallet1/Views/Transactions/TransactionDetailView.swift 
b/TalerWallet1/Views/Transactions/TransactionDetailView.swift
new file mode 100644
index 0000000..7de0960
--- /dev/null
+++ b/TalerWallet1/Views/Transactions/TransactionDetailView.swift
@@ -0,0 +1,255 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+import SymLog
+
+struct TransactionDetailView: View {
+    private let symLog = SymLogV()
+    @AppStorage("listStyle") var myListStyle = MyListStyle.automatic
+
+    var transaction: Transaction
+    var deleteAction: ((_ transactionId: String) async throws -> Void)?
+    var abortAction: ((_ transactionId: String) async throws -> Void)?
+    var doneAction: (() -> Void)?
+
+    var body: some View {
+#if DEBUG
+        let _ = Self._printChanges()
+        let _ = symLog.vlog()       // just to get the # to compare it with 
.onAppear & onDisappear
+#endif
+        let common = transaction.common
+        let pending = transaction.isPending
+        let dateString = TalerDater.dateString(from: common.timestamp)
+        let navTitle = (pending ? String(localized: "Pending ") : "") + 
"\(common.type)"   // TODO: localize type
+
+        Group {
+            List {
+                Text("\(dateString)")
+                    .font(.title2)
+//                .listRowSeparator(.hidden)
+                SwitchCase(transaction: transaction, common: common)
+
+                if transaction.isAbortable { if let abortAction {
+                    AbortButton(common: common, abortAction: abortAction)
+                } }
+                if transaction.isDeleteable { if let deleteAction {
+                    DeleteButton(common: common, deleteAction: deleteAction)
+                } }
+
+                if let doneAction {
+                    DoneButton(doneAction: doneAction)
+                        .onNotification(.TransactionStateTransition) { 
notification in
+print(notification.userInfo)
+                            if let transition = 
notification.userInfo?[TRANSACTIONTRANSITION] as? TransactionTransition {
+print(transition.newTxState.major)
+                                if transition.transactionId == 
common.transactionId {
+                                    doneAction()
+                                }
+                            }
+                        }
+                }
+//                if transaction.isSuspendable { if let suspendAction {
+//                    SuspendButton(common: common, suspendAction: 
suspendAction)
+//                } }
+//                if transaction.isResumable { if let resumeAction {
+//                    ResumeButton(common: common, resumeAction: resumeAction)
+//                } }
+            }
+            .listStyle(myListStyle.style)
+            .anyView
+        }
+        .navigationTitle(navTitle)
+        .onAppear {
+            symLog.log("onAppear")
+            DebugViewC.shared.setViewID(VIEW_TRANSACTIONDETAIL)
+        }
+        .onDisappear {
+            symLog.log("onDisappear")
+        }
+    }
+//}
+//
+//extension TransactionDetail {
+    struct SwitchCase: View {
+        let transaction: Transaction
+        let common: TransactionCommon
+
+        var body: some View {
+            let pending = (common.txState.major == 
TransactionMajorState.pending)
+            switch transaction {
+                case .withdrawal(let withdrawalTransaction):
+                    let details = withdrawalTransaction.details
+                    if pending {
+                        switch details.withdrawalDetails.type {
+                            case .manual:
+                                ManualDetails(common: common, details: 
details.withdrawalDetails)
+
+                            case .bankIntegrated:
+                                QRCodeDetails(transaction: transaction)
+                        }
+                    }
+                    ThreeAmounts(common: common, topTitle: String(localized: 
"Chosen amount to withdraw:"),
+                                baseURL: 
withdrawalTransaction.details.exchangeBaseUrl, large: true)
+                case .payment(let paymentTransaction):
+                    let details = paymentTransaction.details
+                    let info = details.info
+                    Text(info.summary)
+                        .font(.title)
+                        .lineLimit(4)
+                        .padding(.bottom)
+                    ThreeAmounts(common: common, topTitle: String(localized: 
"Sum to be paid:"),
+                                 baseURL: nil, large: true)     // TODO: 
baseURL
+                case .refund(let refundTransaction):
+                    let details = refundTransaction.details                 // 
TODO: more details
+                    ThreeAmounts(common: common, topTitle: String(localized: 
"Refunded amount:"),
+                                 baseURL: nil, large: true)     // TODO: 
baseURL
+                case .reward(let rewardTransaction):
+                    let details = rewardTransaction.details                 // 
TODO: more details
+                    ThreeAmounts(common: common, topTitle: String(localized: 
"Received Reward:"),
+                                 baseURL: details.exchangeBaseUrl, large: true)
+//                case .tip(let tipTransaction):
+//                    let details = tipTransaction.details                  // 
TODO: details
+//                    ThreeAmounts(common: common, topTitle: String(localized: 
"Received Tip:"), large: true)
+                case .refresh(let refreshTransaction):
+                    let details = refreshTransaction.details                // 
TODO: details
+                    ThreeAmounts(common: common, topTitle: String(localized: 
"Refreshed amount:"),
+                                 baseURL: nil, large: true)     // TODO: 
baseURL
+                case .peer2peer(let p2pTransaction):
+                    let details = p2pTransaction.details                    // 
TODO: details
+                    QRCodeDetails(transaction: transaction)
+                    ThreeAmounts(common: common, topTitle: String(localized: 
"Peer to Peer:"),
+                                 baseURL: details.exchangeBaseUrl, large: 
false)
+            }
+        }
+    }
+    struct QRCodeDetails: View {
+        var transaction : Transaction
+        var body: some View {
+            let details = transaction.detailsToShow()
+            let keys = details.keys
+            if keys.contains(TALERURI) {
+                if let talerURI = details[TALERURI] {
+                    if talerURI.count > 10 {
+                        VStack {
+                            QRGeneratorView(text: talerURI)
+                            Text(talerURI)
+                        }
+                    }
+                }
+            } else if keys.contains(EXCHANGEBASEURL) {
+                if let baseURL = details[EXCHANGEBASEURL] {
+                    Text("from \(baseURL.trimURL())")
+                        .font(.title2)
+                        .padding(.bottom)
+                }
+            }
+        }
+    }
+    struct DeleteButton: View {
+        var common : TransactionCommon
+        var deleteAction: (_ transactionId: String) async throws -> Void
+
+        @State var disabled: Bool = false
+        @State var deleted: Bool = false
+        var body: some View {
+            Button(role: .destructive, action: {
+                Task {                  // delete from wallet-core
+                    disabled = true     // don't try this more than once
+                    do {
+                        try await deleteAction(common.transactionId)
+//                        symLog.log("deleted \(common.transactionId)")
+                        deleted = true
+                    } catch {    // TODO: error
+//                        symLog.log(error.localizedDescription)
+                    }
+                }
+            }, label: {
+                HStack {
+                    if deleted {
+                        Text("Deleted from list")
+                    } else {
+                        Text("Delete from list" + "        ")
+                        Image(systemName: "trash")
+                    }
+                }
+                .font(.title)
+                .frame(maxWidth: .infinity)
+            })
+            .buttonStyle(.bordered)
+            .controlSize(.large)
+            .padding(.horizontal)
+            .disabled(disabled)
+        }
+    }
+    struct AbortButton: View {
+        var common : TransactionCommon
+        var abortAction: (_ transactionId: String) async throws -> Void
+
+        @State var disabled: Bool = false
+        @State var aborting: Bool = false
+        var body: some View {
+            Button(role: .cancel, action: {
+                Task {                  // delete from wallet-core
+                    disabled = true     // don't try this more than once
+                    do {
+                        try await abortAction(common.transactionId)
+//                        symLog.log("aborted \(common.transactionId)")
+                        aborting = true
+                    } catch {    // TODO: error
+//                        symLog.log(error.localizedDescription)
+                    }
+                }
+            }, label: {
+                HStack {
+                    if aborting {
+                        Text("Abort pending...")
+                    } else {
+                        Text("Abort" + "        ")
+                        Image(systemName: "x.circle")
+                    }
+                }
+                .font(.title)
+                .frame(maxWidth: .infinity)
+            })
+            .buttonStyle(.bordered)
+            .controlSize(.large)
+            .padding(.horizontal)
+            .disabled(disabled)
+        }
+    }
+    struct DoneButton: View {
+        var doneAction: () -> Void
+
+        var body: some View {
+            Button("Done") {
+                doneAction()
+            }
+            .buttonStyle(TalerButtonStyle(type: .prominent))
+            .padding(.horizontal)
+        }
+    }
+}
+// MARK: -
+#if DEBUG
+struct TransactionDetail_Previews: PreviewProvider {
+    static func deleteTransactionDummy(transactionId: String) async throws {}
+    static func doneActionDummy() {}
+    static var withdrawal = Transaction(incoming: true,
+                                         pending: true,
+                                              id: "some withdrawal ID",
+                                            time: Timestamp(from: 
1_666_000_000_000))
+    static var payment = Transaction(incoming: false,
+                                      pending: false,
+                                           id: "some payment ID",
+                                         time: Timestamp(from: 
1_666_666_000_000))
+    static var previews: some View {
+        Group {
+            TransactionDetailView(transaction: withdrawal, doneAction: 
doneActionDummy)
+            TransactionDetailView(transaction: payment, deleteAction: 
deleteTransactionDummy)
+        }
+    }
+}
+#endif
diff --git a/TalerWallet1/Views/Transactions/TransactionRow.swift 
b/TalerWallet1/Views/Transactions/TransactionRow.swift
deleted file mode 100644
index 899aaf4..0000000
--- a/TalerWallet1/Views/Transactions/TransactionRow.swift
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-import SwiftUI
-import taler_swift
-
-struct TransactionRowCenter: View {
-    var centerTop: String
-    var centerBottom: String
-
-    var body: some View {
-        VStack(alignment: .leading) {
-            Text("\(centerTop)")
-                .font(.headline)
-                .fontWeight(.medium)
-                .padding(.bottom, -2.0)
-            Text("\(centerBottom)")
-                .font(.callout)
-        }
-    }
-}
-
-struct TransactionRow: View {
-    var transaction : Transaction
-
-    var body: some View {
-        let common = transaction.common()
-        let details = transaction.detailsToShow()
-        let keys = details.keys
-        let amount = common.amountEffective
-        let withdraw: Bool = common.type == WITHDRAWAL
-        let payment: Bool = common.type == PAYMENT
-        let refund: Bool = common.type == REFUND
-        let incoming = withdraw || refund
-//        let counterparty = transaction.exchangeBaseUrl
-        let dateString = TalerDater.dateString(from: common.timestamp, 
relative: true)
-
-        HStack {
-            Image(systemName: incoming ? "text.badge.plus" : 
"text.badge.minus")
-                .foregroundColor(incoming ? Color("Incoming")  : 
Color("Outgoing"))
-                .padding(.trailing)
-                .font(.largeTitle)
-
-            if keys.contains(EXCHANGEBASEURL) {
-                if let baseURL = details[EXCHANGEBASEURL] {
-                    TransactionRowCenter(centerTop: baseURL.trimURL(), 
centerBottom: dateString)
-                }
-            } else if payment {
-                TransactionRowCenter(centerTop: "Payment", centerBottom: 
dateString)
-            } else if refund {
-                TransactionRowCenter(centerTop: "Refund", centerBottom: 
dateString)
-            }
-            Spacer()
-            VStack(alignment: .trailing) {
-                let sign = incoming ? "+" : "-"
-                Text(sign + "\(amount.valueStr)")
-                    .font(.title)
-                    .foregroundColor(incoming ? Color("Incoming")  : 
Color("Outgoing"))
-            }
-        }
-        .padding(.top)
-    }
-}
-
-#if DEBUG
-struct TransactionRow_Previews: PreviewProvider {
-    static var withdrawal = Transaction(incoming: true,
-                                              id: "some withdrawal ID",
-                                            time: Timestamp(from: 
1_666_000_000_000))
-    static var payment = Transaction(incoming: false,
-                                        id: "some payment ID",
-                                        time: Timestamp(from: 
1_666_666_000_000))
-    static var previews: some View {
-        VStack {
-            TransactionRow(transaction: withdrawal)
-            TransactionRow(transaction: payment)
-        }
-    }
-}
-#endif
diff --git a/TalerWallet1/Views/Transactions/TransactionRowView.swift 
b/TalerWallet1/Views/Transactions/TransactionRowView.swift
new file mode 100644
index 0000000..98349c8
--- /dev/null
+++ b/TalerWallet1/Views/Transactions/TransactionRowView.swift
@@ -0,0 +1,137 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+
+struct TransactionRowCenter: View {
+    var centerTop: String
+    var centerBottom: String
+
+    var body: some View {
+        VStack(alignment: .leading) {
+            Text("\(centerTop)")
+                .font(.headline)
+                .fontWeight(.medium)
+                .padding(.bottom, -2.0)
+            Text("\(centerBottom)")
+                .font(.callout)
+        }
+    }
+}
+
+struct TransactionRowView: View {
+    var transaction : Transaction
+
+    var body: some View {
+        let common = transaction.common
+        let amount = common.amountEffective
+        let pending = transaction.isPending
+        let done = transaction.isDone
+        let details = transaction.detailsToShow()
+        let keys = details.keys
+
+        let dateString = TalerDater.dateString(from: common.timestamp, 
relative: true)
+        let incoming = common.incoming()
+        let foreColor = pending ? WalletColors().pendingColor(incoming)
+                      : done ? WalletColors().transactionColor(incoming)
+                             : WalletColors().uncompletedColor
+
+        HStack {
+            Image(systemName: incoming ? "text.badge.plus" : 
"text.badge.minus")
+                .foregroundColor(foreColor)
+                .padding(.trailing)
+                .font(.largeTitle)
+                .accessibility(hidden: true)
+
+            if transaction.isWithdrawal {
+                TransactionRowCenter(centerTop: String(localized: 
"Withdrawal"), centerBottom: dateString)
+            } else if transaction.isPayment {
+                TransactionRowCenter(centerTop: String(localized: "Payment"), 
centerBottom: dateString)
+            } else if transaction.isP2pOutgoing {
+                TransactionRowCenter(centerTop: String(localized: "P2P Send"), 
centerBottom: dateString)
+            } else if transaction.isP2pIncoming {
+                TransactionRowCenter(centerTop: String(localized: "P2P 
Receive"), centerBottom: dateString)
+            } else if transaction.isRefund {
+                TransactionRowCenter(centerTop: String(localized: "Refund"), 
centerBottom: dateString)
+            } else if transaction.isRefresh {
+                TransactionRowCenter(centerTop: String(localized: "Refresh"), 
centerBottom: dateString)
+            } else if keys.contains(EXCHANGEBASEURL) {
+                if let baseURL = details[EXCHANGEBASEURL] {
+                    TransactionRowCenter(centerTop: baseURL.trimURL(), 
centerBottom: dateString)
+                }
+            }
+            Spacer()
+            VStack(alignment: .trailing) {
+                let sign = incoming ? "+" : "-"
+                Text(sign + "\(amount.valueStr)")
+                    .font(.title)
+                    .foregroundColor(foreColor)
+            }
+        }
+        .accessibilityElement(children: .combine)
+        .padding(.top)
+    }
+}
+// MARK: -
+#if DEBUG
+struct TransactionRow_Previews: PreviewProvider {
+    static var withdrawal = Transaction(incoming: true,
+                                         pending: false,
+                                              id: "some withdrawal ID",
+                                            time: Timestamp(from: 
1_666_000_000_000))
+    static var payment = Transaction(incoming: false,
+                                      pending: false,
+                                           id: "some payment ID",
+                                         time: Timestamp(from: 
1_666_666_000_000))
+    static var previews: some View {
+        List {
+            TransactionRowView(transaction: withdrawal)
+            TransactionRowView(transaction: payment)
+        }
+    }
+}
+// MARK: -
+extension Transaction {             // for PreViews
+    init(incoming: Bool, pending: Bool, id: String, time: Timestamp) {
+        let currency = LONGCURRENCY
+        let raw = currency + ":5"
+        let effective = currency + (incoming ? ":4.8"
+                                             : ":5.2")
+        let refRaw = currency + ":3"
+        let refEff = currency + ":2.8"
+        let common = TransactionCommon(type: incoming ? .withdrawal : .payment,
+                                    txState: TransactionState(major: pending ? 
TransactionMajorState.pending
+                                                                             : 
TransactionMajorState.done),
+                            amountEffective: try! Amount(fromString: 
effective),
+                                  amountRaw: try! Amount(fromString: raw),
+                              transactionId: id,
+                                  timestamp: time,
+                                  txActions: [.abort])
+        if incoming {
+            // if pending then manual else bank-integrated
+            let payto = 
"payto://iban/SANDBOXX/DE159593?receiver-name=Exchange+Company&amount=KUDOS%3A9.99&message=Taler+Withdrawal+J41FQPJGAP1BED1SFSXHC989EN8HRDYAHK688MQ228H6SKBMV0AG"
+            let withdrawalDetails = WithdrawalDetails(type: pending ? 
WithdrawalDetails.WithdrawalType.manual
+                                                                    : 
WithdrawalDetails.WithdrawalType.bankIntegrated,
+                                                reservePub: 
"PuBlIc_KeY_oF_tHe_ReSeRvE",
+                                            reserveIsReady: false,
+                                         exchangePaytoUris: pending ? [payto] 
: nil)
+            let wDetails = WithdrawalTransactionDetails(exchangeBaseUrl: 
DEMOEXCHANGE,
+                                                      withdrawalDetails: 
withdrawalDetails)
+            self = .withdrawal(WithdrawalTransaction(common: common, details: 
wDetails))
+        } else {
+            let merchant = Merchant(name: "some random shop")
+            let info = OrderShortInfo(orderId: "some order ID",
+                                     merchant: merchant,
+                                      summary: "some product summary",
+                                     products: [])
+            let pDetails = PaymentTransactionDetails(proposalId: "some 
proposal ID",
+                                                 totalRefundRaw: try! 
Amount(fromString: refRaw),
+                                           totalRefundEffective: try! 
Amount(fromString: refEff),
+                                                           info: info)
+            self = .payment(PaymentTransaction(common: common, details: 
pDetails))
+        }
+    }
+}
+#endif
diff --git a/TalerWallet1/Views/Transactions/TransactionsEmptyView.swift 
b/TalerWallet1/Views/Transactions/TransactionsEmptyView.swift
new file mode 100644
index 0000000..1721663
--- /dev/null
+++ b/TalerWallet1/Views/Transactions/TransactionsEmptyView.swift
@@ -0,0 +1,37 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import SymLog
+
+/// This view shows hints if a wallet is empty
+/// It is the very first thing the user sees after installing the app
+
+struct TransactionsEmptyView: View {
+    private let symLog = SymLogV()
+    @AppStorage("listStyle") var myListStyle = MyListStyle.automatic
+
+    let currency: String
+
+    var body: some View {
+        List {
+            Section {
+                Text("There are no transactions for \(currency).")
+            }
+        }
+        .padding(.vertical)
+        .font(.title2)
+        .listStyle(myListStyle.style)
+        .anyView
+        .onAppear() {
+            DebugViewC.shared.setViewID(VIEW_EMPTY)     // 10
+        }
+    }
+}
+
+struct TransactionsEmptyView_Previews: PreviewProvider {
+    static var previews: some View {
+        TransactionsEmptyView(currency: LONGCURRENCY)
+    }
+}
diff --git a/TalerWallet1/Views/Transactions/TransactionsListView.swift 
b/TalerWallet1/Views/Transactions/TransactionsListView.swift
index 284a6c3..e2e5ad1 100644
--- a/TalerWallet1/Views/Transactions/TransactionsListView.swift
+++ b/TalerWallet1/Views/Transactions/TransactionsListView.swift
@@ -1,105 +1,112 @@
 /*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
  */
 import SwiftUI
 import SymLog
 
 struct TransactionsListView: View {
     private let symLog = SymLogV()
-    let navTitle = "Transactions"
+    @AppStorage("listStyle") var myListStyle = MyListStyle.automatic
+    let navTitle: String
+
+    let currency: String
+    let transactions: [Transaction]
+    var reloadAction: () async -> ()
+    let deleteAction: (_ transactionId: String) async throws -> ()
+    let abortAction: (_ transactionId: String) async throws -> ()
 
-    @ObservedObject var viewModel: TransactionsModel
-    
     var body: some View {
-        let reloadAction = viewModel.fetchTransactions
-        VStack {
-            if viewModel.transactions == nil {
-                symLog { LoadingView(backButtonHidden: false) }
-            } else {
-                let count = viewModel.transactions!.count
-                let title: String = "\(count) \(navTitle)"
-                symLog { Content(symLog: symLog, viewModel: viewModel, 
reloadAction: reloadAction)
-                        .navigationTitle(title)
-                }
+#if DEBUG
+        let _ = Self._printChanges()
+        let _ = symLog.vlog()       // just to get the # to compare it with 
.onAppear & onDisappear
+#endif
+        let count = transactions.count
+//        let title = AttributedString(localized: "^[\(count) Ticket](inflect: 
true)")
+        let title: String = "\(count) \(navTitle)"
+        Content(symLog: symLog, currency: currency, transactions: 
transactions, myListStyle: $myListStyle,
+                reloadAction: reloadAction, deleteAction: deleteAction, 
abortAction: abortAction)
+            .navigationTitle(title)
+            .onAppear {
+                DebugViewC.shared.setViewID(VIEW_TRANSACTIONLIST)
             }
-        }.task {
-            symLog.log(".task")
-            do {
-                try await reloadAction()
-            } catch {
-                // TODO: show error
-                symLog.log(error.localizedDescription)
+            .task {
+                symLog.log(".task ")
+                await reloadAction()
             }
-        }
     }
 }
 // MARK: -
 extension TransactionsListView {
     struct Content: View {
         let symLog: SymLogV?
-        @ObservedObject var viewModel: TransactionsModel
-
-        var reloadAction: () async throws -> ()
+        let currency: String
+        let transactions: [Transaction]
+        @Binding var myListStyle: MyListStyle
+        let reloadAction: () async -> ()
+        let deleteAction: (_ transactionId: String) async throws -> ()
+        let abortAction: (_ transactionId: String) async throws -> ()
 
         @State private var upAction: () -> Void = {}
         @State private var downAction: () -> Void = {}
 
+//        func removeItems(at offsets: IndexSet) {
+//            let transactions = model.transactions
+//            var idsToDelete: [String] = []
+//            for n in offsets {  // save IDs of transactions
+//                let common = transactions[n].common
+//                idsToDelete.append(common.transactionId)
+//            }
+//            // then remove items from the list model (and the view)
+//            model.transactions.remove(atOffsets: offsets)
+//            // finally tell wallet-core to delete the saved IDs
+//            Task {  // delete this transaction from wallet-core
+//                for transactionId in idsToDelete {
+//                    do {
+//                        try await deleteAction(transactionId)
+//                        symLog?.log("deleted \(transactionId)")
+//                    } catch {    // TODO: error
+//                        symLog?.log(error.localizedDescription)
+//                    }
+//                }
+//            }
+//        }
+
         var body: some View {
-            let transactions = viewModel.transactions!
+#if DEBUG
+            let _ = Self._printChanges()
+            let _ = symLog?.vlog()       // just to get the # to compare it 
with .onAppear & onDisappear
+#endif
 
             ScrollViewReader { scrollView in
                 List {
-                    ForEach (transactions.indices) { index in
-                        let transaction = transactions[index]
-                        let common = transaction.common()
-                        NavigationLink {
-                            TransactionDetail(transaction: transaction)
-                        } label: {
-                            TransactionRow(transaction: transaction)
+                    ForEach(Array(zip(transactions.indices, transactions)), 
id: \.1) { index, transaction in
+//                        let common = transaction.common
+                        NavigationLink { LazyView {        // whole row like 
in a tableView
+                            // pending may not be deleted, but only aborted
+                            TransactionDetailView(transaction: transaction,
+                                                 deleteAction: deleteAction,
+                                                  abortAction: abortAction)
+                        }} label: {
+                            TransactionRowView(transaction: transaction)
                         }
-                        .id(index)
-                        .swipeActions(edge: .leading, allowsFullSwipe: true) {
-                            Button {
-                                symLog?.log("bookmarked 
\(common.transactionId)")
-                                // TODO: Bookmark
-                            } label: {
-                                Label("Bookmark", systemImage: "bookmark")
-                            }.tint(.indigo)
-                        }
-                        .swipeActions(edge: .trailing, allowsFullSwipe: true) {
-                            Button(role: .destructive) {
-                                symLog?.log("deleted \(common.transactionId)")
-                                // TODO: delete from Model. SwiftUI deletes 
this row from view already :-)
-                            } label: {
-                                Label("Delete", systemImage: "trash")
-                            }
-                        }
-                    }
-                    .onAppear {
-                        upAction = { withAnimation { scrollView.scrollTo(0) }}
-                        downAction = { withAnimation { 
scrollView.scrollTo(transactions.count - 1) }}
-                        downAction()
                     }
+//                    .onDelete(perform: removeItems)     // delete this row 
from the list
                 }
                 .refreshable {
-                    do {
-                        symLog?.log("refreshing")
-                        try await reloadAction()
-                    } catch {
-                        // TODO: error
-                        symLog?.log(error.localizedDescription)
+                    await reloadAction()
+                }
+                .listStyle(myListStyle.style)
+                .anyView
+                .onAppear {
+                    upAction = { withAnimation { scrollView.scrollTo(0) }}
+                    downAction = { withAnimation { 
scrollView.scrollTo(transactions.count - 1) }}
+                    downAction()
+                }
+                .overlay {
+                    if transactions.isEmpty {
+                        TransactionsEmptyView(currency: currency)
+                            
.background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
                     }
                 }
             }
diff --git a/TalerWallet1/Views/Transactions/TransactionsModel.swift 
b/TalerWallet1/Views/Transactions/TransactionsModel.swift
deleted file mode 100644
index 253a557..0000000
--- a/TalerWallet1/Views/Transactions/TransactionsModel.swift
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-import Foundation
-import taler_swift
-import SymLog
-fileprivate let ASYNCDELAY: UInt = 0   //set e.g to 6 or 9 seconds for 
debugging
-
-// MARK: -
-class TransactionsModel: WalletModel {
-    @Published var transactions: [Transaction]?             // update view
-}
-
-// MARK: -
-/// A request to get the transactions in the wallet's history.
-fileprivate struct GetTransactions: WalletBackendFormattedRequest {
-    func operation() -> String { return "getTransactions" }
-    func args() -> Args { return Args(currency: currency, search: search) }
-
-    var currency: String?
-    var search: String?
-
-    struct Args: Encodable {
-        var currency: String?
-        var search: String?
-    }
-
-    struct Response: Decodable {                    // list of transactions
-        var transactions: [Transaction]
-    }
-}
-// MARK: -
-extension TransactionsModel {
-    /// ask wallet-core for its list of transactions filtered by searchString
-    func fetchTransactions() async throws {             // might be called 
from a background thread itself
-        try await fetchTransactions(currency: nil, searchString: nil)
-    }
-    /// fetch Balances from Wallet-Core. No networking involved
-    @MainActor func fetchTransactions(currency: String? = nil, searchString: 
String? = nil)
-      async throws {
-        do {
-            let request = GetTransactions(currency: nil, search: nil)
-            let response = try await sendRequest(request, ASYNCDELAY)
-            transactions = response.transactions        // trigger view update 
in TransactionsListView
-        } catch {
-            throw error
-        }
-    }
-}
diff --git a/TalerWallet1/Views/URLSheet.swift 
b/TalerWallet1/Views/URLSheet.swift
deleted file mode 100644
index 1aaab6b..0000000
--- a/TalerWallet1/Views/URLSheet.swift
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-import SwiftUI
-import SymLog
-
-struct URLSheet: View {
-    private let symLog = SymLogV()
-    var urlToOpen: URL
-    @Environment(\.dismiss) var dismiss     // call dismiss() to get rid of 
the sheet
-    @EnvironmentObject private var controller: Controller
-
-    @State private var urlCommand: UrlCommand? = nil
-
-    var cancelButton: some View {
-        Button("Cancel0") {
-            print(dismiss)
-            dismissTop()
-        }
-    }
-
-    var body: some View {
-        symLog {
-            NavigationView {
-                if urlCommand == UrlCommand.withdraw {
-                    WithdrawURIView(url: urlToOpen, viewModel: 
controller.withdrawURIModel)
-                } else if urlCommand == UrlCommand.pay {
-                    PaymentURIView(url: urlToOpen, viewModel: 
controller.paymentURIModel)
-                } else {
-                    VStack {        // show Error view with cancelButton
-                        Spacer()
-                        Text(controller.messageForSheet ?? 
urlToOpen.absoluteString)
-                            .font(.title)
-                        Spacer()
-                        Spacer()
-                    }
-                        .navigationBarItems(leading: cancelButton)
-                        .navigationTitle("Invalid URL")
-                }
-            }.task {
-                urlCommand = controller.openURL(urlToOpen)
-            }
-        }
-    }
-}
-// MARK: -
-//struct PaySheet_Previews: PreviewProvider {
-//    static var previews: some View {
-            // needs BackendManager
-//        URLSheet(urlToOpen: URL(string: "ftp://this.URL.is.invalid";)!)
-//    }
-//}
diff --git a/TalerWallet1/Views/Withdraw/WithdrawAcceptView.swift 
b/TalerWallet1/Views/Withdraw/WithdrawAcceptView.swift
deleted file mode 100644
index 4f9578c..0000000
--- a/TalerWallet1/Views/Withdraw/WithdrawAcceptView.swift
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-import SwiftUI
-import taler_swift
-import SymLog
-
-struct WithdrawAcceptView: View {
-    private let symLog = SymLogV()
-    var url: URL
-    @ObservedObject var model: WithdrawURIModel
-
-//    @Environment(\.dismiss) var dismiss     // call dismiss() to get rid of 
the sheet
-    let navTitle = "Accept Withdrawal"
-    var cancelButton: some View {
-        Button("Cancel4") { dismissTop() }
-    }
-
-    let detailsForAmount: WithdrawalDetailsForAmount
-    let baseURL: String
-
-    var body: some View {
-        symLog { Group {
-            switch model.withdrawState {
-                case .receivedAmountDetails, .receivedTOS, .receivedTOSAck:
-                    let raw = detailsForAmount.amountRaw
-                    let effective = detailsForAmount.amountEffective
-                    let fee = try! Amount.diff(raw, effective)      // TODO: 
different currencies
-                    Form {
-                        AmountView(title: "Chosen amount to withdraw:",
-                                   value: raw.readableDescription, color: 
Color(UIColor.label))
-                            .padding(.bottom)
-                        AmountView(title: "Exchange fee:",
-                                   value: "- " + fee.readableDescription, 
color: Color("Outgoing"))
-                            .padding(.bottom)
-                        AmountView(title: "Coins to be withdrawn:",
-                                   value: effective.readableDescription, 
color: Color("Incoming"))
-                    }
-                    AwesomeButton(title: "Accept") {
-                        Task {
-                            do {
-                                let bankConfirmationUrl = try await 
model.sendAcceptIntWithdrawal(baseURL, withdrawURL: url.absoluteString)
-                                symLog.log(bankConfirmationUrl as Any)
-                                // TODO: Show Hints that User should Confirm 
on bank website
-                            } catch {
-                                symLog.log(error.localizedDescription)
-                            }
-                            dismissTop()
-                        }
-                    }
-                default:
-                    ErrorView()
-            }
-        }
-            .navigationBarItems(leading: cancelButton)
-            .navigationTitle(navTitle)
-        }
-    }
-}
diff --git a/TalerWallet1/Views/Withdraw/WithdrawProgressView.swift 
b/TalerWallet1/Views/Withdraw/WithdrawProgressView.swift
deleted file mode 100644
index 2c08b75..0000000
--- a/TalerWallet1/Views/Withdraw/WithdrawProgressView.swift
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-import SwiftUI
-
-struct WithdrawProgressView: View {
-    let message: String
-    let action: () -> Void
-
-    var cancelButton: some View {
-        Button("Cancel2") {
-            action()
-        }           // dismiss the sheet
-    }
-
-    var body: some View {                       // show Message with 
cancelButton
-        VStack {
-            Spacer()
-            ProgressView()
-            Spacer()
-            Text(message)
-                .font(.title)
-            Spacer()
-            Spacer()
-        }.navigationBarItems(leading: cancelButton)
-    }
-}
-
-struct WithdrawProgressView_Previews: PreviewProvider {
-    static var previews: some View {
-        WithdrawProgressView(message: "message") {}
-    }
-}
diff --git a/TalerWallet1/Views/Withdraw/WithdrawTOSView.swift 
b/TalerWallet1/Views/Withdraw/WithdrawTOSView.swift
deleted file mode 100644
index b1c3cf6..0000000
--- a/TalerWallet1/Views/Withdraw/WithdrawTOSView.swift
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-import SwiftUI
-import SymLog
-
-struct WithdrawTOSView: View {
-    private let symLog = SymLogV()
-    var url: URL
-    @ObservedObject var model: WithdrawURIModel
-
-//    @Environment(\.dismiss) var dismiss     // call dismiss() to get rid of 
the sheet
-    let navTitle = "Terms of Service"
-    var cancelButton: some View {
-        Button("Cancel3") { dismissTop() }
-    }
-
-    var detailsForUri: WithdrawalDetailsForUri
-    @State var exchangeTOS: ExchangeTermsOfService?
-    @Binding var didAcceptTOS: Bool
-
-    var body: some View {
-        let badURL = "Error in URL: \(url)"
-        let baseURL = detailsForUri.defaultExchangeBaseUrl
-        VStack {
-            switch model.withdrawState {
-                case .waitingForTOS:
-                    WithdrawProgressView(message: baseURL ?? badURL) {
-                        dismissTop()
-                    }.navigationTitle("Loading " + navTitle)
-                case .receivedTOS:
-                    Content(symLog: symLog, exchangeTOS: exchangeTOS) {
-                        Task {
-                            do {
-                                _ = try await 
model.setExchangeTOSAccepted(baseURL!, etag: exchangeTOS!.currentEtag)
-                                didAcceptTOS = true
-                            } catch {
-                                // TODO: Show Error
-                                symLog.log(error.localizedDescription)
-                            }
-                        }
-                    }
-                        .navigationBarTitleDisplayMode(.large)      // .inline
-                        .navigationBarItems(leading: cancelButton)
-                        .navigationTitle(navTitle)
-                default:
-                    ErrorView()
-            }
-        }.task {
-            do {
-                let someTOS = try await 
model.loadExchangeTermsOfService(baseURL!)
-                exchangeTOS = someTOS
-            } catch {
-                // TODO: error
-                symLog.log(error.localizedDescription)
-            }
-        }
-    }
-}
-// MARK: -
-extension WithdrawTOSView {
-    struct Content: View {
-        let symLog: SymLogV
-        var exchangeTOS: ExchangeTermsOfService?
-        var acceptAction: () -> ()
-
-        var body: some View {
-            Group {
-                if let tos = exchangeTOS {
-                    let components = tos.content.components(separatedBy:"\n\n")
-
-                    List (components, id: \.self) { term in
-                        Text(term)
-                    }
-                    AwesomeButton(title: "Accept") {
-                        acceptAction()
-                    }.padding(.vertical)
-                } else {
-                    ErrorView()     // TODO: ???
-                }
-            }
-        }
-    }
-}
diff --git a/TalerWallet1/Views/Withdraw/WithdrawURIView.swift 
b/TalerWallet1/Views/Withdraw/WithdrawURIView.swift
deleted file mode 100644
index e928e44..0000000
--- a/TalerWallet1/Views/Withdraw/WithdrawURIView.swift
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-import SwiftUI
-import SymLog
-
-struct WithdrawURIView: View {
-    private let symLog = SymLogV()
-    var url: URL
-    @ObservedObject var viewModel: WithdrawURIModel
-
-//    @Environment(\.dismiss) var dismiss     // call dismiss() to get rid of 
the sheet
-    let navTitle = "Withdraw"
-    var cancelButton: some View {
-        Button("Cancel1") { dismissTop() }
-    }
-
-    @State var detailsForUri: WithdrawalDetailsForUri?
-    @State var detailsForAmount: WithdrawalDetailsForAmount?
-    @State var didAcceptTOS: Bool = false
-
-    var body: some View {
-        let badURL = "Error in URL: \(url)"
-        VStack {
-            if viewModel.withdrawState == nil {
-                LoadingView(backButtonHidden: false)
-            } else { switch viewModel.withdrawState {
-                case .waitingForUriDetails:
-                    let _ = symLog.vlog("waitingForUriDetails")
-                    WithdrawProgressView(message: url.host ?? badURL) { 
dismissTop() }
-                        .navigationTitle("Contacting Exchange")
-                case .waitingForAmountDetails:
-                    let _ = symLog.vlog("waitingForAmountDetails")
-                    WithdrawProgressView(message: 
detailsForUri!.defaultExchangeBaseUrl ?? badURL) { dismissTop() }
-                        .navigationTitle("Found Exchange")
-                case .receivedAmountDetails, .waitingForTOS, .receivedTOS, 
.receivedTOSAck:
-                    let _ = symLog.vlog("waitingForTOS")
-                    if !didAcceptTOS {
-                        WithdrawTOSView(url: url, model: viewModel, 
detailsForUri: detailsForUri!, didAcceptTOS: $didAcceptTOS)
-                    } else {
-                        // show Amount details and let user accept
-                        WithdrawAcceptView(url: url, model: viewModel, 
detailsForAmount: detailsForAmount!,
-                                           baseURL:  
detailsForUri!.defaultExchangeBaseUrl!)
-                    }
-                default:
-                    symLog {
-                        Content(symLog: symLog, viewModel: viewModel)
-                            .navigationBarItems(leading: cancelButton)
-                            .navigationTitle(navTitle)
-                    }
-            } }
-        }.task {
-            do { // TODO: cancelled
-                symLog.log(".task")
-                detailsForUri = try await 
viewModel.loadWithdrawalDetailsForURI(url.absoluteString)
-                let baseURL = detailsForUri!.defaultExchangeBaseUrl
-                symLog.log("amount: \(detailsForUri!.amount), baseURL: 
\(String(describing: baseURL))")
-                // TODO: let user choose exchange from array
-                detailsForAmount = try await 
viewModel.loadWithdrawalDetailsForAmount(detailsForUri!)
-                symLog.log("raw: \(detailsForAmount!.amountRaw), effective: 
\(detailsForAmount!.amountEffective)")
-                if detailsForAmount!.tosAccepted {
-                    didAcceptTOS = true
-                }
-            } catch {
-                // TODO: error
-            }
-        }
-    }
-}
-// MARK: -
-extension WithdrawURIView {
-    struct Content: View {
-        let symLog: SymLogV?
-        @ObservedObject var viewModel: WithdrawURIModel
-//        @EnvironmentObject var controller : Controller
-
-        var body: some View {
-            Group {
-                Text("Hello")
-//                List(model.pendingOperations!, id: \.self) { pendingOp in
-//                    PendingOpView(pendingOp: pendingOp)
-//                }
-//                .navigationBarTitleDisplayMode(.large)      // .inline
-//                .refreshable {
-//                    symLog?.log("refreshing")
-//                    try? await reloadAction()       // TODO: catch error
-//                }
-            }
-        }
-    }
-}
diff --git a/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawAcceptView.swift 
b/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawAcceptView.swift
new file mode 100644
index 0000000..d0a1269
--- /dev/null
+++ b/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawAcceptView.swift
@@ -0,0 +1,71 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+import SymLog
+
+struct WithdrawAcceptView: View {
+    private let symLog = SymLogV()
+    let url: URL
+    @ObservedObject var model: WithdrawModel
+
+    let navTitle = String(localized: "Accept Withdrawal")
+    let detailsForAmount: ManualWithdrawalDetails
+    let baseURL: String
+
+    func acceptAction() -> () {
+        Task {
+            do {
+                let confirmTransferUrl = try await 
model.sendAcceptIntWithdrawalM(baseURL, withdrawURL: url.absoluteString)
+                symLog.log(confirmTransferUrl as Any)
+                // TODO: Show Hints that User should Confirm on bank website
+                // update balances to show pending withdrawal
+                await BalancesModel.model(currency: "*").fetchBalancesM()
+            } catch {    // TODO: error
+                symLog.log(error.localizedDescription)
+            }
+        }
+    }
+
+    var body: some View {
+        Group {
+            let currState = model.withdrawState
+            switch currState {
+                case .receivedAmountDetails, .receivedTOS, .receivedTOSAck:
+                    let raw = detailsForAmount.amountRaw
+                    let effective = detailsForAmount.amountEffective
+                    let fee = try! Amount.diff(raw, effective)      // TODO: 
different currencies
+                    let outColor = WalletColors().transactionColor(false)
+                    let inColor = WalletColors().transactionColor(true)
+
+                    ThreeAmountsView(topTitle: String(localized: "Chosen 
amount to withdraw:"),
+                                     topAmount: raw, fee: fee,
+                                     bottomTitle: String(localized: "Coins to 
be withdrawn:"),
+                                     bottomAmount: effective,
+                                     large: false, pending: false, incoming: 
true,
+                                     baseURL: baseURL)
+                    .safeAreaInset(edge: .bottom) {
+                        ProminentButton(title: String(localized: "Accept"), 
action: acceptAction)
+                            .padding(.horizontal)
+                    }
+                case .waitingForWithdrAck, .receivedWithdrAck:
+                    // TODO: SHEET_WITHDRAW_CONFIRM
+                    Text("waiting for bank confirmation")
+                        .navigationTitle("Confirm with Bank")
+                        .onAppear() {
+                            
DebugViewC.shared.setSheetID(SHEET_WITHDRAW_CONFIRM)
+                        }
+
+                default:
+                    let _ = symLog.vlog(currState as Any)
+                    ErrorView(errortext: "unknown state")       // TODO: Error
+            }
+        }
+        .navigationTitle(navTitle)
+        .onAppear() {
+            DebugViewC.shared.setSheetID(SHEET_WITHDRAW_ACCEPT)
+        }
+    }
+}
diff --git 
a/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawProgressView.swift 
b/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawProgressView.swift
new file mode 100644
index 0000000..c872021
--- /dev/null
+++ b/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawProgressView.swift
@@ -0,0 +1,27 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+
+struct WithdrawProgressView: View {
+    let message: String
+
+    var body: some View {
+        VStack {
+            Spacer()
+            ProgressView()
+            Spacer()
+            Text(message)
+                .font(.title)
+            Spacer()
+            Spacer()
+        }
+    }
+}
+
+struct WithdrawProgressView_Previews: PreviewProvider {
+    static var previews: some View {
+        WithdrawProgressView(message: "message")
+    }
+}
diff --git a/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawTOSView.swift 
b/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawTOSView.swift
new file mode 100644
index 0000000..48a8c0e
--- /dev/null
+++ b/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawTOSView.swift
@@ -0,0 +1,90 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import SymLog
+
+struct WithdrawTOSView: View {
+    private let symLog = SymLogV()
+    @AppStorage("listStyle") var myListStyle = MyListStyle.automatic
+
+    let navTitle = String(localized: "Terms of Service")
+
+    var exchangeBaseUrl: String
+    @ObservedObject var model: WithdrawModel
+
+    @State var exchangeTOS: ExchangeTermsOfService?
+    let viewID: Int         // either VIEW_WITHDRAW_TOS or SHEET_WITHDRAW_TOS
+
+    let acceptAction: (() -> Void)?
+    @Environment(\.presentationMode) var presentationMode
+
+    var body: some View {
+        VStack {
+            switch model.withdrawState {
+                case .receivedAmountDetails, .waitingForTOS:
+                    WithdrawProgressView(message: exchangeBaseUrl.trimURL())
+                        .navigationTitle("Loading " + navTitle)
+                case .receivedTOS, .waitingForTOSAck, .receivedTOSAck:
+                    Content(symLog: symLog, exchangeTOS: exchangeTOS, 
myListStyle: $myListStyle) {
+                        Task {
+                            do {
+                                _ = try await 
model.setExchangeTOSAcceptedM(exchangeBaseUrl, etag: exchangeTOS!.currentEtag)
+                                if acceptAction != nil {
+                                    acceptAction!()
+                                } else {
+                                    
self.presentationMode.wrappedValue.dismiss()
+                                }
+                            } catch {    // TODO: Show Error
+                                symLog.log(error.localizedDescription)
+                            }
+                        }
+                    }
+                        .navigationBarTitleDisplayMode(.large)      // .inline
+                        .navigationTitle(navTitle)
+                default:
+                    ErrorView(errortext: "unknown state")   // TODO: ???
+            }
+        }.onAppear() {
+            if viewID > SHEET_WITHDRAWAL {
+                DebugViewC.shared.setSheetID(SHEET_WITHDRAW_TOS)
+            } else {
+                DebugViewC.shared.setViewID(VIEW_WITHDRAW_TOS)
+            }
+        }.task {
+            do {
+                let someTOS = try await 
model.loadExchangeTermsOfServiceM(exchangeBaseUrl)
+                exchangeTOS = someTOS
+            } catch {    // TODO: error
+                symLog.log(error.localizedDescription)
+            }
+        }
+    }
+}
+// MARK: -
+extension WithdrawTOSView {
+    struct Content: View {
+        let symLog: SymLogV
+        var exchangeTOS: ExchangeTermsOfService?
+        @Binding var myListStyle: MyListStyle
+        var acceptAction: () -> ()
+
+        var body: some View {
+            if let tos = exchangeTOS {
+                let components = tos.content.components(separatedBy:"\n\n")
+
+                List (components, id: \.self) { term in
+                    Text(term)
+                }.safeAreaInset(edge: .bottom) {
+                    ProminentButton(title: String(localized: "Accept"), 
action: acceptAction)
+                        .padding(.horizontal)
+                }
+                .listStyle(myListStyle.style)
+                .anyView
+            } else {
+                ErrorView(errortext: String(localized: "unknown ToS"))     // 
TODO: ???
+            }
+        }
+    }
+}
diff --git a/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawURIView.swift 
b/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawURIView.swift
new file mode 100644
index 0000000..246daaa
--- /dev/null
+++ b/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawURIView.swift
@@ -0,0 +1,76 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import SymLog
+
+struct WithdrawURIView: View {
+    private let symLog = SymLogV()
+    var url: URL
+    @ObservedObject var model: WithdrawModel
+
+//    @State var withdrawUriInfo: WithdrawUriInfoResponse?
+    @State var exchangeBaseUrl: String?
+    @State var manualWithdrawalDetails: ManualWithdrawalDetails?
+    @State var didAcceptTOS: Bool = false
+
+    var body: some View {
+        let badURL = "Error in URL: \(url)"
+        VStack {
+            let currState = model.withdrawState
+            if currState == nil {
+                LoadingView(backButtonHidden: false)
+            } else {
+                let _ = symLog.vlog(currState as Any)
+                switch currState {
+                    case .waitingForUriDetails, .receivedUriDetails:
+                        WithdrawProgressView(message: url.host ?? badURL)
+                            .navigationTitle("Contacting Exchange")
+                    case .waitingForAmountDetails:
+                        WithdrawProgressView(message: 
exchangeBaseUrl?.trimURL() ?? badURL)
+                            .navigationTitle("Found Exchange")
+                    default:
+                    // .receivedAmountDetails, .waitingForTOS, .receivedTOS, 
.waitingForTOSAck, .receivedTOSAck
+                    // waitingForWithdrAck, receivedWithdrAck
+                        if !didAcceptTOS {
+                            // user must accept ToS first
+                            WithdrawTOSView(exchangeBaseUrl: exchangeBaseUrl!,
+                                                      model: model,
+                                                     viewID: 
SHEET_WITHDRAW_TOS) {
+                                didAcceptTOS = true
+                            }
+                        } else {
+                            // show Amount details and let user accept
+                            WithdrawAcceptView(url: url, model: model,
+                                              detailsForAmount: 
manualWithdrawalDetails!,
+                                                       baseURL: 
exchangeBaseUrl!)
+                        }
+                }
+            }
+        }.onAppear() {
+            DebugViewC.shared.setSheetID(SHEET_WITHDRAWAL)
+        }.task {
+            do { // TODO: cancelled
+                symLog.log(".task")
+                let withdrawUriInfo = try await 
model.loadWithdrawalDetailsForUriM(url.absoluteString)
+                let amount = withdrawUriInfo.amount
+                if let baseURL = withdrawUriInfo.defaultExchangeBaseUrl {
+                    exchangeBaseUrl = baseURL
+                } else {
+                    exchangeBaseUrl = 
withdrawUriInfo.possibleExchanges.first?.exchangeBaseUrl
+                }
+                symLog.log("amount: \(amount), baseURL: \(String(describing: 
exchangeBaseUrl))")
+                // TODO: let user choose exchange from list
+                manualWithdrawalDetails = try await 
model.loadWithdrawalDetailsForAmountM(exchangeBaseUrl!, amount: amount)
+
+                symLog.log("raw: \(manualWithdrawalDetails!.amountRaw), 
effective: \(manualWithdrawalDetails!.amountEffective)")
+                if manualWithdrawalDetails!.tosAccepted {
+                    didAcceptTOS = true
+                }
+            } catch {    // TODO: error
+                symLog.log(error.localizedDescription)
+            }
+        }
+    }
+}
diff --git a/taler-swift/Sources/taler-swift/Amount.swift 
b/taler-swift/Sources/taler-swift/Amount.swift
index d97612e..cfd01e0 100644
--- a/taler-swift/Sources/taler-swift/Amount.swift
+++ b/taler-swift/Sources/taler-swift/Amount.swift
@@ -1,17 +1,6 @@
 /*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
  */
 import Foundation
 
@@ -169,7 +158,12 @@ public class Amount: Codable, Hashable, 
CustomStringConvertible {
         self.integer = integer
         self.fraction = fraction
     }
-    
+    public init(currency: String, value: UInt64) {
+        self.currency = currency
+        self.integer = value / 100          // TODO: fractional digits can be 
0, 2 or 3
+        self.fraction = UInt32(value - (self.integer * 100))
+    }
+
     /// Initializes an amount from a decoder.
     /// - Parameters:
     ///   - from: The decoder to extract the amount from.
@@ -282,8 +276,10 @@ public class Amount: Codable, Hashable, 
CustomStringConvertible {
         }
         var remainder = result.integer % UInt64(divisor)
         result.integer = result.integer / UInt64(divisor)
-        remainder = (remainder * UInt64(Amount.fractionalBase)) + 
UInt64(result.fraction)
-        result.fraction = UInt32(remainder) / divisor
+
+        let fractionalBase = UInt64(Amount.fractionalBase)
+        remainder = (remainder * fractionalBase) + UInt64(result.fraction)
+        result.fraction = UInt32(remainder / UInt64(divisor))
         try result.normalize()
         return result
     }
@@ -392,4 +388,19 @@ public class Amount: Codable, Hashable, 
CustomStringConvertible {
     public static func zero(currency: String) -> Amount {
         return Amount(currency: currency, integer: 0, fraction: 0)
     }
+
+    public static func amountFromCents(_ currency: String, _ cents: UInt64) -> 
Amount {
+        let amount100 = Amount(currency: currency, integer: cents, fraction: 0)
+        do {
+            let amount = try amount100 / 100
+            return amount
+        } catch {       // shouldn't happen, but if it does then truncate
+            return Amount(currency: currency, integer: cents / 100, fraction: 
0)
+        }
+    }
+}
+// MARK: -
+extension Amount: Identifiable {
+    // needed to be passed as value for .sheet
+    public var id: Amount {self}
 }
diff --git a/taler-swift/Sources/taler-swift/Time.swift 
b/taler-swift/Sources/taler-swift/Time.swift
index 1d76053..dffb53d 100644
--- a/taler-swift/Sources/taler-swift/Time.swift
+++ b/taler-swift/Sources/taler-swift/Time.swift
@@ -1,17 +1,6 @@
 /*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
  */
 import Foundation
 
@@ -45,7 +34,7 @@ public enum Timestamp: Codable, Hashable {
             } else {
                 self = Timestamp.milliseconds(try 
container.decode(UInt64.self, forKey: .t_ms))
             }
-        } catch {
+        } catch {       // rethrows or never
             let stringValue = try container.decode(String.self, forKey: .t_s)
             if stringValue == "never" {
                 self = Timestamp.never
@@ -79,6 +68,17 @@ extension Timestamp {
         return Timestamp.milliseconds(Date().millisecondsSince1970)
     }
 
+    public static func tomorrow() -> Timestamp {
+        return Timestamp.inSomeDays(1)
+    }
+
+    public static func inSomeDays(_ days: UInt) -> Timestamp {
+        let now = Date().millisecondsSince1970
+        let seconds: UInt64 = 60 * 60 * 24
+        return Timestamp.milliseconds(now + (UInt64(days) * seconds * 1000))
+    }
+
+
     /// convenience initializer from UInt64 (milliseconds from January 1, 1970)
     public init(from: UInt64) {
         self = Timestamp.milliseconds(from)
diff --git a/taler-swift/Tests/taler-swiftTests/AmountTests.swift 
b/taler-swift/Tests/taler-swiftTests/AmountTests.swift
index 429e9ef..a0b1f27 100644
--- a/taler-swift/Tests/taler-swiftTests/AmountTests.swift
+++ b/taler-swift/Tests/taler-swiftTests/AmountTests.swift
@@ -1,17 +1,6 @@
 /*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
  */
 import XCTest
 @testable import taler_swift
diff --git a/taler-swift/Tests/taler-swiftTests/TimeTests.swift 
b/taler-swift/Tests/taler-swiftTests/TimeTests.swift
index 4bfd5c6..a26128a 100644
--- a/taler-swift/Tests/taler-swiftTests/TimeTests.swift
+++ b/taler-swift/Tests/taler-swiftTests/TimeTests.swift
@@ -1,17 +1,6 @@
 /*
- * This file is part of GNU Taler
- * (C) 2022 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
  */
 import XCTest
 @testable import taler_swift

-- 
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]