gnunet-svn
[Top][All Lists]
Advanced

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

[taler-taler-ios] branch master updated (7ce9180 -> f7f01e5)


From: gnunet
Subject: [taler-taler-ios] branch master updated (7ce9180 -> f7f01e5)
Date: Fri, 30 Jun 2023 22:33:32 +0200

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

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

    from 7ce9180  Version 0.9.3 build 2
     new c4294a7  Big update after DD37
     new d729e1b  Moved AgePicker in its own file
     new 9101f27  Cleaned up buttons
     new a07f109  PopToRoot instead of dismiss sheet
     new 24f4ace  cleanup, back to Swift 5.8 (for now until Xcode 15 is usable)
     new 8074fad  Notifications
     new 2fbfa38  Big Model update, removed unneccessary thread-safety code
     new d30fe6e  Preparations for localization + accessability
     new fdc7309  Launch animation, SideBarView
     new 0222d21  Reduce Logging
     new 50a02d3  Accessibility
     new 8abb8bd  Localization
     new a41e3ea  Overhaul withdraw + p2p
     new 6d1028c  Made Model a Singleton
     new 185b6c0  Suspend-Resume
     new a75849a  Dummy
     new 8c36914  for debugging time-outs
     new 9b92739  remove loaded
     new 8a6ec56  remove dismissFirst
     new cf4e4fc  viewID + comments
     new e6fc4a6  Sounds, P2P receive
     new 78e7674  cleaned up P2P
     new ec19b36  Remove old view
     new 8edd36d  balance-change
     new ef9b59f  failTransaction
     new 5a206a0  PeerPullDebit
     new 3745dae  Logging
     new 7ded59c  playSound
     new 99d5f1a  Model cleanup
     new f513e92  bugfix
     new e1008df  bump Testflight version
     new 536564c  Adjust DebugView for Notch
     new f309f9c  log only release builds
     new d62d3f0  sizeCategory, task
     new 74dd08d  developerMode
     new e77028d  playSound
     new 6154b92  Decodable
     new e68c03a  actions
     new 3acaeac  Scrollview
     new 3f13752  TransactionType
     new 62ed87e  playSound
     new fbb0aa6  BalanceRow
     new fbc5f12  confirmTransferUrl
     new 49c0604  BalanceReloaded
     new 5747e3c  TransactionDetails
     new b77c406  ThreeAmountsSheet
     new ce7f12e  #available(iOS 17.0, *) only with Xcode 15
     new 02c2fa9  Demo Shop, reloading
     new dab9b30  ScrollViewReader
     new 605eefc  withdrawalAmountDetails
     new a68b2d2  remove debugging
     new 974db16  playSound
     new aa5f2fc  ScrollViewReader needs Spacers if too few items
     new f7f01e5  iOS: bump version to 0.9.3 (10)

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


Summary of changes:
 ...rWalletT.entitlements => GNU Taler.entitlements |   0
 Info.plist                                         |  11 +-
 LICENSE.md                                         |  12 +
 TalerTests/WalletBackendTests.swift                |  15 +-
 TalerUITests/TalerUITests.swift                    |  16 +-
 TalerWallet.xcodeproj/project.pbxproj              | 431 +++++++++++++++------
 .../Contents.json                                  |   4 +-
 .../taler-logo-2023-red.svg                        |  19 +
 TalerWallet1/Backend/Transaction.swift             | 424 +++++++++++++++-----
 TalerWallet1/Backend/WalletBackendError.swift      |  17 +-
 TalerWallet1/Backend/WalletBackendRequest.swift    | 201 ++++------
 TalerWallet1/Backend/WalletCore.swift              | 363 +++++++++++------
 TalerWallet1/Controllers/Controller.swift          |  68 ++--
 TalerWallet1/Controllers/DebugViewC.swift          | 189 +++++++++
 TalerWallet1/Controllers/PublicConstants.swift     |  48 +++
 TalerWallet1/Controllers/TalerWallet1App.swift     |  89 +++--
 TalerWallet1/Helper/AgePicker.swift                |  49 +++
 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+id+iban.swift              |  37 ++
 TalerWallet1/Helper/View+Notification.swift        |  75 ++++
 TalerWallet1/Helper/View+dismissTop.swift          |  33 +-
 TalerWallet1/Helper/WalletColors.swift             |  56 +++
 TalerWallet1/Helper/playSound.swift                |  25 ++
 TalerWallet1/Model/ExchangeTestModel.swift         | 115 ------
 TalerWallet1/Model/Model+Balances.swift            |  57 +++
 .../Model+Exchange.swift}                          | 115 +++---
 TalerWallet1/Model/Model+P2P.swift                 | 259 +++++++++++++
 .../Model+Payment.swift}                           |  70 +---
 TalerWallet1/Model/Model+Pending.swift             |  55 +++
 TalerWallet1/Model/Model+Settings.swift            | 100 +++++
 TalerWallet1/Model/Model+Transactions.swift        | 158 ++++++++
 TalerWallet1/Model/Model+Withdraw.swift            | 187 +++++++++
 TalerWallet1/Model/WalletInitModel.swift           |  88 -----
 TalerWallet1/Model/WalletModel.swift               | 137 +++++--
 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 ---
 .../Views/Balances/BalanceRowButtons.swift         |  51 +++
 TalerWallet1/Views/Balances/BalanceRowView.swift   |  98 +++++
 TalerWallet1/Views/Balances/BalancesListView.swift | 171 ++++++++
 TalerWallet1/Views/Balances/BalancesModel.swift    |  73 ----
 .../Views/Balances/BalancesSectionView.swift       | 206 ++++++++++
 .../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 | 177 +++++----
 .../Views/Exchange/ExchangeSectionView.swift       | 102 +++++
 TalerWallet1/Views/Exchange/ManualWithdraw.swift   | 124 ++++++
 .../Views/Exchange/ManualWithdrawDone.swift        |  81 ++++
 TalerWallet1/Views/Exchange/QuiteSomeCoins.swift   | 102 +++++
 TalerWallet1/Views/HelperViews/AmountView.swift    |  28 +-
 TalerWallet1/Views/HelperViews/Buttons.swift       | 231 +++++++++--
 TalerWallet1/Views/HelperViews/CopyShare.swift     |  80 ++++
 TalerWallet1/Views/HelperViews/CurrencyField.swift | 221 +++++++++++
 .../Views/HelperViews/CurrencyInputView.swift      |  60 +++
 .../LaunchAnimationView.swift                      |  18 +-
 TalerWallet1/Views/HelperViews/ListStyle.swift     |  97 +++++
 TalerWallet1/Views/HelperViews/LoadingView.swift   |  39 +-
 .../Views/HelperViews/QRCodeDetailView.swift       |  57 +++
 .../Views/HelperViews/QRGeneratorView.swift        |  62 +++
 TalerWallet1/Views/HelperViews/SelectDays.swift    |  61 +++
 .../Views/HelperViews/TextFieldAlert.swift         |  15 +-
 .../Views/HelperViews/TransactionButton.swift      |  89 +++++
 TalerWallet1/Views/Main/ContentView.swift          |  92 -----
 TalerWallet1/Views/Main/ErrorView.swift            |  24 +-
 TalerWallet1/Views/Main/MainView.swift             | 124 ++++++
 TalerWallet1/Views/Main/SideBarView.swift          |  93 +++--
 TalerWallet1/Views/Main/WalletEmptyView.swift      |  44 +++
 TalerWallet1/Views/Payment/DeleteMe.swift          |  79 ++++
 TalerWallet1/Views/Payment/PaymentAcceptView.swift |  71 ----
 TalerWallet1/Views/Payment/PaymentURIView.swift    | 155 +++++---
 TalerWallet1/Views/Peer2peer/PaymentPurpose.swift  | 111 ++++++
 TalerWallet1/Views/Peer2peer/RequestPayment.swift  |  93 +++++
 TalerWallet1/Views/Peer2peer/SendAmount.swift      | 118 ++++++
 TalerWallet1/Views/Peer2peer/SendNow.swift         |  93 +++++
 TalerWallet1/Views/Peer2peer/SendPurpose.swift     | 119 ++++++
 TalerWallet1/Views/Pending/PendingModel.swift      |  82 ----
 .../Views/Pending/PendingOpsListView.swift         |  75 ----
 .../{ => Settings}/Pending/PendingOpView.swift     |  31 +-
 .../Settings/Pending/PendingOpsListView.swift      |  58 +++
 TalerWallet1/Views/Settings/SettingsItem.swift     |  35 +-
 TalerWallet1/Views/Settings/SettingsView.swift     | 242 +++++++-----
 .../Views/Sheets/P2P_Sheets/P2pAcceptDone.swift    |  63 +++
 .../Views/Sheets/P2P_Sheets/P2pPayURIView.swift    |  71 ++++
 .../Sheets/P2P_Sheets/P2pReceiveURIView.swift      |  78 ++++
 TalerWallet1/Views/Sheets/QRSheet.swift            |  52 +++
 TalerWallet1/Views/Sheets/ShareSheet.swift         |  40 ++
 TalerWallet1/Views/Sheets/Sheet.swift              |  42 ++
 TalerWallet1/Views/Sheets/URLSheet.swift           |  52 +++
 .../Views/Transactions/ManualDetails.swift         |  74 ++++
 TalerWallet1/Views/Transactions/ThreeAmounts.swift | 112 ++++++
 .../Views/Transactions/TransactionDetail.swift     | 136 -------
 .../Views/Transactions/TransactionDetailView.swift | 285 ++++++++++++++
 .../Views/Transactions/TransactionRow.swift        |  92 -----
 .../Views/Transactions/TransactionRowView.swift    | 121 ++++++
 .../Views/Transactions/TransactionsEmptyView.swift |  37 ++
 .../Views/Transactions/TransactionsListView.swift  | 169 ++++----
 .../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/WithdrawURIModel.swift | 213 ----------
 TalerWallet1/Views/Withdraw/WithdrawURIView.swift  | 103 -----
 .../WithdrawAcceptDone.swift                       |  68 ++++
 .../WithdrawAcceptView.swift                       | 111 ++++++
 .../WithdrawProgressView.swift                     |  32 ++
 .../WithdrawBankIntegrated/WithdrawTOSView.swift   |  99 +++++
 .../WithdrawBankIntegrated/WithdrawURIView.swift   | 100 +++++
 taler-swift/Sources/taler-swift/Amount.swift       |  43 +-
 taler-swift/Sources/taler-swift/Time.swift         |  34 +-
 .../Tests/taler-swiftTests/AmountTests.swift       |  15 +-
 taler-swift/Tests/taler-swiftTests/TimeTests.swift |  15 +-
 125 files changed, 8050 insertions(+), 3132 deletions(-)
 rename TalerWalletT.entitlements => GNU Taler.entitlements (100%)
 create mode 100644 LICENSE.md
 copy TalerWallet1/Assets.xcassets/{Taler-logo.imageset => 
taler-logo-2023-red.imageset}/Contents.json (68%)
 create mode 100644 
TalerWallet1/Assets.xcassets/taler-logo-2023-red.imageset/taler-logo-2023-red.svg
 create mode 100644 TalerWallet1/Controllers/DebugViewC.swift
 create mode 100644 TalerWallet1/Controllers/PublicConstants.swift
 create mode 100644 TalerWallet1/Helper/AgePicker.swift
 create mode 100644 TalerWallet1/Helper/AnyTransition+backslide.swift
 create mode 100644 TalerWallet1/Helper/CurrencyFormatter.swift
 create mode 100644 TalerWallet1/Helper/EqualIconWidthDomain.swift
 create mode 100644 TalerWallet1/Helper/KeyboardResponder.swift
 create mode 100644 TalerWallet1/Helper/LocalizedAlertError.swift
 delete mode 100644 TalerWallet1/Helper/PublicConstants.swift
 create mode 100644 TalerWallet1/Helper/URL+id+iban.swift
 create mode 100644 TalerWallet1/Helper/View+Notification.swift
 create mode 100644 TalerWallet1/Helper/WalletColors.swift
 create mode 100644 TalerWallet1/Helper/playSound.swift
 delete mode 100644 TalerWallet1/Model/ExchangeTestModel.swift
 create mode 100644 TalerWallet1/Model/Model+Balances.swift
 rename TalerWallet1/{Views/Exchange/ExchangeModel.swift => 
Model/Model+Exchange.swift} (53%)
 create mode 100644 TalerWallet1/Model/Model+P2P.swift
 rename TalerWallet1/{Views/Payment/PaymentURIModel.swift => 
Model/Model+Payment.swift} (64%)
 create mode 100644 TalerWallet1/Model/Model+Pending.swift
 create mode 100644 TalerWallet1/Model/Model+Settings.swift
 create mode 100644 TalerWallet1/Model/Model+Transactions.swift
 create mode 100644 TalerWallet1/Model/Model+Withdraw.swift
 delete mode 100644 TalerWallet1/Model/WalletInitModel.swift
 create mode 100644 TalerWallet1/Preview Content/transactions.json
 create mode 100644 TalerWallet1/Settings.bundle/Root.plist
 create mode 100644 TalerWallet1/Settings.bundle/en.lproj/Root.strings
 delete mode 100644 TalerWallet1/Views/Balances/BalanceRow.swift
 create mode 100644 TalerWallet1/Views/Balances/BalanceRowButtons.swift
 create mode 100644 TalerWallet1/Views/Balances/BalanceRowView.swift
 create mode 100644 TalerWallet1/Views/Balances/BalancesListView.swift
 delete mode 100644 TalerWallet1/Views/Balances/BalancesModel.swift
 create mode 100644 TalerWallet1/Views/Balances/BalancesSectionView.swift
 delete mode 100644 TalerWallet1/Views/Balances/CurrenciesListView.swift
 delete mode 100644 TalerWallet1/Views/Balances/CurrencyView.swift
 delete mode 100644 TalerWallet1/Views/Balances/PendingRow.swift
 create mode 100644 TalerWallet1/Views/Balances/PendingRowView.swift
 create mode 100644 TalerWallet1/Views/Balances/UncompletedRowView.swift
 delete mode 100644 TalerWallet1/Views/Balances/WalletEmptyView.swift
 create mode 100644 TalerWallet1/Views/Exchange/ExchangeSectionView.swift
 create mode 100644 TalerWallet1/Views/Exchange/ManualWithdraw.swift
 create mode 100644 TalerWallet1/Views/Exchange/ManualWithdrawDone.swift
 create mode 100644 TalerWallet1/Views/Exchange/QuiteSomeCoins.swift
 create mode 100644 TalerWallet1/Views/HelperViews/CopyShare.swift
 create mode 100644 TalerWallet1/Views/HelperViews/CurrencyField.swift
 create mode 100644 TalerWallet1/Views/HelperViews/CurrencyInputView.swift
 rename TalerWallet1/Views/{Main => HelperViews}/LaunchAnimationView.swift (67%)
 create mode 100644 TalerWallet1/Views/HelperViews/ListStyle.swift
 create mode 100644 TalerWallet1/Views/HelperViews/QRCodeDetailView.swift
 create mode 100644 TalerWallet1/Views/HelperViews/QRGeneratorView.swift
 create mode 100644 TalerWallet1/Views/HelperViews/SelectDays.swift
 create mode 100644 TalerWallet1/Views/HelperViews/TransactionButton.swift
 delete mode 100644 TalerWallet1/Views/Main/ContentView.swift
 create mode 100644 TalerWallet1/Views/Main/MainView.swift
 create mode 100644 TalerWallet1/Views/Main/WalletEmptyView.swift
 create mode 100644 TalerWallet1/Views/Payment/DeleteMe.swift
 delete mode 100644 TalerWallet1/Views/Payment/PaymentAcceptView.swift
 create mode 100644 TalerWallet1/Views/Peer2peer/PaymentPurpose.swift
 create mode 100644 TalerWallet1/Views/Peer2peer/RequestPayment.swift
 create mode 100644 TalerWallet1/Views/Peer2peer/SendAmount.swift
 create mode 100644 TalerWallet1/Views/Peer2peer/SendNow.swift
 create mode 100644 TalerWallet1/Views/Peer2peer/SendPurpose.swift
 delete mode 100644 TalerWallet1/Views/Pending/PendingModel.swift
 delete mode 100644 TalerWallet1/Views/Pending/PendingOpsListView.swift
 rename TalerWallet1/Views/{ => Settings}/Pending/PendingOpView.swift (62%)
 create mode 100644 TalerWallet1/Views/Settings/Pending/PendingOpsListView.swift
 create mode 100644 TalerWallet1/Views/Sheets/P2P_Sheets/P2pAcceptDone.swift
 create mode 100644 TalerWallet1/Views/Sheets/P2P_Sheets/P2pPayURIView.swift
 create mode 100644 TalerWallet1/Views/Sheets/P2P_Sheets/P2pReceiveURIView.swift
 create mode 100644 TalerWallet1/Views/Sheets/QRSheet.swift
 create mode 100644 TalerWallet1/Views/Sheets/ShareSheet.swift
 create mode 100644 TalerWallet1/Views/Sheets/Sheet.swift
 create mode 100644 TalerWallet1/Views/Sheets/URLSheet.swift
 create mode 100644 TalerWallet1/Views/Transactions/ManualDetails.swift
 create mode 100644 TalerWallet1/Views/Transactions/ThreeAmounts.swift
 delete mode 100644 TalerWallet1/Views/Transactions/TransactionDetail.swift
 create mode 100644 TalerWallet1/Views/Transactions/TransactionDetailView.swift
 delete mode 100644 TalerWallet1/Views/Transactions/TransactionRow.swift
 create mode 100644 TalerWallet1/Views/Transactions/TransactionRowView.swift
 create mode 100644 TalerWallet1/Views/Transactions/TransactionsEmptyView.swift
 delete mode 100644 TalerWallet1/Views/Transactions/TransactionsModel.swift
 delete mode 100644 TalerWallet1/Views/URLSheet.swift
 delete mode 100644 TalerWallet1/Views/Withdraw/WithdrawAcceptView.swift
 delete mode 100644 TalerWallet1/Views/Withdraw/WithdrawProgressView.swift
 delete mode 100644 TalerWallet1/Views/Withdraw/WithdrawTOSView.swift
 delete mode 100644 TalerWallet1/Views/Withdraw/WithdrawURIModel.swift
 delete mode 100644 TalerWallet1/Views/Withdraw/WithdrawURIView.swift
 create mode 100644 
TalerWallet1/Views/WithdrawBankIntegrated/WithdrawAcceptDone.swift
 create mode 100644 
TalerWallet1/Views/WithdrawBankIntegrated/WithdrawAcceptView.swift
 create mode 100644 
TalerWallet1/Views/WithdrawBankIntegrated/WithdrawProgressView.swift
 create mode 100644 
TalerWallet1/Views/WithdrawBankIntegrated/WithdrawTOSView.swift
 create mode 100644 
TalerWallet1/Views/WithdrawBankIntegrated/WithdrawURIView.swift

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..e8fb295 100644
--- a/TalerWallet.xcodeproj/project.pbxproj
+++ b/TalerWallet.xcodeproj/project.pbxproj
@@ -7,7 +7,39 @@
        objects = {
 
 /* Begin PBXBuildFile section */
+               4E16E12329F3BB99008B9C86 /* CurrencyFormatter.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4E16E12229F3BB99008B9C86 /* 
CurrencyFormatter.swift */; };
+               4E363CBC2A237E0900D7E98C /* URL+id+iban.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4E363CBB2A237E0900D7E98C /* URL+id+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 */; };
+               4E3B4BC12A41E6C200CC88B8 /* P2pReceiveURIView.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4E3B4BC02A41E6C200CC88B8 /* 
P2pReceiveURIView.swift */; };
+               4E3B4BC32A42252300CC88B8 /* P2pAcceptDone.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4E3B4BC22A42252300CC88B8 /* P2pAcceptDone.swift 
*/; };
+               4E3B4BC52A428AF700CC88B8 /* Model+Balances.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4E3B4BC42A428AF700CC88B8 /* 
Model+Balances.swift */; };
+               4E3B4BC72A429F2A00CC88B8 /* View+Notification.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4E3B4BC62A429F2A00CC88B8 /* 
View+Notification.swift */; };
+               4E3B4BC92A42BC4800CC88B8 /* Model+Exchange.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4E3B4BC82A42BC4800CC88B8 /* 
Model+Exchange.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 
*/; };
+               4E578E922A481D8600F21F1C /* playSound.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4E578E912A481D8600F21F1C /* playSound.swift */; 
};
+               4E578E942A4822D500F21F1C /* P2pPayURIView.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4E578E932A4822D500F21F1C /* P2pPayURIView.swift 
*/; };
+               4E5A88F52A38A4FD00072618 /* QRCodeDetailView.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4E5A88F42A38A4FD00072618 /* 
QRCodeDetailView.swift */; };
+               4E5A88F72A3B9E5B00072618 /* WithdrawAcceptDone.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4E5A88F62A3B9E5B00072618 /* 
WithdrawAcceptDone.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 /* PaymentPurpose.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4E9320462A164BC700A87B0E /* 
PaymentPurpose.swift */; };
+               4E9796902A3765ED006F73BC /* AgePicker.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4E97968F2A3765ED006F73BC /* AgePicker.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 */; };
+               4EB065442A4CD1A80039B91D /* BalanceRowButtons.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4EB065432A4CD1A80039B91D /* 
BalanceRowButtons.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,47 +55,55 @@
                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 /* Model+Settings.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4EB095102989CBB00043A8A1 /* 
Model+Settings.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 */; };
                4EB095202989CBCB0043A8A1 /* WalletCore.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EB0951C2989CBCB0043A8A1 /* WalletCore.swift */; 
};
                4EB095212989CBCB0043A8A1 /* WalletBackendError.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4EB0951D2989CBCB0043A8A1 /* 
WalletBackendError.swift */; };
                4EB095222989CBCB0043A8A1 /* Transaction.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EB0951E2989CBCB0043A8A1 /* Transaction.swift 
*/; };
                4EB0954F2989CBFE0043A8A1 /* SettingsView.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EB095252989CBFE0043A8A1 /* SettingsView.swift 
*/; };
                4EB095502989CBFE0043A8A1 /* SettingsItem.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EB095262989CBFE0043A8A1 /* SettingsItem.swift 
*/; };
-               4EB095512989CBFE0043A8A1 /* ExchangeModel.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EB095282989CBFE0043A8A1 /* ExchangeModel.swift 
*/; };
                4EB095522989CBFE0043A8A1 /* ExchangeListView.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4EB095292989CBFE0043A8A1 /* 
ExchangeListView.swift */; };
-               4EB095532989CBFE0043A8A1 /* PaymentAcceptView.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4EB0952B2989CBFE0043A8A1 /* 
PaymentAcceptView.swift */; };
-               4EB095542989CBFE0043A8A1 /* PaymentURIModel.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4EB0952C2989CBFE0043A8A1 /* 
PaymentURIModel.swift */; };
+               4EB095542989CBFE0043A8A1 /* Model+Payment.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EB0952C2989CBFE0043A8A1 /* Model+Payment.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 */; };
-               4EB095592989CBFE0043A8A1 /* TransactionsModel.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4EB095322989CBFE0043A8A1 /* 
TransactionsModel.swift */; };
+               4EB095572989CBFE0043A8A1 /* TransactionRowView.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4EB095302989CBFE0043A8A1 /* 
TransactionRowView.swift */; };
+               4EB095582989CBFE0043A8A1 /* TransactionDetailView.swift in 
Sources */ = {isa = PBXBuildFile; fileRef = 4EB095312989CBFE0043A8A1 /* 
TransactionDetailView.swift */; };
+               4EB095592989CBFE0043A8A1 /* Model+Transactions.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4EB095322989CBFE0043A8A1 /* 
Model+Transactions.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 */; };
-               4EB095632989CBFE0043A8A1 /* WithdrawAcceptView.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4EB0953E2989CBFE0043A8A1 /* 
WithdrawAcceptView.swift */; };
+               4EB095622989CBFE0043A8A1 /* Model+Withdraw.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4EB0953D2989CBFE0043A8A1 /* 
Model+Withdraw.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 */; };
                4EB0956C2989CBFE0043A8A1 /* AmountView.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EB095492989CBFE0043A8A1 /* AmountView.swift */; 
};
                4EB0956D2989CBFE0043A8A1 /* LoadingView.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EB0954A2989CBFE0043A8A1 /* LoadingView.swift 
*/; };
-               4EB0956E2989CBFE0043A8A1 /* PendingModel.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EB0954C2989CBFE0043A8A1 /* PendingModel.swift 
*/; };
+               4EB0956E2989CBFE0043A8A1 /* Model+Pending.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EB0954C2989CBFE0043A8A1 /* Model+Pending.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 */; };
+               4EBA82AB2A3EB2CA00E5F39A /* TransactionButton.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4EBA82AA2A3EB2CA00E5F39A /* 
TransactionButton.swift */; };
+               4EBA82AD2A3F580500E5F39A /* QuiteSomeCoins.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4EBA82AC2A3F580500E5F39A /* 
QuiteSomeCoins.swift */; };
+               4EC90C782A1B528B0071DC58 /* ExchangeSectionView.swift in 
Sources */ = {isa = PBXBuildFile; fileRef = 4EC90C772A1B528B0071DC58 /* 
ExchangeSectionView.swift */; };
+               4ECB62802A0BA6DF004ABBB7 /* Model+P2P.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4ECB627F2A0BA6DF004ABBB7 /* Model+P2P.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 +138,41 @@
 /* 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+id+iban.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= "URL+id+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>"; };
+               4E3B4BC02A41E6C200CC88B8 /* P2pReceiveURIView.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= P2pReceiveURIView.swift; sourceTree = "<group>"; };
+               4E3B4BC22A42252300CC88B8 /* P2pAcceptDone.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= P2pAcceptDone.swift; sourceTree = "<group>"; };
+               4E3B4BC42A428AF700CC88B8 /* Model+Balances.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= "Model+Balances.swift"; sourceTree = "<group>"; };
+               4E3B4BC62A429F2A00CC88B8 /* View+Notification.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= "View+Notification.swift"; sourceTree = "<group>"; };
+               4E3B4BC82A42BC4800CC88B8 /* Model+Exchange.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= "Model+Exchange.swift"; 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>"; };
+               4E578E912A481D8600F21F1C /* playSound.swift */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.swift; path = playSound.swift; 
sourceTree = "<group>"; };
+               4E578E932A4822D500F21F1C /* P2pPayURIView.swift */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.swift; path = 
P2pPayURIView.swift; sourceTree = "<group>"; };
+               4E5A88F42A38A4FD00072618 /* QRCodeDetailView.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= QRCodeDetailView.swift; sourceTree = "<group>"; };
+               4E5A88F62A3B9E5B00072618 /* WithdrawAcceptDone.swift */ = {isa 
= PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; 
path = WithdrawAcceptDone.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 /* PaymentPurpose.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= PaymentPurpose.swift; sourceTree = "<group>"; };
+               4E97968F2A3765ED006F73BC /* AgePicker.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= AgePicker.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>"; };
+               4EB065432A4CD1A80039B91D /* BalanceRowButtons.swift */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.swift; path = 
BalanceRowButtons.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,49 +188,56 @@
                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 /* Model+Settings.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= "Model+Settings.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>"; };
                4EB0951C2989CBCB0043A8A1 /* WalletCore.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= WalletCore.swift; sourceTree = "<group>"; };
                4EB0951D2989CBCB0043A8A1 /* WalletBackendError.swift */ = {isa 
= PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; 
path = WalletBackendError.swift; sourceTree = "<group>"; };
                4EB0951E2989CBCB0043A8A1 /* Transaction.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= Transaction.swift; sourceTree = "<group>"; };
                4EB095252989CBFE0043A8A1 /* SettingsView.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= SettingsView.swift; sourceTree = "<group>"; };
                4EB095262989CBFE0043A8A1 /* SettingsItem.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= SettingsItem.swift; sourceTree = "<group>"; };
-               4EB095282989CBFE0043A8A1 /* ExchangeModel.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= ExchangeModel.swift; sourceTree = "<group>"; };
                4EB095292989CBFE0043A8A1 /* ExchangeListView.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= ExchangeListView.swift; sourceTree = "<group>"; };
-               4EB0952B2989CBFE0043A8A1 /* PaymentAcceptView.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= PaymentAcceptView.swift; sourceTree = "<group>"; };
-               4EB0952C2989CBFE0043A8A1 /* PaymentURIModel.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= PaymentURIModel.swift; sourceTree = "<group>"; };
+               4EB0952C2989CBFE0043A8A1 /* Model+Payment.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= "Model+Payment.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>"; };
-               4EB095322989CBFE0043A8A1 /* TransactionsModel.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= TransactionsModel.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 /* Model+Transactions.swift */ = {isa 
= PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; 
path = "Model+Transactions.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>"; };
-               4EB0953E2989CBFE0043A8A1 /* WithdrawAcceptView.swift */ = {isa 
= PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; 
path = WithdrawAcceptView.swift; sourceTree = "<group>"; };
+               4EB0953D2989CBFE0043A8A1 /* Model+Withdraw.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= "Model+Withdraw.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>"; };
                4EB095492989CBFE0043A8A1 /* AmountView.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= AmountView.swift; sourceTree = "<group>"; };
                4EB0954A2989CBFE0043A8A1 /* LoadingView.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= LoadingView.swift; sourceTree = "<group>"; };
-               4EB0954C2989CBFE0043A8A1 /* PendingModel.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= PendingModel.swift; sourceTree = "<group>"; };
+               4EB0954C2989CBFE0043A8A1 /* Model+Pending.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= "Model+Pending.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>"; };
+               4EBA82AA2A3EB2CA00E5F39A /* TransactionButton.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= TransactionButton.swift; sourceTree = "<group>"; };
+               4EBA82AC2A3F580500E5F39A /* QuiteSomeCoins.swift */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.swift; path = 
QuiteSomeCoins.swift; sourceTree = "<group>"; };
+               4EC90C772A1B528B0071DC58 /* ExchangeSectionView.swift */ = {isa 
= PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; 
path = ExchangeSectionView.swift; sourceTree = "<group>"; };
+               4ECB627F2A0BA6DF004ABBB7 /* Model+P2P.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= "Model+P2P.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 +251,7 @@
                                4EB094FD29897D280043A8A1 /* SymLog in 
Frameworks */,
                                4EB094F829897CA20043A8A1 /* 
FTalerWalletcore.framework in Frameworks */,
                                ABC13AA32859962800D23185 /* taler-swift in 
Frameworks */,
+                               4EEC157629F8ECBF00D46A03 /* CodeScanner in 
Frameworks */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
@@ -191,6 +272,16 @@
 /* End PBXFrameworksBuildPhase section */
 
 /* Begin PBXGroup section */
+               4E3B4BBF2A41E64000CC88B8 /* P2P_Sheets */ = {
+                       isa = PBXGroup;
+                       children = (
+                               4E3B4BC02A41E6C200CC88B8 /* 
P2pReceiveURIView.swift */,
+                               4E3B4BC22A42252300CC88B8 /* P2pAcceptDone.swift 
*/,
+                               4E578E932A4822D500F21F1C /* P2pPayURIView.swift 
*/,
+                       );
+                       path = P2P_Sheets;
+                       sourceTree = "<group>";
+               };
                4EB094EE298979840043A8A1 /* TalerWallet1 */ = {
                        isa = PBXGroup;
                        children = (
@@ -198,9 +289,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 +302,7 @@
                        isa = PBXGroup;
                        children = (
                                4EB094F329897A510043A8A1 /* Preview 
Assets.xcassets */,
+                               4E753A042A08E720002D9328 /* transactions.json 
*/,
                        );
                        path = "Preview Content";
                        sourceTree = "<group>";
@@ -227,6 +320,8 @@
                        children = (
                                4EB094EC298979620043A8A1 /* 
TalerWallet1App.swift */,
                                4EB095012989C9BC0043A8A1 /* Controller.swift */,
+                               4EA1ABBD29A3833A008821EA /* 
PublicConstants.swift */,
+                               4E753A052A0952F7002D9328 /* DebugViewC.swift */,
                        );
                        path = Controllers;
                        sourceTree = "<group>";
@@ -234,10 +329,19 @@
                4EB095052989CB7C0043A8A1 /* Helper */ = {
                        isa = PBXGroup;
                        children = (
+                               4E97968F2A3765ED006F73BC /* AgePicker.swift */,
+                               4E363CBD2A23CB2100D7E98C /* 
AnyTransition+backslide.swift */,
+                               4E16E12229F3BB99008B9C86 /* 
CurrencyFormatter.swift */,
+                               4EAD117529F672FA008EDD0B /* 
KeyboardResponder.swift */,
+                               4E363CC12A2621C200D7E98C /* 
LocalizedAlertError.swift */,
+                               4E578E912A481D8600F21F1C /* playSound.swift */,
                                4EB095062989CB7C0043A8A1 /* TalerDater.swift */,
                                4EB095072989CB7C0043A8A1 /* TalerStrings.swift 
*/,
                                4EB095082989CB7C0043A8A1 /* 
View+dismissTop.swift */,
-                               4EA1ABBD29A3833A008821EA /* 
PublicConstants.swift */,
+                               4E3B4BC62A429F2A00CC88B8 /* 
View+Notification.swift */,
+                               4E363CBB2A237E0900D7E98C /* URL+id+iban.swift 
*/,
+                               4E9320422A14F6EA00A87B0E /* WalletColors.swift 
*/,
+                               4E8E25322A1CD39700A27BFA /* 
EqualIconWidthDomain.swift */,
                        );
                        path = Helper;
                        sourceTree = "<group>";
@@ -253,9 +357,15 @@
                4EB0950F2989CBB00043A8A1 /* Model */ = {
                        isa = PBXGroup;
                        children = (
-                               4EB095102989CBB00043A8A1 /* 
ExchangeTestModel.swift */,
                                4EB095112989CBB00043A8A1 /* WalletModel.swift 
*/,
-                               4EB095142989CBB00043A8A1 /* 
WalletInitModel.swift */,
+                               4E3B4BC42A428AF700CC88B8 /* 
Model+Balances.swift */,
+                               4E3B4BC82A42BC4800CC88B8 /* 
Model+Exchange.swift */,
+                               4ECB627F2A0BA6DF004ABBB7 /* Model+P2P.swift */,
+                               4EB0954C2989CBFE0043A8A1 /* Model+Pending.swift 
*/,
+                               4EB0952C2989CBFE0043A8A1 /* Model+Payment.swift 
*/,
+                               4EB095102989CBB00043A8A1 /* 
Model+Settings.swift */,
+                               4EB095322989CBFE0043A8A1 /* 
Model+Transactions.swift */,
+                               4EB0953D2989CBFE0043A8A1 /* 
Model+Withdraw.swift */,
                        );
                        path = Model;
                        sourceTree = "<group>";
@@ -275,15 +385,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 +403,7 @@
                        children = (
                                4EB095252989CBFE0043A8A1 /* SettingsView.swift 
*/,
                                4EB095262989CBFE0043A8A1 /* SettingsItem.swift 
*/,
+                               4EB0954B2989CBFE0043A8A1 /* Pending */,
                        );
                        path = Settings;
                        sourceTree = "<group>";
@@ -300,8 +411,11 @@
                4EB095272989CBFE0043A8A1 /* Exchange */ = {
                        isa = PBXGroup;
                        children = (
-                               4EB095282989CBFE0043A8A1 /* ExchangeModel.swift 
*/,
                                4EB095292989CBFE0043A8A1 /* 
ExchangeListView.swift */,
+                               4EC90C772A1B528B0071DC58 /* 
ExchangeSectionView.swift */,
+                               4E50B34F2A1BEE8000F9F01C /* 
ManualWithdraw.swift */,
+                               4EBA82AC2A3F580500E5F39A /* 
QuiteSomeCoins.swift */,
+                               4EB431662A1E55C700C5690E /* 
ManualWithdrawDone.swift */,
                        );
                        path = Exchange;
                        sourceTree = "<group>";
@@ -309,8 +423,6 @@
                4EB0952A2989CBFE0043A8A1 /* Payment */ = {
                        isa = PBXGroup;
                        children = (
-                               4EB0952B2989CBFE0043A8A1 /* 
PaymentAcceptView.swift */,
-                               4EB0952C2989CBFE0043A8A1 /* 
PaymentURIModel.swift */,
                                4EB0952D2989CBFE0043A8A1 /* 
PaymentURIView.swift */,
                        );
                        path = Payment;
@@ -320,9 +432,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 +444,34 @@
                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 */,
+                               4EB065432A4CD1A80039B91D /* 
BalanceRowButtons.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 */,
+                               4E5A88F62A3B9E5B00072618 /* 
WithdrawAcceptDone.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 +480,18 @@
                        isa = PBXGroup;
                        children = (
                                4EB095472989CBFE0043A8A1 /* Buttons.swift */,
+                               4EF840A62A0B85F400EE0D47 /* CopyShare.swift */,
+                               4ECB62812A0BB01D004ABBB7 /* SelectDays.swift */,
+                               4E53A33629F50B7B00830EC2 /* CurrencyField.swift 
*/,
+                               4EA551242A2C923600FEC9A8 /* 
CurrencyInputView.swift */,
+                               4EEC157229F8242800D46A03 /* 
QRGeneratorView.swift */,
+                               4E5A88F42A38A4FD00072618 /* 
QRCodeDetailView.swift */,
+                               4E6EDD862A363D8D0031D520 /* ListStyle.swift */,
                                4EB095482989CBFE0043A8A1 /* 
TextFieldAlert.swift */,
+                               4EBA82AA2A3EB2CA00E5F39A /* 
TransactionButton.swift */,
                                4EB095492989CBFE0043A8A1 /* AmountView.swift */,
                                4EB0954A2989CBFE0043A8A1 /* LoadingView.swift 
*/,
+                               4EB095432989CBFE0043A8A1 /* 
LaunchAnimationView.swift */,
                        );
                        path = HelperViews;
                        sourceTree = "<group>";
@@ -377,19 +499,42 @@
                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 /* 
PaymentPurpose.swift */,
+                       );
+                       path = Peer2peer;
+                       sourceTree = "<group>";
+               };
+               4EEC157129F7188B00D46A03 /* Sheets */ = {
+                       isa = PBXGroup;
+                       children = (
+                               4EEC157729F9032900D46A03 /* Sheet.swift */,
+                               4EEC157929F9427F00D46A03 /* QRSheet.swift */,
+                               4E753A072A0B6A5F002D9328 /* ShareSheet.swift */,
+                               4EB095332989CBFE0043A8A1 /* URLSheet.swift */,
+                               4E3B4BBF2A41E64000CC88B8 /* P2P_Sheets */,
+                       );
+                       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 +546,7 @@
                D14AFD1E24D232B300C51073 /* Products */ = {
                        isa = PBXGroup;
                        children = (
-                               D14AFD1D24D232B300C51073 /* TalerWalletT.app */,
+                               D14AFD1D24D232B300C51073 /* GNU Taler.app */,
                                D14AFD3324D232B500C51073 /* TalerTests.xctest 
*/,
                                D14AFD3E24D232B500C51073 /* TalerUITests.xctest 
*/,
                        );
@@ -430,9 +575,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 +588,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 +673,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 +692,7 @@
                        buildActionMask = 2147483647;
                        files = (
                                4EB094F429897A510043A8A1 /* Preview 
Assets.xcassets in Resources */,
+                               4E363CC02A24754200D7E98C /* Settings.bundle in 
Resources */,
                                4EB094F0298979D30043A8A1 /* Assets.xcassets in 
Resources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
@@ -570,43 +718,73 @@
                        isa = PBXSourcesBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
-                               4EB095512989CBFE0043A8A1 /* ExchangeModel.swift 
in Sources */,
+                               4ECB62822A0BB01D004ABBB7 /* SelectDays.swift in 
Sources */,
+                               4E9796902A3765ED006F73BC /* AgePicker.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 */,
+                               4EBA82AB2A3EB2CA00E5F39A /* 
TransactionButton.swift in Sources */,
+                               4EB095602989CBFE0043A8A1 /* 
BalancesSectionView.swift in Sources */,
+                               4EEC157329F8242800D46A03 /* 
QRGeneratorView.swift in Sources */,
+                               4E5A88F72A3B9E5B00072618 /* 
WithdrawAcceptDone.swift in Sources */,
                                4EB095222989CBCB0043A8A1 /* Transaction.swift 
in Sources */,
-                               4EB0955D2989CBFE0043A8A1 /* 
CurrenciesListView.swift in Sources */,
-                               4EB095532989CBFE0043A8A1 /* 
PaymentAcceptView.swift in Sources */,
+                               4E9320432A14F6EA00A87B0E /* WalletColors.swift 
in Sources */,
+                               4EB0955D2989CBFE0043A8A1 /* 
BalancesListView.swift in Sources */,
                                4EB095212989CBCB0043A8A1 /* 
WalletBackendError.swift in Sources */,
-                               4EB0955E2989CBFE0043A8A1 /* PendingRow.swift in 
Sources */,
-                               4EB0955B2989CBFE0043A8A1 /* BalancesModel.swift 
in Sources */,
-                               4EB095632989CBFE0043A8A1 /* 
WithdrawAcceptView.swift in Sources */,
+                               4EB0955E2989CBFE0043A8A1 /* 
PendingRowView.swift in Sources */,
                                4EB0956D2989CBFE0043A8A1 /* LoadingView.swift 
in Sources */,
-                               4EB095542989CBFE0043A8A1 /* 
PaymentURIModel.swift in Sources */,
+                               4E50B3502A1BEE8000F9F01C /* 
ManualWithdraw.swift in Sources */,
+                               4E3B4BC92A42BC4800CC88B8 /* 
Model+Exchange.swift in Sources */,
+                               4E5A88F52A38A4FD00072618 /* 
QRCodeDetailView.swift in Sources */,
+                               4E87C8732A31CB7F001C6406 /* 
TransactionsEmptyView.swift in Sources */,
+                               4E87C8752A34B411001C6406 /* 
UncompletedRowView.swift in Sources */,
+                               4E40E0BE29F25ABB00B85369 /* SendAmount.swift in 
Sources */,
+                               4E8E25332A1CD39700A27BFA /* 
EqualIconWidthDomain.swift in Sources */,
+                               4E578E942A4822D500F21F1C /* P2pPayURIView.swift 
in Sources */,
+                               4EB095542989CBFE0043A8A1 /* Model+Payment.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 */,
+                               4EBA82AD2A3F580500E5F39A /* 
QuiteSomeCoins.swift in Sources */,
+                               4EB431672A1E55C700C5690E /* 
ManualWithdrawDone.swift in Sources */,
+                               4E9320472A164BC700A87B0E /* 
PaymentPurpose.swift in Sources */,
+                               4E753A082A0B6A5F002D9328 /* ShareSheet.swift in 
Sources */,
                                4EB0956C2989CBFE0043A8A1 /* AmountView.swift in 
Sources */,
-                               4EB095592989CBFE0043A8A1 /* 
TransactionsModel.swift in Sources */,
+                               4E3B4BC32A42252300CC88B8 /* P2pAcceptDone.swift 
in Sources */,
+                               4E363CBE2A23CB2100D7E98C /* 
AnyTransition+backslide.swift in Sources */,
+                               4EB065442A4CD1A80039B91D /* 
BalanceRowButtons.swift in Sources */,
+                               4EB095592989CBFE0043A8A1 /* 
Model+Transactions.swift in Sources */,
+                               4E578E922A481D8600F21F1C /* playSound.swift in 
Sources */,
                                4EB0955F2989CBFE0043A8A1 /* 
WalletEmptyView.swift in Sources */,
-                               4EB095192989CBB00043A8A1 /* 
WalletInitModel.swift in Sources */,
+                               4E16E12329F3BB99008B9C86 /* 
CurrencyFormatter.swift in Sources */,
                                4EB095092989CB7C0043A8A1 /* TalerDater.swift in 
Sources */,
+                               4E3B4BC52A428AF700CC88B8 /* 
Model+Balances.swift in Sources */,
+                               4E363CC22A2621C200D7E98C /* 
LocalizedAlertError.swift in Sources */,
                                4EB0950E2989CB9A0043A8A1 /* quickjs.swift in 
Sources */,
-                               4EB095152989CBB00043A8A1 /* 
ExchangeTestModel.swift in Sources */,
+                               4E53A33729F50B7B00830EC2 /* CurrencyField.swift 
in Sources */,
+                               4EB095152989CBB00043A8A1 /* 
Model+Settings.swift in Sources */,
                                4EB095692989CBFE0043A8A1 /* ErrorView.swift in 
Sources */,
-                               4EB0956E2989CBFE0043A8A1 /* PendingModel.swift 
in Sources */,
+                               4E3B4BC72A429F2A00CC88B8 /* 
View+Notification.swift in Sources */,
+                               4EB0956E2989CBFE0043A8A1 /* Model+Pending.swift 
in Sources */,
                                4EB095522989CBFE0043A8A1 /* 
ExchangeListView.swift in Sources */,
                                4EB095642989CBFE0043A8A1 /* 
WithdrawProgressView.swift in Sources */,
-                               4EB095582989CBFE0043A8A1 /* 
TransactionDetail.swift in Sources */,
+                               4EEC157A29F9427F00D46A03 /* QRSheet.swift in 
Sources */,
+                               4E3B4BC12A41E6C200CC88B8 /* 
P2pReceiveURIView.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 +792,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 /* 
Model+Withdraw.swift in Sources */,
+                               4EC90C782A1B528B0071DC58 /* 
ExchangeSectionView.swift in Sources */,
+                               4E7940DE29FC307C00A9AEA1 /* SendPurpose.swift 
in Sources */,
+                               4ECB62802A0BA6DF004ABBB7 /* Model+P2P.swift in 
Sources */,
                                4EB0950A2989CB7C0043A8A1 /* TalerStrings.swift 
in Sources */,
+                               4EA551252A2C923600FEC9A8 /* 
CurrencyInputView.swift in Sources */,
+                               4E363CBC2A237E0900D7E98C /* URL+id+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 +831,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 +962,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 
Developer";
+                               CODE_SIGN_STYLE = Manual;
+                               CURRENT_PROJECT_VERSION = 10;
+                               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 +985,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*]" 
= Dev230612a;
                                SUPPORTED_PLATFORMS = "iphoneos 
iphonesimulator";
                                SUPPORTS_MACCATALYST = NO;
                                SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
@@ -812,16 +1004,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 = 10;
+                               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 +1027,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 +1078,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 +1100,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 +1123,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 +1145,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 +1183,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 +1207,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.imageset/Contents.json 
b/TalerWallet1/Assets.xcassets/taler-logo-2023-red.imageset/Contents.json
similarity index 68%
copy from TalerWallet1/Assets.xcassets/Taler-logo.imageset/Contents.json
copy to TalerWallet1/Assets.xcassets/taler-logo-2023-red.imageset/Contents.json
index eb70695..50ee748 100644
--- a/TalerWallet1/Assets.xcassets/Taler-logo.imageset/Contents.json
+++ b/TalerWallet1/Assets.xcassets/taler-logo-2023-red.imageset/Contents.json
@@ -1,9 +1,9 @@
 {
   "images" : [
     {
-      "filename" : "Taler-logo.jpg",
+      "filename" : "taler-logo-2023-red.svg",
       "idiom" : "universal"
-    }
+    },
   ],
   "info" : {
     "author" : "xcode",
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..cf323aa 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,168 @@ 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
+}
+
+enum TransactionType: String, Codable {
+    case dummy
+    case withdrawal
+    case deposit
+    case payment
+    case refund
+    case refresh
+    case reward         = "tip"     // TODO: reward        // get paid for 
e.g. survey participation
+//    case tip                                  // tip personnel at restaurants
+    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 isWithdrawal : Bool { self == .withdrawal }
+    var isDeposit    : Bool { self == .deposit }
+    var isPayment    : Bool { self == .payment }
+    var isRefund     : Bool { self == .refund }
+    var isRefresh    : Bool { self == .refresh }
+    var isReward     : Bool { self == .reward }
+    //    var isTipPayment : Bool { self == .tip }
+    var isSendCoins  : Bool { self == .peerPushDebit }
+    var isRcvCoins   : Bool { self == .scanPushCredit }
+    var isSendInvoice: Bool { self == .peerPullCredit }
+    var isPayInvoice : Bool { self == .scanPullDebit }
+
+    var isP2pOutgoing: Bool { isSendCoins || isPayInvoice}
+    var isP2pIncoming: Bool { isSendInvoice || isRcvCoins}
+    var isIncoming   : Bool { isP2pIncoming || isWithdrawal || isRefund || 
isReward }
+}
+
 struct TransactionCommon: Decodable {
-    var type: String
+    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 localizedType(_ type: TransactionType) -> String {
+        switch type {
+            case .dummy:          return String("")
+            case .withdrawal:     return String(localized: "Withdrawal",
+                                                  comment: "TransactionType")
+            case .deposit:        return String(localized: "Deposit",
+                                                  comment: "TransactionType")
+            case .payment:        return String(localized: "Payment",
+                                                  comment: "TransactionType")
+            case .refund:         return String(localized: "Refund",
+                                                  comment: "TransactionType")
+            case .refresh:        return String(localized: "Refresh",
+                                                  comment: "TransactionType")
+            case .reward:         return String(localized: "Reward",
+                                                  comment: "TransactionType")
+            case .peerPushDebit:  return String(localized: "P2P Send",
+                                                  comment: "TransactionType, 
send coins to another wallet")
+            case .scanPushCredit: return String(localized: "P2P Receive",
+                                                  comment: "TransactionType, 
scan to receive coins sent from another wallet")
+            case .peerPullCredit: return String(localized: "P2P Invoice",
+                                                  comment: "TransactionType, 
send invoice to another wallet")
+            case .scanPullDebit:  return String(localized: "P2P Payment",
+                                                  comment: "TransactionType, 
scan invoice to pay to another wallet")
+        }
+    }
 
     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 +184,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 +213,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 +231,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 +239,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,111 +284,182 @@ 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
+}
+
+struct DummyTransaction {
+    var common: TransactionCommon
+}
+
 enum Transaction: Decodable, Hashable, Identifiable {
+    case dummy (DummyTransaction)
     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 }
+
+    var localizedType: String {
+        common.localizedType(common.type)
+    }
 
     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 isPending    : Bool { common.txState.major == .pending }
+    var isDone       : Bool { common.txState.major == .done }
+    var isAborting   : Bool { common.txState.major == .aborting }
+    var isAborted    : Bool { common.txState.major == .aborted }
+    var isSuspended  : Bool { common.txState.major == .suspended }
+    var isDialog     : Bool { common.txState.major == .dialog }
+    var isAbSuspended: Bool { common.txState.major == .suspendedAborting }
+    var isFailed     : Bool { common.txState.major == .failed }
+    var isExpired    : Bool { common.txState.major == .expired }
+    var isSpecial    : Bool { !(isDone || isPending) }
+
+    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 .dummy(let dummyTransaction):
+                return dummyTransaction.common
             case .withdrawal(let withdrawalTransaction):
                 return withdrawalTransaction.common
             case .payment(let paymentTransaction):
                 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
         }
     }
 
     func detailsToShow() -> Dictionary<String, String> {
         var result: [String:String] = [:]
         switch self {
+            case .dummy(let dummyTransaction):
+                break
             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..6e0b045 100644
--- a/TalerWallet1/Backend/WalletCore.swift
+++ b/TalerWallet1/Backend/WalletCore.swift
@@ -1,22 +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 SwiftUI              // FOUNDATION has no AppStorage
 import AnyCodable
 import FTalerWalletcore
 import SymLog
+import os
 
 /// Delegate for the wallet backend.
 protocol WalletBackendDelegate {
@@ -24,24 +14,30 @@ 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 quickjs: Quickjs
+    private var queue: DispatchQueue
+    private var semaphore: DispatchSemaphore
+
+    private let 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
     var developDelay: Bool?                 // if set in SettingsView will 
delay wallet-core after each action
+    let logger = Logger (subsystem: "net.taler.gnu", category: "wallet-core")
 
     private struct FullRequest: Encodable {
         let operation: String
         let id: UInt
         let args: AnyEncodable
     }
-    
+
     private struct FullResponse: Decodable {
         let type: String
         let operation: String
@@ -58,102 +54,200 @@ 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()
+        logger.log("shutdown Quickjs")
     // TODO: send shutdown message to talerWalletInstance
 //        quickjs.waitStopped()
     }
 
     init() throws {
+        logger.info("init Quickjs")
         requestsMade = 0
+        queue = DispatchQueue(label: "net.taler.myQueue", attributes: 
.concurrent)
+        semaphore = DispatchSemaphore(value: 1)
         quickjs = Quickjs()
         quickjs.messageHandler = self
+        logger.log("Quickjs running")
     }
 }
 // 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 {
+            logger.error("didn't find requestId in error response")
+            // TODO: show error alert
+            throw WalletBackendError.deserializationError
+        }
+        guard let (timeSent, completion) = completions[requestId] else {
+            logger.error("requestId \(requestId, privacy: .public) not in 
list")
+            // TODO: show error alert
+            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)
+                let jsonData = try JSONEncoder().encode(walletError)
+            } catch {        // JSON encoding of response.result failed / 
should never happen
                 symLog.log(decoded)
-                completion(id, nil, decoded.error)
-            } catch {
-                symLog.log(responseDict)        // TODO: error
-                completion(id, nil, WalletCore.parseFailureError())
-            }
-        } 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())
+                logger.error("cannot encode wallet-core Error")
+                // TODO: show error alert
+                completion(requestId, timeSent, nil, 
WalletCore.parseFailureError())
             }
+            // TODO: decode jsonData to WalletBackendResponseError - or 
HTTPError
+            logger.error("wallet-core sent back an error for request 
\(requestId, privacy: .public)")
+//            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 {
+            logger.error("didn't find requestId in response")
+            symLog.log(decoded)                 // TODO: .error
+            throw WalletBackendError.deserializationError
+        }
+        guard let (timeSent, completion) = completions[requestId] else {
+            logger.error("requestId \(requestId, privacy: .public) not in 
list")
+            throw WalletBackendError.deserializationError
+        }
+        completions[requestId] = nil
+        guard let result = decoded.result else {
+            logger.error("requestId \(requestId, privacy: .public) got no 
result")
+            throw WalletBackendError.deserializationError
+        }
+        do {
+            let jsonData = try JSONEncoder().encode(result)
+            symLog.log(result)
+//            logger.info(result)   TODO: log result
+            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())
         }
     }
 
-    private func handleNotification(dict responseDict: [String : Any]) throws {
+    @MainActor
+    private func postNotificationM(_ aName: NSNotification.Name,
+                           object anObject: Any? = nil,
+                                  userInfo: [AnyHashable: Any]? = nil) async {
+        NotificationCenter.default.post(name: aName, object: anObject, 
userInfo: userInfo)
+    }
+    private func postNotification(_ aName: NSNotification.Name,
+                          object anObject: Any? = nil,
+                                 userInfo: [AnyHashable: Any]? = nil) {
+        Task {
+            if let userInfo { symLog.log(userInfo) } else { symLog.log(aName) }
+            await postNotificationM(aName, object: anObject, userInfo: 
userInfo)
+            logger.log("Notification sent: \(aName.rawValue)")
+        }
+    }
+
+    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 if id.hasPrefix("peer-pull-credit:") {
+            // TODO: handle peer-pull-credit
+//            symLog.log("\(pendingOp): \(id)")
+        } else if id.hasPrefix("peer-push-debit:") {
+            // TODO: handle peer-push-debit
+//            symLog.log("\(pendingOp): \(id)")
+        } else {
+            // TODO: handle other pending-operation-processed
+            logger.log("❗️ \(pendingOp, privacy: .public): \(id, privacy: 
.public)")        // this is a new pendingOp I haven't seen before
+        }
+    }
+    private func handleStateTransition(_ jsonData: Data) throws {
         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)
+            let decoded = try JSONDecoder().decode(TransactionTransition.self, 
from: jsonData)
+            if decoded.newTxState != decoded.oldTxState {
+                if decoded.newTxState.major == .done {
+                    let components = 
decoded.transactionId.components(separatedBy: [":"])
+                    if components.count >= 3 {  // txn:$txtype:$uid
+                        if let type = TransactionType(rawValue: components[1]) 
{
+                            Controller.shared.playSound(type.isIncoming ? 2 : 
1)
                         }
                     }
-                    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
-                case "reserve-registered-with-bank":
-                    symLog.log(payload)
+                }
+                postNotification(.TransactionStateTransition,
+                                 userInfo: [TRANSACTIONTRANSITION: decoded])
+            }
+        } catch {       // rethrows
+            symLog.log(jsonData)       // TODO: .error
+            throw WalletBackendError.deserializationError
+        }
+    }
+
+    private func handleNotification(_ anyCodable: AnyCodable?) throws {
+        guard let anyPayload = anyCodable else { throw 
WalletBackendError.deserializationError }
+        do {
+            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.BalanceChange.rawValue:
+                    postNotification(.BalanceChange)
+                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)
+
+                    // TODO: remove these once wallet-core doesn't send them 
anymore
+//                case "reserve-registered-with-bank":
+//                    symLog.log(anyPayload)
+//                case "withdraw-group-finished",
+//                     "pay-operation-success",
+//                     "withdrawal-group-bank-confirmed",          // replaced 
by transaction-state-transition
+//                     "withdrawal-group-reserve-ready",
+//                     "waiting-for-retry",                        // Bla Bla 
Bla
+                case "refresh-started", "refresh-melted",
+                     "refresh-revealed", "refresh-unwarranted":
                     break
                 default:
-                    symLog.log(payload)
+print("\n❗️ WalletCore.swift:226 Notification: ", 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 +256,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 +267,62 @@ 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()
+              self.symLog.log(error)
+                completionHandler(requestId, now, nil, 
WalletCore.serializeRequestError());
+            }
         }
     }
 }
@@ -219,27 +333,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
+                self.logger.log("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 in 
continuation.resume(throwing:), otherwise keep nil
                     }
                 } 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..c95c5a9 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 {
@@ -28,54 +18,40 @@ enum UrlCommand {
     case unknown
     case withdraw
     case pay
+    case payPull
+    case payPush
 }
 
+// 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
+    @AppStorage("playSounds") var playSounds: Bool = false
 
     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 initWalletCore(_ model: WalletModel)
+      async throws {
         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 versionInfo = try await model.initWalletCoreT()
+                WalletCore.shared.versionInfo = versionInfo
+                backendState = .ready                       // dismiss the 
launch animation
+            } catch {       // rethrows
+                symLog.log(error.localizedDescription)      // TODO: .error
+                backendState = .error                       // ❗️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: 
.fault
         }
     }
 }
@@ -112,7 +88,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 {
@@ -120,6 +96,10 @@ extension Controller {
                 return UrlCommand.withdraw
             case "pay":
                 return UrlCommand.pay
+            case "pay-pull":
+                return UrlCommand.payPull
+            case "pay-push":
+                return UrlCommand.payPush
             default:
                 symLog.log("unknown command taler://\(command)")
         }
diff --git a/TalerWallet1/Controllers/DebugViewC.swift 
b/TalerWallet1/Controllers/DebugViewC.swift
new file mode 100644
index 0000000..0d587db
--- /dev/null
+++ b/TalerWallet1/Controllers/DebugViewC.swift
@@ -0,0 +1,189 @@
+/* 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
+import os.log
+
+// 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_RCV_P2P_TOS = SHEET_RCV_P2P + 1                    // 171 
Receive P2P TOSView
+public let SHEET_RCV_P2P_ACCEPT = SHEET_RCV_P2P_TOS + 1             // 172 
Receive P2P AcceptView
+
+//public let SHEET_REFUND =
+
+extension UIDevice {
+    var hasNotch: Bool {
+        let bottom = UIApplication.shared.keyWindow?.safeAreaInsets.bottom ?? 0
+        return bottom > 0
+    }
+}
+// MARK: -
+struct DebugViewV: View {
+    private let symLog = SymLogV(0)
+    @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()
+            if UIDevice.current.hasNotch {
+                Spacer()
+                Spacer()
+            }
+            Text(viewIDString)
+                .font(.caption2)
+                .foregroundColor(.red)
+            Spacer()
+        }
+        .edgesIgnoringSafeArea(.top)
+    }
+}
+// MARK: -
+class DebugViewC: ObservableObject {
+    private let symLog = SymLogC(0)                         // 0 to switch off 
viewID change logging
+    public static let shared = DebugViewC()
+#if DEBUG
+    @AppStorage("developerMode") var developerMode: Bool = true
+#else
+    @AppStorage("developerMode") var developerMode: Bool = false
+#endif
+    let logger = Logger (subsystem: "net.taler.gnu", category: "DebugView")
+
+    @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)")
+                logger.info("switching ON, \(newID, privacy: .public)")
+                viewID = newID                              // publish ON
+            } else if viewID != newID {
+                symLog.log("switching from \(viewID) to \(newID)")
+                logger.info("switching from \(self.viewID, privacy: .public) 
to \(newID, privacy: .public)")
+                viewID = newID                              // publish new 
viewID
+            } else {
+                symLog.log("\(newID) stays")
+                logger.info("\(newID, privacy: .public) 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)")
+            logger.info("switching OFF, will not use \(newID, privacy: 
.public)")
+            viewID = 0                                      // publish OFF
+        } else {
+            symLog.log("off, will not use \(newID)")
+            logger.info("off, will not use \(newID, privacy: .public)")
+            // 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)")
+                logger.info("switching from \(self.sheetID, privacy: .public) 
to \(newID, privacy: .public) for sheet")
+                sheetID = newID                             // publish new 
sheetID
+            } else {
+                symLog.log("\(newID) stays")
+                logger.info("\(newID, privacy: .public) stays for sheet")
+                // 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)")
+            logger.info("switching OFF, will not use \(newID, privacy: 
.public) for sheet")
+            sheetID = 0                                     // publish OFF
+        } else {
+            symLog.log("off, will not use \(newID)")
+            logger.info("off, will not use \(newID, privacy: .public) for 
sheet")
+            // 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..35ff9f1
--- /dev/null
+++ b/TalerWallet1/Controllers/PublicConstants.swift
@@ -0,0 +1,48 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import Foundation
+
+public let LAUNCHDURATION: Double = 1.60
+public let SLIDEDURATION: Double = 0.45
+
+public let HTTPS = "https://";
+//public let DEMOBANK = HTTPS + "bAnK.dEmO.tAlEr.nEt"             // should be 
weird to read, but still work
+public let DEMOBANK = HTTPS + "bank.demo.taler.net"
+public let DEMOSHOP = HTTPS + "shop.demo.taler.net"
+public let DEMOBACKEND = HTTPS + "backend.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"
+
+/// Notifications sent by wallet-core
+extension Notification.Name {
+    static let BalanceChange = Notification.Name("balance-change")
+    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")
+}
+
+/// Notifications for internal synchronization
+extension Notification.Name {
+    static let BalanceReloaded = Notification.Name("balanceReloaded")
+}
diff --git a/TalerWallet1/Controllers/TalerWallet1App.swift 
b/TalerWallet1/Controllers/TalerWallet1App.swift
index 1eac830..bff3e7a 100644
--- a/TalerWallet1/Controllers/TalerWallet1App.swift
+++ b/TalerWallet1/Controllers/TalerWallet1App.swift
@@ -1,53 +1,75 @@
 /*
- * 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
 import SymLog
+import os.log
 
 @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
+    @State private var soundPlayed = false
 
+    private let walletCore = WalletCore.shared
     // our main controller
-    @StateObject private var controller = Controller.shared
+    private let controller = Controller.shared
+    private let model = WalletModel.shared
+    private let debugViewC = DebugViewC.shared
+    let logger = Logger (subsystem: "net.taler.gnu", category: "Main App")
 
     func scheduleAppRefresh() {
-        let request = BGAppRefreshTaskRequest(identifier: "net.taler.refresh")
+        let request = BGAppRefreshTaskRequest(identifier: 
"net.taler.gnu.refresh")
         request.earliestBeginDate = .now.addingTimeInterval(24 * 3600)
         try? BGTaskScheduler.shared.submit(request)
     }
 
     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(soundPlayed: $soundPlayed)
+                .environmentObject(debugViewC)      // change viewID / sheetID
+                .environmentObject(viewState)       // popToRoot
+                .environmentObject(controller)
+                .environmentObject(model)
+                    /// external events are taler:// or payto:// URLs passed 
to this app
+                    /// we handle them in .onOpenURL in MainView.swift
+                .handlesExternalEvents(preferring: ["*"], allowing: ["*"])
+                .task {
+                    try! await controller.initWalletCore(model)     // will 
(and should) crash on failure
+                }
+                .onReceive(NotificationCenter.default.publisher(for: 
UIApplication.didBecomeActiveNotification, object: nil)) { _ in
+                    logger.log("❗️App Did Become Active")
+                }
+                .onReceive(NotificationCenter.default.publisher(for: 
UIApplication.willResignActiveNotification, object: nil)) { _ in
+                    logger.log("❗️App Will Resign")
+                    isActive = false
+                }
+                .onReceive(NotificationCenter.default.publisher(for: 
UIApplication.willEnterForegroundNotification, object: nil)) { _ in
+                    logger.log("❗️App Will Enter Foreground")
+                    isActive = true
+                }
+                .onReceive(NotificationCenter.default.publisher(for: 
UIApplication.willTerminateNotification, object: nil)) { _ in
+                    logger.log("❗️App Will Terminate")
+                }
+
         }
         .onChange(of: phase) { newPhase in
             switch newPhase {
-                case .background: scheduleAppRefresh()
+                case .active:
+                    logger.log("❗️.onChange() ==> Active")
+                case .background:
+                    logger.log("❗️.onChange() ==> Background)")
+//                    scheduleAppRefresh()
                 default: break
             }
         }
@@ -76,3 +98,16 @@ struct TalerWallet1App: App {
 
     }
 }
+
+final class ViewState : ObservableObject {
+    static let shared = ViewState()
+    @Published var rootViewId = UUID()
+    let logger = Logger (subsystem: "net.taler.gnu", category: "ViewState")
+
+    public func popToRootView() -> Void {
+        logger.info("popToRootView")
+        rootViewId = UUID() // setting a new ID will cause tableView 
popToRootView behaviour
+    }
+
+    private init() { }
+}
diff --git a/TalerWallet1/Helper/AgePicker.swift 
b/TalerWallet1/Helper/AgePicker.swift
new file mode 100644
index 0000000..6f02d82
--- /dev/null
+++ b/TalerWallet1/Helper/AgePicker.swift
@@ -0,0 +1,49 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+
+struct AgePicker: View {
+    @Binding var ageMenuList: [Int]
+    @Binding var selectedAge: Int
+
+    func setAges(ages: [Int]?) {
+        if let ages {
+            var zero: [Int] = []
+            if ages.count > 0 {        // need at least 1 value from exchange 
which is not 0
+                if ages[0] != 0 {               // ensure that the first age 
is "0"
+                    zero.insert(0, at: 0)       // if not, insert "0" at 
position 0
+                }
+                zero += ages
+                if selectedAge >= zero.count {  // check for out of bounds
+                    selectedAge = 0
+                }
+            } else {
+                selectedAge = 0                 // first ensure that selected 
is not out of bounds
+            }
+            ageMenuList = zero                  // set State (will update view)
+        }
+    }
+
+    var body: some View {
+        if ageMenuList.count > 1 {
+            VStack {
+                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) ? "unrestricted"
+                                          : "\(index) years").tag(index)
+                    }
+                }
+            }
+        }
+    }
+}
+
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+id+iban.swift 
b/TalerWallet1/Helper/URL+id+iban.swift
new file mode 100644
index 0000000..ca4689c
--- /dev/null
+++ b/TalerWallet1/Helper/URL+id+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+Notification.swift 
b/TalerWallet1/Helper/View+Notification.swift
new file mode 100644
index 0000000..00760bf
--- /dev/null
+++ b/TalerWallet1/Helper/View+Notification.swift
@@ -0,0 +1,75 @@
+//  MIT License
+//  Copyright © John Sundell
+//
+//  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 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
+        )
+    }
+}
diff --git a/TalerWallet1/Helper/View+dismissTop.swift 
b/TalerWallet1/Helper/View+dismissTop.swift
index bf6721e..40f0084 100644
--- a/TalerWallet1/Helper/View+dismissTop.swift
+++ b/TalerWallet1/Helper/View+dismissTop.swift
@@ -1,18 +1,21 @@
-/*
- * 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
 
 /// This is just a workaround for a SwiftUI bug
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/Helper/playSound.swift 
b/TalerWallet1/Helper/playSound.swift
new file mode 100644
index 0000000..ed39bef
--- /dev/null
+++ b/TalerWallet1/Helper/playSound.swift
@@ -0,0 +1,25 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import Foundation
+import AVFoundation
+
+extension Controller {
+
+    func playSound(_ number: Int) {
+        var soundID: SystemSoundID = 0
+        if number < 999 {
+            let sound = (number == 0) ? "payment_failure" :
+            (number == 1) ? "payment_success" : "PaymentReceived"
+            let fileURL = URL(fileURLWithPath: 
"/System/Library/Audio/UISounds/"
+                              + sound + ".caf")
+            AudioServicesCreateSystemSoundID(fileURL as CFURL, &soundID)
+        } else {
+            soundID = UInt32(number)
+        }
+        if playSounds {
+            AudioServicesPlaySystemSound(soundID);
+        }
+    }
+}
diff --git a/TalerWallet1/Model/ExchangeTestModel.swift 
b/TalerWallet1/Model/ExchangeTestModel.swift
deleted file mode 100644
index 98702d5..0000000
--- a/TalerWallet1/Model/ExchangeTestModel.swift
+++ /dev/null
@@ -1,115 +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
-
-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_MERCHANTBASEURL = "https://backend.demo.taler.net/";
-fileprivate let DEMO_MERCHANTAUTHTOKEN = "secret-token:sandbox"
-
-// MARK: -
-class ExchangeTestModel: WalletModel {
-}
-// MARK: -
-extension ExchangeTestModel {
-    @MainActor func loadTestKudos() async throws {
-        do {
-            let amount = Amount(currency: "KUDOS", 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)
-            symLog?.log("received: \(response)")
-        } catch {
-            throw error
-        }
-    }
-
-    @MainActor func runIntegrationTest() async throws {
-        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 {
-            throw error
-        }
-    }
-}
-
-/// A request to add a test balance to the wallet.
-fileprivate struct WalletBackendWithdrawTestBalance: 
WalletBackendFormattedRequest {
-    typealias Response = String
-    func operation() -> String { return "withdrawTestBalance" }
-    func args() -> Args {
-        return Args(amount: amount, bankBaseUrl: bankBaseUrl,
-                    exchangeBaseUrl: exchangeBaseUrl, bankAccessApiBaseUrl: 
bankAccessApiBaseUrl)
-    }
-
-    var amount: Amount
-    var bankBaseUrl: String
-    var exchangeBaseUrl: String
-    var bankAccessApiBaseUrl: String
-
-    struct Args: Encodable {
-        var amount: Amount
-        var bankBaseUrl: String
-        var exchangeBaseUrl: String
-        var bankAccessApiBaseUrl: String
-    }
-}
-
-/// A request to add a test balance to the wallet.
-fileprivate struct WalletBackendRunIntegration: WalletBackendFormattedRequest {
-    typealias Response = String
-    func operation() -> String { return "runIntegrationTest" }
-    func args() -> Args {
-        return Args(amountToWithdraw: amountToWithdraw,
-                    amountToSpend: amountToSpend,
-                    bankBaseUrl: bankBaseUrl,
-                    exchangeBaseUrl: exchangeBaseUrl,
-                    merchantBaseUrl: merchantBaseUrl,
-                    merchantAuthToken: merchantAuthToken
-        )
-    }
-
-    var amountToWithdraw: Amount
-    var amountToSpend: Amount
-    var bankBaseUrl: String
-    var exchangeBaseUrl: String
-    var merchantBaseUrl: String
-    var merchantAuthToken: String
-
-    struct Args: Encodable {
-        var amountToWithdraw: Amount
-        var amountToSpend: Amount
-        var bankBaseUrl: String
-        var exchangeBaseUrl: String
-        var merchantBaseUrl: String
-        var merchantAuthToken: String
-    }
-}
diff --git a/TalerWallet1/Model/Model+Balances.swift 
b/TalerWallet1/Model/Model+Balances.swift
new file mode 100644
index 0000000..f1a651c
--- /dev/null
+++ b/TalerWallet1/Model/Model+Balances.swift
@@ -0,0 +1,57 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import Foundation
+import taler_swift
+fileprivate let ASYNCDELAY: UInt = 0   //set e.g to 6 or 9 seconds for 
debugging
+
+// MARK: -
+/// A currency balance
+struct Balance: Decodable, Hashable {
+    var available: Amount
+    var scopeInfo: ScopeInfo
+    var requiresUserInput: Bool
+    var hasPendingTransactions: Bool
+
+    public static func == (lhs: Balance, rhs: Balance) -> Bool {
+        return lhs.available == rhs.available &&
+        lhs.scopeInfo == rhs.scopeInfo &&
+        lhs.requiresUserInput == rhs.requiresUserInput &&
+        lhs.hasPendingTransactions == rhs.hasPendingTransactions
+    }
+
+    public func hash(into hasher: inout Hasher) {
+        hasher.combine(available)
+        hasher.combine(scopeInfo)
+        hasher.combine(requiresUserInput)
+        hasher.combine(hasPendingTransactions)
+    }
+}
+// MARK: -
+/// A request to get the balances held in the wallet.
+fileprivate struct Balances: WalletBackendFormattedRequest {
+    func operation() -> String { return "getBalances" }
+    func args() -> Args { return Args() }
+
+    struct Args: Encodable {}                           // no arguments needed
+
+    struct Response: Decodable {                        // list of balances
+        var balances: [Balance]
+    }
+}
+// MARK: -
+extension WalletModel {
+    /// fetch Balances from Wallet-Core. No networking involved
+    @MainActor func balancesM()
+      async -> [Balance] {          // M for MainActor
+        do {
+            let request = Balances()
+            let response = try await sendRequest(request, ASYNCDELAY)
+            return response.balances                // trigger view update in 
BalancesListView
+        } catch {
+            logger.error("balancesM failed: \(error)")
+            return []
+        }
+    }
+}
diff --git a/TalerWallet1/Views/Exchange/ExchangeModel.swift 
b/TalerWallet1/Model/Model+Exchange.swift
similarity index 53%
rename from TalerWallet1/Views/Exchange/ExchangeModel.swift
rename to TalerWallet1/Model/Model+Exchange.swift
index c16e720..8446893 100644
--- a/TalerWallet1/Views/Exchange/ExchangeModel.swift
+++ b/TalerWallet1/Model/Model+Exchange.swift
@@ -1,54 +1,15 @@
 /*
- * 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
 
-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
-
-    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: -
-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 +18,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 +37,64 @@ 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 {
+    struct Response: Decodable {}   // no result - getting no error back means 
success
+    func operation() -> String { return "addExchange" }
+    func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl) }
+
+    var exchangeBaseUrl: String
+
+    struct Args: Encodable {
+        var exchangeBaseUrl: String
+    }
+}
 // MARK: -
-extension ExchangeModel {
+extension WalletModel {
     /// ask wallet-core for its list of known exchanges
-    @MainActor func updateList() async throws {
+    @MainActor func listExchangesM()
+      async -> [Exchange] {   // M for MainActor
         do {
             let request = ListExchanges()
             let response = try await sendRequest(request, ASYNCDELAY)
-            exchanges = response.exchanges              // trigger view update 
in ExchangeListView
-        } catch { // TODO: Error
-            symLog?.log(error.localizedDescription)
-            throw error
+            return response.exchanges
+        } catch {
+            return []               // empty, but not nil
         }
     }
 
-    /// add a new exchange with URL to 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)
-            symLog?.log("added exchange: \(url)")
-            try await updateList()
-        } catch { // TODO: Error
-            symLog?.log(error.localizedDescription)
-            throw error
-        }
+    /// add a new exchange with URL to the wallet's list of known exchanges
+    func addExchange(url: String)
+      async throws  {
+        let request = AddExchange(exchangeBaseUrl: url)
+        logger.info("adding exchange: \(url, privacy: .public)")
+        _ = try await sendRequest(request)
     }
 }
diff --git a/TalerWallet1/Model/Model+P2P.swift 
b/TalerWallet1/Model/Model+P2P.swift
new file mode 100644
index 0000000..a671673
--- /dev/null
+++ b/TalerWallet1/Model/Model+P2P.swift
@@ -0,0 +1,259 @@
+/*
+ * 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
+
+// MARK: common structures
+struct PeerContractTerms: Codable {
+    let amount: Amount
+    let summary: String
+    let purse_expiration: Timestamp
+}
+// MARK: - PeerPushDebit
+/// Check if initiating a peer push payment is possible, check fees
+struct AmountResponse: Codable {
+    let effectiveAmount: Amount
+    let rawAmount: Amount
+}
+fileprivate struct GetMaxPeerPushAmount: WalletBackendFormattedRequest {
+    typealias Response = AmountResponse
+    func operation() -> String { return "GetMaxPeerPushAmount" }
+    func args() -> Args { return Args(currency: currency) }
+
+    var currency: String
+    struct Args: Encodable {
+        var currency: String
+    }
+}
+extension WalletModel {
+    @MainActor
+    func getMaxPeerPushAmountM(_ currency: String)       // M for MainActor
+      async throws -> AmountResponse {
+        let request = GetMaxPeerPushAmount(currency: currency)
+        let response = try await sendRequest(request, ASYNCDELAY)
+        return response
+    }
+} // getMaxPeerPushAmountM
+// - - - - - -
+struct CheckPeerPushDebitResponse: Codable {
+    let exchangeBaseUrl: String?
+    let amountRaw: Amount
+    let amountEffective: Amount
+    let maxExpirationDate: Timestamp?          // TODO: limit expiration (30 
days or 7 days)
+}
+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
+    }
+}
+extension WalletModel {
+    @MainActor
+    func checkPeerPushDebitM(_ amount: Amount)       // M for MainActor
+      async throws -> CheckPeerPushDebitResponse {
+        let request = CheckPeerPushDebit(amount: amount)
+        let response = try await sendRequest(request, ASYNCDELAY)
+        return response
+    }
+} // checkPeerPushDebitM
+// - - - - - -
+/// Initiate an outgoing peer push payment, send coins
+struct InitiatePeerPushDebitResponse: Codable {
+    let contractPriv: String
+    let mergePriv: String
+    let pursePub: String
+    let exchangeBaseUrl: String
+    let talerUri: String
+    let transactionId: String
+}
+fileprivate struct InitiatePeerPushDebit: WalletBackendFormattedRequest {
+    typealias Response = InitiatePeerPushDebitResponse
+    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
+    }
+}
+extension WalletModel {
+    @MainActor
+    func initiatePeerPushDebitM(_ baseURL: String?, terms: PeerContractTerms)  
     // M for MainActor
+    async throws -> InitiatePeerPushDebitResponse {
+        let request = InitiatePeerPushDebit(exchangeBaseUrl: baseURL,
+                                            partialContractTerms: terms)
+        let response = try await sendRequest(request, ASYNCDELAY)
+        return response
+    }
+} // initiatePeerPushDebitM
+// MARK: - PeerPullCredit
+/// Check fees before sending an invoice to another wallet
+struct CheckPeerPullCreditResponse: Codable {
+    let scopeInfo: ScopeInfo?
+    let exchangeBaseUrl: String?
+    let amountRaw: Amount
+    let amountEffective: Amount
+    var numCoins: Int?                  // Number of coins this 
amountEffective will create
+}
+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
+    }
+}
+extension WalletModel {
+    @MainActor
+    func checkPeerPullCreditM(_ amount: Amount, exchangeBaseUrl: String?)      
 // M for MainActor
+      async throws -> CheckPeerPullCreditResponse {
+        let request = CheckPeerPullCredit(exchangeBaseUrl: exchangeBaseUrl, 
amount: amount)
+        let response = try await sendRequest(request, ASYNCDELAY)
+        return response
+    }
+} // checkPeerPullCreditM
+// - - - - - -
+/// Initiate an outgoing peer pull payment, send an invoice
+struct InitiatePeerPullCreditResponse: Codable {
+    let talerUri: String
+    let transactionId: String
+}
+fileprivate struct InitiatePeerPullCredit: WalletBackendFormattedRequest {
+    typealias Response = InitiatePeerPullCreditResponse
+    func operation() -> String { return "initiatePeerPullCredit" }
+    func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl,
+                                 partialContractTerms: partialContractTerms) }
+
+    var exchangeBaseUrl: String?
+    var partialContractTerms: PeerContractTerms
+    struct Args: Encodable {
+        var exchangeBaseUrl: String?
+        var partialContractTerms: PeerContractTerms
+    }
+}
+extension WalletModel {
+    @MainActor
+    func initiatePeerPullCreditM(_ baseURL: String?, terms: PeerContractTerms) 
 // M for MainActor
+      async throws -> InitiatePeerPullCreditResponse {
+        let request = InitiatePeerPullCredit(exchangeBaseUrl: baseURL,
+                                        partialContractTerms: terms)
+        let response = try await sendRequest(request, ASYNCDELAY)
+        return response
+    }
+} // initiatePeerPullCreditM
+// MARK: - PeerPushCredit
+/// Prepare an incoming peer push payment, receive coins
+struct PreparePeerPushCreditResponse: Codable {
+    let contractTerms: PeerContractTerms
+    let amountRaw: Amount
+    let amountEffective: Amount
+    // the dialog transaction is already created in the local DB - must either 
accept or delete
+    let transactionId: String
+}
+fileprivate struct PreparePeerPushCredit: WalletBackendFormattedRequest {
+    typealias Response = PreparePeerPushCreditResponse
+    func operation() -> String { return "preparePeerPushCredit" }
+    func args() -> Args { return Args(talerUri: talerUri) }
+
+    var talerUri: String
+    struct Args: Encodable {
+        var talerUri: String
+    }
+}
+extension WalletModel {
+    @MainActor
+    func preparePeerPushCreditM(_ talerUri: String)       // M for MainActor
+      async throws -> PreparePeerPushCreditResponse {
+        let request = PreparePeerPushCredit(talerUri: talerUri)
+        let response = try await sendRequest(request, ASYNCDELAY)
+        return response
+    }
+} // preparePeerPushCreditM
+// - - - - - -
+/// Accept an incoming peer push payment
+fileprivate struct AcceptPeerPushCredit: WalletBackendFormattedRequest {
+    struct Response: Decodable {}   // no result - getting no error back means 
success
+    func operation() -> String { return "confirmPeerPushCredit" } // should be 
"acceptPeerPushCredit"
+    func args() -> Args { return Args(transactionId: transactionId) }
+
+    var transactionId: String
+    struct Args: Encodable {
+        var transactionId: String
+    }
+}
+extension WalletModel {
+    @MainActor
+    func acceptPeerPushCreditM(_ transactionId: String)       // M for 
MainActor
+      async throws -> Decodable {
+        let request = AcceptPeerPushCredit(transactionId: transactionId)
+        let response = try await sendRequest(request, ASYNCDELAY)
+        return response
+    }
+} // acceptPeerPushCreditM
+// MARK: - PeerPullDebit
+/// Prepare an incoming peer push invoice, pay coins
+struct PreparePeerPullDebitResponse: Codable {
+    let contractTerms: PeerContractTerms
+    let amountRaw: Amount
+    let amountEffective: Amount
+    // the dialog transaction is already created in the local DB - must either 
accept or delete
+    let transactionId: String
+}
+fileprivate struct PreparePeerPullDebit: WalletBackendFormattedRequest {
+    typealias Response = PreparePeerPullDebitResponse
+    func operation() -> String { return "preparePeerPullDebit" }
+    func args() -> Args { return Args(talerUri: talerUri) }
+
+    var talerUri: String
+    struct Args: Encodable {
+        var talerUri: String
+    }
+}
+extension WalletModel {
+    @MainActor
+    func preparePeerPullDebitM(_ talerUri: String)       // M for MainActor
+      async throws -> PreparePeerPullDebitResponse {
+        let request = PreparePeerPullDebit(talerUri: talerUri)
+        let response = try await sendRequest(request, ASYNCDELAY)
+        return response
+    }
+} // preparePeerPullDebitM
+// - - - - - -
+/// Confirm incoming peer push invoice and pay
+fileprivate struct ConfirmPeerPullDebit: WalletBackendFormattedRequest {
+    struct Response: Decodable {}   // no result - getting no error back means 
success
+    func operation() -> String { return "confirmPeerPullDebit" }
+    func args() -> Args { return Args(transactionId: transactionId) }
+
+    var transactionId: String
+    struct Args: Encodable {
+        var transactionId: String
+    }
+}
+extension WalletModel {
+    @MainActor
+    func confirmPeerPullDebitM(_ transactionId: String)       // M for 
MainActor
+      async throws -> Decodable {
+        let request = ConfirmPeerPullDebit(transactionId: transactionId)
+        let response = try await sendRequest(request, ASYNCDELAY)
+        return response
+    }
+} // confirmPeerPullDebitM
diff --git a/TalerWallet1/Views/Payment/PaymentURIModel.swift 
b/TalerWallet1/Model/Model+Payment.swift
similarity index 64%
rename from TalerWallet1/Views/Payment/PaymentURIModel.swift
rename to TalerWallet1/Model/Model+Payment.swift
index 8fd4142..940b126 100644
--- a/TalerWallet1/Views/Payment/PaymentURIModel.swift
+++ b/TalerWallet1/Model/Model+Payment.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,19 +8,6 @@ import AnyCodable
 //import SymLog
 fileprivate let ASYNCDELAY: UInt = 0   //set e.g to 6 or 9 seconds for 
debugging
 
-enum PaymentState {
-    case error
-    case waitingForUriDetails
-    case receivedUriDetails
-    case waitingForPaymentAck
-    case receivedPaymentAck
-}
-
-class PaymentURIModel: WalletModel {
-    @Published var paymentState: PaymentState?
-}
-
-
 // MARK: - ContractTerms
 struct ContractTerms: Codable {
     let amount: Amount
@@ -77,7 +53,6 @@ struct ContractTerms: Codable {
         case wireFeeAmortization = "wire_fee_amortization"
     }
 }
-
 // MARK: - Auditor
 struct Auditor: Codable {
     let name: String
@@ -90,7 +65,6 @@ struct Auditor: Codable {
         case url
     }
 }
-
 // MARK: - Exchange
 struct ExchangeForPay: Codable {
     let url: String
@@ -101,7 +75,6 @@ struct ExchangeForPay: Codable {
         case masterPub = "master_pub"
     }
 }
-
 // MARK: - Extra
 struct Extra: Codable {
     let articleName: String
@@ -110,11 +83,10 @@ struct Extra: Codable {
         case articleName = "article_name"
     }
 }
-
 // 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 +106,7 @@ fileprivate struct PreparePayForUri: 
WalletBackendFormattedRequest {
     }
 }
 // MARK: -
-/// The result from getPaymentDetailsForAmount
+/// The result from confirmPayForUri
 struct ConfirmPayResult: Decodable {
     var type: String
     var contractTerms: ContractTerms
@@ -152,32 +124,20 @@ fileprivate struct confirmPayForUri: 
WalletBackendFormattedRequest {
     }
 }
 // MARK: -
-extension PaymentURIModel {
+extension WalletModel {
     /// load payment details. Networking involved
     @MainActor
-    func preparePayForUri(_ talerPayUri: String) async throws -> 
PaymentDetailsForUri {
-        do {
-            paymentState = .waitingForUriDetails
-            let request = PreparePayForUri(talerPayUri: talerPayUri)
-            let response = try await sendRequest(request, ASYNCDELAY)
-            paymentState = .receivedUriDetails
-            return response
-        } catch {
-            paymentState = .error
-            throw error
-        }
+    func preparePayForUriM(_ talerPayUri: String)       // M for MainActor
+      async throws -> PaymentDetailsForUri {
+          let request = PreparePayForUri(talerPayUri: talerPayUri)
+          let response = try await sendRequest(request, ASYNCDELAY)
+          return response
     }
     @MainActor
-    func confirmPay(_ proposalId: String) async throws -> ConfirmPayResult {
-        do {
-            paymentState = .waitingForPaymentAck
-            let request = confirmPayForUri(proposalId: proposalId)
-            let response = try await sendRequest(request, ASYNCDELAY)
-            paymentState = .receivedPaymentAck
-            return response
-        } catch {
-            paymentState = .error
-            throw error
-        }
+    func confirmPayM(_ proposalId: String)              // M for MainActor
+      async throws -> ConfirmPayResult {
+          let request = confirmPayForUri(proposalId: proposalId)
+          let response = try await sendRequest(request, ASYNCDELAY)
+          return response
     }
 }
diff --git a/TalerWallet1/Model/Model+Pending.swift 
b/TalerWallet1/Model/Model+Pending.swift
new file mode 100644
index 0000000..eb9f447
--- /dev/null
+++ b/TalerWallet1/Model/Model+Pending.swift
@@ -0,0 +1,55 @@
+/*
+ * 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
+
+// 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 WalletModel {
+    @MainActor func getPendingOperationsM()
+      async -> [PendingOperation] {   // M for MainActor
+        do {
+            let request = GetPendingOperations()
+            let response = try await sendRequest(request, ASYNCDELAY)
+            return response.pendingOperations
+        } catch {
+            return []
+        }
+    }
+}
diff --git a/TalerWallet1/Model/Model+Settings.swift 
b/TalerWallet1/Model/Model+Settings.swift
new file mode 100644
index 0000000..6d13b15
--- /dev/null
+++ b/TalerWallet1/Model/Model+Settings.swift
@@ -0,0 +1,100 @@
+/*
+ * 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 = DEMOEXCHANGE
+fileprivate let DEMO_BANKBASEURL     = DEMOBANK
+fileprivate let DEMO_BANKAPIBASEURL  = DEMOBANK + 
"/demobanks/default/access-api/"
+fileprivate let DEMO_MERCHANTBASEURL = DEMOBACKEND
+fileprivate let DEMO_MERCHANTAUTHTOKEN = "secret-token:sandbox"
+
+// MARK: -
+/// A request to add a test balance to the wallet.
+fileprivate struct WalletBackendWithdrawTestBalance: 
WalletBackendFormattedRequest {
+    struct Response: Decodable {}   // no result - getting no error back means 
success
+    func operation() -> String { return "withdrawTestBalance" }
+    func args() -> Args {
+        return Args(amount: amount, bankBaseUrl: bankBaseUrl,
+                    exchangeBaseUrl: exchangeBaseUrl, bankAccessApiBaseUrl: 
bankAccessApiBaseUrl)
+    }
+
+    var amount: Amount
+    var bankBaseUrl: String
+    var exchangeBaseUrl: String
+    var bankAccessApiBaseUrl: String
+
+    struct Args: Encodable {
+        var amount: Amount
+        var bankBaseUrl: String
+        var exchangeBaseUrl: String
+        var bankAccessApiBaseUrl: String
+    }
+}
+extension WalletModel {
+    @MainActor func loadTestKudosM()
+    async throws {          // M for MainActor
+        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)
+    }
+} // loadTestKudosM()
+// MARK: -
+/// A request to add a test balance to the wallet.
+fileprivate struct WalletBackendRunIntegration: WalletBackendFormattedRequest {
+    struct Response: Decodable {}   // no result - getting no error back means 
success
+    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
+
+    struct Args: Encodable {
+        var amountToWithdraw: Amount
+        var amountToSpend: Amount
+        var bankBaseUrl: String
+        var bankAccessApiBaseUrl: String
+        var exchangeBaseUrl: String
+        var merchantBaseUrl: String
+        var merchantAuthToken: String
+    }
+}
+extension WalletModel {
+    @MainActor func runIntegrationTestM(newVersion: Bool)
+    async throws {               // M for MainActor
+        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 sendRequest(request, ASYNCDELAY)
+    }
+} // runIntegrationTestM()
diff --git a/TalerWallet1/Model/Model+Transactions.swift 
b/TalerWallet1/Model/Model+Transactions.swift
new file mode 100644
index 0000000..0795255
--- /dev/null
+++ b/TalerWallet1/Model/Model+Transactions.swift
@@ -0,0 +1,158 @@
+/*
+ * 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: -
+extension WalletModel {
+    static func specialTransactions(_ transactions: [Transaction]) -> 
[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
+        }
+    }
+}
+
+// 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 {
+    struct Response: Decodable {}   // no result - getting no error back means 
success
+    func operation() -> String { return "abortTransaction" }
+    func args() -> Args { return Args(transactionId: transactionId) }
+
+    var transactionId: String
+    struct Args: Encodable {
+        var transactionId: String
+    }
+}
+/// A request to delete a wallet transaction by ID.
+struct DeleteTransaction: WalletBackendFormattedRequest {
+    struct Response: Decodable {}   // no result - getting no error back means 
success
+    func operation() -> String { return "deleteTransaction" }
+    func args() -> Args { return Args(transactionId: transactionId) }
+
+    var transactionId: String
+    struct Args: Encodable {
+        var transactionId: String
+    }
+}
+/// A request to delete a wallet transaction by ID.
+struct FailTransaction: WalletBackendFormattedRequest {
+    struct Response: Decodable {}   // no result - getting no error back means 
success
+    func operation() -> String { return "failTransaction" }
+    func args() -> Args { return Args(transactionId: transactionId) }
+
+    var transactionId: String
+    struct Args: Encodable {
+        var transactionId: String
+    }
+}
+/// A request to suspend a wallet transaction by ID.
+struct SuspendTransaction: WalletBackendFormattedRequest {
+    struct Response: Decodable {}   // no result - getting no error back means 
success
+    func operation() -> String { return "suspendTransaction" }
+    func args() -> Args { return Args(transactionId: transactionId) }
+
+    var transactionId: String
+    struct Args: Encodable {
+        var transactionId: String
+    }
+}
+/// A request to suspend a wallet transaction by ID.
+struct ResumeTransaction: WalletBackendFormattedRequest {
+    struct Response: Decodable {}   // no result - getting no error back means 
success
+    func operation() -> String { return "resumeTransaction" }
+    func args() -> Args { return Args(transactionId: transactionId) }
+
+    var transactionId: String
+    struct Args: Encodable {
+        var transactionId: String
+    }
+}
+// MARK: -
+extension WalletModel {
+    /// ask wallet-core for its list of transactions filtered by searchString
+    func transactionsT(currency: String? = nil, searchString: String? = nil)
+    async -> [Transaction] {                                          // might 
be called from a background thread itself
+        do {
+            let request = GetTransactions(currency: currency, search: 
searchString)
+            let response = try await sendRequest(request, ASYNCDELAY)
+            return response.transactions
+        } catch {
+            return []
+        }
+    }
+    /// fetch transactions from Wallet-Core. No networking involved
+    @MainActor func transactionsMA(currency: String? = nil, searchString: 
String? = nil)
+    async -> [Transaction] {    // M for MainActor
+        return await transactionsT(currency: currency, searchString: 
searchString)
+    }
+
+    /// abort the specified transaction from Wallet-Core. No networking 
involved
+    func abortTransaction(transactionId: String) async throws {
+        let request = AbortTransaction(transactionId: transactionId)
+        logger.info("abortTransaction: \(transactionId, privacy: 
.private(mask: .hash))")
+        let _ = try await sendRequest(request, ASYNCDELAY)
+    }
+
+    /// delete the specified transaction from Wallet-Core. No networking 
involved
+    func deleteTransaction(transactionId: String) async throws {
+        let request = DeleteTransaction(transactionId: transactionId)
+        logger.info("deleteTransaction: \(transactionId, privacy: 
.private(mask: .hash))")
+        let _ = try await sendRequest(request, ASYNCDELAY)
+    }
+
+    func failTransaction(transactionId: String) async throws {
+        let request = FailTransaction(transactionId: transactionId)
+        logger.info("failTransaction: \(transactionId, privacy: .private(mask: 
.hash))")
+        let _ = try await sendRequest(request, ASYNCDELAY)
+    }
+
+    func suspendTransaction(transactionId: String) async throws {
+        let request = SuspendTransaction(transactionId: transactionId)
+        logger.info("suspendTransaction: \(transactionId, privacy: 
.private(mask: .hash))")
+        let _ = try await sendRequest(request, ASYNCDELAY)
+    }
+
+    func resumeTransaction(transactionId: String) async throws {
+        let request = ResumeTransaction(transactionId: transactionId)
+        logger.info("resumeTransaction: \(transactionId, privacy: 
.private(mask: .hash))")
+        let _ = try await sendRequest(request, ASYNCDELAY)
+    }
+}
diff --git a/TalerWallet1/Model/Model+Withdraw.swift 
b/TalerWallet1/Model/Model+Withdraw.swift
new file mode 100644
index 0000000..0a1b5e2
--- /dev/null
+++ b/TalerWallet1/Model/Model+Withdraw.swift
@@ -0,0 +1,187 @@
+/*
+ * 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: -
+/// The result from getWithdrawalDetailsForUri
+struct WithdrawUriInfoResponse: Decodable {
+    var amount: Amount
+    var defaultExchangeBaseUrl: String?             // TODO: might be nil 
❗️Yikes
+    var possibleExchanges: [ExchangeListItem]       // TODO: query these for 
fees?
+}
+struct ExchangeListItem: Codable, Hashable {
+    var exchangeBaseUrl: String
+    var currency: String
+    var paytoUris: [String]
+
+    public static func == (lhs: ExchangeListItem, rhs: ExchangeListItem) -> 
Bool {
+        return lhs.exchangeBaseUrl == rhs.exchangeBaseUrl &&
+        lhs.currency == rhs.currency &&
+        lhs.paytoUris == rhs.paytoUris
+    }
+
+    public func hash(into hasher: inout Hasher) {
+        hasher.combine(exchangeBaseUrl)
+        hasher.combine(currency)
+        hasher.combine(paytoUris)
+    }
+}
+/// A request to get an exchange's withdrawal details.
+fileprivate struct GetWithdrawalDetailsForURI: WalletBackendFormattedRequest {
+    typealias Response = WithdrawUriInfoResponse
+    func operation() -> String { return "getWithdrawalDetailsForUri" }
+    func args() -> Args { return Args(talerWithdrawUri: talerWithdrawUri) }
+
+    var talerWithdrawUri: String
+    struct Args: Encodable {
+        var talerWithdrawUri: String
+    }
+}
+// MARK: -
+/// The result from getWithdrawalDetailsForAmount
+struct WithdrawalAmountDetails: Decodable {
+    var tosAccepted: Bool               // Did the user accept the current 
version of the exchange's terms of service?
+    var amountRaw: Amount               // Amount that the user will transfer 
to the exchange
+    var amountEffective: Amount         // Amount that will be added to the 
user's wallet balance
+    var paytoUris: [String]             // Ways to pay the exchange
+    var ageRestrictionOptions: [Int]?   // Array of ages
+    var numCoins: Int?                  // Number of coins this 
amountEffective will create
+}
+/// A request to get an exchange's withdrawal details.
+fileprivate struct GetWithdrawalDetailsForAmount: 
WalletBackendFormattedRequest {
+    typealias Response = WithdrawalAmountDetails
+    func operation() -> String { return "getWithdrawalDetailsForAmount" }
+    func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl, 
amount: amount) }
+
+    var exchangeBaseUrl: String
+    var amount: Amount
+    struct Args: Encodable {
+        var exchangeBaseUrl: String
+        var amount: Amount
+    }
+}
+// MARK: -
+struct ExchangeTermsOfService: Decodable {
+    var content: String
+    var currentEtag: String
+    var acceptedEtag: String?
+}
+/// A request to query an exchange's terms of service.
+fileprivate struct GetExchangeTermsOfService: WalletBackendFormattedRequest {
+    typealias Response = ExchangeTermsOfService
+    func operation() -> String { return "getExchangeTos" }
+    func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl) }
+
+    var exchangeBaseUrl: String
+    struct Args: Encodable {
+        var exchangeBaseUrl: String
+    }
+}
+/// A request to mark an exchange's terms of service as accepted.
+fileprivate struct SetExchangeTOSAccepted: WalletBackendFormattedRequest {
+    struct Response: Decodable {}   // no result - getting no error back means 
success
+    func operation() -> String { return "setExchangeTosAccepted" }
+    func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl, etag: 
etag) }
+
+    var exchangeBaseUrl: String
+    var etag: String
+
+    struct Args: Encodable {
+        var exchangeBaseUrl: String
+        var etag: String
+    }
+}
+// MARK: -
+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 = AcceptWithdrawalResponse
+    func operation() -> String { return "acceptBankIntegratedWithdrawal" }
+    func args() -> Args { return Args(talerWithdrawUri: talerWithdrawUri, 
exchangeBaseUrl: exchangeBaseUrl) }
+
+    var talerWithdrawUri: String
+    var exchangeBaseUrl: String
+
+    struct Args: Encodable {
+        var talerWithdrawUri: String
+        var exchangeBaseUrl: String
+    }
+}
+// MARK: -
+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 WalletModel {
+    /// load withdrawal details. Networking involved
+    @MainActor
+    func loadWithdrawalDetailsForUriM(_ talerWithdrawUri: String)              
 // M for MainActor
+      async throws -> WithdrawUriInfoResponse {
+        let request = GetWithdrawalDetailsForURI(talerWithdrawUri: 
talerWithdrawUri)
+        let response = try await sendRequest(request, ASYNCDELAY)
+        return response
+    }
+    @MainActor
+    func loadWithdrawalDetailsForAmountM(_ exchangeBaseUrl: String, amount: 
Amount)  // M for MainActor
+      async throws -> WithdrawalAmountDetails {
+        let request = GetWithdrawalDetailsForAmount(exchangeBaseUrl: 
exchangeBaseUrl,
+                                                             amount: amount)
+        let response = try await sendRequest(request, ASYNCDELAY)
+        return response
+    }
+    @MainActor
+    func loadExchangeTermsOfServiceM(_ exchangeBaseUrl: String)             // 
M for MainActor
+      async throws -> ExchangeTermsOfService {
+        let request = GetExchangeTermsOfService(exchangeBaseUrl: 
exchangeBaseUrl)
+        let response = try await sendRequest(request, ASYNCDELAY)
+        return response
+    }
+    @MainActor
+    func setExchangeTOSAcceptedM(_ exchangeBaseUrl: String, etag: String)      
 // M for MainActor
+      async throws -> Decodable {
+        let request = SetExchangeTOSAccepted(exchangeBaseUrl: exchangeBaseUrl, 
etag: etag)
+        let response = try await sendRequest(request, ASYNCDELAY)
+        return response
+    }
+    @MainActor
+    func sendAcceptIntWithdrawalM(_ exchangeBaseUrl: String, withdrawURL: 
String)   // M for MainActor
+      async throws -> AcceptWithdrawalResponse? {
+        let request = AcceptBankIntegratedWithdrawal(talerWithdrawUri: 
withdrawURL, exchangeBaseUrl: exchangeBaseUrl)
+        let response = try await sendRequest(request, ASYNCDELAY)
+        return response
+    }
+    @MainActor
+    func sendAcceptManualWithdrawalM(_ exchangeBaseUrl: String, amount: 
Amount, restrictAge: Int?)   // M for MainActor
+      async throws -> AcceptManualWithdrawalResult? {
+        let request = AcceptManualWithdrawal(exchangeBaseUrl: exchangeBaseUrl, 
amount: amount, restrictAge: restrictAge)
+        let response = try await sendRequest(request, ASYNCDELAY)
+        return response
+    }
+}
diff --git a/TalerWallet1/Model/WalletInitModel.swift 
b/TalerWallet1/Model/WalletInitModel.swift
deleted file mode 100644
index 7be0dff..0000000
--- a/TalerWallet1/Model/WalletInitModel.swift
+++ /dev/null
@@ -1,88 +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 SymLog
-
-let DATABASE = "talerwalletdb-v30"
-
-class WalletInitModel: WalletModel {
-
-}
-// MARK: -
-///  A request to initialize Wallet-core
-fileprivate struct WalletBackendInitRequest: WalletBackendFormattedRequest {
-    func operation() -> String { return "init" }
-    func args() -> Args {
-        return Args(persistentStoragePath: persistentStoragePath,
-//                    cryptoWorkerType: "sync",
-                    logLevel: "info")       //  "trace", "info", "warn", 
"error", "none"
-    }
-
-    struct Args: Encodable {
-        var persistentStoragePath: String
-//        var cryptoWorkerType: String
-        var logLevel: String
-    }
-
-    var persistentStoragePath: String
-
-    struct Response: Decodable {        // versioninfo
-        var versionInfo: VersionInfo
-        enum CodingKeys: String, CodingKey {
-            case versionInfo = "versionInfo"
-        }
-    }
-}
-// MARK: -
-/// The info returned from Wallet-core init
-struct VersionInfo: Decodable {
-    var hash: String
-    var version: String
-    var exchange: String
-    var merchant: String
-    var bank: String
-    var devMode: Bool
-}
-// MARK: -
-extension WalletInitModel {
-    /// initalize Wallet-Core. Will do networking
-    func initWallet() 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
-            return response.versionInfo
-        } catch {
-            symLog?.log("error: \(error)")
-            throw error
-        }
-    }
-
-    private func docPath () throws -> String {
-        let documentUrls = FileManager.default.urls(for: .documentDirectory, 
in: .userDomainMask)
-        if (documentUrls.count > 0) {
-            var storageDir = documentUrls[0]
-            storageDir.appendPathComponent(DATABASE, isDirectory: false)
-            storageDir.appendPathExtension("json")
-            return storageDir.path
-        } else {    // should never happen
-            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..1b56aa2 100644
--- a/TalerWallet1/Model/WalletModel.swift
+++ b/TalerWallet1/Model/WalletModel.swift
@@ -1,55 +1,126 @@
 /*
- * 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
+import os.log
 
+fileprivate let DATABASE = "talerwalletdb-v30"
 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 {
+    public static let shared = WalletModel()
     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
-    }
+    let logger = Logger (subsystem: "net.taler.gnu", category: "WalletModel")
 
-    @MainActor func sendRequest<T: WalletBackendFormattedRequest> (_ request: 
T, _ delay: UInt = 0)
-      async throws -> T.Response {
-        loading = true                                  // enter progressView
+    func sendRequest<T: WalletBackendFormattedRequest> (_ request: T, _ delay: 
UInt = 0)
+      async throws -> T.Response {    // T for any Thread
+#if !DEBUG
+        logger.log("sending: \(request.operation(), privacy: .public)")
+#endif
+        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)
+#if !DEBUG
+            let timeUsed = Date.now - sendTime
+            logger.log("received: \(request.operation(), privacy: .public) 
(\(id, privacy: .public)) after \(timeUsed.milliseconds, privacy: .public) ms")
+#endif
             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...")
                 try? await Task.sleep(nanoseconds: 1_000_000_000 * 
UInt64(asyncDelay))
-                symLog?.log("waking up again after \(asyncDelay) seconds, will 
deliver \(response)")
-            } else {
-                symLog?.log("received: \(response)")
             }
-            loading = false                             // exit progressView
             return response
-        } catch {
+        } catch {       // rethrows
+            let timeUsed = Date.now - sendTime
+            logger.error("\(request.operation(), privacy: .public) failed 
after \(timeUsed.milliseconds, privacy: .public) ms\n\(error)")
             throw error
         }
     }
 
+    func getTransactionByIdT(_ transactionId: String)
+      async throws -> Transaction {              // T for any Thread
+        // might be called from a background thread itself
+        let request = GetTransactionById(transactionId: transactionId)
+        return try await sendRequest(request, ASYNCDELAY)
+    }
+    /// get the specified transaction from Wallet-Core. No networking involved
+    @MainActor func getTransactionByIdM(_ transactionId: String)
+    async throws -> Transaction {                // M for MainActor
+        return try await getTransactionByIdT(transactionId)      // call 
GetTransactionById on main thread
+    }
+}
+// MARK: -
+/// A request to get a wallet transaction by ID.
+fileprivate 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
+    }
+}
+// MARK: -
+/// The info returned from Wallet-core init
+struct VersionInfo: Decodable {
+    var hash: String
+    var version: String
+    var exchange: String
+    var merchant: String
+    var bank: String
+    var devMode: Bool
+}
+// MARK: -
+///  A request to initialize Wallet-core
+fileprivate struct InitRequest: WalletBackendFormattedRequest {
+    func operation() -> String { return "init" }
+    func args() -> Args {
+        return Args(persistentStoragePath: persistentStoragePath,
+//                       cryptoWorkerType: "sync",
+                                 logLevel: "info") // trace, info, warn, 
error, none
+    }
+
+    struct Args: Encodable {
+        var persistentStoragePath: String
+//        var cryptoWorkerType: String
+        var logLevel: String
+    }
+
+    var persistentStoragePath: String
+
+    struct Response: Decodable {
+        var versionInfo: VersionInfo
+    }
+}
+// MARK: -
+extension WalletModel {
+    /// initalize Wallet-Core. Will do networking
+    func initWalletCoreT() async throws -> VersionInfo {
+                    // T for any Thread
+        let docPath = try docPath()
+        let request = InitRequest(persistentStoragePath: docPath)
+        let response = try await sendRequest(request, 0)    // no Delay
+        return response.versionInfo
+    }
+
+    private func docPath () throws -> String {
+        let documentUrls = FileManager.default.urls(for: .documentDirectory, 
in: .userDomainMask)
+        if (documentUrls.count > 0) {
+            var storageDir = documentUrls[0]
+            storageDir.appendPathComponent(DATABASE, isDirectory: false)
+            storageDir.appendPathExtension("json")
+            let docPath = storageDir.path
+            logger.debug("\(docPath)")
+            return docPath
+        } else {    // should never happen
+            logger.error("documentURLs empty")
+            throw WalletBackendError.initializationError
+        }
+    }
 }
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/BalanceRowButtons.swift 
b/TalerWallet1/Views/Balances/BalanceRowButtons.swift
new file mode 100644
index 0000000..1eb0a61
--- /dev/null
+++ b/TalerWallet1/Views/Balances/BalanceRowButtons.swift
@@ -0,0 +1,51 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+
+struct BalanceRowButtons: View {
+    let amount: Amount
+    let narrow: Bool
+    let lineLimit: Int
+    let sendAction: () -> Void
+    let recvAction: () -> Void
+    @Environment(\.sizeCategory) var sizeCategory
+
+    var body: some View {
+        let currency = amount.currencyStr
+        Group {
+            Button("Request\nPayment", action: recvAction)
+                .lineLimit(lineLimit)
+                .disabled(false)
+                .buttonStyle(TalerButtonStyle(type: .bordered, narrow: narrow, 
aligned: .center))
+            Button("Send\n\(currency)", action: sendAction)
+                .lineLimit(lineLimit)
+                .disabled(amount.isZero)
+                .buttonStyle(TalerButtonStyle(type: .bordered, narrow: narrow, 
aligned: .center))
+
+        }
+    }
+}
+
+struct BalanceRowButtons_Previews: PreviewProvider {
+    static var previews: some View {
+        List {
+            VStack {
+                let amount = try! Amount(fromString: LONGCURRENCY + ":1234.56")
+                BalanceRowButtons(amount: Amount(currency: "TestKUDOS", value: 
1234),
+                                  narrow: false, lineLimit: 0,
+                                  sendAction: {}, recvAction: {})
+                BalanceButton(amount: amount, rowAction: {})
+            }
+            HStack {
+                let amount = try! Amount(fromString: "KUDOS" + ":1234.56")
+                BalanceRowButtons(amount: amount,
+                                  narrow: true, lineLimit: 2,
+                                  sendAction: {}, recvAction: {})
+                BalanceButton(amount: amount, rowAction: {})
+            }
+        }
+    }
+}
diff --git a/TalerWallet1/Views/Balances/BalanceRowView.swift 
b/TalerWallet1/Views/Balances/BalanceRowView.swift
new file mode 100644
index 0000000..6f51790
--- /dev/null
+++ b/TalerWallet1/Views/Balances/BalanceRowView.swift
@@ -0,0 +1,98 @@
+/*
+ * 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 BalanceButton: View {
+    let amount: Amount
+    let rowAction: () -> Void
+
+    var body: some View {
+        Button(action: rowAction) {
+            VStack(alignment: .trailing, spacing: 0) {
+                HStack(alignment: .firstTextBaseline, spacing: 0) {
+                    Text("B", comment: "the first letter of Balance - or leave 
empty")
+                        .font(.title2)
+                    Text("alance", comment: "the remaining letters of Balance 
- or all if you left B empty")
+                        .font(.footnote).bold()
+                }
+                Text("\(amount.valueStr)")  // TODO: CurrencyFormatter?
+                    .font(.title)
+            }
+        }   .disabled(false)
+            .accessibilityElement(children: 
/*@START_MENU_TOKEN@*/.ignore/*@END_MENU_TOKEN@*/)
+            .accessibilityLabel("Balance \(amount.readableDescription)")    // 
TODO: CurrencyFormatter!
+            .buttonStyle(TalerButtonStyle(type: .plain, aligned: .trailing))
+//                .background(Color.yellow)
+    }
+}
+
+struct BalanceRowView: View {
+    let amount: Amount
+    let sendAction: () -> Void
+    let recvAction: () -> Void
+    let rowAction: () -> Void
+    @Environment(\.sizeCategory) var sizeCategory
+
+    func needVStack(_ amount: Amount) -> Bool {
+        // Sizes: 320 (SE), 375 (X, Xs, 12, 13 mini), 390 (12,13,14), 414 
(Plus, Max), 428 (Pro Max)
+        guard 350 < UIScreen.main.bounds.width else {return true}   // always 
for iPhone SE 1st Gen
+        var count = amount.currencyStr.count
+//        print(sizeCategory)
+        switch sizeCategory {
+            case ContentSizeCategory.extraSmall:
+                count += 0
+            case ContentSizeCategory.small:
+                count += 1
+            case ContentSizeCategory.medium:
+                count += 2
+            case ContentSizeCategory.large:
+                count += 3
+            case ContentSizeCategory.extraLarge:
+                count += 4
+            default:
+                count += 5
+        }
+        return count > 9
+    }
+
+    var body: some View {
+        Group {
+            if needVStack(amount) {
+                VStack (alignment: .trailing) {
+                    BalanceButton(amount: amount, rowAction: rowAction)
+                    HStack {
+                        BalanceRowButtons(amount: amount, narrow: false, 
lineLimit: 5,
+                                          sendAction: sendAction, recvAction: 
recvAction)
+                    }
+                }
+            } else {
+                HStack {
+                    BalanceRowButtons(amount: amount, narrow: true, lineLimit: 
5,
+                                      sendAction: sendAction, recvAction: 
recvAction)
+                    BalanceButton(amount: amount, rowAction: rowAction)
+                }
+//                .fixedSize(horizontal: true, 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: "TestKUDOS" + 
":1234.56"),
+                           sendAction: {}, recvAction: {}, rowAction: {})
+            BalanceRowView(amount: try! Amount(fromString: "KUDOS" + 
":1234.56"),
+                           sendAction: {}, recvAction: {}, rowAction: {})
+        }
+    }
+}
+#endif
diff --git a/TalerWallet1/Views/Balances/BalancesListView.swift 
b/TalerWallet1/Views/Balances/BalancesListView.swift
new file mode 100644
index 0000000..2628bd3
--- /dev/null
+++ b/TalerWallet1/Views/Balances/BalancesListView.swift
@@ -0,0 +1,171 @@
+/*
+ * 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(0)
+    let navTitle: String
+    let hamburgerAction: () -> Void
+
+    @EnvironmentObject private var model: WalletModel
+    @State private var balances: [Balance] = []
+    @State private var centsToTransfer: UInt64 = 0
+    @State private var summary: String = ""
+    @State private var showQRScanner: Bool = false
+    @State private var showCameraAlert: Bool = false
+
+    private var openSettingsButton: some View {
+        Button("Open Settings") {
+            showCameraAlert = false
+            UIApplication.shared.open(URL(string: 
UIApplication.openSettingsURLString)!)
+        }
+    }
+    var ClosingAnnouncement = AttributedString(localized: "Closing Camera")
+    private var dismissAlertButton: some View {
+        Button("Cancel", role: .cancel) {
+            if #available(iOS 17.0, *) {
+//                
AccessibilityNotification.Announcement(ClosingAnnouncement).post()
+            }
+            showCameraAlert = false
+        }
+    }
+
+    var defaultPriorityAnnouncement = AttributedString(localized: "Opening 
Camera")
+    var lowPriorityAnnouncement: AttributedString {
+        var lowPriorityString = AttributedString ("Camera Loading")
+        if #available(iOS 17.0, *) {
+//            lowPriorityString.accessibilitySpeechAnnouncementPriority = .low
+        }
+        return lowPriorityString
+    }
+    var highPriorityAnnouncement: AttributedString {
+        var highPriorityString = AttributedString("Camera Active")
+        if #available(iOS 17.0, *) {
+//            highPriorityString.accessibilitySpeechAnnouncementPriority = 
.high
+        }
+        return highPriorityString
+    }
+    private func checkCameraAvailable() -> Void {
+        /// Open Camera Code
+        if #available(iOS 17.0, *) {
+//            
AccessibilityNotification.Announcement(defaultPriorityAnnouncement).post()
+        }
+        AVCaptureDevice.requestAccess(for: .video, completionHandler: { 
(granted: Bool) -> Void in
+            if granted {
+                showQRScanner = true
+            } else {
+                // Camera Loaded Code
+                if #available(iOS 17.0, *) {
+//                    
AccessibilityNotification.Announcement(highPriorityAnnouncement).post()
+                }
+                showCameraAlert = true
+            }
+        })
+    }
+
+    private func reloadAction() async -> Int {
+        let reloaded = await model.balancesM()
+        let count = reloaded.count
+        balances = reloaded
+        return count
+    }
+
+    var body: some View {
+#if DEBUG
+        let _ = Self._printChanges()
+        let _ = symLog.vlog()       // just to get the # to compare it with 
.onAppear & onDisappear
+#endif
+        Content(symLog: symLog, balances: $balances,
+                centsToTransfer: $centsToTransfer, summary: $summary,
+                reloadAction: reloadAction)
+            .navigationTitle(navTitle)
+            .navigationBarTitleDisplayMode(.automatic)
+            .navigationBarItems(leading: HamburgerButton(action: 
hamburgerAction),
+                                trailing: QRButton(action: 
checkCameraAvailable))
+            .overlay {
+                if balances.isEmpty {
+                    WalletEmptyView()
+                        .refreshable {  // already async
+                            symLog.log("refreshing")
+                            let count = await reloadAction()
+                            if count > 0 {
+//                                postNotificationM(.BalanceReloaded)
+                                NotificationCenter.default.post(name: 
.BalanceReloaded, object: nil)
+                            }
+                        }
+
+                }
+            }
+            .alert("Scanning QR-codes requires access to the camera",
+                   isPresented: $showCameraAlert,
+                       actions: {   openSettingsButton
+                                    dismissAlertButton },
+                       message: {   Text("Please allow camera access in 
settings.") })
+            .onAppear() {
+                DebugViewC.shared.setViewID(VIEW_BALANCES)
+            }
+            .sheet(isPresented: $showQRScanner) {
+                let sheet = AnyView(QRSheet())
+                Sheet(sheetView: sheet)
+            } // sheet
+            .task {
+                symLog.log(".task getBalances")
+                _ = await reloadAction()
+            } // task
+    }
+}
+// MARK: -
+extension BalancesListView {
+    struct Content: View {
+        let symLog: SymLogV?
+        @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
+        @Binding var balances: [Balance]
+        @Binding var centsToTransfer: UInt64
+        @Binding var summary: String
+        var reloadAction: () async -> Int
+
+        @State private var shouldLoad = false
+        var body: some View {
+#if DEBUG
+            let _ = Self._printChanges()
+            let _ = symLog?.vlog()       // just to get the # to compare it 
with .onAppear & onDisappear
+#endif
+            Group { // necessary for .backslide transition (bug in SwiftUI)
+                List(balances, id: \.self) { balance in
+                    BalancesSectionView(balance: balance,
+                                centsToTransfer: $centsToTransfer,
+                                        summary: $summary)
+                }
+                .refreshable {  // already async
+                    symLog?.log("refreshing")
+                    let count = await reloadAction()
+                    if count > 0 {
+//                        postNotificationM(.BalanceReloaded)
+                        NotificationCenter.default.post(name: 
.BalanceReloaded, object: nil)
+                    }
+                }
+                .listStyle(myListStyle.style).anyView
+            }
+            .onAppear {
+                if shouldLoad {
+                    shouldLoad = false
+                    symLog?.log(".onAppear: shouldLoad ==> reloading balances")
+                    Task { await reloadAction() }
+                }
+            }
+            // automatically reload balances after receiving BalanceChange 
notification ...
+            .onNotification(.BalanceChange) { notification in
+                // doesn't need to be received on main thread because we just 
reload in the background anyway
+                symLog?.log(".onNotification(.BalanceChange) ==> shouldLoad = 
true")
+                shouldLoad = true
+            }
+        } // body
+    } // Content
+}
diff --git a/TalerWallet1/Views/Balances/BalancesModel.swift 
b/TalerWallet1/Views/Balances/BalancesModel.swift
deleted file mode 100644
index 80197d9..0000000
--- a/TalerWallet1/Views/Balances/BalancesModel.swift
+++ /dev/null
@@ -1,73 +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
-
-class BalancesModel: WalletModel {
-    @Published var balances: [Balance]?                 // update view
-}
-// MARK: -
-/// A request to get the balances held in the wallet.
-fileprivate struct GetBalances: WalletBackendFormattedRequest {
-    func operation() -> String { return "getBalances" }
-    func args() -> Args { return Args() }
-
-    struct Args: Encodable {}                           // no arguments needed
-
-    struct Response: Decodable {                        // list of balances
-        var balances: [Balance]
-    }
-}
-// MARK: -
-/// A currency balance
-struct Balance: Decodable, Hashable {
-    var available: Amount
-    var pendingIncoming: Amount
-    var pendingOutgoing: Amount
-    var hasPendingTransactions: Bool
-    var requiresUserInput: Bool
-
-    public static func == (lhs: Balance, rhs: Balance) -> Bool {
-        return lhs.available == rhs.available &&
-            lhs.pendingIncoming == rhs.pendingIncoming &&
-            lhs.pendingOutgoing == rhs.pendingOutgoing &&
-            lhs.hasPendingTransactions == rhs.hasPendingTransactions &&
-            lhs.requiresUserInput == rhs.requiresUserInput
-    }
-
-    public func hash(into hasher: inout Hasher) {
-        hasher.combine(available)
-        hasher.combine(pendingIncoming)
-        hasher.combine(pendingOutgoing)
-        hasher.combine(hasPendingTransactions)
-        hasher.combine(requiresUserInput)
-    }
-}
-// MARK: -
-extension BalancesModel {
-    /// fetch Balances from Wallet-Core. No networking involved
-    @MainActor func fetchBalances() async throws {
-        do {
-            let request = GetBalances()
-            let response = try await sendRequest(request, ASYNCDELAY)
-            balances = response.balances                // trigger view update 
in CurrenciesListView
-        } catch {
-            throw error
-        }
-    }
-}
diff --git a/TalerWallet1/Views/Balances/BalancesSectionView.swift 
b/TalerWallet1/Views/Balances/BalancesSectionView.swift
new file mode 100644
index 0000000..e16015b
--- /dev/null
+++ b/TalerWallet1/Views/Balances/BalancesSectionView.swift
@@ -0,0 +1,206 @@
+/*
+ * 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
+    @Binding var summary: String
+
+    @EnvironmentObject private var model: WalletModel
+
+    @State private var isShowingDetailView = false
+    @State private var buttonSelected: Int? = nil
+
+    @State private var transactions: [Transaction] = []
+    @State private var completedTransactions: [Transaction] = []
+    @State private var pendingTransactions: [Transaction] = []
+    @State private var uncompletedTransactions: [Transaction] = []
+
+    func reloadOneAction(_ transactionId: String) async throws -> Transaction {
+        return try await model.getTransactionByIdT(transactionId)
+    }
+
+    func computePending(currency: String) -> (Amount, Amount) {
+        var incoming = Amount(currency: currency, value: 0)
+        var outgoing = Amount(currency: currency, value: 0)
+        for transaction in pendingTransactions {
+            let effective = transaction.common.amountEffective
+            if currency == effective.currencyStr {
+                do {
+                    if transaction.common.incoming() {
+                        incoming = try incoming + effective
+                    } else {
+                        outgoing = try outgoing + effective
+                    }
+                } catch {
+                    // TODO: log error
+                    symLog.log(error.localizedDescription)
+                }
+            }
+        }
+        return (incoming, outgoing)
+    }
+
+    @State private var sectionID = UUID()
+    @State private var shownSectionID = UUID()  // guaranteed to be different 
the first time
+
+    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 = balance.available.currencyStr
+        let reloadCompleted = {
+            transactions = await model.transactionsT(currency: currency)
+            completedTransactions = 
WalletModel.completedTransactions(transactions)
+//            sectionID = UUID()
+        }
+        let reloadPending = {
+            transactions = await model.transactionsT(currency: currency)
+            pendingTransactions = WalletModel.pendingTransactions(transactions)
+//            sectionID = UUID()
+        }
+        let reloadUncompleted = {
+            transactions = await model.transactionsT(currency: currency)
+            uncompletedTransactions = 
WalletModel.uncompletedTransactions(transactions)
+//            sectionID = UUID()
+        }
+
+        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,
+                               centsToTransfer: $centsToTransfer,
+                                       summary: $summary)
+                  }, tag: 1, selection: $buttonSelected
+                ) { EmptyView() }.frame(width: 0).opacity(0).hidden()          
 // SendAmount
+
+                NavigationLink(destination: LazyView {
+                    RequestPayment(scopeInfo: balance.scopeInfo,
+                             centsToTransfer: $centsToTransfer,
+                                     summary: $summary)
+                  }, tag: 2, selection: $buttonSelected
+                ) { EmptyView() }.frame(width: 0).opacity(0).hidden()          
 // RequestPayment
+
+                NavigationLink(destination: LazyView {
+                    TransactionsListView(navTitle: String(localized: 
"Transactions"), currency: currency,
+                                     transactions: completedTransactions,
+                                  reloadAllAction: reloadCompleted,
+                                  reloadOneAction: reloadOneAction)
+                  }, tag: 3, selection: $buttonSelected
+                ) { EmptyView() }.frame(width: 0).opacity(0).hidden()          
 // TransactionsListView
+
+                BalanceRowView(amount: balance.available, sendAction: {
+                        buttonSelected = 1      // will trigger SendAmount 
NavigationLink
+                    }, recvAction: {
+                        buttonSelected = 2      // will trigger RequestPayment 
NavigationLink
+                    }, rowAction: {
+                        buttonSelected = 3      // will trigger 
TransactionList NavigationLink
+                    })
+            }
+            let hasPending = pendingTransactions.count > 0
+            if hasPending {
+                let (pendingIncoming, pendingOutgoing) = 
computePending(currency: currency)
+
+                NavigationLink {
+//let _ = print("button: Pending Transactions: \(currency)")
+                    LazyView {
+                        TransactionsListView(navTitle: String(localized: 
"Pending"), currency: currency,
+                                         transactions: pendingTransactions,
+                                      reloadAllAction: reloadPending,
+                                      reloadOneAction: reloadOneAction)
+                    }
+                } label: {
+                    VStack(spacing: 6) {
+                        var rows = 0
+                        if !pendingIncoming.isZero {
+                            PendingRowView(amount: pendingIncoming, incoming: 
true)
+                            let _ = (rows+=1)
+                        }
+                        if !pendingOutgoing.isZero {
+                            PendingRowView(amount: pendingOutgoing, incoming: 
false)
+                            let _ = (rows+=1)
+                        }
+                        if rows == 0 {
+                            Text("Some pending transactions")
+                        }
+                    }
+                }
+            }
+            let hasUncompleted = uncompletedTransactions.count > 0
+            if hasUncompleted {
+                NavigationLink {
+//let _ = print("button: Uncompleted Transactions: \(currency)")
+                    LazyView {
+                        TransactionsListView(navTitle: String(localized: 
"Uncompleted"), currency: currency,
+                                             transactions: 
uncompletedTransactions,
+                                          reloadAllAction: reloadUncompleted,
+                                          reloadOneAction: reloadOneAction)
+                    }
+                } label: {
+                    UncompletedRowView(uncompletedTransactions: 
$uncompletedTransactions)
+                }
+
+            }
+        } header: {
+            Text(currency)
+                .font(.title)
+        }.id(sectionID)
+        .task {
+//            if shownSectionID != sectionID {
+                symLog.log("task for BalancesSectionView \(sectionID) - reload 
Transactions")
+                let response = await model.transactionsT(currency: currency)
+                transactions = response
+                pendingTransactions = WalletModel.pendingTransactions(response)
+                uncompletedTransactions = 
WalletModel.uncompletedTransactions(response)
+                shownSectionID = sectionID
+//            } else {
+//                symLog.log("task for BalancesSectionView \(sectionID) ❗️ 
skip reloading Transactions")
+//            }
+        }
+    } // body
+}
+// MARK: -
+#if DEBUG
+fileprivate struct BindingViewContainer : View {
+    @State var centsToTransfer: UInt64 = 333
+    @State private var summary: String = "bla-bla"
+
+    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"),
+                              scopeInfo: scopeInfo,
+                      requiresUserInput: false,
+                 hasPendingTransactions: true)
+        List {
+            BalancesSectionView(balance: balance,
+                        centsToTransfer: $centsToTransfer,
+                                summary: $summary)
+        }
+    }
+}
+
+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..b4637a2
--- /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 {
+    @Binding 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..83ba3b3 100644
--- a/TalerWallet1/Views/Exchange/ExchangeListView.swift
+++ b/TalerWallet1/Views/Exchange/ExchangeListView.swift
@@ -1,103 +1,130 @@
 /*
- * 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"
+    private let symLog = SymLogV(0)
+    let navTitle: String
+    var hamburgerAction: () -> Void
 
-    @ObservedObject var viewModel: ExchangeModel
+    @EnvironmentObject private var model: WalletModel
+
+    @State private var exchanges: [Exchange] = []
+
+    // source of truth for the value the user enters in currencyField for 
exchange withdrawals
+    @State private var centsToTransfer: UInt64 = 0        // TODO: different 
values for different currencies?
+
+    func reloadAction() async -> Void {
+            exchanges = await model.listExchangesM()
+    }
+
+    func addExchange(_ exUrl: String) -> Void {
+        Task {
+            symLog.log("adding: \(exUrl)")
+            do {
+                try await model.addExchange(url: exUrl)
+                symLog.log("added: \(exUrl)")
+            } catch {    // TODO: error handling - couldn't add exchangeURL
+                symLog.log("error: \(error)")
+            }
+        }
+    }
+
+    @State var showAlert: Bool = false
+    @State var newExchange: String = "https://exchange-age.taler.ar/";
 
     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)
+#if DEBUG
+        let _ = Self._printChanges()
+        let _ = symLog.vlog()       // just to get the # to compare it with 
.onAppear & onDisappear
+#endif
+        let plusAction: () -> Void = {
+//        withAnimation { showAlert = true }
+            showAlert = true
+        }
+
+        //Text("Exchanges...")
+        Content(symLog: symLog,
+                exchanges: $exchanges,
+                centsToTransfer: $centsToTransfer,
+                reloadAction: reloadAction)
+        .navigationTitle(navTitle)
+        .navigationBarTitleDisplayMode(.automatic)
+        .navigationBarItems(leading: HamburgerButton(action: hamburgerAction),
+                            trailing: PlusButton(action: plusAction))
+        .overlay {
+            if exchanges.isEmpty {
+                Text("No Exchanges yet...")
             }
-        }.task {
+        }
+        .task {
             symLog.log(".task")
-            do {
-                try await reloadAction()
-            } catch {
-                // TODO: show error
-                symLog.log(error.localizedDescription)
-            }
+            await reloadAction()
         }
+        .textFieldAlert(isPresented: $showAlert, title: "Add Exchange",
+                        doneText: "Add", text: $newExchange, action: 
addExchange)
     }
 }
 // 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
-        var reloadAction: () async throws -> ()
-        @State var showAlert: Bool = false
-        @State var newExchange: String = "https://exchange-age.taler.ar/";
+        let symLog: SymLogV?
+        @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
+        @Binding var exchanges: [Exchange]
+        @Binding var centsToTransfer: UInt64
+        var reloadAction: () async -> Void
+
+        func currenciesDict(_ exchanges: [Exchange]) -> [String : [Exchange]] {
+            var currencies: [String : [Exchange]] = [:]
 
-        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
+            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 }
-                showAlert = true
-            }
-            VStack {
-                if viewModel.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()
-                        }
-                    }
-                        .navigationBarTitleDisplayMode(.large)      // .inline
-                        .refreshable {
-                            do {
-                                symLog.log("refreshing")
-                                try await reloadAction()
-                            } catch {
-                                // TODO: error
-                                symLog.log(error.localizedDescription)
-                            }
-                        }
+            let dict = currenciesDict(exchanges)
+            let sortedDict = dict.sorted{ $0.key < $1.key}
+            Group { // necessary for .backslide transition (bug in SwiftUI)
+                List(sortedDict, id: \.key) { key, value in
+                    ExchangeSectionView(currency: key, exchanges: value, 
centsToTransfer: $centsToTransfer)
                 }
+                .refreshable {
+                    symLog?.log("refreshing")
+                    await reloadAction()
+                }
+                .listStyle(myListStyle.style).anyView
+            }
+            .onAppear() {
+                DebugViewC.shared.setViewID(VIEW_EXCHANGES)
+            }
+            .onNotification(.ExchangeAdded) { notification in
+                // doesn't need to be received on main thread because we just 
reload in the background anyway
+                symLog?.log(".onNotification(.ExchangeAdded) ==> reloading 
exchanges")
+                Task { await reloadAction() }
             }
-                .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..4b6b635
--- /dev/null
+++ b/TalerWallet1/Views/Exchange/ExchangeSectionView.swift
@@ -0,0 +1,102 @@
+/*
+ * 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
+    let currency: String
+    @Binding var centsToTransfer: UInt64
+
+    @State private var buttonSelected: Int? = nil
+    var body: some View {
+        let baseURL = exchange.exchangeBaseUrl
+
+        HStack(spacing: 0) {    // can't use the built in Label because it 
adds the accessory arrow
+            Text(baseURL.trimURL())
+
+            NavigationLink(destination: LazyView {
+                EmptyView()     // TODO: Deposit
+            }, tag: 1, selection: $buttonSelected
+            ) { EmptyView() }.frame(width: 0).opacity(0)
+            NavigationLink(destination: LazyView {
+                ManualWithdraw(exchange: exchange,
+                               centsToTransfer: $centsToTransfer)
+            }, tag: 2, selection: $buttonSelected
+            ) { EmptyView() }.frame(width: 0).opacity(0)
+        }.listRowSeparator(.hidden)
+
+        HStack {        // buttons just set "buttonSelected" so the 
NavigationLink will trigger
+            Button("Deposit\n\(currency)") { buttonSelected = 1 }
+                .multilineTextAlignment(.center)
+                .buttonStyle(TalerButtonStyle(type: .bordered))
+                .disabled(true)     // TODO: after implementing Deposit check 
available
+
+            Button("Withdraw\n\(currency)") { buttonSelected = 2 }
+                .multilineTextAlignment(.center)
+                .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, currency: currency, 
centsToTransfer: $centsToTransfer)
+            }
+            .accessibilityElement(children: .combine)
+        } header: {
+            Text(currency)
+                .font(.title)
+        }
+    }
+}
+// MARK: -
+#if DEBUG
+struct ExchangeRow_Container : View {
+    @State private var centsToTransfer: UInt64 = 100
+
+    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..50b7c38
--- /dev/null
+++ b/TalerWallet1/Views/Exchange/ManualWithdraw.swift
@@ -0,0 +1,124 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+import SymLog
+
+// Will be called by the user tapping "Withdraw Coins" in the exchange list
+struct ManualWithdraw: View {
+    private let symLog = SymLogV()
+
+    let exchange: Exchange
+    @Binding var centsToTransfer: UInt64
+
+    @EnvironmentObject private var model: WalletModel
+
+    @State var withdrawalAmountDetails: WithdrawalAmountDetails? = nil
+
+//    @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 navTitle = String(localized: "Withdraw \(currency)")
+        let currencyField = CurrencyField(value: $centsToTransfer, currency: 
currency) // becomeFirstResponder
+//        let agePicker = AgePicker(ageMenuList: $ageMenuList, selectedAge: 
$selectedAge)
+
+        ScrollViewReader { scrollView in
+          VStack {
+            Text("from \(exchange.exchangeBaseUrl.trimURL())")
+                .font(.title3)
+            CurrencyInputView(currencyField: currencyField,
+                              title: String(localized: "Amount to withdraw:"))
+
+            let someCoins = SomeCoins(details: withdrawalAmountDetails)
+            QuiteSomeCoins(someCoins: someCoins, shouldShowFee: true,
+                           currency: currency, amountEffective: 
withdrawalAmountDetails?.amountEffective)
+
+            if !someCoins.invalid {
+                if !someCoins.tooMany {
+//                    agePicker
+
+                    if let tosAccepted = withdrawalAmountDetails?.tosAccepted {
+                        if tosAccepted {
+//                            let restrictAge: Int? = (selectedAge == 0) ? nil
+//                                                                       : 
selectedAge
+//let _ = print(selectedAge, restrictAge)
+                            NavigationLink(destination: LazyView {
+                                ManualWithdrawDone(exchange: exchange,
+                                            centsToTransfer: centsToTransfer)
+//                                                restrictAge: restrictAge)
+                            }) {
+                                Text("Confirm Withdrawal")      // 
VIEW_WITHDRAW_ACCEPT
+                            }.buttonStyle(TalerButtonStyle(type: .prominent))
+                        } else {
+                            NavigationLink(destination: LazyView {
+                                WithdrawTOSView(exchangeBaseUrl: 
exchange.exchangeBaseUrl,
+                                                         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)
+        .padding(.horizontal)
+        .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
+        .navigationTitle(navTitle)
+        .onAppear {
+            symLog.log("onAppear")
+            DebugViewC.shared.setViewID(VIEW_WITHDRAWAL)
+        }
+        .task(id: centsToTransfer) {
+            let amount = Amount.amountFromCents(currency, centsToTransfer)
+            do {
+                withdrawalAmountDetails = try await 
model.loadWithdrawalDetailsForAmountM(exchange.exchangeBaseUrl, amount: amount)
+//                agePicker.setAges(ages: 
withdrawalAmountDetails?.ageRestrictionOptions)
+            } catch {    // TODO: error
+                symLog.log(error.localizedDescription)
+                withdrawalAmountDetails = nil
+            }
+        }
+    }
+}
+// MARK: -
+#if DEBUG
+struct ManualWithdraw_Container : View {
+    @State private var centsToTransfer: UInt64 = 510
+    @State private var details = WithdrawalAmountDetails(tosAccepted: false,
+                                                           amountRaw: try! 
Amount(fromString: LONGCURRENCY + ":5.1"),
+                                                     amountEffective: try! 
Amount(fromString: LONGCURRENCY + ":5.0"),
+                                                           paytoUris: [],
+                                               ageRestrictionOptions: [],
+                                                            numCoins: 6)
+    var body: some View {
+        let exchange = Exchange(exchangeBaseUrl: DEMOEXCHANGE,
+                                       currency: LONGCURRENCY,
+                                      paytoUris: [],
+                                      tosStatus: "tosStatus",
+                                 exchangeStatus: "exchangeStatus",
+                          ageRestrictionOptions: [],
+                                      permanent: false)
+        ManualWithdraw(exchange: exchange,
+                centsToTransfer: $centsToTransfer,
+        withdrawalAmountDetails: details)
+    }
+}
+
+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..0048820
--- /dev/null
+++ b/TalerWallet1/Views/Exchange/ManualWithdrawDone.swift
@@ -0,0 +1,81 @@
+/*
+ * 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")
+
+    let exchange: Exchange
+    let centsToTransfer: UInt64
+//    let restrictAge: Int?
+
+    @EnvironmentObject private var model: WalletModel
+
+    @State var acceptManualWithdrawalResult: AcceptManualWithdrawalResult?
+    @State var transactionId: String?
+
+    func reloadOneAction(_ transactionId: String) async throws -> Transaction {
+        return try await model.getTransactionByIdT(transactionId)
+    }
+
+    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 transactionId {
+                TransactionDetailView(transactionId: transactionId,
+                                       reloadAction: reloadOneAction,
+                                         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: 0)
+                transactionId = result!.transactionId
+            } 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 exchange = Exchange(exchangeBaseUrl: DEMOEXCHANGE,
+                                       currency: LONGCURRENCY,
+                                      paytoUris: [],
+                                      tosStatus: "tosStatus",
+                                 exchangeStatus: "exchangeStatus",
+                          ageRestrictionOptions: [],
+                                      permanent: false)
+        ManualWithdrawDone(exchange: exchange,
+                    centsToTransfer: centsToTransfer)
+    }
+}
+
+struct ManualWithdrawDone_Previews: PreviewProvider {
+    static var previews: some View {
+        ManualWithdrawDone_Container()
+    }
+}
+#endif
diff --git a/TalerWallet1/Views/Exchange/QuiteSomeCoins.swift 
b/TalerWallet1/Views/Exchange/QuiteSomeCoins.swift
new file mode 100644
index 0000000..7efe434
--- /dev/null
+++ b/TalerWallet1/Views/Exchange/QuiteSomeCoins.swift
@@ -0,0 +1,102 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+import SymLog
+
+struct SomeCoins {
+    let numCoins: Int       // 0 == invalid, -1 == unknown
+    var unknown:   Bool { numCoins < 0 }
+    var invalid:   Bool { numCoins == 0 }
+    var manyCoins: Bool { numCoins > 99 }
+    var quiteSome: Bool { numCoins > 199 }
+    var tooMany:   Bool { numCoins > 999 }
+
+    let fee: String
+    var hasFee:    Bool { fee.count > 0 }
+}
+
+extension SomeCoins {
+    init(details: WithdrawalAmountDetails?) {
+        do {
+            if let details {
+                // Incoming: fee = raw - effective
+                let fee = try details.amountRaw - details.amountEffective
+                self.init(numCoins: details.numCoins ?? -1,    // either the 
number of coins, or unknown
+                               fee: fee.isZero ? "" : fee.readableDescription)
+                return
+            }
+        } catch {}
+        self.init(numCoins: 0, fee:"")      // invalid
+    }
+
+    init(details: CheckPeerPullCreditResponse?) {
+        do {
+            if let details {
+                // Incoming: fee = raw - effective
+                let fee = try details.amountRaw - details.amountEffective
+                self.init(numCoins: details.numCoins ?? -1,    // either the 
number of coins, or unknown
+                               fee: fee.isZero ? "" : fee.readableDescription)
+                return
+            }
+        } catch {}
+        self.init(numCoins: 0, fee:"")      // invalid
+    }
+}
+// MARK: -
+struct QuiteSomeCoins: View {
+    private let symLog = SymLogV()
+    let someCoins: SomeCoins
+    let shouldShowFee: Bool
+    let currency: String
+    let amountEffective: Amount?
+
+    var body: some View {
+        if shouldShowFee {
+            let shownFee = someCoins.hasFee ? String(localized: "- 
\(someCoins.fee)")
+                                            : String(localized: "No")
+            Text(someCoins.invalid ? "invalid amount"
+               : someCoins.tooMany ? "too many coins for a single withdrawal"
+                                   : "\(shownFee) withdrawal fee")
+            .foregroundColor((someCoins.invalid || someCoins.tooMany || 
someCoins.hasFee) ? .red : .primary)
+            .padding(4)
+        }
+        if !someCoins.invalid {
+            HStack {
+                Text(someCoins.unknown ? "Some" : "\(someCoins.numCoins)")
+                    .foregroundColor(someCoins.quiteSome ? .red : .primary)
+                Text(someCoins.tooMany ? "coins" : "coins to obtain:")
+                    .foregroundColor(someCoins.tooMany ? .red : .primary)
+
+                Spacer()
+                if !someCoins.tooMany {
+                    let effective = amountEffective ?? Amount(currency: 
currency, value: 0)
+                    Text(effective.readableDescription)
+                }
+            } // xx coins to obtain:    YYY currency
+//            .font(.title3)
+            .padding(.top)
+
+            if !someCoins.tooMany {
+                if someCoins.manyCoins {
+                    Text(someCoins.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(someCoins.quiteSome ? .red : .primary)
+                } // warnings
+            }
+        }
+    }
+}
+// MARK: -
+struct QuiteSomeCoins_Previews: PreviewProvider {
+    static var previews: some View {
+        QuiteSomeCoins(someCoins: SomeCoins(numCoins: 4, fee: "20 " + 
LONGCURRENCY),
+                   shouldShowFee: true,
+                        currency: LONGCURRENCY,
+                 amountEffective: nil)
+    }
+}
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..294e57b 100644
--- a/TalerWallet1/Views/HelperViews/Buttons.swift
+++ b/TalerWallet1/Views/HelperViews/Buttons.swift
@@ -1,108 +1,261 @@
 /*
- * 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
+
+
+
+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 {
-    let title: String
-    let action: () -> Void
-    var body: some View {
-        Button(action: action) {
-            Text(title)
-                .frame(minWidth: 0, maxWidth: 300)
-                .padding()
-                .foregroundColor(.white)
-                .background(LinearGradient(gradient: Gradient(colors: 
[Color.red, Color.blue]), startPoint: .leading, endPoint: .trailing))
-                .cornerRadius(40)
-                .font(.title)
+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)
+//        }
+        return type == .plain ? WalletColors().fieldForeground :
+            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)
+//        }
+        return type == .plain ? Color.clear :
+            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(.title3)
+//                .font(narrow ? .title3 : .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 Buttons_Previews: PreviewProvider {
     static var previews: some View {
         VStack {
-            HamburgerButton() {}
-                .padding()
-            PlusButton() {}
-                .padding()
+            HamburgerButton() {
+                Controller.shared.playSound(1000)
+            }.padding()
+            QRButton() {
+                Controller.shared.playSound(1001)
+            }.padding()
+            PlusButton() {
+                Controller.shared.playSound(1002) // == 7
+            }.padding()
+            HamburgerButton() {
+                Controller.shared.playSound(1003)
+            }.padding()
+            QRButton() {
+                Controller.shared.playSound(1004)
+            }.padding()
+            PlusButton() {
+                Controller.shared.playSound(1005)
+            }.padding()
             HStack {
-                ReloadButton(disabled: false) {}
-                    .padding()
-                ReloadButton(disabled: true) {}
-                    .padding()
+                ReloadButton(disabled: false) {
+                    Controller.shared.playSound(1006)
+                }.padding()
+                ReloadButton(disabled: true) {
+                }.padding()
             }
-            AwesomeButton(title: "AwesomeButton") {}
+            Button(String(localized: "Accept"), action: {
+                Controller.shared.playSound(1008)
+            })
+                .buttonStyle(TalerButtonStyle(type: .prominent))
+                .padding(.horizontal)
+        }
+    }
+}
+
+#if DEBUG
+fileprivate 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() + 2) {
+                    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 }
         }
     }
 }
+fileprivate 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..b3de098
--- /dev/null
+++ b/TalerWallet1/Views/HelperViews/CopyShare.swift
@@ -0,0 +1,80 @@
+/*
+ * 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
+
+    func shareAction() -> Void {
+        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
+
+    var body: some View {
+        HStack {
+            CopyButton(textToCopy: textToCopy, vertical: false)
+                .buttonStyle(TalerButtonStyle(type: .bordered))
+            ShareButton(textToShare: textToCopy)
+                .buttonStyle(TalerButtonStyle(type: .bordered))
+        } // two buttons
+    }
+}
+// MARK: -
+struct CopyShare_Previews: PreviewProvider {
+    static var previews: some View {
+        CopyShare(textToCopy: "Hallö")
+    }
+}
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..c3f625a
--- /dev/null
+++ b/TalerWallet1/Views/HelperViews/ListStyle.swift
@@ -0,0 +1,97 @@
+/* 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
+    case grouped
+    case inset
+    case insetGrouped
+    case plain
+    case 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" : ["pancakes", "bacon", "orange juice"],
+                                    "Lunch"     : ["sandwich", "chips", 
"lemonade"],
+                                    "Dinner"    : ["spaghetti", "bread", 
"water"]]
+
+
+    var body: some View {
+        VStack {
+            Picker("List Style", selection: $selectedStyle) {
+                ForEach(MyListStyle.allCases, id: \.self) {
+                    Text($0.displayName.capitalized).tag($0)
+                }
+            }
+
+            let keys = Array(sections.keys)
+            List(keys.indices, id: \.self) { index in
+                let key = keys[index]
+                if let section = sections[key] {
+                    Section(key) {
+                        ForEach(section, id: \.self) { item in
+                            Text(item)
+                        }
+                    }
+                }
+            }
+            .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..0c1c452 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.edgesIgnoringSafeArea(.all))
     }
 }
-
+// MARK: -
 struct LoadingView_Previews: PreviewProvider {
     static var previews: some View {
-        LoadingView(backButtonHidden: true)
+        NavigationView {
+            LoadingView(backButtonHidden: true)
+        }
     }
 }
diff --git a/TalerWallet1/Views/HelperViews/QRCodeDetailView.swift 
b/TalerWallet1/Views/HelperViews/QRCodeDetailView.swift
new file mode 100644
index 0000000..e155c48
--- /dev/null
+++ b/TalerWallet1/Views/HelperViews/QRCodeDetailView.swift
@@ -0,0 +1,57 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+import AVFoundation
+
+
+struct QRCodeDetailView: View {
+    let talerURI: String
+    let incoming: Bool
+
+    var body: some View {
+        if talerURI.count > 10 {
+            VStack {
+                Text(incoming ? "Let the payer scan this QR code to pay:"
+                              : "Let the payee scan this QR code to receive:")
+                    .fixedSize(horizontal: false, vertical: true)
+                    .padding(.top, 30)
+                    .font(.title3)
+
+                QRGeneratorView(text: talerURI)
+//                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)
+                    .disabled(false)
+            }
+        }
+    }
+}
+// MARK: -
+#if DEBUG
+fileprivate struct ContentView: View {
+    @State var talerURI: String = 
"taler://pay-push/exchange.demo.taler.net/95ZG4D1AGFGZQ7CNQ1V49D3FT18HXKA6HQT4X3XME9YSJQVFQ520"
+
+    var body: some View {
+        List {
+            QRCodeDetailView(talerURI: talerURI, incoming: false)
+        }
+    }
+}
+struct QRCodeDetailView_Previews: PreviewProvider {
+
+    static var previews: some View {
+        ContentView()
+    }
+}
+#endif
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..9c982e5
--- /dev/null
+++ b/TalerWallet1/Views/HelperViews/SelectDays.swift
@@ -0,0 +1,61 @@
+/*
+ * 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
+#if DEBUG
+    @AppStorage("developerMode") var developerMode: Bool = true
+#else
+    @AppStorage("developerMode") var developerMode: Bool = false
+#endif
+
+    @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(developerMode ? "3 Min." : "1 Day")
+            }.buttonStyle(TalerButtonStyle(type: (selected == 1) ? .prominent 
: .bordered, dimmed: true))
+                .disabled(!isEnabled)
+
+            Button(action: sevenDayAction) {
+                Text(developerMode ? "1 Hour" : "7 Days")
+            }.buttonStyle(TalerButtonStyle(type: (selected == 7) ? .prominent 
: .bordered, dimmed: true))
+                .disabled(!isEnabled || maxExpiration < 7)
+
+            Button(action: thirtyDayAction) {
+                Text(developerMode ? "1 Day" : "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/HelperViews/TransactionButton.swift 
b/TalerWallet1/Views/HelperViews/TransactionButton.swift
new file mode 100644
index 0000000..ed4f3fa
--- /dev/null
+++ b/TalerWallet1/Views/HelperViews/TransactionButton.swift
@@ -0,0 +1,89 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+import AVFoundation
+
+struct TransactionButton: View {
+    let transactionId : String
+    let command : TxAction
+    let action: (_ transactionId: String) async throws -> Void
+
+    @State var disabled: Bool = false
+    @State var executed: Bool = false
+    var body: some View {
+        let isDestructive = (command == .delete) || (command == .fail)
+        let role: ButtonRole? = (command == .abort) ? .cancel
+                              :  isDestructive ? .destructive
+                                               : nil
+        Button(role: role, action: {
+            Task {
+                disabled = true     // don't try this more than once
+                do {
+                    try await action(transactionId)
+//                    symLog.log("\(executed) \(transactionId)")
+                    executed = true
+                } catch {    // TODO: error
+//                    symLog.log(error.localizedDescription)
+                }
+            }
+        }, label: {
+            HStack {
+                if executed {
+                    switch command {
+                        case .delete:
+                            Text("Deleted from list")
+                        case .abort:
+                            Text("Abort pending...")
+                        case .fail:
+                            Text("Failing...")
+                        case .suspend:
+                            Text("Suspending...")
+                        case .resume:
+                            Text("Resuming...")
+                    }
+                } else {
+                    let spaces = "        "
+                    switch command {
+                        case .delete:
+                            Text("Delete from list" + spaces)
+                            Image(systemName: "trash")                  // 􀈑
+                        case .abort:
+                            Text("Abort" + spaces)
+                            Image(systemName: "x.circle")               // 􀀲
+                        case .fail:
+                            Text("Fail" + spaces)
+                            Image(systemName: "fanblades.slash")        // 􁝚
+                        case .suspend:
+                            Text("Suspend" + spaces)
+                            Image(systemName: "clock.badge.xmark")      // 􁜒
+                        case .resume:
+                            Text("Resume" + spaces)
+                            Image(systemName: "clock.arrow.circlepath") // 􀣔
+                    }
+                }
+            }
+            .font(.title)
+            .frame(maxWidth: .infinity)
+        })
+        .buttonStyle(.bordered)
+        .controlSize(.large)
+        .padding(.horizontal)
+        .disabled(disabled)
+    }
+
+}
+
+
+#if DEBUG
+struct TransactionButton_Previews: PreviewProvider {
+
+    static var previews: some View {
+        List {
+            TransactionButton(transactionId: "String", command: .abort, 
action: {transactionId in })
+        }
+    }
+}
+#endif
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..d322200 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()
+    private let symLog = SymLogV(0)
+
+    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..810e052
--- /dev/null
+++ b/TalerWallet1/Views/Main/MainView.swift
@@ -0,0 +1,124 @@
+/*
+ * 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(0)
+    @EnvironmentObject private var viewState: ViewState         // 
popToRootView()
+    @EnvironmentObject private var controller: Controller
+    @State private var sheetPresented = false
+    @State private var urlToOpen: URL? = nil
+    @Binding var soundPlayed: Bool
+
+    func sheetDismissed() -> 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
+        Group {
+            if controller.backendState == .ready {
+                Content(symLog: symLog)
+                // any change to rootViewId triggers popToRootView behaviour
+                    .id(viewState.rootViewId)
+                    .onAppear() {
+                        controller.playSound(1008)     // TODO: Startup chime
+                        soundPlayed = true
+                    }
+            } else if controller.backendState == .error {
+                ErrorView(errortext: nil)            // TODO: show Error View
+            } else {
+                LaunchAnimationView()
+//                    .task {
+//                        let deviceW = UIScreen.main.bounds.width
+//                        let deviceH = UIScreen.main.bounds.height
+//                        print("UIScreen: \(deviceW), \(deviceH)")
+//                    }
+            }
+        }
+        .animation(.easeOut(duration: LAUNCHDURATION), value: 
controller.backendState)
+        .overlay(alignment: .top) {
+            // Show the viewID on top of the app's NavigationView
+            DebugViewV()
+                .id("ViewID")
+        }
+        .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
+        }
+        .sheet(item: $urlToOpen, onDismiss: sheetDismissed) { 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
+        func hamburgerAction() {
+            sidebarVisible = !sidebarVisible
+        }
+
+        let balances = String(localized: "Balances")
+        let exchanges = String(localized: "Exchanges")
+        let settings = String(localized: "Settings")
+        var views: [SidebarItem] {[
+            SidebarItem(name: balances,
+                    sysImage: "creditcard.fill",        // TODO: Wallet Icon
+                        view: AnyView(BalancesListView(navTitle: balances,
+                                                hamburgerAction: 
hamburgerAction)
+                                     )),
+            SidebarItem(name: exchanges,
+                    sysImage: "building.columns",
+                        view: AnyView(ExchangeListView(navTitle: exchanges,
+                                                hamburgerAction: 
hamburgerAction)
+                                     )),
+            SidebarItem(name: settings,    // TODO: "About"?
+                    sysImage: "gearshape.fill",
+                        view: AnyView(SettingsView(navTitle: settings,
+                                            hamburgerAction: hamburgerAction)
+                                     ))
+        ]}
+        @State var currentView: Int = 0
+
+        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) {
+                NavigationView {   // the one and only for all non-sheet views
+                    VStack(alignment: .leading) {   // only needed for 
backslide transition
+                        views[currentView].view
+                            .id(views[currentView].name)
+                            .frame(maxWidth: .infinity, maxHeight: .infinity, 
alignment: .center)
+                            .transition(.backslide)
+                    }
+                }.navigationViewStyle(.stack)
+                // The side view is on top of the current view
+                SideBarView(views: views,
+                      currentView: $currentView,
+                   sidebarVisible: $sidebarVisible)
+            }
+        }
+    } // Content
+}
diff --git a/TalerWallet1/Views/Main/SideBarView.swift 
b/TalerWallet1/Views/Main/SideBarView.swift
index bec2b2c..af47861 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
@@ -26,20 +15,45 @@ struct SidebarItem {
 
 struct SideBarView: View {
     private let symLog = SymLogV(0)
-    var views: [SidebarItem]
+    let views: [SidebarItem]
     @Binding var currentView: Int
     @Binding var sidebarVisible: Bool
+    @State private var rotationEnabled = false
+    @State private var rotationDirection = false
+
+    private let animationTimer = Timer
+        .publish(every: 1.6, on: .current, in: .common)
+        .autoconnect()
 
     var body: some View {
-        symLog {
-            HStack {
-                VStack {
-                    Spacer()
+        HStack {    // sideView left, clear dismiss target right
+            EqualIconWidthDomain {
+                VStack(spacing: 10) {
+                    let gnuTaler = String("GNU Taler")  // this should NOT be 
translated
+                    Link(gnuTaler, destination: 
URL(string:"https://taler.net";)!)
+                        .font(.largeTitle)  //.bold()     iOS 16
+                        .padding(.top, 30)
+                    Image("taler-logo-2023-red")
+                        .resizable()
+                        .scaledToFit()
+                        .frame(width: 100, height: 100)
+                        .rotationEffect(rotationDirection ? Angle(degrees: 0) 
: Angle(degrees: 900))
+                        .accessibilityHidden(true)          // decorative logo
+                        .onTapGesture {
+                            rotationEnabled.toggle()
+                        }
+                        .onReceive(animationTimer) { timerValue in
+                            if rotationEnabled {
+                                withAnimation(.easeInOut(duration: 1.9)) {
+                                    rotationDirection.toggle()
+                                }
+                            }
+                        }
                     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
                         } label: {
                             if let sysImage = views[i].sysImage {
                                 Label(views[i].name, systemImage: sysImage)
@@ -51,39 +65,35 @@ struct SideBarView: View {
                         }
                         .buttonStyle(.bordered)
                         .font(.title)
-//                        .padding(.vertical)
-
-//                        Divider()
+                        .disabled(i == currentView)
+                        .accessibilityHidden(i == currentView)      // don't 
suggest the current item
                     }
                     Spacer()
-                    Spacer()
                 }
-                .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)
-                .animation(.easeInOut, value: sidebarVisible)
-                .ignoresSafeArea()
                 //  .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
-                    }
             }
+            // 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
+                }
         }
+        .animation(.linear  //(duration: SLIDEDURATION)
+                   , value: sidebarVisible)
     }
 }
-
+// MARK: -
 #if DEBUG
-struct BindingViewContainer : View {
+fileprivate struct BindingViewContainer : View {
     @State var currentView: Int = 0
     @State var sidebarVisible: Bool = true
     var views: [SidebarItem]
@@ -91,6 +101,7 @@ struct BindingViewContainer : View {
     var body: some View {
         ZStack(alignment: .leading) {
             views[currentView].view
+                .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: 
.center)
             SideBarView(views: views, currentView: $currentView, 
sidebarVisible: $sidebarVisible)
         }
     }
@@ -99,8 +110,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..1ec8cd7
--- /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(0)
+    @AppStorage("myListStyle") 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
+        .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
+        .onAppear() {
+            DebugViewC.shared.setViewID(VIEW_EMPTY)     // 10
+        }
+    }
+}
+
+struct WalletEmptyView_Previews: PreviewProvider {
+    static var previews: some View {
+        WalletEmptyView()
+    }
+}
diff --git a/TalerWallet1/Views/Payment/DeleteMe.swift 
b/TalerWallet1/Views/Payment/DeleteMe.swift
new file mode 100644
index 0000000..1ff5352
--- /dev/null
+++ b/TalerWallet1/Views/Payment/DeleteMe.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 AVFoundation
+import SymLog
+
+struct PaymentAcceptView: View {
+    private let symLog = SymLogV()
+
+    let detailsForUri: PaymentDetailsForUri
+    let acceptAction: () -> Void
+
+    let navTitle = String(localized: "Accept Payment")
+
+    @State private var disabled = false
+    var body: some View {
+        Group {
+            let raw = detailsForUri.amountRaw
+            let effective = detailsForUri.amountEffective
+            let currency = raw.currencyStr
+            let fee = try! Amount.diff(raw, effective)      // TODO: different 
currencies
+            ThreeAmountsView(topTitle: String(localized: "Amount to pay:"),
+                             topAmount: raw, fee: fee,
+                             bottomTitle: String(localized: "\(currency) to be 
spent:"),
+                             bottomAmount: effective,
+                             large: true, pending: false, incoming: false,
+                             baseURL: 
detailsForUri.contractTerms.exchanges.first?.url)
+                // TODO: payment: popup with all possible exchanges, check fees
+            .safeAreaInset(edge: .bottom) {
+                Button(String(localized: "Accept"), action: acceptAction)
+                    .buttonStyle(TalerButtonStyle(type: .prominent))
+                    .padding(.horizontal)
+            }
+        }
+            .navigationTitle(navTitle)
+    }
+}
+// MARK: -
+struct PaymentAccept_Previews: PreviewProvider {
+    static var previews: some View {
+        let merchant = Merchant(name: "Merchant")
+        let extra = Extra(articleName: "articleName")
+        let product = Product(description: "description")
+        let terms = ContractTerms(amount: try! Amount(fromString: LONGCURRENCY 
+ ":2.2"),
+                                  maxFee: try! Amount(fromString: LONGCURRENCY 
+ ":0.2"),
+                                  maxWireFee: try! Amount(fromString: 
LONGCURRENCY + ":0.2"),
+                                  merchant: merchant,
+                                  extra: extra,
+                                  summary: "summary",
+                                  timestamp: Timestamp.now(),
+                                  payDeadline: Timestamp.tomorrow(),
+                                  refundDeadline: Timestamp.tomorrow(),
+                                  wireTransferDeadline: Timestamp.tomorrow(),
+                                  merchantBaseURL: "merchantBaseURL",
+                                  fulfillmentURL: "fulfillmentURL",
+                                  publicReorderURL: "publicReorderURL",
+                                  auditors: [],
+                                  exchanges: [],
+                                  orderID: "orderID",
+                                  nonce: "nonce",
+                                  merchantPub: "merchantPub",
+                                  products: [product],
+                                  hWire: "hWire",
+                                  wireMethod: "wireMethod",
+                                  wireFeeAmortization: 0)
+        let details = PaymentDetailsForUri(
+            amountRaw: try! Amount(fromString: LONGCURRENCY + ":2.2"),
+            amountEffective: try! Amount(fromString: LONGCURRENCY + ":2.4"),
+            noncePriv: "noncePriv",
+            proposalId: "proposalId",
+            contractTerms: terms,
+            contractTermsHash: "termsHash"
+        )
+        PaymentAcceptView(detailsForUri: details, acceptAction: {})
+    }
+}
diff --git a/TalerWallet1/Views/Payment/PaymentAcceptView.swift 
b/TalerWallet1/Views/Payment/PaymentAcceptView.swift
deleted file mode 100644
index 26eb916..0000000
--- a/TalerWallet1/Views/Payment/PaymentAcceptView.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 PaymentAcceptView: View {
-    private let symLog = SymLogV()
-    @ObservedObject var viewModel: PaymentURIModel
-
-    var 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() }
-    }
-
-    @State var confirmPayResult: ConfirmPayResult?
-
-    var body: some View {
-        symLog { 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()
-                }
-            }
-        }
-            .navigationBarItems(leading: cancelButton)
-            .navigationTitle(navTitle)
-        }
-    }
-}
diff --git a/TalerWallet1/Views/Payment/PaymentURIView.swift 
b/TalerWallet1/Views/Payment/PaymentURIView.swift
index 38ad04c..1bb19c2 100644
--- a/TalerWallet1/Views/Payment/PaymentURIView.swift
+++ b/TalerWallet1/Views/Payment/PaymentURIView.swift
@@ -1,65 +1,122 @@
 /*
- * 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
 
+// Will be called either by the user scanning a QR code or tapping the 
provided link,
+// both from the shop's website. We show the payment details
 struct PaymentURIView: View {
     private let symLog = SymLogV()
-    var url: URL
-    @ObservedObject var viewModel: PaymentURIModel
+    let navTitle = String(localized: "Confirm Payment", comment:"pay merchant")
 
-//    @Environment(\.dismiss) var dismiss     // call dismiss() to get rid of 
the sheet
-    let navTitle = "Payment"
-    var cancelButton: some View {
-        Button("Cancel5") { dismissTop() }
+    @EnvironmentObject private var controller: Controller
+
+    // the scanned URL
+    let url: URL
+
+    @EnvironmentObject private var model: WalletModel
+
+    func acceptAction(detailsForUri: PaymentDetailsForUri) {
+        Task {
+            do {
+                let confirmPayResult = try await 
model.confirmPayM(detailsForUri.proposalId)
+                symLog.log(confirmPayResult as Any)
+                if confirmPayResult.type != "done" {
+                    controller.playSound(0)
+                    // TODO: show error
+                }
+            } catch {
+                controller.playSound(0)
+                // TODO: error
+                symLog.log(error.localizedDescription)
+            }
+            dismissTop()
+        }
     }
 
-    @State var detailsForUri: PaymentDetailsForUri?
+    @State var detailsForUri: PaymentDetailsForUri? = nil
 
     var body: some View {
-        let badURL = "Error in URL: \(url)"
-        VStack {
-            if viewModel.paymentState == nil {
-                LoadingView(backButtonHidden: false)
-            } else { switch viewModel.paymentState {
-                case .waitingForUriDetails:
-                    let _ = symLog.vlog("waitingForUriDetails")
-                    WithdrawProgressView(message: url.host ?? badURL) { 
dismissTop() }
-                        .navigationTitle("Contacting Exchange")
-                case .receivedUriDetails:
-                    let _ = symLog.vlog("waitingForUser")
-                    PaymentAcceptView(viewModel: viewModel, detailsForAmount: 
detailsForUri!)
-                default:
-                    symLog {
-                        Text("Payment")
-                            .navigationBarItems(leading: cancelButton)
-                            .navigationTitle(navTitle)
-                    }
-            } }
-        }.task {
-            do { // TODO: cancelled
-                symLog.log(".task")
-                detailsForUri = try await 
viewModel.preparePayForUri(url.absoluteString)
-//                print(detailsForUri?.status)
-//                print(detailsForUri?.amountRaw.description)
-//                print(detailsForUri?.amountEffective.description)
-//                print(detailsForUri?.proposalId)
-            } catch {
-                symLog.log(error.localizedDescription)                // TODO: 
error
+        if let detailsForUri {
+          ScrollViewReader { scrollView in
+            VStack {
+                let baseURL = detailsForUri.contractTerms.exchanges.first?.url
+                let raw = detailsForUri.amountRaw
+                let effective = detailsForUri.amountEffective
+                let currency = raw.currencyStr
+                let fee = try! Amount.diff(raw, effective)      // TODO: 
different currencies
+                ThreeAmountsView(topTitle: String(localized: "Amount to pay:"),
+                                topAmount: raw, fee: fee,
+                              bottomTitle: String(localized: "\(currency) to 
be spent:"),
+                             bottomAmount: effective,
+                                    large: true, pending: false, incoming: 
false,
+                                  baseURL: baseURL)
+                // TODO: payment: popup with all possible exchanges, check fees
+                .safeAreaInset(edge: .bottom) {
+                    Button(navTitle, action: { acceptAction(detailsForUri: 
detailsForUri) })
+                        .buttonStyle(TalerButtonStyle(type: .prominent))
+                        .padding(.horizontal)
+                }
             }
+            .navigationTitle(navTitle)
+          }
+        } else {
+            let badURL = "Error in URL: \(url)"
+            WithdrawProgressView(message: url.host ?? badURL)
+                .navigationTitle("Contacting Exchange")
+                .task {
+                    do {
+                        symLog.log(".task")
+                        let details = try await 
model.preparePayForUriM(url.absoluteString)
+                        detailsForUri = details
+                    } catch {    // TODO: error
+                        symLog.log(error.localizedDescription)
+                    }
+                }
         }
     }
 }
+// MARK: -
+struct PaymentURIView_Previews: PreviewProvider {
+    static var previews: some View {
+        let merchant = Merchant(name: "Merchant")
+        let extra = Extra(articleName: "articleName")
+        let product = Product(description: "description")
+        let terms = ContractTerms(amount: try! Amount(fromString: LONGCURRENCY 
+ ":2.2"),
+                                  maxFee: try! Amount(fromString: LONGCURRENCY 
+ ":0.2"),
+                                  maxWireFee: try! Amount(fromString: 
LONGCURRENCY + ":0.2"),
+                                  merchant: merchant,
+                                  extra: extra,
+                                  summary: "summary",
+                                  timestamp: Timestamp.now(),
+                                  payDeadline: Timestamp.tomorrow(),
+                                  refundDeadline: Timestamp.tomorrow(),
+                                  wireTransferDeadline: Timestamp.tomorrow(),
+                                  merchantBaseURL: "merchantBaseURL",
+                                  fulfillmentURL: "fulfillmentURL",
+                                  publicReorderURL: "publicReorderURL",
+                                  auditors: [],
+                                  exchanges: [],
+                                  orderID: "orderID",
+                                  nonce: "nonce",
+                                  merchantPub: "merchantPub",
+                                  products: [product],
+                                  hWire: "hWire",
+                                  wireMethod: "wireMethod",
+                                  wireFeeAmortization: 0)
+        let details = PaymentDetailsForUri(
+            amountRaw: try! Amount(fromString: LONGCURRENCY + ":2.2"),
+            amountEffective: try! Amount(fromString: LONGCURRENCY + ":2.4"),
+            noncePriv: "noncePriv",
+            proposalId: "proposalId",
+            contractTerms: terms,
+            contractTermsHash: "termsHash"
+        )
+        let url = URL(string: "taler://pay/some_amount")!
+        
+        PaymentURIView(url: url, detailsForUri: details)
+    }
+}
diff --git a/TalerWallet1/Views/Peer2peer/PaymentPurpose.swift 
b/TalerWallet1/Views/Peer2peer/PaymentPurpose.swift
new file mode 100644
index 0000000..0a69e48
--- /dev/null
+++ b/TalerWallet1/Views/Peer2peer/PaymentPurpose.swift
@@ -0,0 +1,111 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+import SymLog
+
+struct PaymentPurpose: View {
+    private let symLog = SymLogV()
+
+    let scopeInfo: ScopeInfo
+    let centsToTransfer: UInt64
+    let fee: String
+    @Binding var summary: String
+    @Binding var expireDays: UInt
+//    var deactivateAction: () -> Void
+
+    @FocusState private var isFocused: Bool
+
+    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) ?? ""
+    }
+
+    var body: some View {
+        let amount = Amount.amountFromCents(scopeInfo.currency, 
centsToTransfer)
+
+        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: $summary)
+                    .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("\(summary.count)/100")
+                } // maximum 100 characters
+
+                Text("Expires in:")
+                    .font(.title3)
+
+                SelectDays(selected: $expireDays, maxExpiration: 35)
+                    .disabled(false)
+                    .padding(.bottom)
+
+                let disabled = (expireDays == 0) || (summary.count < 1)
+
+                NavigationLink(destination: LazyView {
+                    SendNow(amountToSend: nil,
+                            amountToReceive: amount,
+                            summary: summary, expireDays: expireDays)
+                }) {
+                    Text("Invoice \(label) \(scopeInfo.currency)")
+                        .font(buttonFont)
+                }
+                .buttonStyle(TalerButtonStyle(type: .prominent))
+                .disabled(disabled)
+
+                Spacer()
+            }
+            .frame(maxWidth: .infinity, alignment: .leading)
+            .padding(.horizontal)
+        }
+        .navigationTitle("Invoice")
+        .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
+        .onAppear {
+            DebugViewC.shared.setViewID(VIEW_INVOICE_PURPOSE)
+            print("❗️ PaymentPurpose onAppear")
+        }
+        .onDisappear {
+            print("❗️ PaymentPurpose onDisappear")
+//            deactivateAction()
+        }
+    }
+
+}
+// MARK: -
+#if DEBUG
+struct PaymentPurpose_Previews: PreviewProvider {
+    static var previews: some View {
+        let scopeInfo = ScopeInfo(type: ScopeInfo.ScopeInfoType.exchange, 
exchangeBaseUrl: DEMOEXCHANGE, currency: LONGCURRENCY)
+        @State var summary: String = "pUrPoSe"
+        @State var expireDays: UInt = 0
+        PaymentPurpose(scopeInfo: scopeInfo,
+                 centsToTransfer: 5,
+                             fee: "fee",
+                         summary: $summary,
+                      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..726bfaf
--- /dev/null
+++ b/TalerWallet1/Views/Peer2peer/RequestPayment.swift
@@ -0,0 +1,93 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+import SymLog
+
+// Will be called by the user tapping "Request Payment" in the balances list
+struct RequestPayment: View {
+    private let symLog = SymLogV()
+
+    var scopeInfo: ScopeInfo
+    @Binding var centsToTransfer: UInt64
+    @Binding var summary: String
+
+    @EnvironmentObject private var model: WalletModel
+
+    @State private var peerPullCheck: CheckPeerPullCreditResponse? = nil
+    @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 currency = scopeInfo.currency
+        let navTitle = String(localized: "Request \(currency)")
+        let currencyField = CurrencyField(value: $centsToTransfer, currency: 
currency)
+
+        ScrollViewReader { scrollView in
+          VStack {
+            CurrencyInputView(currencyField: currencyField,
+                              title: String(localized: "Amount to receive:"))
+
+            let someCoins = SomeCoins(details: peerPullCheck)
+            QuiteSomeCoins(someCoins: someCoins, shouldShowFee: true,
+                           currency: currency, amountEffective: 
peerPullCheck?.amountEffective)
+
+            HStack {
+                let disabled = centsToTransfer == 0
+
+                NavigationLink(destination: LazyView {
+                    PaymentPurpose(scopeInfo: scopeInfo,
+                             centsToTransfer: centsToTransfer,
+                                         fee: someCoins.fee,
+                                     summary: $summary,
+                                  expireDays: $expireDays)
+//                        { deactivateAction() }
+                }) {
+                    Text("Create invoice")
+                }
+                .buttonStyle(TalerButtonStyle(type: .prominent))
+                .disabled(disabled)
+            }
+            Spacer()
+          }
+        }
+        .frame(maxWidth: .infinity, alignment: .leading)
+        .padding(.horizontal)
+        .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
+        .navigationTitle(navTitle)
+        .onAppear {   // make CurrencyField show the keyboard
+            DebugViewC.shared.setViewID(VIEW_INVOICE_P2P)
+            symLog.log("❗️Yikes \(navTitle) onAppear")
+        }
+        .onDisappear {
+            symLog.log("❗️Yikes \(navTitle) onDisappear")
+        }
+        .task(id: centsToTransfer) {
+            let amount = Amount.amountFromCents(currency, centsToTransfer)
+            do {
+                let ppCheck = try await model.checkPeerPullCreditM(amount, 
exchangeBaseUrl: nil)
+                peerPullCheck = ppCheck
+                // TODO: set from exchange
+//                agePicker.setAges(ages: peerPushCheck?.ageRestrictionOptions)
+            } catch {    // TODO: error
+                symLog.log(error.localizedDescription)
+                peerPullCheck = nil
+            }
+        }
+    }
+}
+// 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..53d736e
--- /dev/null
+++ b/TalerWallet1/Views/Peer2peer/SendAmount.swift
@@ -0,0 +1,118 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+import SymLog
+
+// Will be called by the user tapping "Send Coins" in the balances list
+struct SendAmount: View {
+    private let symLog = SymLogV()
+
+    let amountAvailable: Amount // TODO: GetMaxPeerPushAmount
+    @Binding var centsToTransfer: UInt64
+    @Binding var summary: String
+
+    @EnvironmentObject private var model: WalletModel
+
+    @State var peerPushCheck: CheckPeerPushDebitResponse? = nil
+    @State private var expireDays: UInt = 0
+
+    private func fee(ppCheck: CheckPeerPushDebitResponse?) -> String {
+        do {
+            if let ppCheck {
+                // Outgoing: fee = effective - raw
+                let fee = try ppCheck.amountEffective - ppCheck.amountRaw
+                return fee.readableDescription
+            }
+        } catch {}
+        return ""
+    }
+
+    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 = amountAvailable.currencyStr
+        let navTitle = String(localized: "Send \(currency)")
+        let currencyField = CurrencyField(value: $centsToTransfer, currency: 
currency)
+
+        let fee = fee(ppCheck: peerPushCheck)
+//        ScrollViewReader { scrollView in
+          VStack {
+            let available = amountAvailable.readableDescription
+            Text("Available: \(available)")
+                .font(.title3)
+            CurrencyInputView(currencyField: currencyField,
+                              title: String(localized: "Amount to send:"))
+
+            Text("+ \(fee) payment fee")
+                .foregroundColor(.red)
+                .padding(4)
+
+            HStack {
+                let disabled = centsToTransfer == 0    // TODO: check 
amountAvailable
+
+                NavigationLink(destination: LazyView {
+                    SendPurpose(amountAvailable: amountAvailable,
+                                centsToTransfer: centsToTransfer,
+                                            fee: fee,
+                                        summary: $summary,
+                                     expireDays: $expireDays)
+//                        { deactivateAction() }
+                }) {
+                    Text("To another wallet")
+                }
+                .buttonStyle(TalerButtonStyle(type: .prominent))
+                .disabled(disabled)
+            }
+            Spacer()
+          }
+//        }
+        .frame(maxWidth: .infinity, alignment: .leading)
+        .padding(.horizontal)
+        .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
+        .navigationTitle(navTitle)
+        .onAppear {   // make CurrencyField show the keyboard
+            DebugViewC.shared.setViewID(VIEW_SEND_P2P)
+            symLog.log("❗️Yikes SendAmount onAppear")
+        }
+        .onDisappear {
+            symLog.log("❗️Yikes SendAmount onDisappear")
+        }
+        .task(id: centsToTransfer) {
+            let amount = Amount.amountFromCents(currency, centsToTransfer)
+            do {
+                let ppCheck = try await model.checkPeerPushDebitM(amount)
+                peerPushCheck = ppCheck
+                // TODO: set from exchange
+//                agePicker.setAges(ages: peerPushCheck?.ageRestrictionOptions)
+            } catch {    // TODO: error
+                symLog.log(error.localizedDescription)
+                peerPushCheck = nil
+            }
+        }
+    }
+}
+// MARK: -
+#if DEBUG
+struct SendAmount_Container : View {
+    @State private var centsToTransfer: UInt64 = 510
+    @State private var summary: String = ""
+
+    var body: some View {
+        let amount = Amount(currency: LONGCURRENCY, integer: 10, fraction: 0)
+        SendAmount(amountAvailable: amount,
+                   centsToTransfer: $centsToTransfer,
+                           summary: $summary)
+    }
+}
+
+struct SendAmount_Previews: PreviewProvider {
+    static var previews: some View {
+        SendAmount_Container()
+    }
+}
+#endif
diff --git a/TalerWallet1/Views/Peer2peer/SendNow.swift 
b/TalerWallet1/Views/Peer2peer/SendNow.swift
new file mode 100644
index 0000000..bc07dbc
--- /dev/null
+++ b/TalerWallet1/Views/Peer2peer/SendNow.swift
@@ -0,0 +1,93 @@
+/*
+ * 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()
+#if DEBUG
+    @AppStorage("developerMode") var developerMode: Bool = true
+#else
+    @AppStorage("developerMode") var developerMode: Bool = false
+#endif
+
+    let amountToSend: Amount?
+    let amountToReceive: Amount?
+    let summary: String
+    let expireDays: UInt
+
+    @EnvironmentObject private var model: WalletModel
+
+    @State var talerURI: String = ""
+
+    var body: some View {
+        VStack {
+            if talerURI.isEmpty {
+                LoadingView(backButtonHidden: true)
+            } else {
+                    QRCodeDetailView(talerURI: talerURI,
+                                     incoming: amountToSend == nil)
+                    .padding()
+                    Text("The QR code can also be copied and shared from 
Transactions later")
+                        .fixedSize(horizontal: false, vertical: true)
+                        .font(.subheadline)
+                        .padding(.vertical, 20)
+
+                    Spacer()
+                    Button("Done") {
+                        withAnimation(){ ViewState.shared.popToRootView() }
+                    }
+                    .buttonStyle(TalerButtonStyle(type: .prominent))
+                    .padding()
+
+            }
+        }
+//        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: 
.leading)
+//        .padding(.horizontal)
+        .interactiveDismissDisabled()       // can only use "Done" button to 
dismiss
+        .navigationBarHidden(true)          // no back button, no title
+        .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
+        .task {
+            symLog.log(".task")
+            do {
+                // generate talerURI
+                var timestamp = developerMode ? 
Timestamp.inSomeMinutes(expireDays > 20 ? (24*60)
+                                                                      : 
expireDays > 5 ? 60 : 3)
+                                              : 
Timestamp.inSomeDays(expireDays)
+                if let amountToSend {
+                    let terms = PeerContractTerms(amount: amountToSend,
+                                                 summary: summary,
+                                        purse_expiration: timestamp)
+                    // TODO: user might choose baseURL
+                    let response = try await model.initiatePeerPushDebitM(nil, 
terms: terms)
+                    talerURI = response.talerUri
+                } else if let amountToReceive {
+                    let terms = PeerContractTerms(amount: amountToReceive,
+                                                 summary: summary,
+                                        purse_expiration: timestamp)
+                    // TODO: user might choose baseURL
+                    let response = try await 
model.initiatePeerPullCreditM(nil, terms: terms)
+                    talerURI = response.talerUri
+                } else { talerURI = "" }
+            } catch {    // TODO: error
+                symLog.log(error.localizedDescription)
+                talerURI = ""
+            }
+        } // task
+    }
+}
+// MARK: -
+struct SendNow_Previews: PreviewProvider {
+    static var previews: some View {
+        Group {
+            SendNow(amountToSend: try! Amount(fromString: LONGCURRENCY + 
":4.8"),
+                    amountToReceive: nil,
+                    summary: "some purpose",
+                    expireDays: 0,
+                    talerURI: 
"taler://pay-push/exchange.demo.taler.net/95ZG4D1AGFGZQ7CNQ1V49D3FT18HXKA6HQT4X3XME9YSJQVFQ520")
+        }
+    }
+}
diff --git a/TalerWallet1/Views/Peer2peer/SendPurpose.swift 
b/TalerWallet1/Views/Peer2peer/SendPurpose.swift
new file mode 100644
index 0000000..660c64f
--- /dev/null
+++ b/TalerWallet1/Views/Peer2peer/SendPurpose.swift
@@ -0,0 +1,119 @@
+/*
+ * 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
+
+    let amountAvailable: Amount
+    let centsToTransfer: UInt64
+    let fee: String
+    @Binding var summary: 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) ?? ""
+    }
+
+    var body: some View {
+        let amount = Amount.amountFromCents(amountAvailable.currencyStr, 
centsToTransfer)
+
+        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: $summary)
+                    .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("\(summary.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 disabled = (expireDays == 0) || (summary.count < 1)    // 
TODO: check amountAvailable
+
+                NavigationLink(destination: LazyView {
+                    SendNow(amountToSend: amount,
+                            amountToReceive: nil,
+                            summary: summary, expireDays: expireDays)
+                }) {
+                    Text("Send \(label) \(amountAvailable.currencyStr) now")
+                        .font(buttonFont)
+                }
+                .buttonStyle(TalerButtonStyle(type: .prominent))
+                .disabled(disabled)
+
+                Spacer()
+            }
+            .frame(maxWidth: .infinity, alignment: .leading)
+            .padding(.horizontal)
+        }
+        .navigationTitle("To another Wallet")
+        .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
+        .onAppear {
+            DebugViewC.shared.setViewID(VIEW_SEND_PURPOSE)
+            print("❗️ SendPurpose onAppear")
+        }
+        .onDisappear {
+            print("❗️ SendPurpose onDisappear")
+//            deactivateAction()
+        }
+        .task {
+            symLog.log(".task")
+            do {
+
+            } catch {    // TODO: error
+                symLog.log(error.localizedDescription)
+            }
+        }
+    }
+
+}
+// MARK: -
+#if DEBUG
+struct SendPurpose_Previews: PreviewProvider {
+    static var previews: some View {
+        @State var summary: String = ""
+        @State var expireDays: UInt = 0
+        let amount = Amount(currency: LONGCURRENCY, integer: 10, fraction: 0)
+        SendPurpose(amountAvailable: amount,
+                    centsToTransfer: 543,
+                                fee: "0,43",
+                            summary: $summary,
+                         expireDays: $expireDays)
+//        { print("deactivateAction") }
+    }
+}
+#endif
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..66691b6
--- /dev/null
+++ b/TalerWallet1/Views/Settings/Pending/PendingOpsListView.swift
@@ -0,0 +1,58 @@
+/*
+ * 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")
+
+    @EnvironmentObject private var model: WalletModel
+
+    @State var pendingOperations: [PendingOperation] = []
+
+    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.getPendingOperationsM
+        Content(symLog: symLog, pendingOperations: $pendingOperations, 
reloadAction: reloadAction)
+            .navigationTitle(navTitle)
+            .task {
+                symLog.log(".task")
+                pendingOperations = await reloadAction()
+            }
+    }
+}
+// MARK: -
+extension PendingOpsListView {
+    struct Content: View {
+        let symLog: SymLogV?
+        @Binding var pendingOperations: [PendingOperation]
+        var reloadAction: () async -> [PendingOperation]
+        @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
+        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(pendingOperations, id: \.self) { pendingOp in
+                    PendingOpView(pendingOp: pendingOp)
+                }
+                .listStyle(myListStyle.style).anyView
+                .navigationBarTitleDisplayMode(.large)
+                .onAppear() {
+                    DebugViewC.shared.setViewID(VIEW_PENDING)
+                }
+                .refreshable {
+                    symLog?.log("refreshing")
+                    pendingOperations = await reloadAction()
+                }
+            }
+        }
+    }
+}
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..51cedff 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,178 @@ 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
+    let navTitle: String
+
+#if DEBUG
+    @AppStorage("developerMode") var developerMode: Bool = true
+#else
     @AppStorage("developerMode") var developerMode: Bool = false
+#endif
+    @AppStorage("playSounds") var playSounds: Bool = false
     @AppStorage("developDelay") var developDelay: Bool = false
+    @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
 
-    var showSidebar: () -> Void
-    init(showSidebar: @escaping () -> Void) {
-        self.showSidebar = showSidebar
-    }
+    var hamburgerAction: () -> Void
+
+    @EnvironmentObject private var model: WalletModel
 
     @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") {}
+                SettingsToggle(name: String(localized: "Play Payment Sounds"), 
value: $playSounds,
+                        description: String(localized: "After a transaction 
finished"))
+                HStack {
+                    Text("Liststyle:")
+                        .font(.title2)
+                    Spacer()
+                    Picker(selection: $myListStyle) {
+                        ForEach(MyListStyle.allCases, id: \.self) {
+                            Text($0.displayName.capitalized).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() }
+                        } 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 {
+                                    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 {
+                                    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 {
+                                    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 {
@@ -143,11 +195,11 @@ extension Bundle {
         return "v\(releaseVersionNumber ?? "1.0.0")"
     }
 }
-
+// MARK: -
+#if DEBUG
 struct SettingsView_Previews: PreviewProvider {
     static var previews: some View {
-        SettingsView {
-            
-        }
+        SettingsView(navTitle: "Settings") { }
     }
 }
+#endif
diff --git a/TalerWallet1/Views/Sheets/P2P_Sheets/P2pAcceptDone.swift 
b/TalerWallet1/Views/Sheets/P2P_Sheets/P2pAcceptDone.swift
new file mode 100644
index 0000000..6eb706d
--- /dev/null
+++ b/TalerWallet1/Views/Sheets/P2P_Sheets/P2pAcceptDone.swift
@@ -0,0 +1,63 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+import SymLog
+
+struct P2pAcceptDone: View {
+    private let symLog = SymLogV()
+
+    let transactionId: String
+    let incoming: Bool
+
+    @EnvironmentObject private var model: WalletModel
+    @EnvironmentObject private var controller: Controller
+
+    @State private var finished: Bool = false
+
+    func reloadOneAction(_ transactionId: String) async throws -> Transaction {
+        return try await model.getTransactionByIdT(transactionId)
+    }
+
+    var body: some View {
+#if DEBUG
+        let _ = Self._printChanges()
+        let _ = symLog.vlog()       // just to get the # to compare it with 
.onAppear & onDisappear
+#endif
+        let navTitle = incoming ? String(localized: "Received P2P")
+                                : String(localized: "Paid P2P")
+      ScrollViewReader { scrollView in
+        VStack {
+            TransactionDetailView(transactionId: transactionId,
+                                   reloadAction: reloadOneAction,
+                                     doneAction: { dismissTop() })
+            .navigationBarBackButtonHidden(true)
+            .interactiveDismissDisabled()           // can only use "Done" 
button to dismiss
+            .navigationTitle(navTitle)
+        }.onAppear() {
+            symLog.log("onAppear")
+            DebugViewC.shared.setSheetID(SHEET_RCV_P2P_ACCEPT)
+        }.task {
+            do {
+                if incoming {
+                    _ = try await model.acceptPeerPushCreditM(transactionId)
+                } else {
+                    _ = try await model.confirmPeerPullDebitM(transactionId)
+                }
+                finished = true
+            } catch {    // TODO: error
+                symLog.log(error.localizedDescription)
+                controller.playSound(0)
+            }
+        }
+      }
+    }
+}
+// MARK: -
+struct P2pAcceptDone_Previews: PreviewProvider {
+    static var previews: some View {
+        P2pAcceptDone(transactionId: "some ID", incoming: true)
+    }
+}
diff --git a/TalerWallet1/Views/Sheets/P2P_Sheets/P2pPayURIView.swift 
b/TalerWallet1/Views/Sheets/P2P_Sheets/P2pPayURIView.swift
new file mode 100644
index 0000000..d3cb92e
--- /dev/null
+++ b/TalerWallet1/Views/Sheets/P2P_Sheets/P2pPayURIView.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
+
+// Will be called either by the user scanning a QR code or tapping the 
provided link,
+// from another user's SendInvoice. We show the P2P details.
+struct P2pPayURIView: View {
+    private let symLog = SymLogV()
+    let navTitle = String(localized: "Pay P2P Invoice")
+
+    // the scanned URL
+    let url: URL
+
+    @EnvironmentObject private var model: WalletModel
+
+    @State private var peerPullDebitResponse: PreparePeerPullDebitResponse?
+
+    var body: some View {
+        VStack {
+            if let peerPullDebitResponse {
+                List {
+                    let raw = peerPullDebitResponse.amountRaw
+                    let effective = peerPullDebitResponse.amountEffective
+                    let currency = raw.currencyStr
+                    let fee = try! Amount.diff(raw, effective)
+                    ThreeAmountsView(topTitle: String(localized: "Amount to 
pay:"),
+                                     topAmount: raw, fee: fee,
+                                     bottomTitle: String(localized: 
"\(currency) to be spent:"),
+                                     bottomAmount: effective,
+                                     large: false, pending: false, incoming: 
false,
+                                     baseURL: nil)
+                }
+                .navigationTitle(navTitle)
+
+                NavigationLink(destination: LazyView {
+                        P2pAcceptDone(transactionId: 
peerPullDebitResponse.transactionId,
+                                           incoming: false)
+                    }) {
+                        Text("Confirm Payment", comment:"pay P2P invoice")     
 // SHEET_PAY_P2P
+                    }.buttonStyle(TalerButtonStyle(type: .prominent))
+                        .padding()
+            } else {
+                WithdrawProgressView(message: url.host ?? "Yikes - no valid 
URL")
+                    .navigationTitle("Contacting Exchange")
+            }
+        }
+        .onAppear() {
+            symLog.log("onAppear")
+            DebugViewC.shared.setSheetID(SHEET_PAY_P2P)
+        }
+        .task {
+            do { // TODO: cancelled
+                symLog.log(".task")
+                let ppDebitResponse = try await 
model.preparePeerPullDebitM(url.absoluteString)
+                peerPullDebitResponse = ppDebitResponse
+            } catch {    // TODO: error
+                symLog.log(error.localizedDescription)
+                peerPullDebitResponse = nil
+            }
+        }
+
+    }
+}
+// MARK: -
+//#Preview {
+//    P2pPayURIView(url: <#T##URL#>, model: <#T##WalletModel#>)
+//}
diff --git a/TalerWallet1/Views/Sheets/P2P_Sheets/P2pReceiveURIView.swift 
b/TalerWallet1/Views/Sheets/P2P_Sheets/P2pReceiveURIView.swift
new file mode 100644
index 0000000..f40a5bf
--- /dev/null
+++ b/TalerWallet1/Views/Sheets/P2P_Sheets/P2pReceiveURIView.swift
@@ -0,0 +1,78 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+import SymLog
+
+// Will be called either by the user scanning a QR code or tapping the 
provided link,
+// from another user's SendCoins. We show the P2P details - but first the ToS 
must be accepted.
+struct P2pReceiveURIView: View {
+    private let symLog = SymLogV()
+    let navTitle = String(localized: "Accept P2P Receive")
+
+    // the scanned URL
+    let url: URL
+    
+    @EnvironmentObject private var model: WalletModel
+
+    @State private var peerPushCreditResponse: PreparePeerPushCreditResponse?
+
+    var body: some View {
+        VStack {
+            if let peerPushCreditResponse {
+                List {
+                    let raw = peerPushCreditResponse.amountRaw
+                    let effective = peerPushCreditResponse.amountEffective
+                    let currency = raw.currencyStr
+                    let fee = try! Amount.diff(raw, effective)
+                    ThreeAmountsView(topTitle: String(localized: "Amount to 
receive:"),
+                                    topAmount: raw, fee: fee,
+                                  bottomTitle: String(localized: "\(currency) 
to be obtained:"),
+                                 bottomAmount: effective,
+                                        large: false, pending: false, 
incoming: true,
+                                      baseURL: nil)
+                }
+                .navigationTitle(navTitle)
+                let tosAccepted = true  // TODO: 
https://bugs.gnunet.org/view.php?id=7869
+                if tosAccepted {
+                    NavigationLink(destination: LazyView {
+                        P2pAcceptDone(transactionId: 
peerPushCreditResponse.transactionId,
+                                           incoming: true)
+                    }) {
+                        Text("Accept Withdrawal")      // SHEET_WITHDRAW_ACCEPT
+                    }.buttonStyle(TalerButtonStyle(type: .prominent))
+                        .padding()
+                } else {
+                    NavigationLink(destination: LazyView {
+                        WithdrawTOSView(exchangeBaseUrl: nil,
+                                                 viewID: SHEET_RCV_P2P_TOS,
+                                           acceptAction: nil)         // pop 
back to here
+                    }) {
+                        Text("Check Terms of Service")
+                    }.buttonStyle(TalerButtonStyle(type: .prominent))
+                        .padding()
+                }
+            } else {
+                // Yikes no details or no baseURL
+//                WithdrawProgressView(message: url.host ?? badURL)
+//                    .navigationTitle("Contacting Exchange")
+            }
+        }
+        .onAppear() {
+            symLog.log("onAppear")
+            DebugViewC.shared.setSheetID(SHEET_RCV_P2P)
+        }
+        .task {
+            do { // TODO: cancelled
+                symLog.log(".task")
+                let ppCreditResponse = try await 
model.preparePeerPushCreditM(url.absoluteString)
+                peerPushCreditResponse = ppCreditResponse
+            } catch {    // TODO: error
+                symLog.log(error.localizedDescription)
+                peerPushCreditResponse = nil
+            }
+        }
+    }
+}
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..43b1895
--- /dev/null
+++ b/TalerWallet1/Views/Sheets/Sheet.swift
@@ -0,0 +1,42 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import SymLog
+import os.log
+
+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
+
+    let logger = Logger (subsystem: "net.taler.gnu", category: "Sheet")
+
+    var cancelButton: some View {
+        Button("Cancel") {
+            logger.log("Cancel")
+            dismissTop()
+        }
+    }
+
+    var body: some View {
+        let idString = debugViewC.sheetID > 0 ? String(debugViewC.sheetID)
+                                              : ""      // show nothing if 0
+        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")
+        }
+    }
+}
diff --git a/TalerWallet1/Views/Sheets/URLSheet.swift 
b/TalerWallet1/Views/Sheets/URLSheet.swift
new file mode 100644
index 0000000..6902d55
--- /dev/null
+++ b/TalerWallet1/Views/Sheets/URLSheet.swift
@@ -0,0 +1,52 @@
+/*
+ * 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: "Examining URL")
+    var urlToOpen: URL
+    @EnvironmentObject private var controller: Controller
+
+    @State private var urlCommand: UrlCommand? = nil
+
+    var body: some View {
+        Group {
+            if let urlCommand {
+                switch urlCommand {
+                    case .withdraw:
+                        WithdrawURIView(url: urlToOpen)
+                    case .pay:
+                        PaymentURIView(url: urlToOpen)
+                    case .payPull:
+                        P2pPayURIView(url: urlToOpen)
+                    case .payPush:
+                        P2pReceiveURIView(url: urlToOpen)
+                    case .unknown:
+                        Text("unknown command")
+                }
+            } 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..0254126
--- /dev/null
+++ b/TalerWallet1/Views/Transactions/ManualDetails.swift
@@ -0,0 +1,74 @@
+/*
+ * 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)
+                    .accessibilityLabel("Copy IBAN")
+                    .disabled(false)
+            }   .padding(.leading)
+                .padding(.vertical, -8)
+                .listRowSeparator(.hidden)
+            Text("and use the transaction subject:")
+                .listRowSeparator(.hidden)
+            HStack {
+                Text(details.reservePub)
+                    .accessibilityLabel("Taler cryptocode")
+                Spacer()
+                CopyButton(textToCopy: details.reservePub, vertical: true)
+                    .accessibilityLabel("Copy subject")
+                    .disabled(false)
+            }   .padding(.leading)
+                .padding(.vertical, -8)
+                .listRowSeparator(.hidden)
+            HStack {
+                Spacer()
+                ShareButton(textToShare: payto)
+                    .accessibilityLabel("Share PayTo ULR")
+                    .disabled(false)
+                Spacer()
+            }   .listRowSeparator(.hidden)
+            Text(verbatim: "PayTo URL")       // the only reason for this 
leading-aligned text is to get a nice full lenght listRowSeparator
+                .font(.footnote)
+                .foregroundColor(Color.yellow)   // clear
+                .padding(.vertical, -8)
+                .listRowSeparator(.automatic)
+                .accessibilityHidden(true)
+        }
+    }
+}
+// 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..5e60ce4
--- /dev/null
+++ b/TalerWallet1/Views/Transactions/ThreeAmounts.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 ThreeAmountsSheet: 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)")
+                        .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 {
+                ThreeAmountsSheet(common: common, topTitle: "Withdrawal", 
baseURL: DEMOEXCHANGE, large: 1==1)
+                    .safeAreaInset(edge: .bottom) {
+                        Button(String(localized: "Accept"), action: {})
+                            .buttonStyle(TalerButtonStyle(type: .prominent))
+                            .padding(.horizontal)
+                            .disabled(true)
+                    }
+            }
+        }
+    }
+}
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..b16877e
--- /dev/null
+++ b/TalerWallet1/Views/Transactions/TransactionDetailView.swift
@@ -0,0 +1,285 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+import SymLog
+
+extension Transaction {             // for Dummys
+    init(dummyCurrency: String) {
+        let amount = try! Amount(fromString: "\(dummyCurrency):0")
+        let now = Timestamp.now()
+        let common = TransactionCommon(type: .dummy,
+                                       txState: TransactionState(major: 
.pending),
+                                       amountEffective: amount,
+                                       amountRaw: amount,
+                                       transactionId: "",
+                                       timestamp: now,
+                                       txActions: [])
+        self = .dummy(DummyTransaction(common: common))
+    }
+}
+// MARK: -
+struct TransactionDetailView: View {
+    private let symLog = SymLogV()
+    @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
+#if DEBUG
+    @AppStorage("developerMode") var developerMode: Bool = true
+#else
+    @AppStorage("developerMode") var developerMode: Bool = false
+#endif
+
+    var transactionId: String
+    @State var transaction: Transaction = Transaction(dummyCurrency: 
DEMOCURRENCY)
+    @State var viewId = UUID()
+
+    var reloadAction: ((_ transactionId: String) async throws -> Transaction)
+    var abortAction: ((_ transactionId: String) async throws -> Void)?
+    var deleteAction: ((_ transactionId: String) async throws -> Void)?
+    var failAction: ((_ transactionId: String) async throws -> Void)?
+    var doneAction: (() -> Void)?
+    var suspendAction: ((_ transactionId: String) async throws -> Void)?
+    var resumeAction: ((_ transactionId: String) async throws -> 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 localizedType = transaction.localizedType
+        let navTitle = pending ? String(localized: "Pending \(localizedType)")
+                               : localizedType
+        Group {
+            List {
+                if developerMode {
+                    if transaction.isSuspendable { if let suspendAction {
+                        TransactionButton(transactionId: common.transactionId,
+                                          command: .suspend, action: 
suspendAction)
+                    } }
+                    if transaction.isResumable { if let resumeAction {
+                        TransactionButton(transactionId: common.transactionId,
+                                          command: .resume, action: 
resumeAction)
+                    } }
+                } // Suspend + Resume buttons
+                Text("\(dateString)")
+                    .font(.title2)
+//                .listRowSeparator(.hidden)
+                SwitchCase(transaction: $transaction)
+
+                if transaction.isAbortable { if let abortAction {
+                    TransactionButton(transactionId: common.transactionId,
+                                      command: .abort, action: abortAction)
+                } } // Abort button
+                if transaction.isFailable { if let failAction {
+                    TransactionButton(transactionId: common.transactionId,
+                                      command: .fail, action: failAction)
+                } } // Fail button
+                if transaction.isDeleteable { if let deleteAction {
+                    TransactionButton(transactionId: common.transactionId,
+                                      command: .delete, action: deleteAction)
+                } } // Delete button
+//                if let doneAction {
+//                    DoneButton(doneAction: doneAction)
+//                } // Done button
+            }.id(viewId)    // change viewId to enforce a draw update
+            .listStyle(myListStyle.style).anyView
+                .safeAreaInset(edge: .bottom) {
+                    if let doneAction {
+                        Button("Done", action: doneAction)
+                            .buttonStyle(TalerButtonStyle(type: .prominent))
+                            .padding(.horizontal)
+                    }
+                }
+        }.onNotification(.TransactionStateTransition) { notification in
+            if let transition = notification.userInfo?[TRANSACTIONTRANSITION] 
as? TransactionTransition {
+                if transition.transactionId == common.transactionId {
+                    let newMajor = transition.newTxState.major
+                    let newMinor = transition.newTxState.minor
+                    if let doneAction {
+                        if newMajor == .done {
+                            symLog.log("newTxState.major == done  => dismiss 
sheet")
+                            doneAction()        // if this view is in a sheet 
this action will dissmiss it
+                        } else if newMajor == .pending {
+                            if let newMinor {
+                                if newMinor == .withdrawCoins { // 
coin-withdrawal has started
+                                    symLog.log("newTxState.minor == 
withdrawCoins  => dismiss sheet")
+                                    doneAction()        // if this view is in 
a sheet this action will dissmiss it
+                                } else {
+                                    symLog.log("ignoring newTxState: 
\(newMajor):\(newMinor)")
+                                }
+                            }
+                        } else {
+                            symLog.log("ignoring newTxState.major: 
\(newMajor)")
+                        }
+                    } else { Task {
+                        do {
+                            symLog.log("newState: \(newMajor), reloading 
transaction")
+                            withAnimation() { transaction = 
Transaction(dummyCurrency: DEMOCURRENCY); viewId = UUID() }
+                            let reloadedTransaction = try await 
reloadAction(common.transactionId)
+                            symLog.log("reloaded transaction: 
\(reloadedTransaction.common.txState.major)")
+                            withAnimation() { transaction = 
reloadedTransaction; viewId = UUID() }      // redraw
+                      } catch {
+                          symLog.log(error.localizedDescription)
+                    }}}
+                }
+            } else {
+                // Yikes - should never happen
+                symLog.log(notification.userInfo as Any)
+            }
+        }
+        .navigationTitle(navTitle)
+        .task {
+            do {
+                symLog.log("task - load transaction")
+                let reloadedTransaction = try await reloadAction(transactionId)
+                withAnimation() { transaction = reloadedTransaction; viewId = 
UUID() }      // redraw
+            } catch {
+                symLog.log(error)
+                withAnimation() { transaction = Transaction(dummyCurrency: 
DEMOCURRENCY); viewId = UUID() }
+            }
+        }
+        .onAppear {
+            symLog.log("onAppear")
+            DebugViewC.shared.setViewID(VIEW_TRANSACTIONDETAIL)
+        }
+        .onDisappear {
+            symLog.log("onDisappear")
+        }
+    }
+//}
+//
+//extension TransactionDetail {
+    struct SwitchCase: View {
+        @Binding var transaction: Transaction
+
+        var body: some View {
+            let common = transaction.common
+            let pending = transaction.isPending
+            Group {
+                switch transaction {
+                    case .dummy(let dummyTransaction):
+                        Text("")
+                    case .withdrawal(let withdrawalTransaction):
+                        let details = withdrawalTransaction.details
+                        if pending {
+                            let withdrawalDetails = details.withdrawalDetails
+                            switch withdrawalDetails.type {
+                                case .manual:               // "Make a wire 
transfer of \(amount) to"
+                                    ManualDetails(common: common, details: 
withdrawalDetails)
+                                    
+                                case .bankIntegrated:       // "Confirm with 
bank"
+                                    VStack {
+                                        if let confirmationUrl = 
withdrawalDetails.bankConfirmationUrl {
+                                            if let destination = URL(string: 
confirmationUrl) {
+                                                // Show Hint that User should 
Confirm on bank website
+                                                Text("Waiting for bank 
confirmation")
+                                                    
.multilineTextAlignment(.leading)
+                                                    .listRowSeparator(.hidden)
+                                                Link("Confirm with bank", 
destination: destination)
+                                                    
.buttonStyle(TalerButtonStyle(type: .prominent, narrow: false, aligned: 
.center))
+                                                    .padding(.horizontal)
+                                                
+                                            }
+                                        }
+                                    }
+                            }
+                        } // ManualDetails or Confirm with bank
+                        ThreeAmountsSheet(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)
+                        ThreeAmountsSheet(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
+                        ThreeAmountsSheet(common: common, topTitle: 
String(localized: "Refunded amount:"),
+                                         baseURL: nil, large: true)     // 
TODO: baseURL
+                    case .reward(let rewardTransaction):
+                        let details = rewardTransaction.details                
 // TODO: more details
+                        ThreeAmountsSheet(common: common, topTitle: 
String(localized: "Received Reward:"),
+                                         baseURL: details.exchangeBaseUrl, 
large: true)
+//                case .tip(let tipTransaction):
+//                    let details = tipTransaction.details                  // 
TODO: details
+//                    ThreeAmountsSheet(common: common, topTitle: 
String(localized: "Received Tip:"),
+//                                      baseURL: nil, large: true)
+                    case .refresh(let refreshTransaction):
+                        let details = refreshTransaction.details               
 // TODO: details
+                        ThreeAmountsSheet(common: common, topTitle: 
String(localized: "Refreshed amount:"),
+                                         baseURL: nil, large: true)     // 
TODO: baseURL
+                    case .peer2peer(let p2pTransaction):
+                        let details = p2pTransaction.details                   
 // TODO: details
+                        // TODO: isSendCoins should show QR only while not 
expired
+                        if pending || transaction.isSendCoins {
+                            QRCodeDetails(transaction: transaction)
+                        }
+                        ThreeAmountsSheet(common: common, topTitle: 
String(localized: "Peer to Peer:"),
+                                         baseURL: details.exchangeBaseUrl, 
large: false)
+                } // switch
+            } // Group
+        }
+    }
+
+    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 {
+                        QRCodeDetailView(talerURI: talerURI,
+                                         incoming: transaction.isP2pIncoming)
+                    }
+                }
+            } else if keys.contains(EXCHANGEBASEURL) {
+                if let baseURL = details[EXCHANGEBASEURL] {
+                    Text("from \(baseURL.trimURL())") 
+                        .font(.title2)
+                        .padding(.bottom)
+                }
+            }
+        }
+    }
+    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 func reloadActionDummy(transactionId: String) async -> 
Transaction { return withdrawal }
+//    static var previews: some View {
+//        Group {
+//            TransactionDetailView(transaction: withdrawal, reloadAction: 
reloadActionDummy, doneAction: doneActionDummy)
+//            TransactionDetailView(transaction: payment, reloadAction: 
reloadActionDummy, 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..b6e9286
--- /dev/null
+++ b/TalerWallet1/Views/Transactions/TransactionRowView.swift
@@ -0,0 +1,121 @@
+/*
+ * 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(spacing: 6) {
+            Image(systemName: incoming ? "text.badge.plus" : 
"text.badge.minus")
+                .foregroundColor(foreColor)
+                .font(.largeTitle)
+                .accessibility(hidden: true)
+
+            TransactionRowCenter(centerTop: transaction.localizedType,
+                                 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..eb7479f
--- /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("myListStyle") 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
+        .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
+        .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..2ae882e 100644
--- a/TalerWallet1/Views/Transactions/TransactionsListView.swift
+++ b/TalerWallet1/Views/Transactions/TransactionsListView.swift
@@ -1,105 +1,125 @@
 /*
- * 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"
+    private let symLog = SymLogV(0)
+    @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
+    let navTitle: String
+
+    let currency: String
+    let transactions: [Transaction]
+    let reloadAllAction: () async -> ()
+    let reloadOneAction: ((_ transactionId: String) async throws -> 
Transaction)
 
-    @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
+        // TODO: Unlock the power of grammatical agreement
+//        let title = AttributedString(localized: "^[\(count) Ticket](inflect: 
true)")
+        let title: String = "\(count) \(navTitle)"
+        Content(symLog: symLog,
+              currency: currency,
+          transactions: transactions,
+           myListStyle: $myListStyle,
+       reloadAllAction: reloadAllAction,
+       reloadOneAction: reloadOneAction)
+            .navigationTitle(title)
+            .navigationBarTitleDisplayMode(.large)      // .inline
+            .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 reloadAllAction()
             }
-        }
     }
 }
 // MARK: -
 extension TransactionsListView {
     struct Content: View {
         let symLog: SymLogV?
-        @ObservedObject var viewModel: TransactionsModel
+        let currency: String
+        let transactions: [Transaction]
+        @Binding var myListStyle: MyListStyle
+        let reloadAllAction: () async -> ()
+        let reloadOneAction: ((_ transactionId: String) async throws -> 
Transaction)
 
-        var reloadAction: () async throws -> ()
+        @EnvironmentObject private var model: WalletModel
 
         @State private var upAction: () -> Void = {}
         @State private var downAction: () -> Void = {}
 
-        var body: some View {
-            let transactions = viewModel.transactions!
+//        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)
+//                    }
+//                }
+//            }
+//        }
 
+        @State var viewId = UUID()
+        var body: some View {
+#if DEBUG
+            let _ = Self._printChanges()
+            let _ = symLog?.vlog()       // just to get the # to compare it 
with .onAppear & onDisappear
+#endif
+            let abortAction = model.abortTransaction
+            let deleteAction = model.deleteTransaction
+            let failAction = model.failTransaction
+            let suspendAction = model.suspendTransaction
+            let resumeAction = model.resumeTransaction
             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
+                        NavigationLink { LazyView {        // whole row like 
in a tableView
+                            // pending may not be deleted, but only aborted
+                            TransactionDetailView(transactionId: 
transaction.id,
+                                                 reloadAction: reloadOneAction,
+                                                  abortAction: abortAction,
+                                                 deleteAction: deleteAction,
+                                                   failAction: failAction,
+                                                suspendAction: suspendAction,
+                                                 resumeAction: resumeAction)
+                        }} 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)
+                    symLog?.log("refreshing")
+                    await reloadAllAction()
+                }.id(viewId)
+                .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)
                     }
                 }
             }
@@ -107,7 +127,6 @@ extension TransactionsListView {
                 ArrowUpButton(action: upAction)
                 ArrowDownButton(action: downAction)
             })
-            .navigationBarTitleDisplayMode(.large)      // .inline
         }
     }
 }
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/WithdrawURIModel.swift 
b/TalerWallet1/Views/Withdraw/WithdrawURIModel.swift
deleted file mode 100644
index f375a1d..0000000
--- a/TalerWallet1/Views/Withdraw/WithdrawURIModel.swift
+++ /dev/null
@@ -1,213 +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
-
-enum WithdrawState {
-    case error
-    case waitingForUriDetails
-    case receivedUriDetails
-    case waitingForAmountDetails
-    case receivedAmountDetails
-    case waitingForTOS
-    case receivedTOS
-    case waitingForTOSAck
-    case receivedTOSAck
-    case waitingForWithdrAck
-    case receivedWithdrAck
-}
-
-class WithdrawURIModel: WalletModel {
-    @Published var withdrawState: WithdrawState?
-}
-
-// MARK: -
-/// The result from getWithdrawalDetailsForUri
-struct WithdrawalDetailsForUri: Decodable {
-    var amount: Amount
-    var defaultExchangeBaseUrl: String?
-    var possibleExchanges: [ExchangeListItem]
-}
-struct ExchangeListItem: Codable, Hashable {
-    var exchangeBaseUrl: String
-    var currency: String
-    var paytoUris: [String]
-
-    public static func == (lhs: ExchangeListItem, rhs: ExchangeListItem) -> 
Bool {
-        return lhs.exchangeBaseUrl == rhs.exchangeBaseUrl &&
-        lhs.currency == rhs.currency &&
-        lhs.paytoUris == rhs.paytoUris
-    }
-
-    public func hash(into hasher: inout Hasher) {
-        hasher.combine(exchangeBaseUrl)
-        hasher.combine(currency)
-        hasher.combine(paytoUris)
-    }
-}
-/// A request to get an exchange's withdrawal details.
-fileprivate struct GetWithdrawalDetailsForURI: WalletBackendFormattedRequest {
-    typealias Response = WithdrawalDetailsForUri
-    func operation() -> String { return "getWithdrawalDetailsForUri" }
-    func args() -> Args { return Args(talerWithdrawUri: talerWithdrawUri) }
-
-    var talerWithdrawUri: String
-    struct Args: Encodable {
-        var talerWithdrawUri: String
-    }
-}
-// MARK: -
-/// The result from getWithdrawalDetailsForAmount
-struct WithdrawalDetailsForAmount: Decodable {
-    var tosAccepted: Bool
-    var amountRaw: Amount
-    var amountEffective: Amount
-}
-/// A request to get an exchange's withdrawal details.
-fileprivate struct GetWithdrawalDetailsForAmount: 
WalletBackendFormattedRequest {
-    typealias Response = WithdrawalDetailsForAmount
-    func operation() -> String { return "getWithdrawalDetailsForAmount" }
-    func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl, 
amount: amount) }
-
-    var exchangeBaseUrl: String
-    var amount: Amount
-    struct Args: Encodable {
-        var exchangeBaseUrl: String
-        var amount: Amount
-    }
-}
-// MARK: -
-struct ExchangeTermsOfService: Decodable {
-    var content: String
-    var currentEtag: String
-    var acceptedEtag: String?
-}
-/// A request to query an exchange's terms of service.
-fileprivate struct GetExchangeTermsOfService: WalletBackendFormattedRequest {
-    typealias Response = ExchangeTermsOfService
-    func operation() -> String { return "getExchangeTos" }
-    func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl) }
-
-    var exchangeBaseUrl: String
-    struct Args: Encodable {
-        var exchangeBaseUrl: String
-    }
-}
-// MARK: -
-/// A request to mark an exchange's terms of service as accepted.
-fileprivate struct SetExchangeTOSAccepted: WalletBackendFormattedRequest {
-    struct Response: Decodable {}
-    func operation() -> String { return "setExchangeTosAccepted" }
-    func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl, etag: 
etag) }
-
-    var exchangeBaseUrl: String
-    var etag: String
-
-    struct Args: Encodable {
-        var exchangeBaseUrl: String
-        var etag: String
-    }
-}
-// MARK: -
-struct BankConfirmation: Decodable {
-    var bankConfirmationUrl: String?
-}
-/// A request to accept a bank-integrated withdrawl.
-fileprivate struct AcceptBankIntegratedWithdrawal: 
WalletBackendFormattedRequest {
-    typealias Response = BankConfirmation
-    func operation() -> String { return "acceptBankIntegratedWithdrawal" }
-    func args() -> Args { return Args(talerWithdrawUri: talerWithdrawUri, 
exchangeBaseUrl: exchangeBaseUrl) }
-
-    var talerWithdrawUri: String
-    var exchangeBaseUrl: String
-
-    struct Args: Encodable {
-        var talerWithdrawUri: String
-        var exchangeBaseUrl: String
-    }
-}
-// MARK: -
-extension WithdrawURIModel {
-    /// load withdrawal details. Networking involved
-    @MainActor
-    func loadWithdrawalDetailsForURI(_ talerWithdrawUri: String) async throws 
-> WithdrawalDetailsForUri {
-        do {
-            withdrawState = .waitingForUriDetails
-            let request = GetWithdrawalDetailsForURI(talerWithdrawUri: 
talerWithdrawUri)
-            let response = try await sendRequest(request, ASYNCDELAY)
-            withdrawState = .receivedUriDetails
-            return response
-        } catch {
-            withdrawState = .error
-            throw error
-        }
-    }
-    @MainActor
-    func loadWithdrawalDetailsForAmount(_ detailsForUri: 
WithdrawalDetailsForUri) async throws -> WithdrawalDetailsForAmount {
-        do {
-            withdrawState = .waitingForAmountDetails
-            let baseURL = detailsForUri.defaultExchangeBaseUrl!
-            let request = GetWithdrawalDetailsForAmount(exchangeBaseUrl: 
baseURL, amount: detailsForUri.amount)
-            let response = try await sendRequest(request, ASYNCDELAY)
-            withdrawState = .receivedAmountDetails
-            return response
-        } catch {
-            withdrawState = .error
-            throw error
-        }
-    }
-    @MainActor
-    func loadExchangeTermsOfService(_ exchangeBaseUrl: String) async throws -> 
ExchangeTermsOfService {
-        do {
-            withdrawState = .waitingForTOS
-            let request = GetExchangeTermsOfService(exchangeBaseUrl: 
exchangeBaseUrl)
-            let response = try await sendRequest(request, ASYNCDELAY)
-            withdrawState = .receivedTOS
-            return response
-        } catch {
-            withdrawState = .error
-            throw error
-        }
-    }
-    @MainActor
-    func setExchangeTOSAccepted(_ exchangeBaseUrl: String, etag: String) async 
throws -> Decodable {
-        do {
-            withdrawState = .waitingForTOSAck
-            let request = SetExchangeTOSAccepted(exchangeBaseUrl: 
exchangeBaseUrl, etag: etag)
-            let response = try await sendRequest(request, ASYNCDELAY)
-            withdrawState = .receivedTOSAck
-            return response
-        } catch {
-            withdrawState = .error
-            throw error
-        }
-    }
-    @MainActor
-    func sendAcceptIntWithdrawal(_ exchangeBaseUrl: String, withdrawURL: 
String) async throws -> String? {
-        do {
-            withdrawState = .waitingForWithdrAck
-            let request = AcceptBankIntegratedWithdrawal(talerWithdrawUri: 
withdrawURL, exchangeBaseUrl: exchangeBaseUrl)
-            let response = try await sendRequest(request, ASYNCDELAY)
-            withdrawState = .receivedWithdrAck
-            return response.bankConfirmationUrl
-        } catch {
-            withdrawState = .error
-            throw error
-        }
-    }
-}
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/WithdrawAcceptDone.swift 
b/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawAcceptDone.swift
new file mode 100644
index 0000000..1a802f1
--- /dev/null
+++ b/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawAcceptDone.swift
@@ -0,0 +1,68 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+import SymLog
+
+struct WithdrawAcceptDone: View {
+    private let symLog = SymLogV()
+    let navTitle = String(localized: "Confirm with Bank")
+
+    let exchangeBaseUrl: String?
+    let url: URL
+
+    @EnvironmentObject private var model: WalletModel
+    @EnvironmentObject private var controller: Controller
+
+    @State private var transactionId: String? = nil
+
+    func reloadOneAction(_ transactionId: String) async throws -> Transaction {
+            return try await model.getTransactionByIdT(transactionId)
+    }
+
+    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
+        VStack {
+            if let transactionId {
+                TransactionDetailView(transactionId: transactionId,
+                                       reloadAction: reloadOneAction,
+                                         doneAction: { dismissTop() })
+                .navigationBarBackButtonHidden(true)
+                .interactiveDismissDisabled()           // can only use "Done" 
button to dismiss
+                .navigationTitle(navTitle)
+            } else {
+                WithdrawProgressView(message: "Bank Confirmation")
+                    .navigationTitle("Loading...")
+            }
+        }.onAppear() {
+            symLog.log("onAppear")
+            DebugViewC.shared.setSheetID(SHEET_WITHDRAW_CONFIRM)
+        }.task {
+            do {
+                if let exchangeBaseUrl {
+                    let result = try await 
model.sendAcceptIntWithdrawalM(exchangeBaseUrl, withdrawURL: url.absoluteString)
+                    let confirmTransferUrl = result!.confirmTransferUrl
+                    symLog.log(confirmTransferUrl)
+                    transactionId = result!.transactionId
+                }
+            } catch {    // TODO: error
+                symLog.log(error.localizedDescription)
+                controller.playSound(0)
+            }
+        }
+      }
+    }
+}
+// MARK: -
+struct WithdrawAcceptDone_Previews: PreviewProvider {
+    static var previews: some View {
+        WithdrawAcceptDone(exchangeBaseUrl: DEMOEXCHANGE,
+                           url: URL(string: DEMOSHOP)!)
+    }
+}
diff --git a/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawAcceptView.swift 
b/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawAcceptView.swift
new file mode 100644
index 0000000..ada134f
--- /dev/null
+++ b/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawAcceptView.swift
@@ -0,0 +1,111 @@
+/*
+ * 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 navTitle = String(localized: "Accept Withdrawal")
+
+    let exchangeBaseUrl: String
+    let model: WithdrawModel?
+    let amount: Amount?
+    let url: URL
+
+    @State private var buttonSelected: Int? = nil
+    @State private var confirmTransferUrl: String? = nil
+    @State private var transactionId: String? = nil
+    @State var manualWithdrawalDetails: ManualWithdrawalDetails?
+
+    func acceptAction() -> () {
+        Task {
+            do {
+                if let model {
+                    if let acceptWithdrawalResponse = try await 
model.sendAcceptIntWithdrawalM(exchangeBaseUrl, withdrawURL: 
url.absoluteString) {
+                        confirmTransferUrl = 
acceptWithdrawalResponse.confirmTransferUrl
+                        transactionId = acceptWithdrawalResponse.transactionId
+                        symLog.log(confirmTransferUrl ?? "❗️Yikes: No 
confirmTransferUrl")
+                        buttonSelected = 1      // trigger NavigationLink
+                    } else {
+                        // TODO: error sendAcceptIntWithdrawal failed
+                    }
+                }
+            } catch {    // TODO: error
+                symLog.log(error.localizedDescription)
+            }
+        }
+    }
+
+    var body: some View {
+        VStack {
+            if let manualWithdrawalDetails {
+                List {
+
+                    HStack(spacing: 0) {
+                        NavigationLink(destination: LazyView {
+                            WithdrawAcceptDone(model: model, 
confirmTransferUrl: confirmTransferUrl, transactionId: transactionId)
+                        }, tag: 1, selection: $buttonSelected
+                        ) { EmptyView() }.frame(width: 0).opacity(0).hidden()
+
+                        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: exchangeBaseUrl)
+                    }
+                }
+                .safeAreaInset(edge: .bottom) {
+                    Button("Confirm Withdrawal", action: acceptAction)
+                        .lineLimit(2)
+                        .disabled(false)
+                        .buttonStyle(TalerButtonStyle(type: .prominent, 
narrow: false, aligned: .center))
+                        .padding()
+                }
+                .navigationTitle(navTitle)
+            } else {
+                WithdrawProgressView(message: exchangeBaseUrl.trimURL())
+                    .navigationTitle("Found Exchange")
+            }
+        }
+//        .overlay {
+//            VStack {
+//                ErrorView(errortext: "unknown state")       // TODO: Error
+//            }.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, 
maxHeight: .infinity)
+//                
.background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
+//        }
+        .onAppear() {
+            DebugViewC.shared.setSheetID(SHEET_WITHDRAW_ACCEPT)
+        }
+        .task { if let amount, let model {
+            do { // TODO: cancelled
+                symLog.log(".task")
+                if exchangeBaseUrl.hasPrefix(HTTPS) {
+                    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)")
+                } else {
+                    // TODO: error no exchange!
+                }
+            } catch {    // TODO: error
+                symLog.log(error.localizedDescription)
+            }
+        } else {
+            // TODO: error no amount!
+        } }
+    }
+}
+// MARK: -
+struct WithdrawAcceptView_Previews: PreviewProvider {
+    static var previews: some View {
+        let amount = try! Amount(fromString: LONGCURRENCY + ":2.4")
+        WithdrawAcceptView(exchangeBaseUrl: DEMOEXCHANGE,
+                                     model: nil,
+                                    amount: amount,
+                                       url: URL(string: DEMOSHOP)!)
+    }
+}
diff --git 
a/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawProgressView.swift 
b/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawProgressView.swift
new file mode 100644
index 0000000..f623c3a
--- /dev/null
+++ b/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawProgressView.swift
@@ -0,0 +1,32 @@
+/*
+ * 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()
+            HStack {
+                Spacer()
+                Text(message)
+                    .font(.title)
+                Spacer()
+            }
+            Spacer()
+            Spacer()
+        }
+        .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
+    }
+}
+
+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..6197077
--- /dev/null
+++ b/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawTOSView.swift
@@ -0,0 +1,99 @@
+/*
+ * 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("myListStyle") var myListStyle: MyListStyle = .automatic
+
+    let navTitle = String(localized: "Terms of Service")
+
+    var exchangeBaseUrl: String?
+
+    @EnvironmentObject private var model: WalletModel
+
+    @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 {
+            Content(symLog: symLog, exchangeTOS: exchangeTOS, myListStyle: 
$myListStyle) {
+                Task {
+                    do {
+                        if let exchangeBaseUrl {
+                            _ = try await 
model.setExchangeTOSAcceptedM(exchangeBaseUrl, etag: exchangeTOS!.currentEtag)
+                            if acceptAction != nil {
+                                acceptAction!()
+                            } else { // just go back - caller will reload
+                                self.presentationMode.wrappedValue.dismiss()
+                            }
+                        }
+                    } catch {    // TODO: Show Error
+                        symLog.log(error.localizedDescription)
+                    }
+                }
+            }
+            .navigationBarTitleDisplayMode(.large)      // .inline
+            .navigationTitle(navTitle)
+            .overlay {
+                if exchangeTOS == nil {
+                    if let exchangeBaseUrl {
+                        WithdrawProgressView(message: 
exchangeBaseUrl.trimURL())
+                            .navigationTitle("Loading " + navTitle)
+                    } else {
+                        // Yikes!
+                        WithdrawProgressView(message: "No exchangeBaseUrl!")
+                            .navigationTitle("Loading " + navTitle)
+                    }
+                }
+            }
+        }.onAppear() {
+            if viewID > SHEET_WITHDRAWAL {
+                DebugViewC.shared.setSheetID(SHEET_WITHDRAW_TOS)
+            } else {
+                DebugViewC.shared.setViewID(VIEW_WITHDRAW_TOS)
+            }
+        }.task {
+            do {
+                if let exchangeBaseUrl {
+                    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) {
+                    Button(String(localized: "Accept ToS"), action: 
acceptAction)
+                        .buttonStyle(TalerButtonStyle(type: .prominent))
+                        .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..da3f547
--- /dev/null
+++ b/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawURIView.swift
@@ -0,0 +1,100 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+import SymLog
+
+// Will be called either by the user scanning a QR code or tapping the 
provided link, both from the bank's website
+// we show the user the withdrawal details - but first the ToS must be accepted
+// after the user confirmed the withdrawal, we remind them to return to the 
bank website to confirm there, too
+struct WithdrawURIView: View {
+    private let symLog = SymLogV()
+    let navTitle = String(localized: "Accept Withdrawal")
+
+    // the URL from the bank website
+    let url: URL
+
+    @EnvironmentObject private var model: WalletModel
+
+    // the exchange used for this withdrawal.
+    @State private var exchangeBaseUrl: String? = nil
+    @State private var withdrawalAmountDetails: WithdrawalAmountDetails?
+
+    var body: some View {
+        let badURL = "Error in URL: \(url)"
+        VStack {
+            if let withdrawalAmountDetails, let exchangeBaseUrl {
+                List {
+                    let raw = withdrawalAmountDetails.amountRaw
+                    let effective = withdrawalAmountDetails.amountEffective
+                    let currency = raw.currencyStr
+                    let fee = try! Amount.diff(raw, effective)
+                    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: "\(currency) 
to be withdrawn:"),
+                                 bottomAmount: effective,
+                                        large: false, pending: false, 
incoming: true,
+                                      baseURL: exchangeBaseUrl)
+                    let someCoins = SomeCoins(details: withdrawalAmountDetails)
+                    QuiteSomeCoins(someCoins: someCoins, shouldShowFee: false,
+                                   currency: raw.currencyStr, amountEffective: 
effective)
+                }
+                .navigationTitle(navTitle)
+                let tosAccepted = withdrawalAmountDetails.tosAccepted
+                if tosAccepted {
+                    NavigationLink(destination: LazyView {
+                        WithdrawAcceptDone(exchangeBaseUrl: exchangeBaseUrl, 
url: url)
+                    }) {
+                        Text("Confirm Withdrawal")      // 
SHEET_WITHDRAW_ACCEPT
+                    }.buttonStyle(TalerButtonStyle(type: .prominent))
+                        .padding()
+                } else {
+                    NavigationLink(destination: LazyView {
+                        WithdrawTOSView(exchangeBaseUrl: exchangeBaseUrl,
+                                                 viewID: SHEET_WITHDRAW_TOS,
+                                           acceptAction: nil)         // pop 
back to here
+                    }) {
+                        Text("Check Terms of Service")  // VIEW_WITHDRAW_TOS
+                    }.buttonStyle(TalerButtonStyle(type: .prominent))
+                        .padding()
+                }
+            } else {
+                // Yikes no details or no baseURL
+//                WithdrawProgressView(message: url.host ?? badURL)
+//                    .navigationTitle("Contacting Exchange")
+            }
+        }
+        .onAppear() {
+            symLog.log("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 if let first = withdrawUriInfo.possibleExchanges.first {
+                    exchangeBaseUrl = first.exchangeBaseUrl
+                }
+                if let exchangeBaseUrl {
+                    let details = try await 
model.loadWithdrawalDetailsForAmountM(exchangeBaseUrl, amount: amount)
+                    withdrawalAmountDetails = details
+//                  agePicker.setAges(ages: details?.ageRestrictionOptions)
+                } else {    // TODO: error
+                    symLog.log("no exchangeBaseUrl")
+                    withdrawalAmountDetails = nil
+                }
+            } catch {    // TODO: error
+                symLog.log(error.localizedDescription)
+                withdrawalAmountDetails = nil
+            }
+        }
+    }
+}
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..43ada5d 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,23 @@ 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))
+    }
+
+    public static func inSomeMinutes(_ minutes: UInt) -> Timestamp {
+        let now = Date().millisecondsSince1970
+        let seconds: UInt64 = 60
+        return Timestamp.milliseconds(now + (UInt64(minutes) * 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]