gnunet-svn
[Top][All Lists]
Advanced

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

[taler-taler-ios] branch master updated (78ef82c -> df4fe35)


From: gnunet
Subject: [taler-taler-ios] branch master updated (78ef82c -> df4fe35)
Date: Wed, 01 Feb 2023 09:28:49 +0100

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 78ef82c  Update to wallet-core 0.9.0-dev.29
     new b813f20  enhanced Amount+Time
     new cdb3907  removed iono
     new 1951270  most of Info.plist moved into the project
     new d9c01e5  Assets
     new 94b44a2  Testing
     new 86364f0  App sources
     new a1a89d8  Project uses QuickJS instead of iono
     new ca0bb4a  more to ignore...
     new df4fe35  Build instructions

The 9 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:
 .gitignore                                         |   83 +-
 .gitmodules                                        |    3 -
 Info.plist                                         |   22 +
 README.md                                          |   31 +-
 Taler.xcodeproj/project.pbxproj                    |  863 ---------------
 .../project.xcworkspace/contents.xcworkspacedata   |    7 -
 .../xcshareddata/IDEWorkspaceChecks.plist          |    8 -
 .../xcshareddata/swiftpm/Package.resolved          |   14 -
 .../xcshareddata/xcschemes/Taler.xcscheme          |   98 --
 Taler.xcworkspace/contents.xcworkspacedata         |   10 +
 Taler/Base.lproj/LaunchScreen.storyboard           |   25 -
 Taler/Info.plist                                   |   51 -
 Taler/Model/BalancesModel.swift                    |   48 -
 Taler/Model/ExchangeManager.swift                  |   69 --
 Taler/Model/PendingManager.swift                   |   49 -
 Taler/Model/TransactionsModel.swift                |   48 -
 Taler/Model/WithdrawModel.swift                    |  179 ---
 Taler/Views/BalancesView.swift                     |  106 --
 Taler/Views/ContentView.swift                      |   85 --
 Taler/Views/PendingView.swift                      |   68 --
 Taler/Views/SettingsView.swift                     |  252 -----
 Taler/Views/WithdrawView.swift                     |  142 ---
 Taler/WalletBackend.swift                          | 1167 --------------------
 TalerTests/Info.plist                              |   22 -
 TalerTests/TalerWalletTests.swift                  |   36 +
 TalerUITests/Info.plist                            |   22 -
 ...TalerUITests.swift => TalerWalletUITests.swift} |   26 +-
 TalerUITests/TalerWalletUITestsLaunchTests.swift   |   32 +
 TalerWallet.xcodeproj/project.pbxproj              | 1010 +++++++++++++++++
 .../AppIcon.appiconset/Contents.json               |   59 +
 .../AppIcon.appiconset/appstore1024.png            |  Bin 0 -> 926369 bytes
 .../AppIcon.appiconset/iphone120.png               |  Bin 0 -> 41886 bytes
 .../AppIcon.appiconset/iphone180.png               |  Bin 0 -> 84105 bytes
 .../AppIcon.appiconset/notification40.png          |  Bin 0 -> 8741 bytes
 .../AppIcon.appiconset/notification60.png          |  Bin 0 -> 14837 bytes
 .../AppIcon.appiconset/settings58.png              |  Bin 0 -> 13807 bytes
 .../AppIcon.appiconset/settings87.png              |  Bin 0 -> 25494 bytes
 .../AppIcon.appiconset/spotlight120.png            |  Bin 0 -> 41886 bytes
 .../AppIcon.appiconset/spotlight80.png             |  Bin 0 -> 21490 bytes
 .../AppIconBackground.colorset/Contents.json       |   38 +
 .../Assets.xcassets/Contents.json                  |    0
 .../Incoming.colorset/Contents.json                |   38 +
 .../Outgoing.colorset/Contents.json                |   38 +
 .../PendingIncoming.colorset/Contents.json         |   38 +
 .../PendingOutgoing.colorset/Contents.json         |   38 +
 .../Taler-logo.imageset/Contents.json              |   12 +
 .../Taler-logo.imageset/Taler-logo.jpg             |  Bin 0 -> 11038 bytes
 .../TalerLogoBackground.colorset/Contents.json     |   38 +
 TalerWallet1/Backend/Transaction.swift             |  314 ++++++
 TalerWallet1/Backend/WalletBackendError.swift      |   54 +
 TalerWallet1/Backend/WalletBackendRequest.swift    |  434 ++++++++
 TalerWallet1/Backend/WalletCore.swift              |  262 +++++
 TalerWallet1/Controllers/Controller.swift          |  128 +++
 TalerWallet1/Controllers/TalerWallet1App.swift     |   81 ++
 TalerWallet1/Helper/TalerDater.swift               |  102 ++
 .../Helper/TalerStrings.swift                      |   16 +-
 TalerWallet1/Helper/View+dismissTop.swift          |   41 +
 TalerWallet1/Model/ExchangeTestModel.swift         |  138 +++
 TalerWallet1/Model/WalletInitModel.swift           |   86 ++
 TalerWallet1/Model/WalletModel.swift               |   55 +
 .../Preview Assets.xcassets/Contents.json          |    0
 TalerWallet1/Quickjs/quickjs.swift                 |   81 ++
 TalerWallet1/Views/Balances/BalanceRow.swift       |   46 +
 TalerWallet1/Views/Balances/BalancesModel.swift    |   73 ++
 .../Views/Balances/CurrenciesListView.swift        |   78 ++
 TalerWallet1/Views/Balances/CurrencyView.swift     |   58 +
 TalerWallet1/Views/Balances/PendingRow.swift       |   61 +
 .../Views/Balances/WalletEmptyView.swift           |   36 +-
 TalerWallet1/Views/Exchange/ExchangeListView.swift |  103 ++
 TalerWallet1/Views/Exchange/ExchangeModel.swift    |  115 ++
 TalerWallet1/Views/HelperViews/AmountView.swift    |   43 +
 TalerWallet1/Views/HelperViews/Buttons.swift       |   86 ++
 TalerWallet1/Views/HelperViews/LoadingView.swift   |   45 +
 .../Views/HelperViews/TextFieldAlert.swift         |   73 ++
 TalerWallet1/Views/Main/ContentView.swift          |   92 ++
 .../Views/Main/ErrorView.swift                     |   23 +-
 TalerWallet1/Views/Main/LaunchAnimationView.swift  |   33 +
 TalerWallet1/Views/Main/SideBarView.swift          |  110 ++
 TalerWallet1/Views/Payment/PaymentAcceptView.swift |   71 ++
 TalerWallet1/Views/Payment/PaymentURIModel.swift   |  183 +++
 TalerWallet1/Views/Payment/PaymentURIView.swift    |   65 ++
 TalerWallet1/Views/Pending/PendingModel.swift      |   82 ++
 TalerWallet1/Views/Pending/PendingOpView.swift     |   64 ++
 .../Views/Pending/PendingOpsListView.swift         |   65 ++
 TalerWallet1/Views/Settings/SettingsItem.swift     |   93 ++
 TalerWallet1/Views/Settings/SettingsView.swift     |  139 +++
 .../Views/Transactions/TransactionDetail.swift     |   79 ++
 .../Views/Transactions/TransactionRow.swift        |   81 ++
 .../Views/Transactions/TransactionsListView.swift  |   91 ++
 .../Views/Transactions/TransactionsModel.swift     |   69 ++
 TalerWallet1/Views/URLSheet.swift                  |   64 ++
 .../Views/Withdraw/WithdrawAcceptView.swift        |   71 ++
 .../Views/Withdraw/WithdrawProgressView.swift      |   37 +-
 TalerWallet1/Views/Withdraw/WithdrawTOSView.swift  |   96 ++
 TalerWallet1/Views/Withdraw/WithdrawURIModel.swift |  213 ++++
 TalerWallet1/Views/Withdraw/WithdrawURIView.swift  |  103 ++
 bootstrap                                          |   21 -
 iono                                               |    1 -
 taler-swift/Sources/taler-swift/Amount.swift       |  161 ++-
 taler-swift/Sources/taler-swift/Time.swift         |   58 +-
 100 files changed, 6167 insertions(+), 3482 deletions(-)
 delete mode 100644 .gitmodules
 create mode 100644 Info.plist
 delete mode 100644 Taler.xcodeproj/project.pbxproj
 delete mode 100644 Taler.xcodeproj/project.xcworkspace/contents.xcworkspacedata
 delete mode 100644 
Taler.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
 delete mode 100644 
Taler.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
 delete mode 100644 Taler.xcodeproj/xcshareddata/xcschemes/Taler.xcscheme
 create mode 100644 Taler.xcworkspace/contents.xcworkspacedata
 delete mode 100644 Taler/Base.lproj/LaunchScreen.storyboard
 delete mode 100644 Taler/Info.plist
 delete mode 100644 Taler/Model/BalancesModel.swift
 delete mode 100644 Taler/Model/ExchangeManager.swift
 delete mode 100644 Taler/Model/PendingManager.swift
 delete mode 100644 Taler/Model/TransactionsModel.swift
 delete mode 100644 Taler/Model/WithdrawModel.swift
 delete mode 100644 Taler/Views/BalancesView.swift
 delete mode 100644 Taler/Views/ContentView.swift
 delete mode 100644 Taler/Views/PendingView.swift
 delete mode 100644 Taler/Views/SettingsView.swift
 delete mode 100644 Taler/Views/WithdrawView.swift
 delete mode 100644 Taler/WalletBackend.swift
 delete mode 100644 TalerTests/Info.plist
 create mode 100644 TalerTests/TalerWalletTests.swift
 delete mode 100644 TalerUITests/Info.plist
 copy TalerUITests/{TalerUITests.swift => TalerWalletUITests.swift} (57%)
 create mode 100644 TalerUITests/TalerWalletUITestsLaunchTests.swift
 create mode 100644 TalerWallet.xcodeproj/project.pbxproj
 rename {Taler => 
TalerWallet1}/Assets.xcassets/AppIcon.appiconset/Contents.json (57%)
 create mode 100644 
TalerWallet1/Assets.xcassets/AppIcon.appiconset/appstore1024.png
 create mode 100644 
TalerWallet1/Assets.xcassets/AppIcon.appiconset/iphone120.png
 create mode 100644 
TalerWallet1/Assets.xcassets/AppIcon.appiconset/iphone180.png
 create mode 100644 
TalerWallet1/Assets.xcassets/AppIcon.appiconset/notification40.png
 create mode 100644 
TalerWallet1/Assets.xcassets/AppIcon.appiconset/notification60.png
 create mode 100644 
TalerWallet1/Assets.xcassets/AppIcon.appiconset/settings58.png
 create mode 100644 
TalerWallet1/Assets.xcassets/AppIcon.appiconset/settings87.png
 create mode 100644 
TalerWallet1/Assets.xcassets/AppIcon.appiconset/spotlight120.png
 create mode 100644 
TalerWallet1/Assets.xcassets/AppIcon.appiconset/spotlight80.png
 create mode 100644 
TalerWallet1/Assets.xcassets/AppIconBackground.colorset/Contents.json
 rename {Taler => TalerWallet1}/Assets.xcassets/Contents.json (100%)
 create mode 100644 TalerWallet1/Assets.xcassets/Incoming.colorset/Contents.json
 create mode 100644 TalerWallet1/Assets.xcassets/Outgoing.colorset/Contents.json
 create mode 100644 
TalerWallet1/Assets.xcassets/PendingIncoming.colorset/Contents.json
 create mode 100644 
TalerWallet1/Assets.xcassets/PendingOutgoing.colorset/Contents.json
 create mode 100644 
TalerWallet1/Assets.xcassets/Taler-logo.imageset/Contents.json
 create mode 100644 
TalerWallet1/Assets.xcassets/Taler-logo.imageset/Taler-logo.jpg
 create mode 100644 
TalerWallet1/Assets.xcassets/TalerLogoBackground.colorset/Contents.json
 create mode 100644 TalerWallet1/Backend/Transaction.swift
 create mode 100644 TalerWallet1/Backend/WalletBackendError.swift
 create mode 100644 TalerWallet1/Backend/WalletBackendRequest.swift
 create mode 100644 TalerWallet1/Backend/WalletCore.swift
 create mode 100644 TalerWallet1/Controllers/Controller.swift
 create mode 100644 TalerWallet1/Controllers/TalerWallet1App.swift
 create mode 100644 TalerWallet1/Helper/TalerDater.swift
 rename Taler/TalerApp.swift => TalerWallet1/Helper/TalerStrings.swift (72%)
 create mode 100644 TalerWallet1/Helper/View+dismissTop.swift
 create mode 100644 TalerWallet1/Model/ExchangeTestModel.swift
 create mode 100644 TalerWallet1/Model/WalletInitModel.swift
 create mode 100644 TalerWallet1/Model/WalletModel.swift
 rename {Taler => TalerWallet1}/Preview Content/Preview 
Assets.xcassets/Contents.json (100%)
 create mode 100644 TalerWallet1/Quickjs/quickjs.swift
 create mode 100644 TalerWallet1/Views/Balances/BalanceRow.swift
 create mode 100644 TalerWallet1/Views/Balances/BalancesModel.swift
 create mode 100644 TalerWallet1/Views/Balances/CurrenciesListView.swift
 create mode 100644 TalerWallet1/Views/Balances/CurrencyView.swift
 create mode 100644 TalerWallet1/Views/Balances/PendingRow.swift
 copy Taler/Model/BackendManager.swift => 
TalerWallet1/Views/Balances/WalletEmptyView.swift (51%)
 create mode 100644 TalerWallet1/Views/Exchange/ExchangeListView.swift
 create mode 100644 TalerWallet1/Views/Exchange/ExchangeModel.swift
 create mode 100644 TalerWallet1/Views/HelperViews/AmountView.swift
 create mode 100644 TalerWallet1/Views/HelperViews/Buttons.swift
 create mode 100644 TalerWallet1/Views/HelperViews/LoadingView.swift
 create mode 100644 TalerWallet1/Views/HelperViews/TextFieldAlert.swift
 create mode 100644 TalerWallet1/Views/Main/ContentView.swift
 copy Taler/Model/BackendManager.swift => 
TalerWallet1/Views/Main/ErrorView.swift (64%)
 create mode 100644 TalerWallet1/Views/Main/LaunchAnimationView.swift
 create mode 100644 TalerWallet1/Views/Main/SideBarView.swift
 create mode 100644 TalerWallet1/Views/Payment/PaymentAcceptView.swift
 create mode 100644 TalerWallet1/Views/Payment/PaymentURIModel.swift
 create mode 100644 TalerWallet1/Views/Payment/PaymentURIView.swift
 create mode 100644 TalerWallet1/Views/Pending/PendingModel.swift
 create mode 100644 TalerWallet1/Views/Pending/PendingOpView.swift
 create mode 100644 TalerWallet1/Views/Pending/PendingOpsListView.swift
 create mode 100644 TalerWallet1/Views/Settings/SettingsItem.swift
 create mode 100644 TalerWallet1/Views/Settings/SettingsView.swift
 create mode 100644 TalerWallet1/Views/Transactions/TransactionDetail.swift
 create mode 100644 TalerWallet1/Views/Transactions/TransactionRow.swift
 create mode 100644 TalerWallet1/Views/Transactions/TransactionsListView.swift
 create mode 100644 TalerWallet1/Views/Transactions/TransactionsModel.swift
 create mode 100644 TalerWallet1/Views/URLSheet.swift
 create mode 100644 TalerWallet1/Views/Withdraw/WithdrawAcceptView.swift
 rename Taler/Model/BackendManager.swift => 
TalerWallet1/Views/Withdraw/WithdrawProgressView.swift (50%)
 create mode 100644 TalerWallet1/Views/Withdraw/WithdrawTOSView.swift
 create mode 100644 TalerWallet1/Views/Withdraw/WithdrawURIModel.swift
 create mode 100644 TalerWallet1/Views/Withdraw/WithdrawURIView.swift
 delete mode 100755 bootstrap
 delete mode 160000 iono

diff --git a/.gitignore b/.gitignore
index 8789e6b..7f68344 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,81 @@
-Taler.xcodeproj/**/xcuserdata
-taler-wallet-embedded.js
+# Xcode
+#
+# gitignore contributors: remember to update Global/Xcode.gitignore, 
Objective-C.gitignore & Swift.gitignore
+
+## User settings
+xcuserdata/
+
+## compatibility with Xcode 8 and earlier (ignoring not required starting 
Xcode 9)
+*.xcscmblueprint
+*.xccheckout
+
+## Obj-C/Swift specific
+*.hmap
+
+### macOS ###
+# General
+.DS_Store
+
+## App packaging
+*.ipa
+*.dSYM.zip
+*.dSYM
+
+## Playgrounds
+timeline.xctimeline
+playground.xcworkspace
+
+# Swift Package Manager
+#
+# Add this line if you want to avoid checking in source code from Swift 
Package Manager dependencies.
+# Packages/
+# Package.pins
+Package.resolved
+# *.xcodeproj
+#
+# Xcode automatically generates this directory with a .xcworkspacedata file 
and xcuserdata
+# hence it is not needed unless you have added a package configuration file to 
your project
+.swiftpm
+*/xcshareddata/IDEWorkspaceChecks.plist
+.build/
+
+# CocoaPods
+#
+# We recommend against adding the Pods directory to your .gitignore. However
+# you should judge for yourself, the pros and cons are mentioned at:
+# 
https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
+#
+# Pods/
+#
+# Add this line if you want to avoid checking in source code from the Xcode 
workspace
+# *.xcworkspace
+
+# Carthage
+#
+# Add this line if you want to avoid checking in source code from Carthage 
dependencies.
+# Carthage/Checkouts
+
+Carthage/Build/
+
+# Accio dependency management
+Dependencies/
+.accio/
+
+# fastlane
+#
+# It is recommended to not store the screenshots in the git repo.
+# Instead, use fastlane to re-generate the screenshots whenever they are 
needed.
+# For more information about the recommended setup visit:
+# https://docs.fastlane.tools/best-practices/source-control/#source-control
+
+fastlane/report.xml
+fastlane/Preview.html
+fastlane/screenshots/**/*.png
+fastlane/test_output
+
+# Code Injection
+#
+# After new code Injection tools there's a generated folder 
/iOSInjectionProject
+# https://github.com/johnno1962/injectionforxcode
+
+iOSInjectionProject/
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index 51cf16d..0000000
--- a/.gitmodules
+++ /dev/null
@@ -1,3 +0,0 @@
-[submodule "iono"]
-       path = iono
-       url = git://git.taler.net/iono.git
diff --git a/Info.plist b/Info.plist
new file mode 100644
index 0000000..b918fac
--- /dev/null
+++ b/Info.plist
@@ -0,0 +1,22 @@
+<?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>CFBundleURLTypes</key>
+       <array>
+               <dict>
+                       <key>CFBundleTypeRole</key>
+                       <string>Viewer</string>
+                       <key>CFBundleURLName</key>
+                       <string>net.taler.talerwallet</string>
+                       <key>CFBundleURLSchemes</key>
+                       <array>
+                               <string>taler</string>
+                               <string>payto</string>
+                       </array>
+               </dict>
+       </array>
+       <key>ITSAppUsesNonExemptEncryption</key>
+       <false/>
+</dict>
+</plist>
diff --git a/README.md b/README.md
index bd2bf6b..63cb7f1 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,31 @@
-# GNU Taler iOS Code Repository
+# GNU Taler Wallet iOS Code Repository
 
 This git repository contains code for GNU Taler iOS apps and libraries.
 The official location is: 
-
     https://git.taler.net/taler-ios.git
 
+
+## Compatibility
+
+The minimum version of iOS supported is 15.0.
+This app runs on all iPhone models at least as new as the iPhone 6S.
+
+
 ## Building
 
-Before building anything, you should initialize and update the submodules by 
running
-    
-    $ ./bootstrap
-    
-To build the app, open `Taler.xcodeproj` with Xcode. The minimum version of 
iOS supported is 14.0. This iOS version is compatible on all iPhone models at 
least as new as the iPhone 6S.
+Before building the iOS wallet, you must first checkout the quickjs-tart repo:
+    https://git.taler.net/quickjs-tart.git
+and the wallet-core repo:
+    https://git.taler.net/wallet-core.git
+Build wallet-core first, then copy/move its product "taler-wallet-core-qjs.mjs"
+into your quickjs-tart folder.
+
+Open Taler.xcworkspace, and set scheme / target to TalerWalletT. Build&run...
+
+Don't open QuickJS-rt.xcodeproj or TalerWallet.xcodeproj and build anything
+there - all needed libraries and frameworks will be built automatically from
+Taler.xcworkspace.
+
+Have all 3 local repos adjacent at the same level (e.g. in a "GNU Taler" 
folder)
+- Taler.xcworkspace expects the QuickJS framework sub-project to be at
+  ../quickjs-tart/QuickJS-rt.xcodeproj
diff --git a/Taler.xcodeproj/project.pbxproj b/Taler.xcodeproj/project.pbxproj
deleted file mode 100644
index 99b68e5..0000000
--- a/Taler.xcodeproj/project.pbxproj
+++ /dev/null
@@ -1,863 +0,0 @@
-// !$*UTF8*$!
-{
-       archiveVersion = 1;
-       classes = {
-       };
-       objectVersion = 54;
-       objects = {
-
-/* Begin PBXBuildFile section */
-               AB1F87C82887C94700AB82A0 /* TalerApp.swift in Sources */ = {isa 
= PBXBuildFile; fileRef = AB1F87C72887C94700AB82A0 /* TalerApp.swift */; };
-               AB1F87CA2887D2F400AB82A0 /* ContentView.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = AB1F87C92887D2F400AB82A0 /* ContentView.swift 
*/; };
-               AB32199128B18859008AAC75 /* TransactionsModel.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = AB32199028B18859008AAC75 /* 
TransactionsModel.swift */; };
-               AB4C534A28AC21C9003004F7 /* BalancesView.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = AB4C534928AC21C9003004F7 /* BalancesView.swift 
*/; };
-               AB4C534C28AC25FC003004F7 /* BalancesModel.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = AB4C534B28AC25FC003004F7 /* BalancesModel.swift 
*/; };
-               AB69F9FA28AAED53005CCC2E /* WithdrawModel.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = AB69F9F928AAED53005CCC2E /* WithdrawModel.swift 
*/; };
-               AB7356F928B0203B009C5D8C /* WithdrawView.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = AB7356F828B0203B009C5D8C /* WithdrawView.swift 
*/; };
-               AB8C3807286A88A600E0A1DD /* WalletBackendTests.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = AB8C3806286A88A500E0A1DD /* 
WalletBackendTests.swift */; };
-               ABB33065289C5BBB00668B42 /* ExchangeManager.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = ABB33064289C5BBB00668B42 /* 
ExchangeManager.swift */; };
-               ABB33067289C658900668B42 /* BackendManager.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = ABB33066289C658900668B42 /* 
BackendManager.swift */; };
-               ABB762AD2891059600E88634 /* SettingsView.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = ABB762AC2891059600E88634 /* SettingsView.swift 
*/; };
-               ABC13AA32859962800D23185 /* taler-swift in Frameworks */ = {isa 
= PBXBuildFile; productRef = ABC13AA22859962800D23185 /* taler-swift */; };
-               ABC4AC3B28A4619C0047A56F /* PendingView.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = ABC4AC3A28A4619C0047A56F /* PendingView.swift 
*/; };
-               ABC4AC3F28A473070047A56F /* PendingManager.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = ABC4AC3E28A473070047A56F /* 
PendingManager.swift */; };
-               ABE97B1D286D82BF00580772 /* AnyCodable in Frameworks */ = {isa 
= PBXBuildFile; productRef = ABE97B1C286D82BF00580772 /* AnyCodable */; };
-               D112510026B12E3200D02E00 /* taler-wallet-embedded.js in 
CopyFiles */ = {isa = PBXBuildFile; fileRef = D11250FF26B12E3200D02E00 /* 
taler-wallet-embedded.js */; };
-               D14AFD4324D232B500C51073 /* TalerUITests.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = D14AFD4224D232B500C51073 /* TalerUITests.swift 
*/; };
-               D1AFF0F3268D59C200FBB744 /* libiono.a in Frameworks */ = {isa = 
PBXBuildFile; fileRef = D1AFF0F2268D59A500FBB744 /* libiono.a */; };
-               D1D65B9826992E4600C1012A /* WalletBackend.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = D1D65B9726992E4600C1012A /* WalletBackend.swift 
*/; };
-/* End PBXBuildFile section */
-
-/* Begin PBXContainerItemProxy section */
-               D14AFD3424D232B500C51073 /* PBXContainerItemProxy */ = {
-                       isa = PBXContainerItemProxy;
-                       containerPortal = D14AFD1524D232B300C51073 /* Project 
object */;
-                       proxyType = 1;
-                       remoteGlobalIDString = D14AFD1C24D232B300C51073;
-                       remoteInfo = Taler;
-               };
-               D14AFD3F24D232B500C51073 /* PBXContainerItemProxy */ = {
-                       isa = PBXContainerItemProxy;
-                       containerPortal = D14AFD1524D232B300C51073 /* Project 
object */;
-                       proxyType = 1;
-                       remoteGlobalIDString = D14AFD1C24D232B300C51073;
-                       remoteInfo = Taler;
-               };
-/* End PBXContainerItemProxy section */
-
-/* Begin PBXCopyFilesBuildPhase section */
-               D11250FA26B12D4400D02E00 /* CopyFiles */ = {
-                       isa = PBXCopyFilesBuildPhase;
-                       buildActionMask = 12;
-                       dstPath = "";
-                       dstSubfolderSpec = 7;
-                       files = (
-                               D112510026B12E3200D02E00 /* 
taler-wallet-embedded.js in CopyFiles */,
-                       );
-                       runOnlyForDeploymentPostprocessing = 0;
-               };
-/* End PBXCopyFilesBuildPhase section */
-
-/* Begin PBXFileReference section */
-               AB1F87C72887C94700AB82A0 /* TalerApp.swift */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.swift; path = TalerApp.swift; 
sourceTree = "<group>"; };
-               AB1F87C92887D2F400AB82A0 /* ContentView.swift */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.swift; path = 
ContentView.swift; sourceTree = "<group>"; };
-               AB32199028B18859008AAC75 /* TransactionsModel.swift */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.swift; path = 
TransactionsModel.swift; sourceTree = "<group>"; };
-               AB4C534928AC21C9003004F7 /* BalancesView.swift */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.swift; path = 
BalancesView.swift; sourceTree = "<group>"; };
-               AB4C534B28AC25FC003004F7 /* BalancesModel.swift */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.swift; path = 
BalancesModel.swift; sourceTree = "<group>"; };
-               AB69F9F928AAED53005CCC2E /* WithdrawModel.swift */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.swift; path = 
WithdrawModel.swift; sourceTree = "<group>"; };
-               AB710490285995B6008B04F0 /* taler-swift */ = {isa = 
PBXFileReference; lastKnownFileType = text; path = "taler-swift"; sourceTree = 
SOURCE_ROOT; };
-               AB7356F828B0203B009C5D8C /* WithdrawView.swift */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.swift; path = 
WithdrawView.swift; sourceTree = "<group>"; };
-               AB8C3806286A88A500E0A1DD /* WalletBackendTests.swift */ = {isa 
= PBXFileReference; lastKnownFileType = sourcecode.swift; path = 
WalletBackendTests.swift; sourceTree = "<group>"; };
-               ABB33064289C5BBB00668B42 /* ExchangeManager.swift */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.swift; path = 
ExchangeManager.swift; sourceTree = "<group>"; };
-               ABB33066289C658900668B42 /* BackendManager.swift */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.swift; path = 
BackendManager.swift; sourceTree = "<group>"; };
-               ABB762AC2891059600E88634 /* SettingsView.swift */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.swift; path = 
SettingsView.swift; sourceTree = "<group>"; };
-               ABC4AC3A28A4619C0047A56F /* PendingView.swift */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.swift; path = 
PendingView.swift; sourceTree = "<group>"; };
-               ABC4AC3E28A473070047A56F /* PendingManager.swift */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.swift; path = 
PendingManager.swift; sourceTree = "<group>"; };
-               D11250FF26B12E3200D02E00 /* taler-wallet-embedded.js */ = {isa 
= PBXFileReference; lastKnownFileType = sourcecode.javascript; path = 
"taler-wallet-embedded.js"; sourceTree = "<group>"; };
-               D14AFD1D24D232B300C51073 /* Taler.app */ = {isa = 
PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; 
path = Taler.app; sourceTree = BUILT_PRODUCTS_DIR; };
-               D14AFD2624D232B500C51073 /* Assets.xcassets */ = {isa = 
PBXFileReference; lastKnownFileType = folder.assetcatalog; path = 
Assets.xcassets; sourceTree = "<group>"; };
-               D14AFD2C24D232B500C51073 /* Base */ = {isa = PBXFileReference; 
lastKnownFileType = file.storyboard; name = Base; path = 
Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
-               D14AFD2E24D232B500C51073 /* Info.plist */ = {isa = 
PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; 
sourceTree = "<group>"; };
-               D14AFD3324D232B500C51073 /* TalerTests.xctest */ = {isa = 
PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path 
= TalerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
-               D14AFD3924D232B500C51073 /* Info.plist */ = {isa = 
PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; 
sourceTree = "<group>"; };
-               D14AFD3E24D232B500C51073 /* TalerUITests.xctest */ = {isa = 
PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path 
= TalerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
-               D14AFD4224D232B500C51073 /* TalerUITests.swift */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.swift; path = 
TalerUITests.swift; sourceTree = "<group>"; };
-               D14AFD4424D232B500C51073 /* Info.plist */ = {isa = 
PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; 
sourceTree = "<group>"; };
-               D1AFF0F2268D59A500FBB744 /* libiono.a */ = {isa = 
PBXFileReference; lastKnownFileType = archive.ar; name = libiono.a; path = 
iono/compiled/x64/libiono.a; sourceTree = "<group>"; };
-               D1D65B9726992E4600C1012A /* WalletBackend.swift */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.swift; path = 
WalletBackend.swift; sourceTree = "<group>"; };
-/* End PBXFileReference section */
-
-/* Begin PBXFrameworksBuildPhase section */
-               D14AFD1A24D232B300C51073 /* Frameworks */ = {
-                       isa = PBXFrameworksBuildPhase;
-                       buildActionMask = 2147483647;
-                       files = (
-                               ABE97B1D286D82BF00580772 /* AnyCodable in 
Frameworks */,
-                               D1AFF0F3268D59C200FBB744 /* libiono.a in 
Frameworks */,
-                               ABC13AA32859962800D23185 /* taler-swift in 
Frameworks */,
-                       );
-                       runOnlyForDeploymentPostprocessing = 0;
-               };
-               D14AFD3024D232B500C51073 /* Frameworks */ = {
-                       isa = PBXFrameworksBuildPhase;
-                       buildActionMask = 2147483647;
-                       files = (
-                       );
-                       runOnlyForDeploymentPostprocessing = 0;
-               };
-               D14AFD3B24D232B500C51073 /* Frameworks */ = {
-                       isa = PBXFrameworksBuildPhase;
-                       buildActionMask = 2147483647;
-                       files = (
-                       );
-                       runOnlyForDeploymentPostprocessing = 0;
-               };
-/* End PBXFrameworksBuildPhase section */
-
-/* Begin PBXGroup section */
-               ABC4AC3C28A470C40047A56F /* Views */ = {
-                       isa = PBXGroup;
-                       children = (
-                               AB1F87C92887D2F400AB82A0 /* ContentView.swift 
*/,
-                               ABB762AC2891059600E88634 /* SettingsView.swift 
*/,
-                               ABC4AC3A28A4619C0047A56F /* PendingView.swift 
*/,
-                               AB4C534928AC21C9003004F7 /* BalancesView.swift 
*/,
-                               AB7356F828B0203B009C5D8C /* WithdrawView.swift 
*/,
-                       );
-                       path = Views;
-                       sourceTree = "<group>";
-               };
-               ABC4AC3D28A4729E0047A56F /* Model */ = {
-                       isa = PBXGroup;
-                       children = (
-                               ABB33066289C658900668B42 /* 
BackendManager.swift */,
-                               AB32199028B18859008AAC75 /* 
TransactionsModel.swift */,
-                               ABB33064289C5BBB00668B42 /* 
ExchangeManager.swift */,
-                               ABC4AC3E28A473070047A56F /* 
PendingManager.swift */,
-                               AB69F9F928AAED53005CCC2E /* WithdrawModel.swift 
*/,
-                               AB4C534B28AC25FC003004F7 /* BalancesModel.swift 
*/,
-                       );
-                       path = Model;
-                       sourceTree = "<group>";
-               };
-               D14AFD1424D232B300C51073 = {
-                       isa = PBXGroup;
-                       children = (
-                               AB710490285995B6008B04F0 /* taler-swift */,
-                               D11250FF26B12E3200D02E00 /* 
taler-wallet-embedded.js */,
-                               D14AFD1F24D232B300C51073 /* Taler */,
-                               D14AFD3624D232B500C51073 /* TalerTests */,
-                               D14AFD4124D232B500C51073 /* TalerUITests */,
-                               D14AFD1E24D232B300C51073 /* Products */,
-                               D1EFA41824D2619B0008B97F /* Frameworks */,
-                       );
-                       sourceTree = "<group>";
-               };
-               D14AFD1E24D232B300C51073 /* Products */ = {
-                       isa = PBXGroup;
-                       children = (
-                               D14AFD1D24D232B300C51073 /* Taler.app */,
-                               D14AFD3324D232B500C51073 /* TalerTests.xctest 
*/,
-                               D14AFD3E24D232B500C51073 /* TalerUITests.xctest 
*/,
-                       );
-                       name = Products;
-                       sourceTree = "<group>";
-               };
-               D14AFD1F24D232B300C51073 /* Taler */ = {
-                       isa = PBXGroup;
-                       children = (
-                               ABC4AC3D28A4729E0047A56F /* Model */,
-                               ABC4AC3C28A470C40047A56F /* Views */,
-                               D1D65B9726992E4600C1012A /* WalletBackend.swift 
*/,
-                               D14AFD2624D232B500C51073 /* Assets.xcassets */,
-                               D14AFD2B24D232B500C51073 /* 
LaunchScreen.storyboard */,
-                               D14AFD2E24D232B500C51073 /* Info.plist */,
-                               AB1F87C72887C94700AB82A0 /* TalerApp.swift */,
-                       );
-                       path = Taler;
-                       sourceTree = "<group>";
-               };
-               D14AFD3624D232B500C51073 /* TalerTests */ = {
-                       isa = PBXGroup;
-                       children = (
-                               D14AFD3924D232B500C51073 /* Info.plist */,
-                               AB8C3806286A88A500E0A1DD /* 
WalletBackendTests.swift */,
-                       );
-                       path = TalerTests;
-                       sourceTree = "<group>";
-               };
-               D14AFD4124D232B500C51073 /* TalerUITests */ = {
-                       isa = PBXGroup;
-                       children = (
-                               D14AFD4224D232B500C51073 /* TalerUITests.swift 
*/,
-                               D14AFD4424D232B500C51073 /* Info.plist */,
-                       );
-                       path = TalerUITests;
-                       sourceTree = "<group>";
-               };
-               D1EFA41824D2619B0008B97F /* Frameworks */ = {
-                       isa = PBXGroup;
-                       children = (
-                               D1AFF0F2268D59A500FBB744 /* libiono.a */,
-                       );
-                       name = Frameworks;
-                       sourceTree = "<group>";
-               };
-/* End PBXGroup section */
-
-/* Begin PBXNativeTarget section */
-               D14AFD1C24D232B300C51073 /* Taler */ = {
-                       isa = PBXNativeTarget;
-                       buildConfigurationList = D14AFD4724D232B500C51073 /* 
Build configuration list for PBXNativeTarget "Taler" */;
-                       buildPhases = (
-                               D14AFD1924D232B300C51073 /* Sources */,
-                               D14AFD1A24D232B300C51073 /* Frameworks */,
-                               D11250F726B12A3500D02E00 /* ShellScript */,
-                               D11250FA26B12D4400D02E00 /* CopyFiles */,
-                               D14AFD1B24D232B300C51073 /* Resources */,
-                       );
-                       buildRules = (
-                       );
-                       dependencies = (
-                       );
-                       name = Taler;
-                       packageProductDependencies = (
-                               ABC13AA22859962800D23185 /* taler-swift */,
-                               ABE97B1C286D82BF00580772 /* AnyCodable */,
-                       );
-                       productName = Taler;
-                       productReference = D14AFD1D24D232B300C51073 /* 
Taler.app */;
-                       productType = "com.apple.product-type.application";
-               };
-               D14AFD3224D232B500C51073 /* TalerTests */ = {
-                       isa = PBXNativeTarget;
-                       buildConfigurationList = D14AFD4A24D232B500C51073 /* 
Build configuration list for PBXNativeTarget "TalerTests" */;
-                       buildPhases = (
-                               D14AFD2F24D232B500C51073 /* Sources */,
-                               D14AFD3024D232B500C51073 /* Frameworks */,
-                               D14AFD3124D232B500C51073 /* Resources */,
-                               D11250F626B1278700D02E00 /* Run Script */,
-                       );
-                       buildRules = (
-                       );
-                       dependencies = (
-                               D14AFD3524D232B500C51073 /* PBXTargetDependency 
*/,
-                       );
-                       name = TalerTests;
-                       productName = TalerTests;
-                       productReference = D14AFD3324D232B500C51073 /* 
TalerTests.xctest */;
-                       productType = "com.apple.product-type.bundle.unit-test";
-               };
-               D14AFD3D24D232B500C51073 /* TalerUITests */ = {
-                       isa = PBXNativeTarget;
-                       buildConfigurationList = D14AFD4D24D232B500C51073 /* 
Build configuration list for PBXNativeTarget "TalerUITests" */;
-                       buildPhases = (
-                               D14AFD3A24D232B500C51073 /* Sources */,
-                               D14AFD3B24D232B500C51073 /* Frameworks */,
-                               D14AFD3C24D232B500C51073 /* Resources */,
-                       );
-                       buildRules = (
-                       );
-                       dependencies = (
-                               D14AFD4024D232B500C51073 /* PBXTargetDependency 
*/,
-                       );
-                       name = TalerUITests;
-                       productName = TalerUITests;
-                       productReference = D14AFD3E24D232B500C51073 /* 
TalerUITests.xctest */;
-                       productType = 
"com.apple.product-type.bundle.ui-testing";
-               };
-/* End PBXNativeTarget section */
-
-/* Begin PBXProject section */
-               D14AFD1524D232B300C51073 /* Project object */ = {
-                       isa = PBXProject;
-                       attributes = {
-                               LastSwiftUpdateCheck = 1160;
-                               LastUpgradeCheck = 1230;
-                               ORGANIZATIONNAME = Taler;
-                               TargetAttributes = {
-                                       D14AFD1C24D232B300C51073 = {
-                                               CreatedOnToolsVersion = 11.6;
-                                               LastSwiftMigration = 1230;
-                                       };
-                                       D14AFD3224D232B500C51073 = {
-                                               CreatedOnToolsVersion = 11.6;
-                                               LastSwiftMigration = 1340;
-                                               TestTargetID = 
D14AFD1C24D232B300C51073;
-                                       };
-                                       D14AFD3D24D232B500C51073 = {
-                                               CreatedOnToolsVersion = 11.6;
-                                               TestTargetID = 
D14AFD1C24D232B300C51073;
-                                       };
-                               };
-                       };
-                       buildConfigurationList = D14AFD1824D232B300C51073 /* 
Build configuration list for PBXProject "Taler" */;
-                       compatibilityVersion = "Xcode 9.3";
-                       developmentRegion = en;
-                       hasScannedForEncodings = 0;
-                       knownRegions = (
-                               en,
-                               Base,
-                       );
-                       mainGroup = D14AFD1424D232B300C51073;
-                       packageReferences = (
-                               ABE97B1B286D82BF00580772 /* 
XCRemoteSwiftPackageReference "AnyCodable" */,
-                       );
-                       productRefGroup = D14AFD1E24D232B300C51073 /* Products 
*/;
-                       projectDirPath = "";
-                       projectRoot = "";
-                       targets = (
-                               D14AFD1C24D232B300C51073 /* Taler */,
-                               D14AFD3224D232B500C51073 /* TalerTests */,
-                               D14AFD3D24D232B500C51073 /* TalerUITests */,
-                       );
-               };
-/* End PBXProject section */
-
-/* Begin PBXResourcesBuildPhase section */
-               D14AFD1B24D232B300C51073 /* Resources */ = {
-                       isa = PBXResourcesBuildPhase;
-                       buildActionMask = 2147483647;
-                       files = (
-                       );
-                       runOnlyForDeploymentPostprocessing = 0;
-               };
-               D14AFD3124D232B500C51073 /* Resources */ = {
-                       isa = PBXResourcesBuildPhase;
-                       buildActionMask = 2147483647;
-                       files = (
-                       );
-                       runOnlyForDeploymentPostprocessing = 0;
-               };
-               D14AFD3C24D232B500C51073 /* Resources */ = {
-                       isa = PBXResourcesBuildPhase;
-                       buildActionMask = 2147483647;
-                       files = (
-                       );
-                       runOnlyForDeploymentPostprocessing = 0;
-               };
-/* End PBXResourcesBuildPhase section */
-
-/* Begin PBXShellScriptBuildPhase section */
-               D11250F626B1278700D02E00 /* Run Script */ = {
-                       isa = PBXShellScriptBuildPhase;
-                       alwaysOutOfDate = 1;
-                       buildActionMask = 2147483647;
-                       files = (
-                       );
-                       inputFileListPaths = (
-                       );
-                       inputPaths = (
-                       );
-                       name = "Run Script";
-                       outputFileListPaths = (
-                       );
-                       outputPaths = (
-                       );
-                       runOnlyForDeploymentPostprocessing = 0;
-                       shellPath = /bin/sh;
-                       shellScript = "
-";
-               };
-               D11250F726B12A3500D02E00 /* ShellScript */ = {
-                       isa = PBXShellScriptBuildPhase;
-                       buildActionMask = 2147483647;
-                       files = (
-                       );
-                       inputFileListPaths = (
-                       );
-                       inputPaths = (
-                       );
-                       outputFileListPaths = (
-                       );
-                       outputPaths = (
-                               "$(SRCROOT)/taler-wallet-embedded.js",
-                       );
-                       runOnlyForDeploymentPostprocessing = 0;
-                       shellPath = /bin/sh;
-                       shellScript = "exit 
0\nWALLET_CORE_VERSION=\"v0.8.1\"\nWALLET_CORE_HASH=\"23bf89b663f0fd0e84a3d7e54a19766766c7306e5704e43a25df57da72056fa7\"\nWALLET_SRC=\"https://git.taler.net/wallet-core.git/plain/${WALLET_CORE_VERSION}/taler-wallet-embedded.js?h=prebuilt\"\nWALLET_DST=\"${SRCROOT}/taler-wallet-embedded.js\"\n\n[
 ! -e $WALLET_DST ] || rm $WALLET_DST\ncurl $WALLET_SRC --output 
$WALLET_DST\n\nRECEIVED_HASH=$(openssl sha256 -r 
$WALLET_DST)\nRECEIVED_HASH_SPLIT=($RECEIVED_HASH)\nif [ $W [...]
-               };
-/* End PBXShellScriptBuildPhase section */
-
-/* Begin PBXSourcesBuildPhase section */
-               D14AFD1924D232B300C51073 /* Sources */ = {
-                       isa = PBXSourcesBuildPhase;
-                       buildActionMask = 2147483647;
-                       files = (
-                               AB1F87C82887C94700AB82A0 /* TalerApp.swift in 
Sources */,
-                               AB1F87CA2887D2F400AB82A0 /* ContentView.swift 
in Sources */,
-                               AB7356F928B0203B009C5D8C /* WithdrawView.swift 
in Sources */,
-                               ABB33067289C658900668B42 /* 
BackendManager.swift in Sources */,
-                               AB69F9FA28AAED53005CCC2E /* WithdrawModel.swift 
in Sources */,
-                               AB4C534C28AC25FC003004F7 /* BalancesModel.swift 
in Sources */,
-                               ABB33065289C5BBB00668B42 /* 
ExchangeManager.swift in Sources */,
-                               AB4C534A28AC21C9003004F7 /* BalancesView.swift 
in Sources */,
-                               D1D65B9826992E4600C1012A /* WalletBackend.swift 
in Sources */,
-                               AB32199128B18859008AAC75 /* 
TransactionsModel.swift in Sources */,
-                               ABB762AD2891059600E88634 /* SettingsView.swift 
in Sources */,
-                               ABC4AC3B28A4619C0047A56F /* PendingView.swift 
in Sources */,
-                               ABC4AC3F28A473070047A56F /* 
PendingManager.swift in Sources */,
-                       );
-                       runOnlyForDeploymentPostprocessing = 0;
-               };
-               D14AFD2F24D232B500C51073 /* Sources */ = {
-                       isa = PBXSourcesBuildPhase;
-                       buildActionMask = 2147483647;
-                       files = (
-                               AB8C3807286A88A600E0A1DD /* 
WalletBackendTests.swift in Sources */,
-                       );
-                       runOnlyForDeploymentPostprocessing = 0;
-               };
-               D14AFD3A24D232B500C51073 /* Sources */ = {
-                       isa = PBXSourcesBuildPhase;
-                       buildActionMask = 2147483647;
-                       files = (
-                               D14AFD4324D232B500C51073 /* TalerUITests.swift 
in Sources */,
-                       );
-                       runOnlyForDeploymentPostprocessing = 0;
-               };
-/* End PBXSourcesBuildPhase section */
-
-/* Begin PBXTargetDependency section */
-               D14AFD3524D232B500C51073 /* PBXTargetDependency */ = {
-                       isa = PBXTargetDependency;
-                       target = D14AFD1C24D232B300C51073 /* Taler */;
-                       targetProxy = D14AFD3424D232B500C51073 /* 
PBXContainerItemProxy */;
-               };
-               D14AFD4024D232B500C51073 /* PBXTargetDependency */ = {
-                       isa = PBXTargetDependency;
-                       target = D14AFD1C24D232B300C51073 /* Taler */;
-                       targetProxy = D14AFD3F24D232B500C51073 /* 
PBXContainerItemProxy */;
-               };
-/* End PBXTargetDependency section */
-
-/* Begin PBXVariantGroup section */
-               D14AFD2B24D232B500C51073 /* LaunchScreen.storyboard */ = {
-                       isa = PBXVariantGroup;
-                       children = (
-                               D14AFD2C24D232B500C51073 /* Base */,
-                       );
-                       name = LaunchScreen.storyboard;
-                       sourceTree = "<group>";
-               };
-/* End PBXVariantGroup section */
-
-/* Begin XCBuildConfiguration section */
-               D14AFD4524D232B500C51073 /* Debug */ = {
-                       isa = XCBuildConfiguration;
-                       buildSettings = {
-                               ALWAYS_SEARCH_USER_PATHS = NO;
-                               CLANG_ANALYZER_NONNULL = YES;
-                               CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = 
YES_AGGRESSIVE;
-                               CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
-                               CLANG_CXX_LIBRARY = "libc++";
-                               CLANG_ENABLE_MODULES = YES;
-                               CLANG_ENABLE_OBJC_ARC = YES;
-                               CLANG_ENABLE_OBJC_WEAK = YES;
-                               CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
-                               CLANG_WARN_BOOL_CONVERSION = YES;
-                               CLANG_WARN_COMMA = YES;
-                               CLANG_WARN_CONSTANT_CONVERSION = YES;
-                               CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = 
YES;
-                               CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
-                               CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
-                               CLANG_WARN_EMPTY_BODY = YES;
-                               CLANG_WARN_ENUM_CONVERSION = YES;
-                               CLANG_WARN_INFINITE_RECURSION = YES;
-                               CLANG_WARN_INT_CONVERSION = YES;
-                               CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
-                               CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
-                               CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
-                               CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
-                               CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = 
YES;
-                               CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
-                               CLANG_WARN_STRICT_PROTOTYPES = YES;
-                               CLANG_WARN_SUSPICIOUS_MOVE = YES;
-                               CLANG_WARN_UNGUARDED_AVAILABILITY = 
YES_AGGRESSIVE;
-                               CLANG_WARN_UNREACHABLE_CODE = YES;
-                               CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
-                               COPY_PHASE_STRIP = NO;
-                               DEBUG_INFORMATION_FORMAT = dwarf;
-                               ENABLE_STRICT_OBJC_MSGSEND = YES;
-                               ENABLE_TESTABILITY = YES;
-                               GCC_C_LANGUAGE_STANDARD = gnu11;
-                               GCC_DYNAMIC_NO_PIC = NO;
-                               GCC_NO_COMMON_BLOCKS = YES;
-                               GCC_OPTIMIZATION_LEVEL = 0;
-                               GCC_PREPROCESSOR_DEFINITIONS = (
-                                       "DEBUG=1",
-                                       "$(inherited)",
-                               );
-                               GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
-                               GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
-                               GCC_WARN_UNDECLARED_SELECTOR = YES;
-                               GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
-                               GCC_WARN_UNUSED_FUNCTION = YES;
-                               GCC_WARN_UNUSED_VARIABLE = YES;
-                               IPHONEOS_DEPLOYMENT_TARGET = 14.5;
-                               MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
-                               MTL_FAST_MATH = YES;
-                               ONLY_ACTIVE_ARCH = YES;
-                               OTHER_LDFLAGS = "-lc++";
-                               SDKROOT = iphoneos;
-                               SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
-                               SWIFT_OPTIMIZATION_LEVEL = "-Onone";
-                       };
-                       name = Debug;
-               };
-               D14AFD4624D232B500C51073 /* Release */ = {
-                       isa = XCBuildConfiguration;
-                       buildSettings = {
-                               ALWAYS_SEARCH_USER_PATHS = NO;
-                               CLANG_ANALYZER_NONNULL = YES;
-                               CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = 
YES_AGGRESSIVE;
-                               CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
-                               CLANG_CXX_LIBRARY = "libc++";
-                               CLANG_ENABLE_MODULES = YES;
-                               CLANG_ENABLE_OBJC_ARC = YES;
-                               CLANG_ENABLE_OBJC_WEAK = YES;
-                               CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
-                               CLANG_WARN_BOOL_CONVERSION = YES;
-                               CLANG_WARN_COMMA = YES;
-                               CLANG_WARN_CONSTANT_CONVERSION = YES;
-                               CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = 
YES;
-                               CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
-                               CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
-                               CLANG_WARN_EMPTY_BODY = YES;
-                               CLANG_WARN_ENUM_CONVERSION = YES;
-                               CLANG_WARN_INFINITE_RECURSION = YES;
-                               CLANG_WARN_INT_CONVERSION = YES;
-                               CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
-                               CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
-                               CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
-                               CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
-                               CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = 
YES;
-                               CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
-                               CLANG_WARN_STRICT_PROTOTYPES = YES;
-                               CLANG_WARN_SUSPICIOUS_MOVE = YES;
-                               CLANG_WARN_UNGUARDED_AVAILABILITY = 
YES_AGGRESSIVE;
-                               CLANG_WARN_UNREACHABLE_CODE = YES;
-                               CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
-                               COPY_PHASE_STRIP = NO;
-                               DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
-                               ENABLE_NS_ASSERTIONS = NO;
-                               ENABLE_STRICT_OBJC_MSGSEND = YES;
-                               GCC_C_LANGUAGE_STANDARD = gnu11;
-                               GCC_NO_COMMON_BLOCKS = YES;
-                               GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
-                               GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
-                               GCC_WARN_UNDECLARED_SELECTOR = YES;
-                               GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
-                               GCC_WARN_UNUSED_FUNCTION = YES;
-                               GCC_WARN_UNUSED_VARIABLE = YES;
-                               IPHONEOS_DEPLOYMENT_TARGET = 14.5;
-                               MTL_ENABLE_DEBUG_INFO = NO;
-                               MTL_FAST_MATH = YES;
-                               OTHER_LDFLAGS = "-lc++";
-                               SDKROOT = iphoneos;
-                               SWIFT_COMPILATION_MODE = wholemodule;
-                               SWIFT_OPTIMIZATION_LEVEL = "-O";
-                               VALIDATE_PRODUCT = YES;
-                       };
-                       name = Release;
-               };
-               D14AFD4824D232B500C51073 /* Debug */ = {
-                       isa = XCBuildConfiguration;
-                       buildSettings = {
-                               ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
-                               CLANG_ENABLE_MODULES = YES;
-                               CODE_SIGN_IDENTITY = "Apple Development";
-                               CODE_SIGN_STYLE = Automatic;
-                               CURRENT_PROJECT_VERSION = 2;
-                               DEVELOPMENT_ASSET_PATHS = "";
-                               DEVELOPMENT_TEAM = AY2R7MK22Y;
-                               ENABLE_PREVIEWS = YES;
-                               FRAMEWORK_SEARCH_PATHS = "$(inherited)";
-                               HEADER_SEARCH_PATHS = (
-                                       "$(PROJECT_DIR)/iono/ios-node-v8/src",
-                                       
"$(PROJECT_DIR)/iono/ios-node-v8/deps/v8/include",
-                                       
"$(PROJECT_DIR)/iono/ios-node-v8/deps/uv/include",
-                               );
-                               INFOPLIST_FILE = Taler/Info.plist;
-                               IPHONEOS_DEPLOYMENT_TARGET = 14.0;
-                               LD_RUNPATH_SEARCH_PATHS = (
-                                       "$(inherited)",
-                                       "@executable_path/Frameworks",
-                               );
-                               LIBRARY_SEARCH_PATHS = (
-                                       "$(inherited)",
-                                       "$(PROJECT_DIR)",
-                                       "$(PROJECT_DIR)/iono/compiled/x64",
-                               );
-                               "LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = (
-                                       
"$(PROJECT_DIR)/iono/ios-node-v8/out-arm64/Debug",
-                                       "$(PROJECT_DIR)/iono/compiled/arm64",
-                               );
-                               "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*]" = (
-                                       
"$(PROJECT_DIR)/iono/ios-node-v8/out-x64/Debug",
-                                       "$(PROJECT_DIR)/iono/compiled/x64",
-                               );
-                               OTHER_CFLAGS = "";
-                               OTHER_LDFLAGS = (
-                                       "-lc++",
-                                       "-lbrotli",
-                                       "-lcares",
-                                       "-lhistogram",
-                                       "-lllhttp",
-                                       "-lnghttp2",
-                                       "-lnode",
-                                       "-lopenssl",
-                                       "-ltorque_base",
-                                       "-luv",
-                                       "-luvwasi",
-                                       "-lv8_base_without_compiler",
-                                       "-lv8_compiler",
-                                       "-lv8_init",
-                                       "-lv8_initializers",
-                                       "-lv8_libbase",
-                                       "-lv8_libplatform",
-                                       "-lv8_libsampler",
-                                       "-lv8_snapshot",
-                                       "-lv8_zlib",
-                                       "-lzlib",
-                               );
-                               PRODUCT_BUNDLE_IDENTIFIER = com.taler.Taler;
-                               PRODUCT_NAME = "$(TARGET_NAME)";
-                               PROVISIONING_PROFILE_SPECIFIER = "";
-                               SWIFT_INCLUDE_PATHS = 
"$(PROJECT_DIR)/iono/iono";
-                               "SWIFT_INCLUDE_PATHS[sdk=iphoneos*]" = 
"$(PROJECT_DIR)/iono/compiled/arm64";
-                               "SWIFT_INCLUDE_PATHS[sdk=iphonesimulator*]" = 
"$(PROJECT_DIR)/iono/compiled/x64";
-                               SWIFT_OBJC_BRIDGING_HEADER = "";
-                               SWIFT_OPTIMIZATION_LEVEL = "-Onone";
-                               SWIFT_VERSION = 5.0;
-                               TARGETED_DEVICE_FAMILY = "1,2";
-                               VALIDATE_WORKSPACE = YES;
-                       };
-                       name = Debug;
-               };
-               D14AFD4924D232B500C51073 /* Release */ = {
-                       isa = XCBuildConfiguration;
-                       buildSettings = {
-                               ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
-                               CLANG_ENABLE_MODULES = YES;
-                               CODE_SIGN_IDENTITY = "Apple Development";
-                               CODE_SIGN_STYLE = Automatic;
-                               CURRENT_PROJECT_VERSION = 2;
-                               DEVELOPMENT_ASSET_PATHS = "";
-                               DEVELOPMENT_TEAM = AY2R7MK22Y;
-                               ENABLE_PREVIEWS = YES;
-                               FRAMEWORK_SEARCH_PATHS = "$(inherited)";
-                               HEADER_SEARCH_PATHS = (
-                                       "$(PROJECT_DIR)/iono/ios-node-v8/src",
-                                       
"$(PROJECT_DIR)/iono/ios-node-v8/deps/v8/include",
-                                       
"$(PROJECT_DIR)/iono/ios-node-v8/deps/uv/include",
-                               );
-                               INFOPLIST_FILE = Taler/Info.plist;
-                               IPHONEOS_DEPLOYMENT_TARGET = 14.0;
-                               LD_RUNPATH_SEARCH_PATHS = (
-                                       "$(inherited)",
-                                       "@executable_path/Frameworks",
-                               );
-                               LIBRARY_SEARCH_PATHS = (
-                                       "$(inherited)",
-                                       "$(PROJECT_DIR)",
-                                       "$(PROJECT_DIR)/iono/compiled/x64",
-                               );
-                               "LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = (
-                                       
"$(PROJECT_DIR)/iono/ios-node-v8/out-arm64/Release",
-                                       "$(PROJECT_DIR)/iono/compiled/arm64",
-                               );
-                               "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*]" = (
-                                       
"$(PROJECT_DIR)/iono/ios-node-v8/out-x64/Release",
-                                       "$(PROJECT_DIR)/iono/compiled/x64",
-                               );
-                               OTHER_CFLAGS = "";
-                               OTHER_LDFLAGS = (
-                                       "-lc++",
-                                       "-lbrotli",
-                                       "-lcares",
-                                       "-lhistogram",
-                                       "-lllhttp",
-                                       "-lnghttp2",
-                                       "-lnode",
-                                       "-lopenssl",
-                                       "-ltorque_base",
-                                       "-luv",
-                                       "-luvwasi",
-                                       "-lv8_base_without_compiler",
-                                       "-lv8_compiler",
-                                       "-lv8_init",
-                                       "-lv8_initializers",
-                                       "-lv8_libbase",
-                                       "-lv8_libplatform",
-                                       "-lv8_libsampler",
-                                       "-lv8_snapshot",
-                                       "-lv8_zlib",
-                                       "-lzlib",
-                               );
-                               PRODUCT_BUNDLE_IDENTIFIER = com.taler.Taler;
-                               PRODUCT_NAME = "$(TARGET_NAME)";
-                               PROVISIONING_PROFILE_SPECIFIER = "";
-                               SWIFT_INCLUDE_PATHS = 
"$(PROJECT_DIR)/iono/iono";
-                               "SWIFT_INCLUDE_PATHS[sdk=iphoneos*]" = 
"$(PROJECT_DIR)/iono/compiled/arm64";
-                               "SWIFT_INCLUDE_PATHS[sdk=iphonesimulator*]" = 
"$(PROJECT_DIR)/iono/compiled/x64";
-                               SWIFT_OBJC_BRIDGING_HEADER = "";
-                               SWIFT_VERSION = 5.0;
-                               TARGETED_DEVICE_FAMILY = "1,2";
-                               VALIDATE_WORKSPACE = YES;
-                       };
-                       name = Release;
-               };
-               D14AFD4B24D232B500C51073 /* Debug */ = {
-                       isa = XCBuildConfiguration;
-                       buildSettings = {
-                               ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
-                               BUNDLE_LOADER = "$(TEST_HOST)";
-                               CLANG_ENABLE_MODULES = YES;
-                               "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple 
Development";
-                               CODE_SIGN_STYLE = Automatic;
-                               DEVELOPMENT_TEAM = "";
-                               INFOPLIST_FILE = TalerTests/Info.plist;
-                               IPHONEOS_DEPLOYMENT_TARGET = 14.5;
-                               LD_RUNPATH_SEARCH_PATHS = (
-                                       "$(inherited)",
-                                       "@executable_path/Frameworks",
-                                       "@loader_path/Frameworks",
-                               );
-                               PRODUCT_BUNDLE_IDENTIFIER = 
com.taler.TalerTests;
-                               PRODUCT_NAME = "$(TARGET_NAME)";
-                               SWIFT_OPTIMIZATION_LEVEL = "-Onone";
-                               SWIFT_VERSION = 5.0;
-                               TARGETED_DEVICE_FAMILY = "1,2";
-                               TEST_HOST = 
"$(BUILT_PRODUCTS_DIR)/Taler.app/Taler";
-                       };
-                       name = Debug;
-               };
-               D14AFD4C24D232B500C51073 /* Release */ = {
-                       isa = XCBuildConfiguration;
-                       buildSettings = {
-                               ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
-                               BUNDLE_LOADER = "$(TEST_HOST)";
-                               CLANG_ENABLE_MODULES = YES;
-                               "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple 
Development";
-                               CODE_SIGN_STYLE = Automatic;
-                               DEVELOPMENT_TEAM = "";
-                               INFOPLIST_FILE = TalerTests/Info.plist;
-                               IPHONEOS_DEPLOYMENT_TARGET = 14.5;
-                               LD_RUNPATH_SEARCH_PATHS = (
-                                       "$(inherited)",
-                                       "@executable_path/Frameworks",
-                                       "@loader_path/Frameworks",
-                               );
-                               PRODUCT_BUNDLE_IDENTIFIER = 
com.taler.TalerTests;
-                               PRODUCT_NAME = "$(TARGET_NAME)";
-                               SWIFT_VERSION = 5.0;
-                               TARGETED_DEVICE_FAMILY = "1,2";
-                               TEST_HOST = 
"$(BUILT_PRODUCTS_DIR)/Taler.app/Taler";
-                       };
-                       name = Release;
-               };
-               D14AFD4E24D232B500C51073 /* Debug */ = {
-                       isa = XCBuildConfiguration;
-                       buildSettings = {
-                               ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
-                               CODE_SIGN_STYLE = Automatic;
-                               INFOPLIST_FILE = TalerUITests/Info.plist;
-                               LD_RUNPATH_SEARCH_PATHS = (
-                                       "$(inherited)",
-                                       "@executable_path/Frameworks",
-                                       "@loader_path/Frameworks",
-                               );
-                               PRODUCT_BUNDLE_IDENTIFIER = 
com.taler.TalerUITests;
-                               PRODUCT_NAME = "$(TARGET_NAME)";
-                               SWIFT_VERSION = 5.0;
-                               TARGETED_DEVICE_FAMILY = "1,2";
-                               TEST_TARGET_NAME = Taler;
-                       };
-                       name = Debug;
-               };
-               D14AFD4F24D232B500C51073 /* Release */ = {
-                       isa = XCBuildConfiguration;
-                       buildSettings = {
-                               ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
-                               CODE_SIGN_STYLE = Automatic;
-                               INFOPLIST_FILE = TalerUITests/Info.plist;
-                               LD_RUNPATH_SEARCH_PATHS = (
-                                       "$(inherited)",
-                                       "@executable_path/Frameworks",
-                                       "@loader_path/Frameworks",
-                               );
-                               PRODUCT_BUNDLE_IDENTIFIER = 
com.taler.TalerUITests;
-                               PRODUCT_NAME = "$(TARGET_NAME)";
-                               SWIFT_VERSION = 5.0;
-                               TARGETED_DEVICE_FAMILY = "1,2";
-                               TEST_TARGET_NAME = Taler;
-                       };
-                       name = Release;
-               };
-/* End XCBuildConfiguration section */
-
-/* Begin XCConfigurationList section */
-               D14AFD1824D232B300C51073 /* Build configuration list for 
PBXProject "Taler" */ = {
-                       isa = XCConfigurationList;
-                       buildConfigurations = (
-                               D14AFD4524D232B500C51073 /* Debug */,
-                               D14AFD4624D232B500C51073 /* Release */,
-                       );
-                       defaultConfigurationIsVisible = 0;
-                       defaultConfigurationName = Release;
-               };
-               D14AFD4724D232B500C51073 /* Build configuration list for 
PBXNativeTarget "Taler" */ = {
-                       isa = XCConfigurationList;
-                       buildConfigurations = (
-                               D14AFD4824D232B500C51073 /* Debug */,
-                               D14AFD4924D232B500C51073 /* Release */,
-                       );
-                       defaultConfigurationIsVisible = 0;
-                       defaultConfigurationName = Release;
-               };
-               D14AFD4A24D232B500C51073 /* Build configuration list for 
PBXNativeTarget "TalerTests" */ = {
-                       isa = XCConfigurationList;
-                       buildConfigurations = (
-                               D14AFD4B24D232B500C51073 /* Debug */,
-                               D14AFD4C24D232B500C51073 /* Release */,
-                       );
-                       defaultConfigurationIsVisible = 0;
-                       defaultConfigurationName = Release;
-               };
-               D14AFD4D24D232B500C51073 /* Build configuration list for 
PBXNativeTarget "TalerUITests" */ = {
-                       isa = XCConfigurationList;
-                       buildConfigurations = (
-                               D14AFD4E24D232B500C51073 /* Debug */,
-                               D14AFD4F24D232B500C51073 /* Release */,
-                       );
-                       defaultConfigurationIsVisible = 0;
-                       defaultConfigurationName = Release;
-               };
-/* End XCConfigurationList section */
-
-/* Begin XCRemoteSwiftPackageReference section */
-               ABE97B1B286D82BF00580772 /* XCRemoteSwiftPackageReference 
"AnyCodable" */ = {
-                       isa = XCRemoteSwiftPackageReference;
-                       repositoryURL = 
"https://github.com/Flight-School/AnyCodable";;
-                       requirement = {
-                               kind = upToNextMajorVersion;
-                               minimumVersion = 0.6.5;
-                       };
-               };
-/* End XCRemoteSwiftPackageReference section */
-
-/* Begin XCSwiftPackageProductDependency section */
-               ABC13AA22859962800D23185 /* taler-swift */ = {
-                       isa = XCSwiftPackageProductDependency;
-                       productName = "taler-swift";
-               };
-               ABE97B1C286D82BF00580772 /* AnyCodable */ = {
-                       isa = XCSwiftPackageProductDependency;
-                       package = ABE97B1B286D82BF00580772 /* 
XCRemoteSwiftPackageReference "AnyCodable" */;
-                       productName = AnyCodable;
-               };
-/* End XCSwiftPackageProductDependency section */
-       };
-       rootObject = D14AFD1524D232B300C51073 /* Project object */;
-}
diff --git a/Taler.xcodeproj/project.xcworkspace/contents.xcworkspacedata 
b/Taler.xcodeproj/project.xcworkspace/contents.xcworkspacedata
deleted file mode 100644
index 919434a..0000000
--- a/Taler.xcodeproj/project.xcworkspace/contents.xcworkspacedata
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<Workspace
-   version = "1.0">
-   <FileRef
-      location = "self:">
-   </FileRef>
-</Workspace>
diff --git 
a/Taler.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist 
b/Taler.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
deleted file mode 100644
index 18d9810..0000000
--- a/Taler.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
+++ /dev/null
@@ -1,8 +0,0 @@
-<?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>IDEDidComputeMac32BitWarning</key>
-       <true/>
-</dict>
-</plist>
diff --git 
a/Taler.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved 
b/Taler.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
deleted file mode 100644
index 7eca6e5..0000000
--- a/Taler.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ /dev/null
@@ -1,14 +0,0 @@
-{
-  "pins" : [
-    {
-      "identity" : "anycodable",
-      "kind" : "remoteSourceControl",
-      "location" : "https://github.com/Flight-School/AnyCodable";,
-      "state" : {
-        "revision" : "f9fda69a7b704d46fb5123005f2f7e43dbb8a0fa",
-        "version" : "0.6.5"
-      }
-    }
-  ],
-  "version" : 2
-}
diff --git a/Taler.xcodeproj/xcshareddata/xcschemes/Taler.xcscheme 
b/Taler.xcodeproj/xcshareddata/xcschemes/Taler.xcscheme
deleted file mode 100644
index 2a82732..0000000
--- a/Taler.xcodeproj/xcshareddata/xcschemes/Taler.xcscheme
+++ /dev/null
@@ -1,98 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<Scheme
-   LastUpgradeVersion = "1230"
-   version = "1.3">
-   <BuildAction
-      parallelizeBuildables = "YES"
-      buildImplicitDependencies = "YES">
-      <BuildActionEntries>
-         <BuildActionEntry
-            buildForTesting = "YES"
-            buildForRunning = "YES"
-            buildForProfiling = "YES"
-            buildForArchiving = "YES"
-            buildForAnalyzing = "YES">
-            <BuildableReference
-               BuildableIdentifier = "primary"
-               BlueprintIdentifier = "D14AFD1C24D232B300C51073"
-               BuildableName = "Taler.app"
-               BlueprintName = "Taler"
-               ReferencedContainer = "container:Taler.xcodeproj">
-            </BuildableReference>
-         </BuildActionEntry>
-      </BuildActionEntries>
-   </BuildAction>
-   <TestAction
-      buildConfiguration = "Debug"
-      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
-      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
-      shouldUseLaunchSchemeArgsEnv = "YES">
-      <Testables>
-         <TestableReference
-            skipped = "NO">
-            <BuildableReference
-               BuildableIdentifier = "primary"
-               BlueprintIdentifier = "D14AFD3224D232B500C51073"
-               BuildableName = "TalerTests.xctest"
-               BlueprintName = "TalerTests"
-               ReferencedContainer = "container:Taler.xcodeproj">
-            </BuildableReference>
-         </TestableReference>
-         <TestableReference
-            skipped = "NO">
-            <BuildableReference
-               BuildableIdentifier = "primary"
-               BlueprintIdentifier = "D14AFD3D24D232B500C51073"
-               BuildableName = "TalerUITests.xctest"
-               BlueprintName = "TalerUITests"
-               ReferencedContainer = "container:Taler.xcodeproj">
-            </BuildableReference>
-         </TestableReference>
-      </Testables>
-   </TestAction>
-   <LaunchAction
-      buildConfiguration = "Debug"
-      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
-      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
-      launchStyle = "0"
-      useCustomWorkingDirectory = "NO"
-      ignoresPersistentStateOnLaunch = "NO"
-      debugDocumentVersioning = "YES"
-      debugServiceExtension = "internal"
-      allowLocationSimulation = "YES">
-      <BuildableProductRunnable
-         runnableDebuggingMode = "0">
-         <BuildableReference
-            BuildableIdentifier = "primary"
-            BlueprintIdentifier = "D14AFD1C24D232B300C51073"
-            BuildableName = "Taler.app"
-            BlueprintName = "Taler"
-            ReferencedContainer = "container:Taler.xcodeproj">
-         </BuildableReference>
-      </BuildableProductRunnable>
-   </LaunchAction>
-   <ProfileAction
-      buildConfiguration = "Release"
-      shouldUseLaunchSchemeArgsEnv = "YES"
-      savedToolIdentifier = ""
-      useCustomWorkingDirectory = "NO"
-      debugDocumentVersioning = "YES">
-      <BuildableProductRunnable
-         runnableDebuggingMode = "0">
-         <BuildableReference
-            BuildableIdentifier = "primary"
-            BlueprintIdentifier = "D14AFD1C24D232B300C51073"
-            BuildableName = "Taler.app"
-            BlueprintName = "Taler"
-            ReferencedContainer = "container:Taler.xcodeproj">
-         </BuildableReference>
-      </BuildableProductRunnable>
-   </ProfileAction>
-   <AnalyzeAction
-      buildConfiguration = "Debug">
-   </AnalyzeAction>
-   <ArchiveAction
-      buildConfiguration = "Release"
-      revealArchiveInOrganizer = "YES">
-   </ArchiveAction>
-</Scheme>
diff --git a/Taler.xcworkspace/contents.xcworkspacedata 
b/Taler.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..fdb8a75
--- /dev/null
+++ b/Taler.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "group:TalerWallet.xcodeproj">
+   </FileRef>
+   <FileRef
+      location = "group:../quickjs-tart/QuickJS-rt.xcodeproj">
+   </FileRef>
+</Workspace>
diff --git a/Taler/Base.lproj/LaunchScreen.storyboard 
b/Taler/Base.lproj/LaunchScreen.storyboard
deleted file mode 100644
index 865e932..0000000
--- a/Taler/Base.lproj/LaunchScreen.storyboard
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" 
version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" 
propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" 
useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" 
initialViewController="01J-lp-oVM">
-    <dependencies>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" 
version="13104.12"/>
-        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
-        <capability name="documents saved in the Xcode 8 format" 
minToolsVersion="8.0"/>
-    </dependencies>
-    <scenes>
-        <!--View Controller-->
-        <scene sceneID="EHf-IW-A2E">
-            <objects>
-                <viewController id="01J-lp-oVM" sceneMemberID="viewController">
-                    <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
-                        <rect key="frame" x="0.0" y="0.0" width="375" 
height="667"/>
-                        <autoresizingMask key="autoresizingMask" 
widthSizable="YES" heightSizable="YES"/>
-                        <color key="backgroundColor" 
xcode11CocoaTouchSystemColor="systemBackgroundColor" 
cocoaTouchSystemColor="whiteColor"/>
-                        <viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
-                    </view>
-                </viewController>
-                <placeholder placeholderIdentifier="IBFirstResponder" 
id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
-            </objects>
-            <point key="canvasLocation" x="53" y="375"/>
-        </scene>
-    </scenes>
-</document>
diff --git a/Taler/Info.plist b/Taler/Info.plist
deleted file mode 100644
index 14a59ff..0000000
--- a/Taler/Info.plist
+++ /dev/null
@@ -1,51 +0,0 @@
-<?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>CFBundleDevelopmentRegion</key>
-       <string>$(DEVELOPMENT_LANGUAGE)</string>
-       <key>CFBundleExecutable</key>
-       <string>$(EXECUTABLE_NAME)</string>
-       <key>CFBundleIdentifier</key>
-       <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
-       <key>CFBundleInfoDictionaryVersion</key>
-       <string>6.0</string>
-       <key>CFBundleName</key>
-       <string>$(PRODUCT_NAME)</string>
-       <key>CFBundlePackageType</key>
-       <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
-       <key>CFBundleShortVersionString</key>
-       <string>1.0</string>
-       <key>CFBundleVersion</key>
-       <string>$(CURRENT_PROJECT_VERSION)</string>
-       <key>LSRequiresIPhoneOS</key>
-       <true/>
-       <key>UIApplicationSceneManifest</key>
-       <dict>
-               <key>UIApplicationSupportsMultipleScenes</key>
-               <true/>
-       </dict>
-       <key>UILaunchScreen</key>
-       <dict>
-               <key>UIImageName</key>
-               <string></string>
-       </dict>
-       <key>UIRequiredDeviceCapabilities</key>
-       <array>
-               <string>armv7</string>
-       </array>
-       <key>UISupportedInterfaceOrientations</key>
-       <array>
-               <string>UIInterfaceOrientationPortrait</string>
-               <string>UIInterfaceOrientationLandscapeLeft</string>
-               <string>UIInterfaceOrientationLandscapeRight</string>
-       </array>
-       <key>UISupportedInterfaceOrientations~ipad</key>
-       <array>
-               <string>UIInterfaceOrientationPortrait</string>
-               <string>UIInterfaceOrientationPortraitUpsideDown</string>
-               <string>UIInterfaceOrientationLandscapeLeft</string>
-               <string>UIInterfaceOrientationLandscapeRight</string>
-       </array>
-</dict>
-</plist>
diff --git a/Taler/Model/BalancesModel.swift b/Taler/Model/BalancesModel.swift
deleted file mode 100644
index 29a6656..0000000
--- a/Taler/Model/BalancesModel.swift
+++ /dev/null
@@ -1,48 +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
-
-class BalancesModel: ObservableObject {
-    var backend: WalletBackend
-    
-    @Published var loading: Bool = false
-    @Published var balances: [Balance]?
-    
-    init(backend: WalletBackend) {
-        self.backend = backend
-    }
-    
-    func getBalances() {
-        self.loading = true
-        let req = WalletBackendGetBalancesRequest()
-        backend.sendFormattedRequest(request: req) { response, err in
-            // TODO: Use Combine instead
-            DispatchQueue.main.async {
-                self.loading = false
-                if let res = response {
-                    self.balances = res.balances
-                } else {
-                    // TODO: Handle error.
-                }
-            }
-        }
-    }
-    
-    func getTransactionsModel() -> TransactionsModel {
-        return TransactionsModel(backend: self.backend, currency: nil)
-    }
-}
diff --git a/Taler/Model/ExchangeManager.swift 
b/Taler/Model/ExchangeManager.swift
deleted file mode 100644
index ca4942e..0000000
--- a/Taler/Model/ExchangeManager.swift
+++ /dev/null
@@ -1,69 +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
-
-typealias ExchangeItem = WalletBackendListExchanges.ExchangeListItem
-
-class ExchangeManager: ObservableObject {
-    var backend: WalletBackend
-    
-    @Published var loading: Bool
-    @Published var exchanges: [ExchangeItem]?
-    
-    init(_backend: WalletBackend) {
-        self.backend = _backend
-        self.loading = false
-        self.exchanges = nil
-    }
-    
-    func updateList() {
-        let listRequest = WalletBackendListExchanges()
-        backend.sendFormattedRequest(request: listRequest) { response, err in
-            // TODO: Use Combine instead.
-            DispatchQueue.main.async {
-                self.loading = false
-                if let result = response {
-                    self.exchanges = result.exchanges
-                } else {
-                    // TODO: Show error.
-                }
-            }
-        }
-        self.loading = true
-    }
-    
-    func add(url: String) {
-        let addRequest = WalletBackendAddExchangeRequest(exchangeBaseUrl: url)
-        backend.sendFormattedRequest(request: addRequest) { response, err in
-            // TODO: Use Combine instead.
-            DispatchQueue.main.async {
-                self.loading = false
-                if let _ = response {
-                    self.updateList()
-                } else {
-                    // TODO: Show error.
-                }
-            }
-        }
-        self.loading = true
-    }
-    
-    func withdraw(exchange: ExchangeItem) -> WithdrawModel {
-        return WithdrawModel(backend: self.backend, exchange: exchange)
-    }
-}
diff --git a/Taler/Model/PendingManager.swift b/Taler/Model/PendingManager.swift
deleted file mode 100644
index 02a7e37..0000000
--- a/Taler/Model/PendingManager.swift
+++ /dev/null
@@ -1,49 +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
-
-class PendingManager: ObservableObject {
-    var backend: WalletBackend
-    
-    @Published var loading: Bool
-    @Published var items: [String]?
-    
-    init(_backend: WalletBackend) {
-        self.backend = _backend
-        self.loading = false
-        self.items = nil
-    }
-    
-    func update() {
-        let req = WalletBackendPendingRequest()
-        backend.sendFormattedRequest(request: req) { response, err in
-            // TODO: Use Combine instead.
-            DispatchQueue.main.async {
-                self.loading = false
-                if let x = response {
-                    self.items = x.pendingOperations.map({ op in
-                        let encoded = try! JSONEncoder().encode(op)
-                        let str = String(data: encoded, encoding: .utf8)!
-                        return str
-                    })
-                }
-            }
-        }
-        self.loading = true
-    }
-}
diff --git a/Taler/Model/TransactionsModel.swift 
b/Taler/Model/TransactionsModel.swift
deleted file mode 100644
index 753d5cd..0000000
--- a/Taler/Model/TransactionsModel.swift
+++ /dev/null
@@ -1,48 +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
-
-class TransactionsModel: ObservableObject {
-    var backend: WalletBackend
-    var currency: String?
-    
-    @Published var loading: Bool = false
-    @Published var transactions: [Transaction]?
-    
-    init(backend: WalletBackend, currency: String?) {
-        self.backend = backend
-        self.currency = currency
-    }
-    
-    func loadTransactions(searchString: String? = nil) {
-        self.loading = true
-        let req = WalletBackendGetTransactionsRequest(currency: self.currency,
-                                                      search: searchString)
-        backend.sendFormattedRequest(request: req) { response, err in
-            // TODO: Use Combine instead
-            DispatchQueue.main.async {
-                self.loading = false
-                if let res = response {
-                    print("x")
-                    self.transactions = res.transactions
-                } else {
-                    // TODO: Handle error.
-                }
-            }
-        }
-    }
-}
diff --git a/Taler/Model/WithdrawModel.swift b/Taler/Model/WithdrawModel.swift
deleted file mode 100644
index 02027cc..0000000
--- a/Taler/Model/WithdrawModel.swift
+++ /dev/null
@@ -1,179 +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
-
-func paytoUriGetIban(uri: String) -> String {
-    let url = URL(string: uri)!
-    return url.lastPathComponent
-}
-
-func paytoUriGetSubject(uri: String) -> String {
-    let url = URLComponents(string: uri)!
-    return url.queryItems!.first(where: { item in
-        item.name == "message"
-    })!.value!.removingPercentEncoding!.replacingOccurrences(of: "+", with: " 
")
-}
-
-typealias WithdrawDetails = 
WalletBackendGetWithdrawalDetailsForAmountRequest.Response
-typealias TOSDetails = WalletBackendGetExchangeTermsOfService.Response
-
-class ManualTransferModel: ObservableObject {
-    let backend: WalletBackend
-    let exchange: ExchangeItem
-    var details: WithdrawDetails!
-    var paytoUri: String!
-    @Published var loading: Bool = false
-    @Published var nav: Bool = false
-
-    init(backend: WalletBackend, exchange: ExchangeItem) {
-        self.backend = backend
-        self.exchange = exchange
-    }
-    
-    func loadDetails(_ newDetails: WithdrawDetails, _ newPaytoUri: String) {
-        self.details = newDetails
-        self.paytoUri = newPaytoUri
-    }
-}
-
-class PromptWithdrawModel: ObservableObject {
-    let backend: WalletBackend
-    let exchange: ExchangeItem
-    var details: WithdrawDetails!
-    var tosDetails: TOSDetails?
-    @Published var tosAccepted: Bool!
-    @Published var loading: Bool = false
-    @Published var navTos: Bool = false
-    @Published var nav: Bool = false
-    
-    var manualTransferModel: ManualTransferModel
-    
-    init(backend: WalletBackend, exchange: ExchangeItem) {
-        self.backend = backend
-        self.exchange = exchange
-        self.manualTransferModel = ManualTransferModel(backend: backend, 
exchange: exchange)
-    }
-    
-    func loadDetails(_ newDetails: WithdrawDetails) {
-        self.details = newDetails
-        self.tosAccepted = details.tosAccepted
-    }
-    
-    func acceptTos() {
-        self.loading = true
-        let req = 
WalletBackendSetExchangeTermsOfServiceAccepted(exchangeBaseUrl: 
exchange.exchangeBaseUrl,
-                                                                 etag: 
tosDetails!.currentEtag)
-        backend.sendFormattedRequest(request: req) { response, err in
-            // TODO: Use Combine instead
-            DispatchQueue.main.async {
-                self.loading = false
-                if let _ = response {
-                    self.tosAccepted = true
-                    self.navTos = false
-                } else {
-                    // TODO: Handle error.
-                }
-            }
-        }
-    }
-    
-    func acceptWithdraw() {
-        // TODO: Include an option for a withdraw payto uri.
-        self.loading = true
-        let req = WalletBackendAcceptManualWithdrawalRequest(exchangeBaseUrl: 
exchange.exchangeBaseUrl,
-                                                             amount: 
details.amountRaw)
-        backend.sendFormattedRequest(request: req) { response, err in
-            // TODO: Use Combine instead
-            DispatchQueue.main.async {
-                self.loading = false
-                if let res = response {
-                    self.manualTransferModel.loadDetails(self.details, 
res.exchangePaytoUris[0])
-                    self.nav = true
-                } else {
-                    // TODO: Show error.
-                    self.loading = false
-                }
-            }
-        }
-    }
-}
-
-class WithdrawModel: ObservableObject {
-    let backend: WalletBackend
-    let exchange: ExchangeItem
-    var details: WithdrawDetails?
-    @Published var loading: Bool = false
-    @Published var nav: Bool = false
-    
-    var promptModel: PromptWithdrawModel
-    
-    init(backend: WalletBackend, exchange: ExchangeItem) {
-        self.backend = backend
-        self.exchange = exchange
-        self.promptModel = PromptWithdrawModel(backend: backend, exchange: 
exchange)
-    }
-    
-    func getWithdrawDetails(amountStr: String) {
-        self.loading = true
-        do {
-            let amount = try Amount(fromString: amountStr)
-            let req = 
WalletBackendGetWithdrawalDetailsForAmountRequest(exchangeBaseUrl: 
exchange.exchangeBaseUrl,
-                                                                        
amount: amount)
-            backend.sendFormattedRequest(request: req) { response, err in
-                // TODO: Use Combine instead.
-                DispatchQueue.main.async {
-                    if let res = response {
-                        self.details = res
-                        self.promptModel.loadDetails(res)
-                        if res.tosAccepted {
-                            self.loading = false
-                            self.nav = true
-                        } else {
-                            self.getTos()
-                        }
-                    } else {
-                        // TODO: Show error.
-                        self.loading = false
-                    }
-                }
-            }
-        } catch {
-            // TODO: Show error.
-            self.loading = false
-        }
-    }
-    
-    private func getTos() {
-        self.loading = true
-        let req = WalletBackendGetExchangeTermsOfService(exchangeBaseUrl: 
exchange.exchangeBaseUrl)
-        backend.sendFormattedRequest(request: req) { response, err in
-            // TODO: Use Combine instead
-            DispatchQueue.main.async {
-                self.loading = false
-                if let res = response {
-                    self.promptModel.tosDetails = res
-                    self.loading = false
-                    self.nav = true
-                } else {
-                    // TODO: Show error.
-                    self.loading = false
-                }
-            }
-        }
-    }
-}
diff --git a/Taler/Views/BalancesView.swift b/Taler/Views/BalancesView.swift
deleted file mode 100644
index a8eab64..0000000
--- a/Taler/Views/BalancesView.swift
+++ /dev/null
@@ -1,106 +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 TransactionsView: View {
-    @ObservedObject var model: TransactionsModel
-    
-    var body: some View {
-        VStack {
-            if model.transactions == nil {
-                ProgressView()
-                    .onAppear {
-                        model.loadTransactions()
-                    }
-            } else if model.loading {
-                ProgressView()
-            } else {
-                List(model.transactions!, id: \.self) { tx in
-                    VStack {
-                        Text("Transaction: \(tx.transactionId)")
-                    }
-                }
-                Text("Loaded")
-                /*VStack {
-                    Text("Balances")
-                    NavigationLink {
-                        TransactionsView(model: 
self.balancesModel.getTransactionsModel())
-                    } label: {
-                        Text("Transactions")
-                    }
-
-                }
-                    .padding(16)
-                    .navigationTitle("Balances")
-                    .navigationBarItems(
-                        leading: Button(action: self.showSidebar, label: {
-                            Image(systemName: "line.3.horizontal")
-                        })
-                    )*/
-            }
-        }
-            .navigationTitle("Transactions")
-    }
-}
-
-struct BalancesView: View {
-    @ObservedObject var balancesModel: BalancesModel
-    @EnvironmentObject var backend: BackendManager
-    var showSidebar: () -> Void
-    
-    var body: some View {
-        NavigationView {
-            if balancesModel.balances == nil {
-                ProgressView()
-                    .navigationTitle("Balances")
-                    .navigationBarItems(
-                        leading: Button(action: self.showSidebar, label: {
-                            Image(systemName: "line.3.horizontal")
-                        })
-                    )
-                    .onAppear {
-                        balancesModel.getBalances()
-                    }
-            } else if balancesModel.loading {
-                ProgressView()
-                    .navigationTitle("Balances")
-                    .navigationBarItems(
-                        leading: Button(action: self.showSidebar, label: {
-                            Image(systemName: "line.3.horizontal")
-                        })
-                    )
-            } else {
-                VStack {
-                    Text("Balances")
-                    NavigationLink {
-                        TransactionsView(model: 
self.balancesModel.getTransactionsModel())
-                    } label: {
-                        Text("Transactions")
-                    }
-
-                }
-                    .padding(16)
-                    .navigationTitle("Balances")
-                    .navigationBarItems(
-                        leading: Button(action: self.showSidebar, label: {
-                            Image(systemName: "line.3.horizontal")
-                        })
-                    )
-            }
-        }
-    }
-}
diff --git a/Taler/Views/ContentView.swift b/Taler/Views/ContentView.swift
deleted file mode 100644
index 237b33a..0000000
--- a/Taler/Views/ContentView.swift
+++ /dev/null
@@ -1,85 +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 SidebarItem {
-    var name: String
-    var view: AnyView
-}
-
-struct ContentView: View {
-    @StateObject var backend: BackendManager = BackendManager()
-    
-    @State var sidebarVisible: Bool = false
-    var views: [SidebarItem] {[
-        SidebarItem(name: "Balances",
-                    view: AnyView(BalancesView(balancesModel: 
BalancesModel(backend: self.backend.backend)) {
-                        self.sidebarVisible = true
-                    }.environmentObject(backend))),
-        SidebarItem(name: "Settings",
-                    view: AnyView(SettingsView {
-                        self.sidebarVisible = true
-                    }.environmentObject(backend))),
-        SidebarItem(name: "Pending Operations",
-                    view: AnyView(PendingView(_showSidebar: {
-                        self.sidebarVisible = true
-                    }, pending: 
backend.pendingManager).environmentObject(backend)))
-    ]}
-    @State var currentView: Int = 0
-    
-    var body: some View {
-        ZStack(alignment: .leading) {
-            
-            views[currentView].view
-                .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: 
.center)
-
-            VStack {
-                Spacer()
-                
-                Button {
-                    self.sidebarVisible = false
-                } label: {
-                    Text("Close")
-                }
-                Divider()
-                
-                ForEach(0..<views.count, id: \.self) { i in
-                    Button {
-                        self.sidebarVisible = false
-                        self.currentView = i
-                    } label: {
-                        Text(views[i].name)
-                    }
-                    Divider()
-                }
-                
-                Spacer()
-            }
-                .background(Color.gray)
-                .frame(width: 100, alignment: .center)
-                .offset(x: sidebarVisible ? 0 : -100)
-                .animation(.easeInOut, value: sidebarVisible)
-                .ignoresSafeArea()
-        }
-    }
-}
-
-struct ContentView_Previews: PreviewProvider {
-    static var previews: some View {
-        ContentView()
-    }
-}
diff --git a/Taler/Views/PendingView.swift b/Taler/Views/PendingView.swift
deleted file mode 100644
index 40fecd0..0000000
--- a/Taler/Views/PendingView.swift
+++ /dev/null
@@ -1,68 +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 PendingView: View {
-    @ObservedObject var pendingManager: PendingManager
-    
-    var showSidebar: () -> Void
-    var body: some View {
-        NavigationView {
-            if pendingManager.items == nil {
-                ProgressView()
-                    .navigationTitle("Pending")
-                    .navigationBarItems(
-                        leading: Button(action: self.showSidebar, label: {
-                            Image(systemName: "line.3.horizontal")
-                        }))
-                    .onAppear {
-                        pendingManager.update()
-                    }
-            } else if pendingManager.loading {
-                ProgressView()
-                    .navigationTitle("Pending")
-                    .navigationBarItems(
-                        leading: Button(action: self.showSidebar, label: {
-                            Image(systemName: "line.3.horizontal")
-                        }))
-            } else {
-                let items = pendingManager.items!
-                List(items, id: \.self) { item in
-                    VStack {
-                        Text(item)
-                            .font(.system(size: 14, design: .monospaced))
-                    }
-                }
-                    .navigationTitle("Pending")
-                    .navigationBarItems(
-                        leading: Button(action: self.showSidebar, label: {
-                            Image(systemName: "line.3.horizontal")
-                        }),
-                        trailing: Button(action: {
-                            pendingManager.update()
-                        }, label: {
-                            Image(systemName: "arrow.clockwise")
-                        }))
-            }
-        }
-    }
-    
-    init(_showSidebar: @escaping () -> Void, pending: PendingManager) {
-        self.showSidebar = _showSidebar
-        self.pendingManager = pending
-    }
-}
diff --git a/Taler/Views/SettingsView.swift b/Taler/Views/SettingsView.swift
deleted file mode 100644
index a86778f..0000000
--- a/Taler/Views/SettingsView.swift
+++ /dev/null
@@ -1,252 +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 TextInputPopup: ViewModifier {
-    @State var exchangeUrl: String = "https://";
-    var onCancel: () -> Void
-    var onOk: (String) -> Void
-    
-    init(cancel: @escaping () -> Void, ok: @escaping (String) -> Void) {
-        self.onCancel = cancel
-        self.onOk = ok
-    }
-    
-    func body(content: Content) -> some View {
-        return content
-            .overlay(
-                VStack {
-                    Text("Add Exchange")
-                    TextField("Exchange URL", text: $exchangeUrl)
-                    HStack {
-                        Button {
-                            self.onCancel()
-                        } label: {
-                            Text("Cancel")
-                        }
-                        Button {
-                            self.onOk(exchangeUrl)
-                        } label: {
-                            Text("Ok")
-                        }
-                    }
-                }
-                    .padding(8)
-                    .frame(width: UIScreen.main.bounds.width - 100, height: 
150, alignment: .center)
-                    .background(Color.green)
-                    .cornerRadius(8)
-            , alignment: .center)
-            .animation(.easeIn)
-    }
-}
-
-extension View {
-    func textInputPopup(cancel: @escaping () -> Void, ok: @escaping (String) 
-> Void, showing: Bool) -> some View {
-        if showing {
-            return AnyView(modifier(TextInputPopup(cancel: cancel, ok: ok)))
-        } else {
-            return AnyView(self)
-        }
-    }
-}
-
-struct ExchangeListView: View {
-    @ObservedObject var exchangeManager: ExchangeManager
-    @State var showPopup: Bool = false
-    
-    var body: some View {
-        if exchangeManager.exchanges == nil {
-            ProgressView()
-                .navigationTitle("Exchanges")
-                .onAppear {
-                    exchangeManager.updateList()
-                }
-        } else if exchangeManager.loading {
-            ProgressView()
-                .navigationTitle("Exchanges")
-        } else {
-            let exchanges = exchangeManager.exchanges!
-            if exchanges.count == 0 {
-                Text("No Exchanges")
-                    .navigationTitle("Exchanges")
-                    .navigationBarItems(trailing: Button(action: {
-                        withAnimation {
-                            showPopup = true
-                        }
-                    }, label: {
-                        Image(systemName: "plus")
-                    }))
-                    .textInputPopup(cancel: {
-                        self.showPopup = false
-                    }, ok: { exchangeUrl in
-                        self.showPopup = false
-                        exchangeManager.add(url: exchangeUrl)
-                        print(exchangeUrl)
-                    }, showing: showPopup)
-            } else {
-                List(exchanges, id: \.self) { exchange in
-                    VStack {
-                        Text(exchange.exchangeBaseUrl)
-                            .frame(maxWidth: .infinity)
-                        Text("Currency: " + exchange.currency)
-                            .frame(maxWidth: .infinity)
-                        NavigationLink {
-                            WithdrawView(model: 
exchangeManager.withdraw(exchange: exchange))
-                        } label: {
-                            Text("Withdraw")
-                        }
-                    }
-                }
-                    .navigationTitle("Exchanges")
-                    .navigationBarItems(trailing: Button(action: {
-                        withAnimation {
-                            showPopup = true
-                        }
-                    }, label: {
-                        Image(systemName: "plus")
-                    }))
-                    .textInputPopup(cancel: {
-                        self.showPopup = false
-                    }, ok: { exchangeUrl in
-                        self.showPopup = false
-                        exchangeManager.add(url: exchangeUrl)
-                        print(exchangeUrl)
-                    }, showing: showPopup)
-            }
-        }
-    }
-}
-
-/*
- * Exchanges
- * Manage list of exchanges known to this wallet
- *
- * Backup
- * Last backup: 5 hr. ago
- *
- * Developer Mode [toggle]
- * Shows more information intended for debugging
- *
- * Withdraw TESTKUDOS
- * Get money for testing
- *
- * Debug log
- * View/send internal log
- *
- * App Version
- * v0.9.0-dev.11 (fdroid 11)
- *
- * Wallet Core Version
- * v0.9.0-dev.11
- *
- * Supported Exchange Versions
- * 12:0:0
- *
- * Supported Merchant Versions
- * 2:0:1
- *
- * Reset Wallet (dangerous!)
- * Throws away your money
- */
-
-struct SettingsItem<Content: View>: View {
-    var name: String
-    var description: String?
-    var content: () -> Content
-    
-    init(name: String, description: String? = nil, @ViewBuilder content: 
@escaping () -> Content) {
-        self.name = name
-        self.description = description
-        self.content = content
-    }
-    
-    var body: some View {
-        HStack {
-            Image(systemName: "line.3.horizontal")
-            VStack {
-                Text(name)
-                    .frame(maxWidth: .infinity, alignment: .leading)
-                    .font(.title2)
-                if let desc = description {
-                    Text(desc)
-                        .frame(maxWidth: .infinity, alignment: .leading)
-                        .font(.caption)
-                }
-            }
-            content()
-        }
-            .padding([.bottom], 8)
-    }
-}
-
-struct SettingsView: View {
-    @EnvironmentObject var backend: BackendManager
-    @AppStorage("developerMode") var developerMode: Bool = false
-    
-    var showSidebar: () -> Void
-    var body: some View {
-        NavigationView {
-            VStack {
-                SettingsItem(name: "Exchanges", description: "Manage list of 
exchanges known to this wallet") {
-                    NavigationLink {
-                        ExchangeListView(exchangeManager: 
backend.exchangeManager)
-                    } label: {
-                        Text("View")
-                    }
-                }
-                SettingsItem(name: "Developer Mode", description: "Shows more 
information intended for debugging") {
-                    Toggle(isOn: $developerMode) { }
-                }
-                if developerMode {
-                    SettingsItem(name: "App Version") {
-                        Text("v0.9.0-dev.11")
-                    }
-                    SettingsItem(name: "Wallet Core Version") {
-                        Text("v0.9.0-dev.11")
-                    }
-                    SettingsItem(name: "Supported Exchange Versions") {
-                        Text("12:0:0")
-                    }
-                    SettingsItem(name: "Supported Merchant Versions") {
-                        Text("2:0:1")
-                    }
-                }
-                Spacer()
-            }
-                .padding(16)
-                .navigationTitle("Settings")
-                .navigationBarItems(
-                    leading: Button(action: self.showSidebar, label: {
-                        Image(systemName: "line.3.horizontal")
-                    })
-                )
-        }
-    }
-    
-    init(_showSidebar: @escaping () -> Void) {
-        self.showSidebar = _showSidebar
-    }
-}
-
-struct SettingsView_Previews: PreviewProvider {
-    static var previews: some View {
-        SettingsView {
-            
-        }
-    }
-}
diff --git a/Taler/Views/WithdrawView.swift b/Taler/Views/WithdrawView.swift
deleted file mode 100644
index 946249e..0000000
--- a/Taler/Views/WithdrawView.swift
+++ /dev/null
@@ -1,142 +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 TransferView: View {
-    @ObservedObject var model: ManualTransferModel
-    
-    var body: some View {
-        VStack {
-            Text("Exchange is ready for withdrawal!")
-            Text("To complete the process you need to wire 
\(self.model.details.amountRaw.readableDescription) to the exchange bank 
account.")
-            HStack {
-                Text("IBAN: ")
-                Text(paytoUriGetIban(uri: self.model.paytoUri))
-            }
-            HStack {
-                Text("Subject: ")
-                Text(paytoUriGetSubject(uri: self.model.paytoUri))
-            }
-            HStack {
-                Text("Chosen Amount: ")
-                Text(self.model.details.amountRaw.readableDescription)
-            }
-            HStack {
-                Text("Exchange: ")
-                Text(self.model.exchange.exchangeBaseUrl)
-            }
-            Text("Make sure to use the correct subject, otherwise the money 
will not arrive in this wallet.")
-        }
-            .navigationTitle("Manual Transfer")
-    }
-}
-
-struct PromptWithdrawView: View {
-    @ObservedObject var model: PromptWithdrawModel
-    
-    var body: some View {
-        VStack {
-            NavigationLink("", isActive: $model.nav) {
-                TransferView(model: model.manualTransferModel)
-                    .onDisappear {
-                        self.model.nav = false
-                    }
-            }
-            if model.loading {
-                ProgressView()
-            } else {
-                if model.tosAccepted {
-                    Text("Withdraw")
-                    
Text(self.model.details.amountEffective.readableDescription)
-                    Text("Chosen Amount")
-                    Text(self.model.details.amountRaw.readableDescription)
-                    Text("Fee")
-                    Text("- \((try! self.model.details.amountRaw - 
self.model.details.amountEffective).readableDescription)")
-                    Text("Exchange")
-                    Text(model.exchange.name)
-                    Button {
-                        self.model.acceptWithdraw()
-                    } label: {
-                        Text("Confirm Withdraw")
-                    }
-                } else {
-                    Text("Withdraw")
-                    
Text(self.model.details.amountEffective.readableDescription)
-                    Text("Chosen Amount")
-                    Text(self.model.details.amountRaw.readableDescription)
-                    Text("Fee")
-                    Text("- \((try! self.model.details.amountRaw - 
self.model.details.amountEffective).readableDescription)")
-                    Text("Exchange")
-                    Text(model.exchange.name)
-                    NavigationLink(isActive: $model.navTos) {
-                        VStack {
-                            ScrollView {
-                                Text(model.tosDetails!.content)
-                            }
-                            Button {
-                                model.acceptTos()
-                            } label: {
-                                Text("Accept Terms of Service")
-                            }
-                        }
-                            .navigationTitle("Review Terms of Service")
-                    } label: {
-                        Text("Review Terms")
-                    }
-                }
-            }
-        }
-            .navigationTitle("Review Withdraw")
-    }
-}
-
-struct WithdrawView: View {
-    @ObservedObject var model: WithdrawModel
-    @State var amount: String = ""
-    
-    var body: some View {
-        VStack {
-            NavigationLink("", isActive: $model.nav) {
-                PromptWithdrawView(model: model.promptModel)
-                    .onDisappear {
-                        self.model.nav = false
-                    }
-            }
-            if self.model.loading {
-                ProgressView()
-            } else {
-                Button {
-                    
-                } label: {
-                    Text("Scan Taler QR Code")
-                }
-                Text("Or transfer manually:")
-                HStack {
-                    TextField(model.exchange.currency, text: $amount)
-                }
-                Button {
-                    // TODO: Handle when the user inputs a non-valid amount
-                    model.getWithdrawDetails(amountStr: 
model.exchange.currency + ":" + amount)
-                } label: {
-                    Text("Check Fees")
-                }
-            }
-        }
-            .navigationTitle("Withdraw")
-    }
-}
diff --git a/Taler/WalletBackend.swift b/Taler/WalletBackend.swift
deleted file mode 100644
index e6df597..0000000
--- a/Taler/WalletBackend.swift
+++ /dev/null
@@ -1,1167 +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 iono
-import taler_swift
-import AnyCodable
-
-/// Information supplied by the backend describing an error.
-struct WalletBackendResponseError: Decodable {
-    /// Numeric error code defined defined in the GANA gnu-taler-error-codes 
registry.
-    var talerErrorCode: Int
-    
-    /// English description of the error code.
-    var talerErrorHint: String
-    
-    /// English diagnostic message that can give details for the instance of 
the error.
-    var message: String
-    
-    /// Error details, type depends on `talerErrorCode`.
-    var details: Data?
-}
-
-/// A request sent to the wallet backend.
-struct WalletBackendRequest: Encodable {
-    /// The operation name of the request.
-    var operation: String
-    
-    /// The body of the request as JSON.
-    var args: AnyEncodable
-}
-
-protocol WalletBackendFormattedRequest {
-    associatedtype Args: Encodable
-    associatedtype Response: Decodable
-    
-    func operation() -> String
-    func args() -> Args
-}
-
-fileprivate struct WalletBackendInitRequest: WalletBackendFormattedRequest {
-    var persistentStoragePath: String
-    
-    struct Args: Encodable {
-        var persistentStoragePath: String
-    }
-    
-    struct Response: Codable {
-        struct SupportedProtocolVersions: Codable {
-            var exchange: String
-            var merchant: String
-        }
-        var supportedProtocolVersions: SupportedProtocolVersions
-        enum CodingKeys: String, CodingKey {
-            case supportedProtocolVersions = "supported_protocol_versions"
-        }
-    }
-    
-    func operation() -> String {
-        return "init"
-    }
-    
-    func args() -> Args {
-        return Args(persistentStoragePath: persistentStoragePath)
-    }
-}
-
-/// An balance on a wallet.
-struct Balance: Decodable {
-    var available: Amount
-    var pendingIncoming: Amount
-    var pendingOutgoing: Amount
-    var requiresUserInput: Bool
-}
-
-/// A request to get the balances held in the wallet.
-struct WalletBackendGetBalancesRequest: WalletBackendFormattedRequest {
-    struct Args: Encodable {
-        
-    }
-    
-    struct Response: Decodable {
-        var balances: [Balance]
-    }
-    
-    func operation() -> String {
-        return "getBalances"
-    }
-    
-    func args() -> Args {
-        return Args()
-    }
-}
-
-/// A billing or mailing location.
-struct Location: Codable {
-    var country: String?
-    var country_subdivision: String?
-    var district: String?
-    var town: String?
-    var town_location: String?
-    var post_code: String?
-    var street: String?
-    var building_name: String?
-    var building_number: String?
-    var address_lines: [String]?
-}
-
-/// Information identifying a merchant.
-struct Merchant: Codable {
-    var name: String
-    var address: Location?
-    var jurisdiction: Location?
-}
-
-/// A tax made on a payment.
-struct Tax: Codable {
-    var name: String
-    var tax: Amount
-}
-
-/// A product being purchased from a merchant.
-struct Product: Codable {
-    var product_id: String?
-    var description: String
-    // description_i18n?
-    var quantity: Int
-    var unit: String
-    var price: Amount?
-    var image: String // URL to a product image
-    var taxes: [Tax]?
-    var delivery_date: Timestamp?
-}
-
-/// Brief information about an order.
-struct OrderShortInfo: Codable {
-    var orderId: String
-    var merchant: Merchant
-    var summary: String
-    // summary_i18n?
-    var products: [Product]
-    var fulfillmentUrl: String?
-    var fulfillmentMessage: String?
-    // fulfillmentMessage_i18n?
-}
-
-enum TransactionTypeError: Error {
-    case unknownTypeError
-}
-
-/// Different types of transactions.
-enum TransactionType: Codable {
-    case withdrawal
-    case payment
-    case refund
-    case tip
-    case refresh
-    
-    init(from decoder: Decoder) throws {
-        let value = try decoder.singleValueContainer()
-        let str = try value.decode(String.self)
-        let codingNames = [
-            "TransactionWithdrawal" : TransactionType.withdrawal,
-            "TransactionPayment" : TransactionType.payment,
-            "TransactionRefund" : TransactionType.refund,
-            "TransactionTip" : TransactionType.tip,
-            "TransactionRefresh" : TransactionType.refresh
-        ]
-        if let type = codingNames[str] {
-            self = type
-        } else {
-            throw TransactionTypeError.unknownTypeError
-        }
-    }
-    
-    func encode(to encoder: Encoder) throws {
-        var value = encoder.singleValueContainer()
-        switch self {
-        case .withdrawal:
-            try value.encode("TransactionWithdrawal")
-        case .payment:
-            try value.encode("TransactionPayment")
-        case .refund:
-            try value.encode("TransactionRefund")
-        case .tip:
-            try value.encode("TransactionTip")
-        case .refresh:
-            try value.encode("TransactionRefresh")
-        }
-    }
-}
-
-enum TransactionDecodingError: Error {
-    case invalidStringValue
-}
-
-/// Details for a manual withdrawal.
-struct ManualWithdrawalDetails: Codable {
-    /// The payto URIs that the exchange supports.
-    var exchangePaytoUris: [String]
-    
-    /// The public key of the newly created reserve.
-    var reservePub: String
-}
-
-/// Details for a bank-integrated withdrawal.
-struct BankIntegratedWithdrawalDetails: Codable {
-    /// Whether the bank has confirmed the withdrawal.
-    var confirmed: Bool
-    
-    /// URL for user-initiated confirmation
-    var bankConfirmationUrl: String?
-}
-
-/// A withdrawal transaction.
-struct TransactionWithdrawal: Decodable {
-    enum WithdrawalDetails {
-        case manual(ManualWithdrawalDetails)
-        case bankIntegrated(BankIntegratedWithdrawalDetails)
-    }
-    
-    /// The exchange that was withdrawn from.
-    var exchangeBaseUrl: String
-    
-    /// The amount of the withdrawal, including fees.
-    var amountRaw: Amount
-    
-    /// The amount that will be added to the withdrawer's account.
-    var amountEffective: Amount
-    
-    /// The details of the withdrawal.
-    var withdrawalDetails: WithdrawalDetails
-    
-    init(from decoder: Decoder) throws {
-        enum CodingKeys: String, CodingKey {
-            case exchangeBaseUrl
-            case amountRaw
-            case amountEffective
-            case withdrawalDetails
-            case type
-            case exchangePaytoUris
-            case reservePub
-            case confirmed
-            case bankConfirmationUrl
-        }
-        
-        let value = try decoder.container(keyedBy: CodingKeys.self)
-        self.exchangeBaseUrl = try value.decode(String.self, forKey: 
.exchangeBaseUrl)
-        self.amountRaw = try value.decode(Amount.self, forKey: .amountRaw)
-        self.amountEffective = try value.decode(Amount.self, forKey: 
.amountEffective)
-        
-        let detail = try value.nestedContainer(keyedBy: CodingKeys.self, 
forKey: .withdrawalDetails)
-        let detailType = try detail.decode(String.self, forKey: .type)
-        if detailType == "manual-transfer" {
-            let paytoUris = try detail.decode([String].self, forKey: 
.exchangePaytoUris)
-            let reservePub = try detail.decode(String.self, forKey: 
.reservePub)
-            let manual = ManualWithdrawalDetails(exchangePaytoUris: paytoUris, 
reservePub: reservePub)
-            self.withdrawalDetails = .manual(manual)
-        } else if detailType == "taler-bank-integration-api" {
-            let confirmed = try detail.decode(Bool.self, forKey: .confirmed)
-            var bankConfirmationUrl: String? = nil
-            if detail.contains(.bankConfirmationUrl) {
-                bankConfirmationUrl = try detail.decode(String.self, forKey: 
.bankConfirmationUrl)
-            }
-            let bankDetails = BankIntegratedWithdrawalDetails(confirmed: 
confirmed, bankConfirmationUrl: bankConfirmationUrl)
-            self.withdrawalDetails = .bankIntegrated(bankDetails)
-        } else {
-            throw TransactionDecodingError.invalidStringValue
-        }
-    }
-}
-
-/// A payment transaction.
-struct TransactionPayment: Codable {
-    /// Additional information about the payment.
-    // TODO
-    
-    /// An identifier for the payment.
-    var proposalId: String
-    
-    /// The current status of the payment.
-    // TODO
-    
-    /// The amount that must be paid.
-    var amountRaw: Amount
-    
-    /// The amount that was paid.
-    var amountEffective: Amount
-}
-
-/// A refund transaction.
-struct TransactionRefund: Codable {
-    /// Identifier for the refund.
-    var refundedTransactionId: String
-    
-    /// Additional information about the refund
-    // TODO
-    
-    /// The amount that couldn't be applied because refund permissions expired.
-    var amountInvalid: Amount
-    
-    /// The amount refunded by the merchant.
-    var amountRaw: Amount
-    
-    /// The amount paid to the wallet after fees.
-    var amountEffective: Amount
-}
-
-/// A tip transaction.
-struct TransactionTip: Codable {
-    /// The current status of the tip.
-    // TODO
-    
-    /// The exchange that the tip will be withdrawn from
-    var exchangeBaseUrl: String
-    
-    /// More information about the merchant sending the tip.
-    // TODO
-    
-    /// The raw amount of the tip without fees.
-    var amountRaw: Amount
-    
-    /// The amount added to the recipient's wallet.
-    var amountEffective: Amount
-}
-
-/// A refresh transaction.
-struct TransactionRefresh: Codable {
-    /// The exchange that the coins are refreshed with.
-    var exchangeBaseUrl: String
-    
-    /// The raw amount to refresh.
-    var amountRaw: Amount
-    
-    /// The amount to be paid as fees for the refresh.
-    var amountEffective: Amount
-}
-
-/// A wallet transaction.
-struct Transaction: Decodable, Hashable {
-    enum TransactionDetail {
-        case withdrawal(TransactionWithdrawal)
-    }
-    
-    var transactionId: String
-    var timestamp: Timestamp
-    var pending: Bool
-    var error: AnyCodable?
-    var amountRaw: Amount
-    var amountEffective: Amount
-    var detail: TransactionDetail
-    
-    init(from decoder: Decoder) throws {
-        enum CodingKeys: String, CodingKey {
-            case transactionId
-            case timestamp
-            case pending
-            case error
-            case amountRaw
-            case amountEffective
-            case type
-        }
-        
-        let value = try decoder.container(keyedBy: CodingKeys.self)
-        self.transactionId = try value.decode(String.self, forKey: 
.transactionId)
-        self.timestamp = try value.decode(Timestamp.self, forKey: .timestamp)
-        self.pending = try value.decode(Bool.self, forKey: .pending)
-        if value.contains(.error) {
-            self.error = try value.decode(AnyCodable.self, forKey: .error)
-        }
-        self.amountRaw = try value.decode(Amount.self, forKey: .amountRaw)
-        self.amountEffective = try value.decode(Amount.self, forKey: 
.amountEffective)
-        
-        let type = try value.decode(String.self, forKey: .type)
-        if type == "withdrawal" {
-            let withdrawDetail = try TransactionWithdrawal.init(from: decoder)
-            self.detail = .withdrawal(withdrawDetail)
-        } else {
-            throw TransactionDecodingError.invalidStringValue
-        }
-    }
-    
-    static func == (lhs: Transaction, rhs: Transaction) -> Bool {
-        return lhs.transactionId == rhs.transactionId
-    }
-    
-    func hash(into hasher: inout Hasher) {
-        transactionId.hash(into: &hasher)
-    }
-}
-
-/// A request to get the transactions in the wallet's history.
-struct WalletBackendGetTransactionsRequest: WalletBackendFormattedRequest {
-    var currency: String?
-    var search: String?
-    
-    struct Args: Encodable {
-        var currency: String?
-        var search: String?
-    }
-    
-    struct Response: Decodable {
-        var transactions: [Transaction]
-    }
-    
-    func operation() -> String {
-        return "getTransactions"
-    }
-    
-    func args() -> Args {
-        return Args(currency: currency, search: search)
-    }
-}
-
-/// 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)
-    }
-}
-
-/// A request to process a refund.
-struct WalletBackendApplyRefundRequest: WalletBackendFormattedRequest {
-    var talerRefundUri: String
-    
-    struct Args: Encodable {
-        var talerRefundUri: String
-    }
-    
-    struct Response: Decodable {
-        var contractTermsHash: String
-        var amountEffectivePaid: Amount
-        var amountRefundGranted: Amount
-        var amountRefundGone: Amount
-        var pendingAtExchange: Bool
-        var info: OrderShortInfo
-    }
-    
-    func operation() -> String {
-        return "applyRefund"
-    }
-    
-    func args() -> Args {
-        return Args(talerRefundUri: talerRefundUri)
-    }
-}
-
-/// A request to list exchanges.
-struct WalletBackendListExchanges: WalletBackendFormattedRequest {
-    struct Args: Encodable {
-        
-    }
-    
-    struct ExchangeListItem: Decodable, Hashable {
-        var exchangeBaseUrl: String
-        var currency: String
-        var paytoUris: [String]
-        
-        var name: String {
-            let url = URL(string: exchangeBaseUrl)!
-            return url.host!
-        }
-    }
-    
-    struct Response: Decodable {
-        var exchanges: [ExchangeListItem]
-    }
-    
-    func operation() -> String {
-        return "listExchanges"
-    }
-    
-    func args() -> Args {
-        return Args()
-    }
-}
-
-/// A request to add an exchange.
-struct WalletBackendAddExchangeRequest: WalletBackendFormattedRequest {
-    var exchangeBaseUrl: String
-    
-    struct Args: Encodable {
-        var exchangeBaseUrl: String
-    }
-    
-    struct Response: Decodable {
-        
-    }
-    
-    func operation() -> String {
-        return "addExchange"
-    }
-    
-    func args() -> Args {
-        return Args(exchangeBaseUrl: exchangeBaseUrl)
-    }
-}
-
-/// A request to force update an exchange.
-struct WalletBackendForceUpdateRequest: WalletBackendFormattedRequest {
-    var exchangeBaseUrl: String
-    
-    struct Args: Encodable {
-        var exchangeBaseUrl: String
-    }
-    
-    struct Response: Decodable {
-        
-    }
-    
-    func operation() -> String {
-        return "addRequest"
-    }
-    
-    func args() -> Args {
-        return Args(exchangeBaseUrl: exchangeBaseUrl)
-    }
-}
-
-/// A request to query an exchange's terms of service.
-struct WalletBackendGetExchangeTermsOfService: WalletBackendFormattedRequest {
-    var exchangeBaseUrl: String
-    
-    struct Args: Encodable {
-        var exchangeBaseUrl: String
-    }
-    
-    struct Response: Decodable {
-        var content: String
-        var currentEtag: String
-        var acceptedEtag: String?
-    }
-    
-    func operation() -> String {
-        return "getExchangeTos"
-    }
-    
-    func args() -> Args {
-        return Args(exchangeBaseUrl: exchangeBaseUrl)
-    }
-}
-
-/// A request to mark an exchange's terms of service as accepted.
-struct WalletBackendSetExchangeTermsOfServiceAccepted: 
WalletBackendFormattedRequest {
-    var exchangeBaseUrl: String
-    var etag: String
-    
-    struct Args: Encodable {
-        var exchangeBaseUrl: String
-        var etag: String
-    }
-    
-    struct Response: Decodable {
-        
-    }
-    
-    func operation() -> String {
-        return "setExchangeTosAccepted"
-    }
-    
-    func args() -> Args {
-        return Args(exchangeBaseUrl: exchangeBaseUrl, etag: etag)
-    }
-}
-
-struct ExchangeListItem: Codable {
-    var exchangeBaseUrl: String
-    var currency: String
-    var paytoUris: [String]
-}
-
-/// A request to get an exchange's withdrawal details.
-struct WalletBackendGetWithdrawalDetailsForURIRequest: 
WalletBackendFormattedRequest {
-    var talerWithdrawUri: String
-    
-    struct Args: Encodable {
-        var talerWithdrawUri: String
-    }
-    
-    struct Response: Decodable {
-        var amount: Amount
-        var defaultExchangeBaseUrl: String?
-        var possibleExchanges: [ExchangeListItem]
-    }
-    
-    func operation() -> String {
-        return "getWithdrawalDetailsForUri"
-    }
-    
-    func args() -> Args {
-        return Args(talerWithdrawUri: talerWithdrawUri)
-    }
-}
-
-/// A request to get an exchange's withdrawal details.
-struct WalletBackendGetWithdrawalDetailsForAmountRequest: 
WalletBackendFormattedRequest {
-    var exchangeBaseUrl: String
-    var amount: Amount
-    
-    struct Args: Encodable {
-        var exchangeBaseUrl: String
-        var amount: Amount
-    }
-    
-    struct Response: Decodable {
-        var tosAccepted: Bool
-        var amountRaw: Amount
-        var amountEffective: Amount
-    }
-    
-    func operation() -> String {
-        return "getWithdrawalDetailsForAmount"
-    }
-    
-    func args() -> Args {
-        return Args(exchangeBaseUrl: exchangeBaseUrl, amount: amount)
-    }
-}
-
-/// 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
-    var amount: Amount
-    
-    struct Args: Encodable {
-        var depositPayToUri: String
-        var amount: Amount
-    }
-    
-    struct Response: Decodable {
-        var depositGroupId: String
-    }
-    
-    func operation() -> String {
-        return "createDepositGroup"
-    }
-    
-    func args() -> Args {
-        return Args(depositPayToUri: depositePayToUri, amount: amount)
-    }
-}
-
-/// A request to get information about a payment request.
-struct WalletBackendPreparePayRequest: WalletBackendFormattedRequest {
-    var talerPayUri: String
-    
-    struct Args: Encodable {
-        var talerPayUri: String
-    }
-    
-    struct Response: Decodable {
-        
-    }
-    
-    func operation() -> String {
-        return "preparePay"
-    }
-    
-    func args() -> Args {
-        return Args(talerPayUri: talerPayUri)
-    }
-}
-
-/// A request to confirm a payment.
-struct WalletBackendConfirmPayRequest: WalletBackendFormattedRequest {
-    var proposalId: String
-    
-    struct Args: Encodable {
-        var proposalId: String
-    }
-    
-    struct Response: Decodable {
-        
-    }
-    
-    func operation() -> String {
-        return "abortFailedPayWithRefund"
-    }
-    
-    func args() -> Args {
-        return Args(proposalId: proposalId)
-    }
-}
-
-/// 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)
-    }
-}
-
-/// A request to accept a tip.
-struct WalletBackendAcceptTipRequest: WalletBackendFormattedRequest {
-    var walletTipId: String
-    
-    struct Args: Encodable {
-        var walletTipId: String
-    }
-    
-    struct Response: Decodable {
-        
-    }
-    
-    func operation() -> String {
-        return "acceptTip"
-    }
-    
-    func args() -> Args {
-        return Args(walletTipId: walletTipId)
-    }
-}
-
-/// A request to abort a failed payment.
-struct WalletBackendAbortFailedPaymentRequest: WalletBackendFormattedRequest {
-    var proposalId: String
-    
-    struct Args: Encodable {
-        var proposalId: String
-    }
-    
-    struct Response: Decodable {
-        
-    }
-    
-    func operation() -> String {
-        return "confirmPay"
-    }
-    
-    func args() -> Args {
-        return Args(proposalId: proposalId)
-    }
-}
-
-/// A request to withdraw a balance from the TESTKUDOS environment.
-struct WalletBackendWithdrawTestkudosRequest: WalletBackendFormattedRequest {
-    struct Args: Encodable {
-        
-    }
-    
-    struct Response: Decodable {
-        
-    }
-    
-    func operation() -> String {
-        return "withdrawTestkudos"
-    }
-    
-    func args() -> Args {
-        return Args()
-    }
-}
-
-/// A request to add a test balance to the wallet.
-struct WalletBackendWithdrawTestBalance: WalletBackendFormattedRequest {
-    var amount: Amount
-    var bankBaseUrl: String
-    var exchangeBaseUrl: String
-    
-    struct Args: Encodable {
-        var amount: Amount
-        var bankBaseUrl: String
-        var exchangeBaseUrl: String
-    }
-    typealias Response = String
-    
-    func operation() -> String {
-        return "withdrawTestBalance"
-    }
-    
-    func args() -> Args {
-        return Args(amount: amount, bankBaseUrl: bankBaseUrl, exchangeBaseUrl: 
exchangeBaseUrl)
-    }
-}
-
-struct IntegrationTestArgs: Codable {
-    var exchangeBaseUrl: String
-    var bankBaseUrl: String
-    var merchantBaseUrl: String
-    var merchantApiKey: String
-    var amountToWithdraw: String
-    var amountToSpend: String
-}
-
-/// A request to run a basic integration test.
-struct WalletBackendRunIntegrationTestRequest: WalletBackendFormattedRequest {
-    var integrationTestArgs: IntegrationTestArgs
-    
-    typealias Args = IntegrationTestArgs
-    
-    struct Response: Decodable {
-        
-    }
-    
-    func operation() -> String {
-        return "runIntegrationTest"
-    }
-    
-    func args() -> Args {
-        return integrationTestArgs
-    }
-}
-
-struct TestPayArgs: Codable {
-    var merchantBaseUrl: String
-    var merchantApiKey: String
-    var amount: String
-    var summary: String
-}
-
-/// A request to make a test payment.
-struct WalletBackendTestPayRequest: WalletBackendFormattedRequest {
-    var testPayArgs: TestPayArgs
-    
-    typealias Args = TestPayArgs
-    
-    struct Response: Decodable {
-        
-    }
-    
-    func operation() -> String {
-        return "testPay"
-    }
-    
-    func args() -> Args {
-        return testPayArgs
-    }
-}
-
-struct Coin: Codable {
-    var denom_pub: String
-    var denom_pub_hash: String
-    var denom_value: String
-    var coin_pub: String
-    var exchange_base_url: String
-    var remaining_value: String
-    var refresh_parent_coin_pub: String
-    var withdrawal_reserve_pub: String
-    var coin_suspended: Bool
-}
-
-/// A request to dump all coins to JSON.
-struct WalletBackendDumpCoinsRequest: WalletBackendFormattedRequest {
-    struct Args: Encodable {
-        
-    }
-    
-    struct Response: Decodable {
-        var coins: [Coin]
-    }
-    
-    func operation() -> String {
-        return "dumpCoins"
-    }
-    
-    func args() -> Args {
-        return Args()
-    }
-}
-
-/// A request to suspend or unsuspend a coin.
-struct WalletBackendSuspendCoinRequest: WalletBackendFormattedRequest {
-    var coinPub: String
-    var suspended: Bool
-    
-    struct Args: Encodable {
-        var coinPub: String
-        var suspended: Bool
-    }
-    
-    struct Response: Decodable {
-        
-    }
-    
-    func operation() -> String {
-        return "setCoinSuspended"
-    }
-    
-    func args() -> Args {
-        return Args(coinPub: coinPub, suspended: suspended)
-    }
-}
-
-typealias PendingOperation = AnyCodable
-
-/// A request to list the backend's currently pending operations.
-struct WalletBackendPendingRequest: WalletBackendFormattedRequest {
-    struct Args: Encodable {
-        
-    }
-    
-    struct Response: Decodable {
-        var pendingOperations: [PendingOperation]
-    }
-    
-    func operation() -> String {
-        return "getPendingOperations"
-    }
-    
-    func args() -> Args {
-        Args()
-    }
-}
-
-/// Errors for `WalletBackend`.
-enum WalletBackendError: Error {
-    /// An error that prevented the wallet from being initialized occurred.
-    case initializationError
-    case serializationError
-    case deserializationError
-}
-
-/// Delegate for the wallet backend.
-protocol WalletBackendDelegate {
-    /// Called when the backend interface receives a message it does not know 
how to handle.
-    func walletBackendReceivedUnknownMessage(_ walletBackend: WalletBackend, 
message: String)
-}
-
-/// An interface to the wallet backend.
-class WalletBackend: IonoMessageHandler {
-    private var iono: Iono
-    private var requestsMade: UInt
-    private var backendReady: Bool
-    private var backendReadyCondition: NSCondition
-    private var requests: [UInt : (AnyCodable?, WalletBackendResponseError?) 
-> Void] = [:]
-    var delegate: WalletBackendDelegate?
-    
-    private struct FullRequest: Encodable {
-        let operation: String
-        let id: UInt
-        let args: AnyEncodable
-    }
-    
-    private struct FullResponse: Decodable {
-        let type: String
-        let operation: String
-        let id: UInt
-        let result: AnyCodable
-    }
-    
-    private struct FullError: Decodable {
-        let type: String
-        let operation: String
-        let id: UInt
-        let error: WalletBackendResponseError
-    }
-    
-    init() throws {
-        iono = Iono()
-        requestsMade = 0
-        self.backendReady = false
-        self.backendReadyCondition = NSCondition()
-        
-        iono.messageHandler = self
-        
-        let js_path = URL(fileURLWithPath: Bundle.main.path(forResource: 
"taler-wallet-embedded", ofType: "js")!)
-        do {
-            let js = try String(contentsOf: js_path, encoding: .utf8)
-            iono.putModuleCode(modName: "@gnu-taler/taler-wallet-embedded", 
code: js)
-            iono.evalNodeCode(source: "require('iono');")
-            iono.evalNodeCode(source: "tw = 
require('@gnu-taler/taler-wallet-embedded');")
-            iono.evalNodeCode(source: "tw.installNativeWalletListener();")
-        } catch {
-            throw WalletBackendError.initializationError
-        }
-        
-        // Send the init message
-        let documentUrls = FileManager.default.urls(for: .documentDirectory, 
in: .userDomainMask)
-        if (documentUrls.count > 0) {
-            var storageDir = documentUrls[0]
-            storageDir.appendPathComponent("talerwalletdb-v30", isDirectory: 
false)
-            storageDir.appendPathExtension("json")
-            sendFormattedRequest(request: 
WalletBackendInitRequest(persistentStoragePath: storageDir.path), 
completionHandler: { (resp: WalletBackendInitRequest.Response?, err: 
WalletBackendResponseError?) in
-                self.backendReady = true
-                self.backendReadyCondition.broadcast()
-            })
-        }
-        
-        waitUntilReady()
-    }
-    
-    deinit {
-        iono.waitStopped()
-    }
-    
-    func waitUntilReady() {
-        backendReadyCondition.lock()
-        while (!self.backendReady) {
-            backendReadyCondition.wait()
-        }
-        backendReadyCondition.unlock()
-    }
-    
-    func handleMessage(message: String) {
-        print(message)
-        do {
-            guard let messageData = message.data(using: .utf8) else { throw 
WalletBackendError.deserializationError }
-            let data = try JSONSerialization.jsonObject(with: messageData, 
options: .allowFragments) as? [String : Any]
-            if let responseData = data {
-                let type = (responseData["type"] as? String) ?? ""
-                if type == "response" {
-                    guard let id = responseData["id"] as? UInt else { throw 
WalletBackendError.deserializationError }
-                    guard let request = requests[id] else { throw 
WalletBackendError.deserializationError }
-                    do {
-                        let decoded = try 
JSONDecoder().decode(FullResponse.self, from: messageData)
-                        request(decoded.result, nil)
-                    } catch {
-                        request(nil, WalletBackend.parseResponseError())
-                    }
-                    requests[id] = nil
-                } else if type == "tunnelHttp" {
-                    // TODO: Handle
-                } else if type == "notification" {
-                    // TODO: Handle
-                } else if type == "error" {
-                    guard let id = responseData["id"] as? UInt else { throw 
WalletBackendError.deserializationError }
-                    guard let request = requests[id] else { throw 
WalletBackendError.deserializationError }
-                    do {
-                        let decoded = try JSONDecoder().decode(FullError.self, 
from: messageData)
-                        request(nil, decoded.error)
-                    } catch {
-                        request(nil, WalletBackend.parseFailureError())
-                    }
-                    requests[id] = nil
-                } else {
-                    throw WalletBackendError.deserializationError
-                }
-            }
-        } catch {
-            self.delegate?.walletBackendReceivedUnknownMessage(self, message: 
message)
-        }
-    }
-    
-    func sendRequest(request: WalletBackendRequest, completionHandler: 
@escaping (AnyCodable?, WalletBackendResponseError?) -> Void) {
-        /* Encode the request and send it to the backend. */
-        do {
-            let full = FullRequest(operation: request.operation, id: 
requestsMade, args: request.args)
-            let encoded = try JSONEncoder().encode(full)
-            guard let jsonString = String(data: encoded, encoding: .utf8) else 
{ throw WalletBackendError.serializationError }
-            requests[full.id] = completionHandler
-            requestsMade += 1
-            iono.sendMessage(message: jsonString)
-        } catch {
-            completionHandler(nil, WalletBackend.serializeRequestError());
-        }
-    }
-    
-    func sendFormattedRequest<T: WalletBackendFormattedRequest>(request: T, 
completionHandler: @escaping (T.Response?, WalletBackendResponseError?) -> 
Void) {
-        let reqData = WalletBackendRequest(operation: request.operation(), 
args: AnyEncodable(request.args()))
-        sendRequest(request: reqData) { (result: AnyCodable?, err: 
WalletBackendResponseError?) in
-            if let res = result {
-                do {
-                    /* TODO: Don't use a hack (there is no reason to pass to 
JSON): */
-                    let jsonStr = try JSONEncoder().encode(res)
-                    let decoded = try JSONDecoder().decode(T.Response.self, 
from: jsonStr)
-                    completionHandler(decoded, err)
-                } catch {
-                    completionHandler(nil, WalletBackend.parseResponseError())
-                }
-            } else {
-                completionHandler(nil, err)
-            }
-        }
-    }
-    
-    static func serializeRequestError() -> WalletBackendResponseError {
-        return WalletBackendResponseError(talerErrorCode: -1, talerErrorHint: 
"Could not serialize request.", message: "")
-    }
-    
-    static func parseResponseError() -> WalletBackendResponseError {
-        return WalletBackendResponseError(talerErrorCode: -2, talerErrorHint: 
"Could not parse response.", message: "")
-    }
-    
-    static func parseFailureError() -> WalletBackendResponseError {
-        return WalletBackendResponseError(talerErrorCode: -3, talerErrorHint: 
"Could not parse error detail.", message: "")
-    }
-}
diff --git a/TalerTests/Info.plist b/TalerTests/Info.plist
deleted file mode 100644
index 64d65ca..0000000
--- a/TalerTests/Info.plist
+++ /dev/null
@@ -1,22 +0,0 @@
-<?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>CFBundleDevelopmentRegion</key>
-       <string>$(DEVELOPMENT_LANGUAGE)</string>
-       <key>CFBundleExecutable</key>
-       <string>$(EXECUTABLE_NAME)</string>
-       <key>CFBundleIdentifier</key>
-       <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
-       <key>CFBundleInfoDictionaryVersion</key>
-       <string>6.0</string>
-       <key>CFBundleName</key>
-       <string>$(PRODUCT_NAME)</string>
-       <key>CFBundlePackageType</key>
-       <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
-       <key>CFBundleShortVersionString</key>
-       <string>1.0</string>
-       <key>CFBundleVersion</key>
-       <string>1</string>
-</dict>
-</plist>
diff --git a/TalerTests/TalerWalletTests.swift 
b/TalerTests/TalerWalletTests.swift
new file mode 100644
index 0000000..9d10794
--- /dev/null
+++ b/TalerTests/TalerWalletTests.swift
@@ -0,0 +1,36 @@
+//
+//  TalerWalletTests.swift
+//  TalerWalletTests
+//
+//  Created by Marc Stibane on 31.01.23.
+//
+
+import XCTest
+@testable import TalerWallet
+
+final class TalerWalletTests: XCTestCase {
+
+    override func setUpWithError() throws {
+        // Put setup code here. This method is called before the invocation of 
each test method in the class.
+    }
+
+    override func tearDownWithError() throws {
+        // Put teardown code here. This method is called after the invocation 
of each test method in the class.
+    }
+
+    func testExample() throws {
+        // This is an example of a functional test case.
+        // Use XCTAssert and related functions to verify your tests produce 
the correct results.
+        // Any test you write for XCTest can be annotated as throws and async.
+        // Mark your test throws to produce an unexpected failure when your 
test encounters an uncaught error.
+        // Mark your test async to allow awaiting for asynchronous code to 
complete. Check the results with assertions afterwards.
+    }
+
+    func testPerformanceExample() throws {
+        // This is an example of a performance test case.
+        self.measure {
+            // Put the code you want to measure the time of here.
+        }
+    }
+
+}
diff --git a/TalerUITests/Info.plist b/TalerUITests/Info.plist
deleted file mode 100644
index 64d65ca..0000000
--- a/TalerUITests/Info.plist
+++ /dev/null
@@ -1,22 +0,0 @@
-<?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>CFBundleDevelopmentRegion</key>
-       <string>$(DEVELOPMENT_LANGUAGE)</string>
-       <key>CFBundleExecutable</key>
-       <string>$(EXECUTABLE_NAME)</string>
-       <key>CFBundleIdentifier</key>
-       <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
-       <key>CFBundleInfoDictionaryVersion</key>
-       <string>6.0</string>
-       <key>CFBundleName</key>
-       <string>$(PRODUCT_NAME)</string>
-       <key>CFBundlePackageType</key>
-       <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
-       <key>CFBundleShortVersionString</key>
-       <string>1.0</string>
-       <key>CFBundleVersion</key>
-       <string>1</string>
-</dict>
-</plist>
diff --git a/TalerUITests/TalerUITests.swift 
b/TalerUITests/TalerWalletUITests.swift
similarity index 57%
copy from TalerUITests/TalerUITests.swift
copy to TalerUITests/TalerWalletUITests.swift
index 24b772e..b6247dc 100644
--- a/TalerUITests/TalerUITests.swift
+++ b/TalerUITests/TalerWalletUITests.swift
@@ -1,22 +1,13 @@
-/*
- * 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/>
- */
+//
+//  TalerWalletUITests.swift
+//  TalerWalletUITests
+//
+//  Created by Marc Stibane on 31.01.23.
+//
 
 import XCTest
 
-class TalerUITests: XCTestCase {
+final class TalerWalletUITests: XCTestCase {
 
     override func setUpWithError() throws {
         // Put setup code here. This method is called before the invocation of 
each test method in the class.
@@ -36,12 +27,11 @@ class TalerUITests: XCTestCase {
         let app = XCUIApplication()
         app.launch()
 
-        // Use recording to get started writing UI tests.
         // Use XCTAssert and related functions to verify your tests produce 
the correct results.
     }
 
     func testLaunchPerformance() throws {
-        if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) {
+        if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) {
             // This measures how long it takes to launch your application.
             measure(metrics: [XCTApplicationLaunchMetric()]) {
                 XCUIApplication().launch()
diff --git a/TalerUITests/TalerWalletUITestsLaunchTests.swift 
b/TalerUITests/TalerWalletUITestsLaunchTests.swift
new file mode 100644
index 0000000..eac1675
--- /dev/null
+++ b/TalerUITests/TalerWalletUITestsLaunchTests.swift
@@ -0,0 +1,32 @@
+//
+//  TalerWalletUITestsLaunchTests.swift
+//  TalerWalletUITests
+//
+//  Created by Marc Stibane on 31.01.23.
+//
+
+import XCTest
+
+final class TalerWalletUITestsLaunchTests: XCTestCase {
+
+    override class var runsForEachTargetApplicationUIConfiguration: Bool {
+        true
+    }
+
+    override func setUpWithError() throws {
+        continueAfterFailure = false
+    }
+
+    func testLaunch() throws {
+        let app = XCUIApplication()
+        app.launch()
+
+        // Insert steps here to perform after app launch but before taking a 
screenshot,
+        // such as logging into a test account or navigating somewhere in the 
app
+
+        let attachment = XCTAttachment(screenshot: app.screenshot())
+        attachment.name = "Launch Screen"
+        attachment.lifetime = .keepAlways
+        add(attachment)
+    }
+}
diff --git a/TalerWallet.xcodeproj/project.pbxproj 
b/TalerWallet.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..4d986fe
--- /dev/null
+++ b/TalerWallet.xcodeproj/project.pbxproj
@@ -0,0 +1,1010 @@
+// !$*UTF8*$!
+{
+       archiveVersion = 1;
+       classes = {
+       };
+       objectVersion = 52;
+       objects = {
+
+/* Begin PBXBuildFile section */
+               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 */; };
+               4EB094DD29896D030043A8A1 /* TalerUITests.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EB094DA29896D030043A8A1 /* TalerUITests.swift 
*/; };
+               4EB094DE29896D030043A8A1 /* TalerWalletUITests.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4EB094DB29896D030043A8A1 /* 
TalerWalletUITests.swift */; };
+               4EB094ED298979620043A8A1 /* TalerWallet1App.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4EB094EC298979620043A8A1 /* 
TalerWallet1App.swift */; };
+               4EB094F0298979D30043A8A1 /* Assets.xcassets in Resources */ = 
{isa = PBXBuildFile; fileRef = 4EB094EF298979D30043A8A1 /* Assets.xcassets */; 
};
+               4EB094F429897A510043A8A1 /* Preview Assets.xcassets in 
Resources */ = {isa = PBXBuildFile; fileRef = 4EB094F329897A510043A8A1 /* 
Preview Assets.xcassets */; };
+               4EB094F829897CA20043A8A1 /* FTalerWalletcore.framework in 
Frameworks */ = {isa = PBXBuildFile; fileRef = 4EB094F729897CA20043A8A1 /* 
FTalerWalletcore.framework */; };
+               4EB094FD29897D280043A8A1 /* SymLog in Frameworks */ = {isa = 
PBXBuildFile; productRef = 4EB094FC29897D280043A8A1 /* SymLog */; };
+               4EB095032989C9BC0043A8A1 /* Controller.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EB095012989C9BC0043A8A1 /* Controller.swift */; 
};
+               4EB095092989CB7C0043A8A1 /* TalerDater.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EB095062989CB7C0043A8A1 /* TalerDater.swift */; 
};
+               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 */; };
+               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 */; };
+               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 */; };
+               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 */; 
};
+               4EB0955F2989CBFE0043A8A1 /* WalletEmptyView.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4EB095392989CBFE0043A8A1 /* 
WalletEmptyView.swift */; };
+               4EB095602989CBFE0043A8A1 /* CurrencyView.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EB0953A2989CBFE0043A8A1 /* CurrencyView.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 */; };
+               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 
*/; };
+               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 
*/; };
+               4EB0956F2989CBFE0043A8A1 /* PendingOpView.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EB0954D2989CBFE0043A8A1 /* PendingOpView.swift 
*/; };
+               4EB095702989CBFE0043A8A1 /* PendingOpsListView.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4EB0954E2989CBFE0043A8A1 /* 
PendingOpsListView.swift */; };
+               ABC13AA32859962800D23185 /* taler-swift in Frameworks */ = {isa 
= PBXBuildFile; productRef = ABC13AA22859962800D23185 /* taler-swift */; };
+               ABE97B1D286D82BF00580772 /* AnyCodable in Frameworks */ = {isa 
= PBXBuildFile; productRef = ABE97B1C286D82BF00580772 /* AnyCodable */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+               D14AFD3424D232B500C51073 /* PBXContainerItemProxy */ = {
+                       isa = PBXContainerItemProxy;
+                       containerPortal = D14AFD1524D232B300C51073 /* Project 
object */;
+                       proxyType = 1;
+                       remoteGlobalIDString = D14AFD1C24D232B300C51073;
+                       remoteInfo = Taler;
+               };
+               D14AFD3F24D232B500C51073 /* PBXContainerItemProxy */ = {
+                       isa = PBXContainerItemProxy;
+                       containerPortal = D14AFD1524D232B300C51073 /* Project 
object */;
+                       proxyType = 1;
+                       remoteGlobalIDString = D14AFD1C24D232B300C51073;
+                       remoteInfo = Taler;
+               };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+               D11250FA26B12D4400D02E00 /* CopyFiles */ = {
+                       isa = PBXCopyFilesBuildPhase;
+                       buildActionMask = 12;
+                       dstPath = "";
+                       dstSubfolderSpec = 7;
+                       files = (
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+               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>"; };
+               4EB094DA29896D030043A8A1 /* TalerUITests.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= TalerUITests.swift; sourceTree = "<group>"; };
+               4EB094DB29896D030043A8A1 /* TalerWalletUITests.swift */ = {isa 
= PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; 
path = TalerWalletUITests.swift; sourceTree = "<group>"; };
+               4EB094E129896FED0043A8A1 /* Info.plist */ = {isa = 
PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; 
sourceTree = "<group>"; };
+               4EB094EC298979620043A8A1 /* TalerWallet1App.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= TalerWallet1App.swift; sourceTree = "<group>"; };
+               4EB094EF298979D30043A8A1 /* Assets.xcassets */ = {isa = 
PBXFileReference; lastKnownFileType = folder.assetcatalog; path = 
Assets.xcassets; sourceTree = "<group>"; };
+               4EB094F329897A510043A8A1 /* Preview Assets.xcassets */ = {isa = 
PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview 
Assets.xcassets"; sourceTree = "<group>"; };
+               4EB094F729897CA20043A8A1 /* FTalerWalletcore.framework */ = 
{isa = PBXFileReference; explicitFileType = wrapper.framework; path = 
FTalerWalletcore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+               4EB095012989C9BC0043A8A1 /* Controller.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= Controller.swift; sourceTree = "<group>"; };
+               4EB095062989CB7C0043A8A1 /* TalerDater.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= TalerDater.swift; sourceTree = "<group>"; };
+               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>"; };
+               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>"; };
+               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>"; };
+               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>"; };
+               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>"; };
+               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>"; };
+               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>"; };
+               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>"; };
+               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>"; };
+               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; };
+               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 */
+
+/* Begin PBXFrameworksBuildPhase section */
+               D14AFD1A24D232B300C51073 /* Frameworks */ = {
+                       isa = PBXFrameworksBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               ABE97B1D286D82BF00580772 /* AnyCodable in 
Frameworks */,
+                               4EB094FD29897D280043A8A1 /* SymLog in 
Frameworks */,
+                               4EB094F829897CA20043A8A1 /* 
FTalerWalletcore.framework in Frameworks */,
+                               ABC13AA32859962800D23185 /* taler-swift in 
Frameworks */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+               D14AFD3024D232B500C51073 /* Frameworks */ = {
+                       isa = PBXFrameworksBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+               D14AFD3B24D232B500C51073 /* Frameworks */ = {
+                       isa = PBXFrameworksBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+               4EB094EE298979840043A8A1 /* TalerWallet1 */ = {
+                       isa = PBXGroup;
+                       children = (
+                               4EB095002989C9BC0043A8A1 /* Controllers */,
+                               4EB095232989CBFE0043A8A1 /* Views */,
+                               4EB0950F2989CBB00043A8A1 /* Model */,
+                               4EB0951A2989CBCB0043A8A1 /* Backend */,
+                               4EB0950C2989CB9A0043A8A1 /* Quickjs */,
+                               4EB095052989CB7C0043A8A1 /* Helper */,
+                               4EB094EF298979D30043A8A1 /* Assets.xcassets */,
+                               4EB094F529897A9A0043A8A1 /* Preview Content */,
+                       );
+                       path = TalerWallet1;
+                       sourceTree = "<group>";
+               };
+               4EB094F529897A9A0043A8A1 /* Preview Content */ = {
+                       isa = PBXGroup;
+                       children = (
+                               4EB094F329897A510043A8A1 /* Preview 
Assets.xcassets */,
+                       );
+                       path = "Preview Content";
+                       sourceTree = "<group>";
+               };
+               4EB094F629897CA20043A8A1 /* Frameworks */ = {
+                       isa = PBXGroup;
+                       children = (
+                               4EB094F729897CA20043A8A1 /* 
FTalerWalletcore.framework */,
+                       );
+                       name = Frameworks;
+                       sourceTree = "<group>";
+               };
+               4EB095002989C9BC0043A8A1 /* Controllers */ = {
+                       isa = PBXGroup;
+                       children = (
+                               4EB094EC298979620043A8A1 /* 
TalerWallet1App.swift */,
+                               4EB095012989C9BC0043A8A1 /* Controller.swift */,
+                       );
+                       path = Controllers;
+                       sourceTree = "<group>";
+               };
+               4EB095052989CB7C0043A8A1 /* Helper */ = {
+                       isa = PBXGroup;
+                       children = (
+                               4EB095062989CB7C0043A8A1 /* TalerDater.swift */,
+                               4EB095072989CB7C0043A8A1 /* TalerStrings.swift 
*/,
+                               4EB095082989CB7C0043A8A1 /* 
View+dismissTop.swift */,
+                       );
+                       path = Helper;
+                       sourceTree = "<group>";
+               };
+               4EB0950C2989CB9A0043A8A1 /* Quickjs */ = {
+                       isa = PBXGroup;
+                       children = (
+                               4EB0950D2989CB9A0043A8A1 /* quickjs.swift */,
+                       );
+                       path = Quickjs;
+                       sourceTree = "<group>";
+               };
+               4EB0950F2989CBB00043A8A1 /* Model */ = {
+                       isa = PBXGroup;
+                       children = (
+                               4EB095102989CBB00043A8A1 /* 
ExchangeTestModel.swift */,
+                               4EB095112989CBB00043A8A1 /* WalletModel.swift 
*/,
+                               4EB095142989CBB00043A8A1 /* 
WalletInitModel.swift */,
+                       );
+                       path = Model;
+                       sourceTree = "<group>";
+               };
+               4EB0951A2989CBCB0043A8A1 /* Backend */ = {
+                       isa = PBXGroup;
+                       children = (
+                               4EB0951B2989CBCB0043A8A1 /* 
WalletBackendRequest.swift */,
+                               4EB0951C2989CBCB0043A8A1 /* WalletCore.swift */,
+                               4EB0951D2989CBCB0043A8A1 /* 
WalletBackendError.swift */,
+                               4EB0951E2989CBCB0043A8A1 /* Transaction.swift 
*/,
+                       );
+                       path = Backend;
+                       sourceTree = "<group>";
+               };
+               4EB095232989CBFE0043A8A1 /* Views */ = {
+                       isa = PBXGroup;
+                       children = (
+                               4EB095412989CBFE0043A8A1 /* Main */,
+                               4EB095242989CBFE0043A8A1 /* Settings */,
+                               4EB095272989CBFE0043A8A1 /* Exchange */,
+                               4EB0952A2989CBFE0043A8A1 /* Payment */,
+                               4EB0952E2989CBFE0043A8A1 /* Transactions */,
+                               4EB095332989CBFE0043A8A1 /* URLSheet.swift */,
+                               4EB095342989CBFE0043A8A1 /* Balances */,
+                               4EB0953B2989CBFE0043A8A1 /* Withdraw */,
+                               4EB095462989CBFE0043A8A1 /* HelperViews */,
+                               4EB0954B2989CBFE0043A8A1 /* Pending */,
+                       );
+                       path = Views;
+                       sourceTree = "<group>";
+               };
+               4EB095242989CBFE0043A8A1 /* Settings */ = {
+                       isa = PBXGroup;
+                       children = (
+                               4EB095252989CBFE0043A8A1 /* SettingsView.swift 
*/,
+                               4EB095262989CBFE0043A8A1 /* SettingsItem.swift 
*/,
+                       );
+                       path = Settings;
+                       sourceTree = "<group>";
+               };
+               4EB095272989CBFE0043A8A1 /* Exchange */ = {
+                       isa = PBXGroup;
+                       children = (
+                               4EB095282989CBFE0043A8A1 /* ExchangeModel.swift 
*/,
+                               4EB095292989CBFE0043A8A1 /* 
ExchangeListView.swift */,
+                       );
+                       path = Exchange;
+                       sourceTree = "<group>";
+               };
+               4EB0952A2989CBFE0043A8A1 /* Payment */ = {
+                       isa = PBXGroup;
+                       children = (
+                               4EB0952B2989CBFE0043A8A1 /* 
PaymentAcceptView.swift */,
+                               4EB0952C2989CBFE0043A8A1 /* 
PaymentURIModel.swift */,
+                               4EB0952D2989CBFE0043A8A1 /* 
PaymentURIView.swift */,
+                       );
+                       path = Payment;
+                       sourceTree = "<group>";
+               };
+               4EB0952E2989CBFE0043A8A1 /* Transactions */ = {
+                       isa = PBXGroup;
+                       children = (
+                               4EB0952F2989CBFE0043A8A1 /* 
TransactionsListView.swift */,
+                               4EB095302989CBFE0043A8A1 /* 
TransactionRow.swift */,
+                               4EB095312989CBFE0043A8A1 /* 
TransactionDetail.swift */,
+                               4EB095322989CBFE0043A8A1 /* 
TransactionsModel.swift */,
+                       );
+                       path = Transactions;
+                       sourceTree = "<group>";
+               };
+               4EB095342989CBFE0043A8A1 /* Balances */ = {
+                       isa = PBXGroup;
+                       children = (
+                               4EB095352989CBFE0043A8A1 /* BalancesModel.swift 
*/,
+                               4EB095362989CBFE0043A8A1 /* BalanceRow.swift */,
+                               4EB095372989CBFE0043A8A1 /* 
CurrenciesListView.swift */,
+                               4EB095382989CBFE0043A8A1 /* PendingRow.swift */,
+                               4EB095392989CBFE0043A8A1 /* 
WalletEmptyView.swift */,
+                               4EB0953A2989CBFE0043A8A1 /* CurrencyView.swift 
*/,
+                       );
+                       path = Balances;
+                       sourceTree = "<group>";
+               };
+               4EB0953B2989CBFE0043A8A1 /* Withdraw */ = {
+                       isa = PBXGroup;
+                       children = (
+                               4EB0953C2989CBFE0043A8A1 /* 
WithdrawURIView.swift */,
+                               4EB0953D2989CBFE0043A8A1 /* 
WithdrawURIModel.swift */,
+                               4EB0953E2989CBFE0043A8A1 /* 
WithdrawAcceptView.swift */,
+                               4EB0953F2989CBFE0043A8A1 /* 
WithdrawProgressView.swift */,
+                               4EB095402989CBFE0043A8A1 /* 
WithdrawTOSView.swift */,
+                       );
+                       path = Withdraw;
+                       sourceTree = "<group>";
+               };
+               4EB095412989CBFE0043A8A1 /* Main */ = {
+                       isa = PBXGroup;
+                       children = (
+                               4EB095442989CBFE0043A8A1 /* ContentView.swift 
*/,
+                               4EB095422989CBFE0043A8A1 /* SideBarView.swift 
*/,
+                               4EB095432989CBFE0043A8A1 /* 
LaunchAnimationView.swift */,
+                               4EB095452989CBFE0043A8A1 /* ErrorView.swift */,
+                       );
+                       path = Main;
+                       sourceTree = "<group>";
+               };
+               4EB095462989CBFE0043A8A1 /* HelperViews */ = {
+                       isa = PBXGroup;
+                       children = (
+                               4EB095472989CBFE0043A8A1 /* Buttons.swift */,
+                               4EB095482989CBFE0043A8A1 /* 
TextFieldAlert.swift */,
+                               4EB095492989CBFE0043A8A1 /* AmountView.swift */,
+                               4EB0954A2989CBFE0043A8A1 /* LoadingView.swift 
*/,
+                       );
+                       path = HelperViews;
+                       sourceTree = "<group>";
+               };
+               4EB0954B2989CBFE0043A8A1 /* Pending */ = {
+                       isa = PBXGroup;
+                       children = (
+                               4EB0954C2989CBFE0043A8A1 /* PendingModel.swift 
*/,
+                               4EB0954D2989CBFE0043A8A1 /* PendingOpView.swift 
*/,
+                               4EB0954E2989CBFE0043A8A1 /* 
PendingOpsListView.swift */,
+                       );
+                       path = Pending;
+                       sourceTree = "<group>";
+               };
+               D14AFD1424D232B300C51073 = {
+                       isa = PBXGroup;
+                       children = (
+                               4EB094EE298979840043A8A1 /* TalerWallet1 */,
+                               4EB094E129896FED0043A8A1 /* Info.plist */,
+                               AB710490285995B6008B04F0 /* taler-swift */,
+                               D14AFD3624D232B500C51073 /* TalerTests */,
+                               D14AFD4124D232B500C51073 /* TalerUITests */,
+                               D14AFD1E24D232B300C51073 /* Products */,
+                               4EB094F629897CA20043A8A1 /* Frameworks */,
+                       );
+                       sourceTree = "<group>";
+               };
+               D14AFD1E24D232B300C51073 /* Products */ = {
+                       isa = PBXGroup;
+                       children = (
+                               D14AFD1D24D232B300C51073 /* TalerWalletT.app */,
+                               D14AFD3324D232B500C51073 /* TalerTests.xctest 
*/,
+                               D14AFD3E24D232B500C51073 /* TalerUITests.xctest 
*/,
+                       );
+                       name = Products;
+                       sourceTree = "<group>";
+               };
+               D14AFD3624D232B500C51073 /* TalerTests */ = {
+                       isa = PBXGroup;
+                       children = (
+                               4EB094D429896CD20043A8A1 /* 
TalerWalletTests.swift */,
+                               4EB094D529896CD20043A8A1 /* 
WalletBackendTests.swift */,
+                       );
+                       path = TalerTests;
+                       sourceTree = "<group>";
+               };
+               D14AFD4124D232B500C51073 /* TalerUITests */ = {
+                       isa = PBXGroup;
+                       children = (
+                               4EB094DA29896D030043A8A1 /* TalerUITests.swift 
*/,
+                               4EB094DB29896D030043A8A1 /* 
TalerWalletUITests.swift */,
+                               4EB094D929896D030043A8A1 /* 
TalerWalletUITestsLaunchTests.swift */,
+                       );
+                       path = TalerUITests;
+                       sourceTree = "<group>";
+               };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+               D14AFD1C24D232B300C51073 /* TalerWalletT */ = {
+                       isa = PBXNativeTarget;
+                       buildConfigurationList = D14AFD4724D232B500C51073 /* 
Build configuration list for PBXNativeTarget "TalerWalletT" */;
+                       buildPhases = (
+                               D14AFD1924D232B300C51073 /* Sources */,
+                               D14AFD1A24D232B300C51073 /* Frameworks */,
+                               D11250FA26B12D4400D02E00 /* CopyFiles */,
+                               D14AFD1B24D232B300C51073 /* Resources */,
+                       );
+                       buildRules = (
+                       );
+                       dependencies = (
+                       );
+                       name = TalerWalletT;
+                       packageProductDependencies = (
+                               ABC13AA22859962800D23185 /* taler-swift */,
+                               ABE97B1C286D82BF00580772 /* AnyCodable */,
+                               4EB094FC29897D280043A8A1 /* SymLog */,
+                       );
+                       productName = Taler;
+                       productReference = D14AFD1D24D232B300C51073 /* 
TalerWalletT.app */;
+                       productType = "com.apple.product-type.application";
+               };
+               D14AFD3224D232B500C51073 /* TalerTests */ = {
+                       isa = PBXNativeTarget;
+                       buildConfigurationList = D14AFD4A24D232B500C51073 /* 
Build configuration list for PBXNativeTarget "TalerTests" */;
+                       buildPhases = (
+                               D14AFD2F24D232B500C51073 /* Sources */,
+                               D14AFD3024D232B500C51073 /* Frameworks */,
+                               D14AFD3124D232B500C51073 /* Resources */,
+                       );
+                       buildRules = (
+                       );
+                       dependencies = (
+                               D14AFD3524D232B500C51073 /* PBXTargetDependency 
*/,
+                       );
+                       name = TalerTests;
+                       productName = TalerTests;
+                       productReference = D14AFD3324D232B500C51073 /* 
TalerTests.xctest */;
+                       productType = "com.apple.product-type.bundle.unit-test";
+               };
+               D14AFD3D24D232B500C51073 /* TalerUITests */ = {
+                       isa = PBXNativeTarget;
+                       buildConfigurationList = D14AFD4D24D232B500C51073 /* 
Build configuration list for PBXNativeTarget "TalerUITests" */;
+                       buildPhases = (
+                               D14AFD3A24D232B500C51073 /* Sources */,
+                               D14AFD3B24D232B500C51073 /* Frameworks */,
+                               D14AFD3C24D232B500C51073 /* Resources */,
+                       );
+                       buildRules = (
+                       );
+                       dependencies = (
+                               D14AFD4024D232B500C51073 /* PBXTargetDependency 
*/,
+                       );
+                       name = TalerUITests;
+                       productName = TalerUITests;
+                       productReference = D14AFD3E24D232B500C51073 /* 
TalerUITests.xctest */;
+                       productType = 
"com.apple.product-type.bundle.ui-testing";
+               };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+               D14AFD1524D232B300C51073 /* Project object */ = {
+                       isa = PBXProject;
+                       attributes = {
+                               LastSwiftUpdateCheck = 1160;
+                               LastUpgradeCheck = 1230;
+                               ORGANIZATIONNAME = Taler;
+                               TargetAttributes = {
+                                       D14AFD1C24D232B300C51073 = {
+                                               CreatedOnToolsVersion = 11.6;
+                                               LastSwiftMigration = 1420;
+                                       };
+                                       D14AFD3224D232B500C51073 = {
+                                               CreatedOnToolsVersion = 11.6;
+                                               LastSwiftMigration = 1420;
+                                               TestTargetID = 
D14AFD1C24D232B300C51073;
+                                       };
+                                       D14AFD3D24D232B500C51073 = {
+                                               CreatedOnToolsVersion = 11.6;
+                                               LastSwiftMigration = 1420;
+                                               TestTargetID = 
D14AFD1C24D232B300C51073;
+                                       };
+                               };
+                       };
+                       buildConfigurationList = D14AFD1824D232B300C51073 /* 
Build configuration list for PBXProject "TalerWallet" */;
+                       compatibilityVersion = "Xcode 9.3";
+                       developmentRegion = en;
+                       hasScannedForEncodings = 0;
+                       knownRegions = (
+                               en,
+                               Base,
+                       );
+                       mainGroup = D14AFD1424D232B300C51073;
+                       packageReferences = (
+                               ABE97B1B286D82BF00580772 /* 
XCRemoteSwiftPackageReference "AnyCodable" */,
+                               4EB094FB29897D280043A8A1 /* 
XCRemoteSwiftPackageReference "SymLog" */,
+                       );
+                       productRefGroup = D14AFD1E24D232B300C51073 /* Products 
*/;
+                       projectDirPath = "";
+                       projectRoot = "";
+                       targets = (
+                               D14AFD1C24D232B300C51073 /* TalerWalletT */,
+                               D14AFD3224D232B500C51073 /* TalerTests */,
+                               D14AFD3D24D232B500C51073 /* TalerUITests */,
+                       );
+               };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+               D14AFD1B24D232B300C51073 /* Resources */ = {
+                       isa = PBXResourcesBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               4EB094F429897A510043A8A1 /* Preview 
Assets.xcassets in Resources */,
+                               4EB094F0298979D30043A8A1 /* Assets.xcassets in 
Resources */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+               D14AFD3124D232B500C51073 /* Resources */ = {
+                       isa = PBXResourcesBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+               D14AFD3C24D232B500C51073 /* Resources */ = {
+                       isa = PBXResourcesBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+               D14AFD1924D232B300C51073 /* Sources */ = {
+                       isa = PBXSourcesBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               4EB095512989CBFE0043A8A1 /* ExchangeModel.swift 
in Sources */,
+                               4EB095032989C9BC0043A8A1 /* Controller.swift in 
Sources */,
+                               4EB095682989CBFE0043A8A1 /* ContentView.swift 
in Sources */,
+                               4EB0956A2989CBFE0043A8A1 /* Buttons.swift in 
Sources */,
+                               4EB095602989CBFE0043A8A1 /* CurrencyView.swift 
in Sources */,
+                               4EB095222989CBCB0043A8A1 /* Transaction.swift 
in Sources */,
+                               4EB0955D2989CBFE0043A8A1 /* 
CurrenciesListView.swift in Sources */,
+                               4EB095532989CBFE0043A8A1 /* 
PaymentAcceptView.swift in Sources */,
+                               4EB095212989CBCB0043A8A1 /* 
WalletBackendError.swift in Sources */,
+                               4EB0955E2989CBFE0043A8A1 /* PendingRow.swift in 
Sources */,
+                               4EB0955B2989CBFE0043A8A1 /* BalancesModel.swift 
in Sources */,
+                               4EB095632989CBFE0043A8A1 /* 
WithdrawAcceptView.swift in Sources */,
+                               4EB0956D2989CBFE0043A8A1 /* LoadingView.swift 
in Sources */,
+                               4EB095542989CBFE0043A8A1 /* 
PaymentURIModel.swift in Sources */,
+                               4EB0954F2989CBFE0043A8A1 /* SettingsView.swift 
in Sources */,
+                               4EB095552989CBFE0043A8A1 /* 
PaymentURIView.swift in Sources */,
+                               4EB095612989CBFE0043A8A1 /* 
WithdrawURIView.swift in Sources */,
+                               4EB094ED298979620043A8A1 /* 
TalerWallet1App.swift in Sources */,
+                               4EB095652989CBFE0043A8A1 /* 
WithdrawTOSView.swift in Sources */,
+                               4EB0950B2989CB7C0043A8A1 /* 
View+dismissTop.swift in Sources */,
+                               4EB095562989CBFE0043A8A1 /* 
TransactionsListView.swift in Sources */,
+                               4EB0951F2989CBCB0043A8A1 /* 
WalletBackendRequest.swift in Sources */,
+                               4EB095572989CBFE0043A8A1 /* 
TransactionRow.swift in Sources */,
+                               4EB0956B2989CBFE0043A8A1 /* 
TextFieldAlert.swift in Sources */,
+                               4EB0956C2989CBFE0043A8A1 /* AmountView.swift in 
Sources */,
+                               4EB095592989CBFE0043A8A1 /* 
TransactionsModel.swift in Sources */,
+                               4EB0955F2989CBFE0043A8A1 /* 
WalletEmptyView.swift in Sources */,
+                               4EB095192989CBB00043A8A1 /* 
WalletInitModel.swift in Sources */,
+                               4EB095092989CB7C0043A8A1 /* TalerDater.swift in 
Sources */,
+                               4EB0950E2989CB9A0043A8A1 /* quickjs.swift in 
Sources */,
+                               4EB095152989CBB00043A8A1 /* 
ExchangeTestModel.swift in Sources */,
+                               4EB095692989CBFE0043A8A1 /* ErrorView.swift in 
Sources */,
+                               4EB0956E2989CBFE0043A8A1 /* PendingModel.swift 
in Sources */,
+                               4EB095522989CBFE0043A8A1 /* 
ExchangeListView.swift in Sources */,
+                               4EB095642989CBFE0043A8A1 /* 
WithdrawProgressView.swift in Sources */,
+                               4EB095582989CBFE0043A8A1 /* 
TransactionDetail.swift in Sources */,
+                               4EB095202989CBCB0043A8A1 /* WalletCore.swift in 
Sources */,
+                               4EB095672989CBFE0043A8A1 /* 
LaunchAnimationView.swift in Sources */,
+                               4EB095662989CBFE0043A8A1 /* SideBarView.swift 
in Sources */,
+                               4EB0956F2989CBFE0043A8A1 /* PendingOpView.swift 
in Sources */,
+                               4EB095702989CBFE0043A8A1 /* 
PendingOpsListView.swift in Sources */,
+                               4EB095162989CBB00043A8A1 /* WalletModel.swift 
in Sources */,
+                               4EB0955A2989CBFE0043A8A1 /* URLSheet.swift in 
Sources */,
+                               4EB095622989CBFE0043A8A1 /* 
WithdrawURIModel.swift in Sources */,
+                               4EB0950A2989CB7C0043A8A1 /* TalerStrings.swift 
in Sources */,
+                               4EB095502989CBFE0043A8A1 /* SettingsItem.swift 
in Sources */,
+                               4EB0955C2989CBFE0043A8A1 /* BalanceRow.swift in 
Sources */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+               D14AFD2F24D232B500C51073 /* Sources */ = {
+                       isa = PBXSourcesBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               4EB094D629896CD20043A8A1 /* 
TalerWalletTests.swift in Sources */,
+                               4EB094D729896CD20043A8A1 /* 
WalletBackendTests.swift in Sources */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+               D14AFD3A24D232B500C51073 /* Sources */ = {
+                       isa = PBXSourcesBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               4EB094DD29896D030043A8A1 /* TalerUITests.swift 
in Sources */,
+                               4EB094DE29896D030043A8A1 /* 
TalerWalletUITests.swift in Sources */,
+                               4EB094DC29896D030043A8A1 /* 
TalerWalletUITestsLaunchTests.swift in Sources */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+               D14AFD3524D232B500C51073 /* PBXTargetDependency */ = {
+                       isa = PBXTargetDependency;
+                       target = D14AFD1C24D232B300C51073 /* TalerWalletT */;
+                       targetProxy = D14AFD3424D232B500C51073 /* 
PBXContainerItemProxy */;
+               };
+               D14AFD4024D232B500C51073 /* PBXTargetDependency */ = {
+                       isa = PBXTargetDependency;
+                       target = D14AFD1C24D232B300C51073 /* TalerWalletT */;
+                       targetProxy = D14AFD3F24D232B500C51073 /* 
PBXContainerItemProxy */;
+               };
+/* End PBXTargetDependency section */
+
+/* Begin XCBuildConfiguration section */
+               D14AFD4524D232B500C51073 /* Debug */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               ALWAYS_SEARCH_USER_PATHS = NO;
+                               CLANG_ANALYZER_NONNULL = YES;
+                               CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = 
YES_AGGRESSIVE;
+                               CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+                               CLANG_CXX_LIBRARY = "libc++";
+                               CLANG_ENABLE_MODULES = YES;
+                               CLANG_ENABLE_OBJC_ARC = YES;
+                               CLANG_ENABLE_OBJC_WEAK = YES;
+                               CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+                               CLANG_WARN_BOOL_CONVERSION = YES;
+                               CLANG_WARN_COMMA = YES;
+                               CLANG_WARN_CONSTANT_CONVERSION = YES;
+                               CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = 
YES;
+                               CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+                               CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+                               CLANG_WARN_EMPTY_BODY = YES;
+                               CLANG_WARN_ENUM_CONVERSION = YES;
+                               CLANG_WARN_INFINITE_RECURSION = YES;
+                               CLANG_WARN_INT_CONVERSION = YES;
+                               CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+                               CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+                               CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+                               CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+                               CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = 
YES;
+                               CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+                               CLANG_WARN_STRICT_PROTOTYPES = YES;
+                               CLANG_WARN_SUSPICIOUS_MOVE = YES;
+                               CLANG_WARN_UNGUARDED_AVAILABILITY = 
YES_AGGRESSIVE;
+                               CLANG_WARN_UNREACHABLE_CODE = YES;
+                               CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+                               DEBUG_INFORMATION_FORMAT = dwarf;
+                               ENABLE_STRICT_OBJC_MSGSEND = YES;
+                               ENABLE_TESTABILITY = YES;
+                               GCC_NO_COMMON_BLOCKS = YES;
+                               GCC_OPTIMIZATION_LEVEL = 0;
+                               GCC_PREPROCESSOR_DEFINITIONS = (
+                                       "DEBUG=1",
+                                       "$(inherited)",
+                               );
+                               GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+                               GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+                               GCC_WARN_UNDECLARED_SELECTOR = YES;
+                               GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+                               GCC_WARN_UNUSED_FUNCTION = YES;
+                               GCC_WARN_UNUSED_VARIABLE = YES;
+                               IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+                               MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+                               MTL_FAST_MATH = YES;
+                               ONLY_ACTIVE_ARCH = YES;
+                               OTHER_LDFLAGS = "-lc++";
+                               SDKROOT = iphoneos;
+                               SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+                               SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+                               SWIFT_VERSION = 5.0;
+                       };
+                       name = Debug;
+               };
+               D14AFD4624D232B500C51073 /* Release */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               ALWAYS_SEARCH_USER_PATHS = NO;
+                               CLANG_ANALYZER_NONNULL = YES;
+                               CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = 
YES_AGGRESSIVE;
+                               CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+                               CLANG_CXX_LIBRARY = "libc++";
+                               CLANG_ENABLE_MODULES = YES;
+                               CLANG_ENABLE_OBJC_ARC = YES;
+                               CLANG_ENABLE_OBJC_WEAK = YES;
+                               CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+                               CLANG_WARN_BOOL_CONVERSION = YES;
+                               CLANG_WARN_COMMA = YES;
+                               CLANG_WARN_CONSTANT_CONVERSION = YES;
+                               CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = 
YES;
+                               CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+                               CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+                               CLANG_WARN_EMPTY_BODY = YES;
+                               CLANG_WARN_ENUM_CONVERSION = YES;
+                               CLANG_WARN_INFINITE_RECURSION = YES;
+                               CLANG_WARN_INT_CONVERSION = YES;
+                               CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+                               CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+                               CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+                               CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+                               CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = 
YES;
+                               CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+                               CLANG_WARN_STRICT_PROTOTYPES = YES;
+                               CLANG_WARN_SUSPICIOUS_MOVE = YES;
+                               CLANG_WARN_UNGUARDED_AVAILABILITY = 
YES_AGGRESSIVE;
+                               CLANG_WARN_UNREACHABLE_CODE = YES;
+                               CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+                               DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+                               ENABLE_NS_ASSERTIONS = NO;
+                               ENABLE_STRICT_OBJC_MSGSEND = YES;
+                               GCC_NO_COMMON_BLOCKS = YES;
+                               GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+                               GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+                               GCC_WARN_UNDECLARED_SELECTOR = YES;
+                               GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+                               GCC_WARN_UNUSED_FUNCTION = YES;
+                               GCC_WARN_UNUSED_VARIABLE = YES;
+                               IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+                               MTL_ENABLE_DEBUG_INFO = NO;
+                               MTL_FAST_MATH = YES;
+                               OTHER_LDFLAGS = "-lc++";
+                               SDKROOT = iphoneos;
+                               SWIFT_COMPILATION_MODE = wholemodule;
+                               SWIFT_OPTIMIZATION_LEVEL = "-O";
+                               SWIFT_VERSION = 5.0;
+                               VALIDATE_PRODUCT = YES;
+                       };
+                       name = Release;
+               };
+               D14AFD4824D232B500C51073 /* Debug */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+                               CLANG_ENABLE_MODULES = YES;
+                               CODE_SIGN_STYLE = Automatic;
+                               CURRENT_PROJECT_VERSION = 2;
+                               DEVELOPMENT_TEAM = "";
+                               ENABLE_PREVIEWS = YES;
+                               GENERATE_INFOPLIST_FILE = YES;
+                               INFOPLIST_FILE = Info.plist;
+                               INFOPLIST_KEY_CFBundleDisplayName = "Taler 
Wallet";
+                               INFOPLIST_KEY_LSApplicationCategoryType = 
"public.app-category.finance";
+                               INFOPLIST_KEY_NSHumanReadableCopyright = "© 
Taler.net";
+                               
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
+                               INFOPLIST_KEY_UILaunchScreen_Generation = YES;
+                               INFOPLIST_KEY_UISupportedInterfaceOrientations 
= UIInterfaceOrientationPortrait;
+                               
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = 
"UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
+                               IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+                               LD_RUNPATH_SEARCH_PATHS = (
+                                       "$(inherited)",
+                                       "@executable_path/Frameworks",
+                               );
+                               MARKETING_VERSION = 0.9.1;
+                               PRODUCT_BUNDLE_IDENTIFIER = 
net.taler.talerwallet;
+                               PRODUCT_NAME = "$(TARGET_NAME)";
+                               SUPPORTED_PLATFORMS = "iphoneos 
iphonesimulator";
+                               SUPPORTS_MACCATALYST = NO;
+                               SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
+                               SWIFT_EMIT_LOC_STRINGS = YES;
+                               TARGETED_DEVICE_FAMILY = 1;
+                               VALIDATE_WORKSPACE = YES;
+                       };
+                       name = Debug;
+               };
+               D14AFD4924D232B500C51073 /* Release */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+                               ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME 
= AccentColor;
+                               CLANG_ENABLE_MODULES = YES;
+                               CODE_SIGN_STYLE = Automatic;
+                               CURRENT_PROJECT_VERSION = 2;
+                               DEVELOPMENT_TEAM = "";
+                               ENABLE_PREVIEWS = YES;
+                               GENERATE_INFOPLIST_FILE = YES;
+                               INFOPLIST_FILE = Info.plist;
+                               INFOPLIST_KEY_CFBundleDisplayName = "Taler 
Wallet";
+                               INFOPLIST_KEY_LSApplicationCategoryType = 
"public.app-category.finance";
+                               INFOPLIST_KEY_NSHumanReadableCopyright = "© 
Taler.net";
+                               
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
+                               INFOPLIST_KEY_UILaunchScreen_Generation = YES;
+                               INFOPLIST_KEY_UISupportedInterfaceOrientations 
= UIInterfaceOrientationPortrait;
+                               
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = 
"UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
+                               IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+                               LD_RUNPATH_SEARCH_PATHS = (
+                                       "$(inherited)",
+                                       "@executable_path/Frameworks",
+                               );
+                               MARKETING_VERSION = 0.9.1;
+                               PRODUCT_BUNDLE_IDENTIFIER = 
net.taler.talerwallet;
+                               PRODUCT_NAME = "$(TARGET_NAME)";
+                               SUPPORTED_PLATFORMS = "iphoneos 
iphonesimulator";
+                               SUPPORTS_MACCATALYST = NO;
+                               SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
+                               SWIFT_EMIT_LOC_STRINGS = YES;
+                               TARGETED_DEVICE_FAMILY = 1;
+                               VALIDATE_WORKSPACE = YES;
+                       };
+                       name = Release;
+               };
+               D14AFD4B24D232B500C51073 /* Debug */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
+                               BUNDLE_LOADER = "$(TEST_HOST)";
+                               CLANG_ENABLE_MODULES = YES;
+                               CODE_SIGN_STYLE = Automatic;
+                               GENERATE_INFOPLIST_FILE = YES;
+                               IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+                               LD_RUNPATH_SEARCH_PATHS = (
+                                       "$(inherited)",
+                                       "@executable_path/Frameworks",
+                                       "@loader_path/Frameworks",
+                               );
+                               PRODUCT_BUNDLE_IDENTIFIER = 
com.taler.TalerTests;
+                               PRODUCT_NAME = "$(TARGET_NAME)";
+                               SWIFT_EMIT_LOC_STRINGS = NO;
+                               SWIFT_OBJC_BRIDGING_HEADER = 
"TalerTests/TalerTests-Bridging-Header.h";
+                               SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+                               TARGETED_DEVICE_FAMILY = "1,2";
+                               TEST_HOST = 
"$(BUILT_PRODUCTS_DIR)/TalerWallet.app/TalerWallet";
+                       };
+                       name = Debug;
+               };
+               D14AFD4C24D232B500C51073 /* Release */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
+                               BUNDLE_LOADER = "$(TEST_HOST)";
+                               CLANG_ENABLE_MODULES = YES;
+                               CODE_SIGN_STYLE = Automatic;
+                               GENERATE_INFOPLIST_FILE = YES;
+                               IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+                               LD_RUNPATH_SEARCH_PATHS = (
+                                       "$(inherited)",
+                                       "@executable_path/Frameworks",
+                                       "@loader_path/Frameworks",
+                               );
+                               MARKETING_VERSION = 1.0;
+                               PRODUCT_BUNDLE_IDENTIFIER = 
com.taler.TalerTests;
+                               PRODUCT_NAME = "$(TARGET_NAME)";
+                               SWIFT_EMIT_LOC_STRINGS = NO;
+                               SWIFT_OBJC_BRIDGING_HEADER = 
"TalerTests/TalerTests-Bridging-Header.h";
+                               TARGETED_DEVICE_FAMILY = "1,2";
+                               TEST_HOST = 
"$(BUILT_PRODUCTS_DIR)/TalerWallet.app/TalerWallet";
+                       };
+                       name = Release;
+               };
+               D14AFD4E24D232B500C51073 /* Debug */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
+                               CLANG_ENABLE_MODULES = YES;
+                               CODE_SIGN_STYLE = Automatic;
+                               GENERATE_INFOPLIST_FILE = YES;
+                               LD_RUNPATH_SEARCH_PATHS = (
+                                       "$(inherited)",
+                                       "@executable_path/Frameworks",
+                                       "@loader_path/Frameworks",
+                               );
+                               MARKETING_VERSION = 1.0;
+                               PRODUCT_BUNDLE_IDENTIFIER = 
com.taler.TalerUITests;
+                               PRODUCT_NAME = "$(TARGET_NAME)";
+                               SWIFT_EMIT_LOC_STRINGS = NO;
+                               SWIFT_OBJC_BRIDGING_HEADER = 
"TalerUITests/TalerUITests-Bridging-Header.h";
+                               SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+                               TARGETED_DEVICE_FAMILY = "1,2";
+                               TEST_TARGET_NAME = Taler;
+                       };
+                       name = Debug;
+               };
+               D14AFD4F24D232B500C51073 /* Release */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
+                               CLANG_ENABLE_MODULES = YES;
+                               CODE_SIGN_STYLE = Automatic;
+                               GENERATE_INFOPLIST_FILE = YES;
+                               LD_RUNPATH_SEARCH_PATHS = (
+                                       "$(inherited)",
+                                       "@executable_path/Frameworks",
+                                       "@loader_path/Frameworks",
+                               );
+                               MARKETING_VERSION = 1.0;
+                               PRODUCT_BUNDLE_IDENTIFIER = 
com.taler.TalerUITests;
+                               PRODUCT_NAME = "$(TARGET_NAME)";
+                               SWIFT_EMIT_LOC_STRINGS = NO;
+                               SWIFT_OBJC_BRIDGING_HEADER = 
"TalerUITests/TalerUITests-Bridging-Header.h";
+                               TARGETED_DEVICE_FAMILY = "1,2";
+                               TEST_TARGET_NAME = Taler;
+                       };
+                       name = Release;
+               };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+               D14AFD1824D232B300C51073 /* Build configuration list for 
PBXProject "TalerWallet" */ = {
+                       isa = XCConfigurationList;
+                       buildConfigurations = (
+                               D14AFD4524D232B500C51073 /* Debug */,
+                               D14AFD4624D232B500C51073 /* Release */,
+                       );
+                       defaultConfigurationIsVisible = 0;
+                       defaultConfigurationName = Release;
+               };
+               D14AFD4724D232B500C51073 /* Build configuration list for 
PBXNativeTarget "TalerWalletT" */ = {
+                       isa = XCConfigurationList;
+                       buildConfigurations = (
+                               D14AFD4824D232B500C51073 /* Debug */,
+                               D14AFD4924D232B500C51073 /* Release */,
+                       );
+                       defaultConfigurationIsVisible = 0;
+                       defaultConfigurationName = Release;
+               };
+               D14AFD4A24D232B500C51073 /* Build configuration list for 
PBXNativeTarget "TalerTests" */ = {
+                       isa = XCConfigurationList;
+                       buildConfigurations = (
+                               D14AFD4B24D232B500C51073 /* Debug */,
+                               D14AFD4C24D232B500C51073 /* Release */,
+                       );
+                       defaultConfigurationIsVisible = 0;
+                       defaultConfigurationName = Release;
+               };
+               D14AFD4D24D232B500C51073 /* Build configuration list for 
PBXNativeTarget "TalerUITests" */ = {
+                       isa = XCConfigurationList;
+                       buildConfigurations = (
+                               D14AFD4E24D232B500C51073 /* Debug */,
+                               D14AFD4F24D232B500C51073 /* Release */,
+                       );
+                       defaultConfigurationIsVisible = 0;
+                       defaultConfigurationName = Release;
+               };
+/* End XCConfigurationList section */
+
+/* Begin XCRemoteSwiftPackageReference section */
+               4EB094FB29897D280043A8A1 /* XCRemoteSwiftPackageReference 
"SymLog" */ = {
+                       isa = XCRemoteSwiftPackageReference;
+                       repositoryURL = "https://github.com/Fesh-com/SymLog";;
+                       requirement = {
+                               kind = upToNextMajorVersion;
+                               minimumVersion = 0.1.0;
+                       };
+               };
+               ABE97B1B286D82BF00580772 /* XCRemoteSwiftPackageReference 
"AnyCodable" */ = {
+                       isa = XCRemoteSwiftPackageReference;
+                       repositoryURL = 
"https://github.com/Flight-School/AnyCodable";;
+                       requirement = {
+                               kind = upToNextMajorVersion;
+                               minimumVersion = 0.6.5;
+                       };
+               };
+/* End XCRemoteSwiftPackageReference section */
+
+/* Begin XCSwiftPackageProductDependency section */
+               4EB094FC29897D280043A8A1 /* SymLog */ = {
+                       isa = XCSwiftPackageProductDependency;
+                       package = 4EB094FB29897D280043A8A1 /* 
XCRemoteSwiftPackageReference "SymLog" */;
+                       productName = SymLog;
+               };
+               ABC13AA22859962800D23185 /* taler-swift */ = {
+                       isa = XCSwiftPackageProductDependency;
+                       productName = "taler-swift";
+               };
+               ABE97B1C286D82BF00580772 /* AnyCodable */ = {
+                       isa = XCSwiftPackageProductDependency;
+                       package = ABE97B1B286D82BF00580772 /* 
XCRemoteSwiftPackageReference "AnyCodable" */;
+                       productName = AnyCodable;
+               };
+/* End XCSwiftPackageProductDependency section */
+       };
+       rootObject = D14AFD1524D232B300C51073 /* Project object */;
+}
diff --git a/Taler/Assets.xcassets/AppIcon.appiconset/Contents.json 
b/TalerWallet1/Assets.xcassets/AppIcon.appiconset/Contents.json
similarity index 57%
rename from Taler/Assets.xcassets/AppIcon.appiconset/Contents.json
rename to TalerWallet1/Assets.xcassets/AppIcon.appiconset/Contents.json
index 9221b9b..2005c8a 100644
--- a/Taler/Assets.xcassets/AppIcon.appiconset/Contents.json
+++ b/TalerWallet1/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -1,41 +1,49 @@
 {
   "images" : [
     {
+      "filename" : "notification40.png",
       "idiom" : "iphone",
       "scale" : "2x",
       "size" : "20x20"
     },
     {
+      "filename" : "notification60.png",
       "idiom" : "iphone",
       "scale" : "3x",
       "size" : "20x20"
     },
     {
+      "filename" : "settings58.png",
       "idiom" : "iphone",
       "scale" : "2x",
       "size" : "29x29"
     },
     {
+      "filename" : "settings87.png",
       "idiom" : "iphone",
       "scale" : "3x",
       "size" : "29x29"
     },
     {
+      "filename" : "spotlight80.png",
       "idiom" : "iphone",
       "scale" : "2x",
       "size" : "40x40"
     },
     {
+      "filename" : "spotlight120.png",
       "idiom" : "iphone",
       "scale" : "3x",
       "size" : "40x40"
     },
     {
+      "filename" : "iphone120.png",
       "idiom" : "iphone",
       "scale" : "2x",
       "size" : "60x60"
     },
     {
+      "filename" : "iphone180.png",
       "idiom" : "iphone",
       "scale" : "3x",
       "size" : "60x60"
@@ -86,9 +94,60 @@
       "size" : "83.5x83.5"
     },
     {
+      "filename" : "appstore1024.png",
       "idiom" : "ios-marketing",
       "scale" : "1x",
       "size" : "1024x1024"
+    },
+    {
+      "idiom" : "mac",
+      "scale" : "1x",
+      "size" : "16x16"
+    },
+    {
+      "idiom" : "mac",
+      "scale" : "2x",
+      "size" : "16x16"
+    },
+    {
+      "idiom" : "mac",
+      "scale" : "1x",
+      "size" : "32x32"
+    },
+    {
+      "idiom" : "mac",
+      "scale" : "2x",
+      "size" : "32x32"
+    },
+    {
+      "idiom" : "mac",
+      "scale" : "1x",
+      "size" : "128x128"
+    },
+    {
+      "idiom" : "mac",
+      "scale" : "2x",
+      "size" : "128x128"
+    },
+    {
+      "idiom" : "mac",
+      "scale" : "1x",
+      "size" : "256x256"
+    },
+    {
+      "idiom" : "mac",
+      "scale" : "2x",
+      "size" : "256x256"
+    },
+    {
+      "idiom" : "mac",
+      "scale" : "1x",
+      "size" : "512x512"
+    },
+    {
+      "idiom" : "mac",
+      "scale" : "2x",
+      "size" : "512x512"
     }
   ],
   "info" : {
diff --git a/TalerWallet1/Assets.xcassets/AppIcon.appiconset/appstore1024.png 
b/TalerWallet1/Assets.xcassets/AppIcon.appiconset/appstore1024.png
new file mode 100644
index 0000000..d36b1ab
Binary files /dev/null and 
b/TalerWallet1/Assets.xcassets/AppIcon.appiconset/appstore1024.png differ
diff --git a/TalerWallet1/Assets.xcassets/AppIcon.appiconset/iphone120.png 
b/TalerWallet1/Assets.xcassets/AppIcon.appiconset/iphone120.png
new file mode 100644
index 0000000..260cb20
Binary files /dev/null and 
b/TalerWallet1/Assets.xcassets/AppIcon.appiconset/iphone120.png differ
diff --git a/TalerWallet1/Assets.xcassets/AppIcon.appiconset/iphone180.png 
b/TalerWallet1/Assets.xcassets/AppIcon.appiconset/iphone180.png
new file mode 100644
index 0000000..f021a43
Binary files /dev/null and 
b/TalerWallet1/Assets.xcassets/AppIcon.appiconset/iphone180.png differ
diff --git a/TalerWallet1/Assets.xcassets/AppIcon.appiconset/notification40.png 
b/TalerWallet1/Assets.xcassets/AppIcon.appiconset/notification40.png
new file mode 100644
index 0000000..1312c2a
Binary files /dev/null and 
b/TalerWallet1/Assets.xcassets/AppIcon.appiconset/notification40.png differ
diff --git a/TalerWallet1/Assets.xcassets/AppIcon.appiconset/notification60.png 
b/TalerWallet1/Assets.xcassets/AppIcon.appiconset/notification60.png
new file mode 100644
index 0000000..756f283
Binary files /dev/null and 
b/TalerWallet1/Assets.xcassets/AppIcon.appiconset/notification60.png differ
diff --git a/TalerWallet1/Assets.xcassets/AppIcon.appiconset/settings58.png 
b/TalerWallet1/Assets.xcassets/AppIcon.appiconset/settings58.png
new file mode 100644
index 0000000..951e2c0
Binary files /dev/null and 
b/TalerWallet1/Assets.xcassets/AppIcon.appiconset/settings58.png differ
diff --git a/TalerWallet1/Assets.xcassets/AppIcon.appiconset/settings87.png 
b/TalerWallet1/Assets.xcassets/AppIcon.appiconset/settings87.png
new file mode 100644
index 0000000..d159be6
Binary files /dev/null and 
b/TalerWallet1/Assets.xcassets/AppIcon.appiconset/settings87.png differ
diff --git a/TalerWallet1/Assets.xcassets/AppIcon.appiconset/spotlight120.png 
b/TalerWallet1/Assets.xcassets/AppIcon.appiconset/spotlight120.png
new file mode 100644
index 0000000..260cb20
Binary files /dev/null and 
b/TalerWallet1/Assets.xcassets/AppIcon.appiconset/spotlight120.png differ
diff --git a/TalerWallet1/Assets.xcassets/AppIcon.appiconset/spotlight80.png 
b/TalerWallet1/Assets.xcassets/AppIcon.appiconset/spotlight80.png
new file mode 100644
index 0000000..591820e
Binary files /dev/null and 
b/TalerWallet1/Assets.xcassets/AppIcon.appiconset/spotlight80.png differ
diff --git 
a/TalerWallet1/Assets.xcassets/AppIconBackground.colorset/Contents.json 
b/TalerWallet1/Assets.xcassets/AppIconBackground.colorset/Contents.json
new file mode 100644
index 0000000..537c338
--- /dev/null
+++ b/TalerWallet1/Assets.xcassets/AppIconBackground.colorset/Contents.json
@@ -0,0 +1,38 @@
+{
+  "colors" : [
+    {
+      "color" : {
+        "color-space" : "srgb",
+        "components" : {
+          "alpha" : "1.000",
+          "blue" : "0.000",
+          "green" : "0.000",
+          "red" : "0.733"
+        }
+      },
+      "idiom" : "universal"
+    },
+    {
+      "appearances" : [
+        {
+          "appearance" : "luminosity",
+          "value" : "dark"
+        }
+      ],
+      "color" : {
+        "color-space" : "srgb",
+        "components" : {
+          "alpha" : "1.000",
+          "blue" : "0.000",
+          "green" : "0.000",
+          "red" : "0.733"
+        }
+      },
+      "idiom" : "universal"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}
diff --git a/Taler/Assets.xcassets/Contents.json 
b/TalerWallet1/Assets.xcassets/Contents.json
similarity index 100%
rename from Taler/Assets.xcassets/Contents.json
rename to TalerWallet1/Assets.xcassets/Contents.json
diff --git a/TalerWallet1/Assets.xcassets/Incoming.colorset/Contents.json 
b/TalerWallet1/Assets.xcassets/Incoming.colorset/Contents.json
new file mode 100644
index 0000000..875eed1
--- /dev/null
+++ b/TalerWallet1/Assets.xcassets/Incoming.colorset/Contents.json
@@ -0,0 +1,38 @@
+{
+  "colors" : [
+    {
+      "color" : {
+        "color-space" : "srgb",
+        "components" : {
+          "alpha" : "1.000",
+          "blue" : "0.000",
+          "green" : "0.634",
+          "red" : "0.144"
+        }
+      },
+      "idiom" : "universal"
+    },
+    {
+      "appearances" : [
+        {
+          "appearance" : "luminosity",
+          "value" : "dark"
+        }
+      ],
+      "color" : {
+        "color-space" : "srgb",
+        "components" : {
+          "alpha" : "1.000",
+          "blue" : "255",
+          "green" : "255",
+          "red" : "255"
+        }
+      },
+      "idiom" : "universal"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}
diff --git a/TalerWallet1/Assets.xcassets/Outgoing.colorset/Contents.json 
b/TalerWallet1/Assets.xcassets/Outgoing.colorset/Contents.json
new file mode 100644
index 0000000..a6fa511
--- /dev/null
+++ b/TalerWallet1/Assets.xcassets/Outgoing.colorset/Contents.json
@@ -0,0 +1,38 @@
+{
+  "colors" : [
+    {
+      "color" : {
+        "color-space" : "srgb",
+        "components" : {
+          "alpha" : "1.000",
+          "blue" : "0.001",
+          "green" : "0.063",
+          "red" : "0.693"
+        }
+      },
+      "idiom" : "universal"
+    },
+    {
+      "appearances" : [
+        {
+          "appearance" : "luminosity",
+          "value" : "dark"
+        }
+      ],
+      "color" : {
+        "color-space" : "srgb",
+        "components" : {
+          "alpha" : "1.000",
+          "blue" : "255",
+          "green" : "255",
+          "red" : "255"
+        }
+      },
+      "idiom" : "universal"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}
diff --git 
a/TalerWallet1/Assets.xcassets/PendingIncoming.colorset/Contents.json 
b/TalerWallet1/Assets.xcassets/PendingIncoming.colorset/Contents.json
new file mode 100644
index 0000000..a41d661
--- /dev/null
+++ b/TalerWallet1/Assets.xcassets/PendingIncoming.colorset/Contents.json
@@ -0,0 +1,38 @@
+{
+  "colors" : [
+    {
+      "color" : {
+        "color-space" : "srgb",
+        "components" : {
+          "alpha" : "1.000",
+          "blue" : "0.000",
+          "green" : "0.343",
+          "red" : "0.078"
+        }
+      },
+      "idiom" : "universal"
+    },
+    {
+      "appearances" : [
+        {
+          "appearance" : "luminosity",
+          "value" : "dark"
+        }
+      ],
+      "color" : {
+        "color-space" : "srgb",
+        "components" : {
+          "alpha" : "1.000",
+          "blue" : "255",
+          "green" : "255",
+          "red" : "255"
+        }
+      },
+      "idiom" : "universal"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}
diff --git 
a/TalerWallet1/Assets.xcassets/PendingOutgoing.colorset/Contents.json 
b/TalerWallet1/Assets.xcassets/PendingOutgoing.colorset/Contents.json
new file mode 100644
index 0000000..b89ad7e
--- /dev/null
+++ b/TalerWallet1/Assets.xcassets/PendingOutgoing.colorset/Contents.json
@@ -0,0 +1,38 @@
+{
+  "colors" : [
+    {
+      "color" : {
+        "color-space" : "srgb",
+        "components" : {
+          "alpha" : "1.000",
+          "blue" : "0.001",
+          "green" : "0.031",
+          "red" : "0.343"
+        }
+      },
+      "idiom" : "universal"
+    },
+    {
+      "appearances" : [
+        {
+          "appearance" : "luminosity",
+          "value" : "dark"
+        }
+      ],
+      "color" : {
+        "color-space" : "srgb",
+        "components" : {
+          "alpha" : "1.000",
+          "blue" : "255",
+          "green" : "255",
+          "red" : "255"
+        }
+      },
+      "idiom" : "universal"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}
diff --git a/TalerWallet1/Assets.xcassets/Taler-logo.imageset/Contents.json 
b/TalerWallet1/Assets.xcassets/Taler-logo.imageset/Contents.json
new file mode 100644
index 0000000..eb70695
--- /dev/null
+++ b/TalerWallet1/Assets.xcassets/Taler-logo.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+  "images" : [
+    {
+      "filename" : "Taler-logo.jpg",
+      "idiom" : "universal"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}
diff --git a/TalerWallet1/Assets.xcassets/Taler-logo.imageset/Taler-logo.jpg 
b/TalerWallet1/Assets.xcassets/Taler-logo.imageset/Taler-logo.jpg
new file mode 100644
index 0000000..d499bdf
Binary files /dev/null and 
b/TalerWallet1/Assets.xcassets/Taler-logo.imageset/Taler-logo.jpg differ
diff --git 
a/TalerWallet1/Assets.xcassets/TalerLogoBackground.colorset/Contents.json 
b/TalerWallet1/Assets.xcassets/TalerLogoBackground.colorset/Contents.json
new file mode 100644
index 0000000..d791e44
--- /dev/null
+++ b/TalerWallet1/Assets.xcassets/TalerLogoBackground.colorset/Contents.json
@@ -0,0 +1,38 @@
+{
+  "colors" : [
+    {
+      "color" : {
+        "color-space" : "srgb",
+        "components" : {
+          "alpha" : "1.000",
+          "blue" : "0.824",
+          "green" : "0.796",
+          "red" : "0.784"
+        }
+      },
+      "idiom" : "universal"
+    },
+    {
+      "appearances" : [
+        {
+          "appearance" : "luminosity",
+          "value" : "dark"
+        }
+      ],
+      "color" : {
+        "color-space" : "srgb",
+        "components" : {
+          "alpha" : "1.000",
+          "blue" : "0.824",
+          "green" : "0.796",
+          "red" : "0.784"
+        }
+      },
+      "idiom" : "universal"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}
diff --git a/TalerWallet1/Backend/Transaction.swift 
b/TalerWallet1/Backend/Transaction.swift
new file mode 100644
index 0000000..86a0ae9
--- /dev/null
+++ b/TalerWallet1/Backend/Transaction.swift
@@ -0,0 +1,314 @@
+/*
+ * 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
+
+enum TransactionTypeError: Error {
+    case unknownTypeError
+}
+
+/// Different types of transactions.
+enum TransactionType: Codable {
+    case withdrawal
+    case payment
+    case refund
+    case tip
+    case refresh
+    
+    init(from decoder: Decoder) throws {
+        let value = try decoder.singleValueContainer()
+        let str = try value.decode(String.self)
+        let codingNames = [
+            "TransactionWithdrawal" : TransactionType.withdrawal,
+            "TransactionPayment" : TransactionType.payment,
+            "TransactionRefund" : TransactionType.refund,
+            "TransactionTip" : TransactionType.tip,
+            "TransactionRefresh" : TransactionType.refresh
+        ]
+        if let type = codingNames[str] {
+            self = type
+        } else {
+            throw TransactionTypeError.unknownTypeError
+        }
+    }
+    
+    func encode(to encoder: Encoder) throws {
+        var value = encoder.singleValueContainer()
+        switch self {
+            case .withdrawal:
+                try value.encode("TransactionWithdrawal")
+            case .payment:
+                try value.encode("TransactionPayment")
+            case .refund:
+                try value.encode("TransactionRefund")
+            case .tip:
+                try value.encode("TransactionTip")
+            case .refresh:
+                try value.encode("TransactionRefresh")
+        }
+    }
+}
+
+enum TransactionDecodingError: Error {
+    case invalidStringValue
+}
+
+/// Details for a manual withdrawal.
+struct ManualWithdrawalDetails: Codable {
+    /// The payto URIs that the exchange supports.
+    var exchangePaytoUris: [String]
+    
+    /// The public key of the newly created reserve.
+    var reservePub: String
+}
+
+/// Details for a bank-integrated withdrawal.
+struct BankIntegratedWithdrawalDetails: Codable {
+    /// Whether the bank has confirmed the withdrawal.
+    var confirmed: Bool
+
+    /// The public key of the newly created reserve.
+    var reservePub: String?
+
+    /// URL for user-initiated confirmation
+    var bankConfirmationUrl: String?
+}
+
+/// A withdrawal transaction.
+struct TransactionWithdrawal: Decodable {
+    enum WithdrawalDetails {
+        case manual(ManualWithdrawalDetails)
+        case bankIntegrated(BankIntegratedWithdrawalDetails)
+    }
+    
+    /// The exchange that was withdrawn from.
+    var exchangeBaseUrl: String
+    
+    /// The amount of the withdrawal, including fees.
+    var amountRaw: Amount
+    
+    /// The amount that will be added to the withdrawer's account.
+    var amountEffective: Amount
+    
+    /// The details of the withdrawal.
+    var withdrawalDetails: WithdrawalDetails
+    
+    init(from decoder: Decoder) throws {
+        enum CodingKeys: String, CodingKey {
+            case exchangeBaseUrl
+            case amountRaw
+            case amountEffective
+            case withdrawalDetails
+            case type
+            case exchangePaytoUris
+            case reservePub
+            case confirmed
+            case bankConfirmationUrl
+        }
+        
+        let value = try decoder.container(keyedBy: CodingKeys.self)
+        self.exchangeBaseUrl = try value.decode(String.self, forKey: 
.exchangeBaseUrl)
+        self.amountRaw = try value.decode(Amount.self, forKey: .amountRaw)
+        self.amountEffective = try value.decode(Amount.self, forKey: 
.amountEffective)
+        
+        let detail = try value.nestedContainer(keyedBy: CodingKeys.self, 
forKey: .withdrawalDetails)
+        let detailType = try detail.decode(String.self, forKey: .type)
+        if detailType == "manual-transfer" {
+            let paytoUris = try detail.decode([String].self, forKey: 
.exchangePaytoUris)
+            let reservePub = try detail.decode(String.self, forKey: 
.reservePub)
+            let manual = ManualWithdrawalDetails(exchangePaytoUris: paytoUris, 
reservePub: reservePub)
+            self.withdrawalDetails = .manual(manual)
+        } else if detailType == "taler-bank-integration-api" {
+            let confirmed = try detail.decode(Bool.self, forKey: .confirmed)
+            var bankConfirmationUrl: String? = nil
+            if detail.contains(.bankConfirmationUrl) {
+                bankConfirmationUrl = try detail.decode(String.self, forKey: 
.bankConfirmationUrl)
+            }
+            var reservePub : String? = nil
+            if detail.contains(.reservePub) {
+                reservePub = try detail.decode(String.self, forKey: 
.reservePub)
+            }
+            let bankDetails = BankIntegratedWithdrawalDetails(confirmed: 
confirmed, reservePub: reservePub,
+                                                              
bankConfirmationUrl: bankConfirmationUrl)
+            self.withdrawalDetails = .bankIntegrated(bankDetails)
+        } else {
+            throw TransactionDecodingError.invalidStringValue
+        }
+    }
+}
+#if DEBUG
+extension TransactionWithdrawal {       // for PreViews
+    init(url: String) {
+        self.exchangeBaseUrl = url
+        self.amountRaw = try! Amount(fromString: "Taler:5")
+        self.amountEffective = try! Amount(fromString: "Taler:4.8")
+        let bankDetails = BankIntegratedWithdrawalDetails(confirmed: true, 
reservePub: nil,
+                                                          bankConfirmationUrl: 
nil)
+        self.withdrawalDetails = .bankIntegrated(bankDetails)
+    }
+}
+#endif
+
+/// A payment transaction.
+struct TransactionPayment: Codable {
+    /// Additional information about the payment.
+    // TODO
+    
+    /// An identifier for the payment.
+    var proposalId: String
+    
+    /// The current status of the payment.
+    // TODO
+    
+    /// The amount that must be paid.
+    var amountRaw: Amount
+    
+    /// The amount that was paid.
+    var amountEffective: Amount
+}
+
+/// A refund transaction.
+struct TransactionRefund: Codable {
+    /// Identifier for the refund.
+    var refundedTransactionId: String
+    
+    /// Additional information about the refund
+    // TODO
+    
+    /// The amount that couldn't be applied because refund permissions expired.
+    var amountInvalid: Amount
+    
+    /// The amount refunded by the merchant.
+    var amountRaw: Amount
+    
+    /// The amount paid to the wallet after fees.
+    var amountEffective: Amount
+}
+
+/// A tip transaction.
+struct TransactionTip: Codable {
+    /// The current status of the tip.
+    // TODO
+    
+    /// The exchange that the tip will be withdrawn from
+    var exchangeBaseUrl: String
+    
+    /// More information about the merchant sending the tip.
+    // TODO
+    
+    /// The raw amount of the tip without fees.
+    var amountRaw: Amount
+    
+    /// The amount added to the recipient's wallet.
+    var amountEffective: Amount
+}
+
+/// A refresh transaction.
+struct TransactionRefresh: Codable {
+    /// The exchange that the coins are refreshed with.
+    var exchangeBaseUrl: String
+    
+    /// The raw amount to refresh.
+    var amountRaw: Amount
+    
+    /// The amount to be paid as fees for the refresh.
+    var amountEffective: Amount
+}
+
+/// A wallet transaction.
+struct Transaction: Decodable, Hashable {
+//    private let symLog = SymLogC(0)
+
+    var type: String
+    var amountRaw: Amount
+    var amountEffective: Amount
+    var transactionId: String
+    var timestamp: Timestamp
+    var extendedStatus: String
+    var pending: Bool
+    var frozen: Bool
+
+    var error: AnyCodable?
+    var exchangeBaseUrl: String?
+
+
+
+//    enum TransactionDetail {
+//        case withdrawal(TransactionWithdrawal)
+//    }
+    
+//    var detail: TransactionDetail
+    
+//    init(from decoder: Decoder) throws {
+//        enum CodingKeys: String, CodingKey {
+//            case transactionId
+//            case timestamp
+//            case pending
+//            case error
+//            case amountRaw
+//            case amountEffective
+//            case type
+//        }
+//
+//        let value = try decoder.container(keyedBy: CodingKeys.self)
+//        self.transactionId = try value.decode(String.self, forKey: 
.transactionId)
+//        self.timestamp = try value.decode(Timestamp.self, forKey: .timestamp)
+//        self.pending = try value.decode(Bool.self, forKey: .pending)
+//        if value.contains(.error) {
+//            self.error = try value.decode(AnyCodable.self, forKey: .error)
+//        }
+//        self.amountRaw = try value.decode(Amount.self, forKey: .amountRaw)
+//        self.amountEffective = try value.decode(Amount.self, forKey: 
.amountEffective)
+//
+//        let type = try value.decode(String.self, forKey: .type)
+//        if type == "withdrawal" {
+//            let withdrawDetail = try TransactionWithdrawal.init(from: 
decoder)
+//            self.detail = .withdrawal(withdrawDetail)
+//        } else {
+//            throw TransactionDecodingError.invalidStringValue
+//        }
+//        symLog.log("\(self)")
+//    }
+    
+    static func == (lhs: Transaction, rhs: Transaction) -> Bool {
+        return lhs.transactionId == rhs.transactionId
+    }
+    
+    func hash(into hasher: inout Hasher) {
+        transactionId.hash(into: &hasher)
+    }
+}
+
+#if DEBUG
+extension Transaction {             // for PreViews
+    init(id: String, time: Timestamp) {
+        self.type = "withdrawal"
+        self.amountRaw = try! Amount(fromString: "Taler:5")
+        self.amountEffective = try! Amount(fromString: "Taler:4.8")
+        self.transactionId = id
+        self.timestamp = time
+        self.extendedStatus = "done"
+        self.pending = false
+        self.frozen = false
+        self.error = nil
+        self.exchangeBaseUrl = "Exchange.Demo.Taler.net"
+//        let withdrawDetail = TransactionWithdrawal(url: 
"Exchange.Demo.Taler.net")
+//        self.detail = .withdrawal(withdrawDetail)
+    }
+}
+#endif
diff --git a/TalerWallet1/Backend/WalletBackendError.swift 
b/TalerWallet1/Backend/WalletBackendError.swift
new file mode 100644
index 0000000..cb0bfeb
--- /dev/null
+++ b/TalerWallet1/Backend/WalletBackendError.swift
@@ -0,0 +1,54 @@
+/*
+ * 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
+
+/// Errors for `WalletBackend`.
+enum WalletBackendError: Error {
+    /// An error that prevented the wallet from being initialized occurred.
+    case initializationError
+    case serializationError
+    case deserializationError
+    case walletCoreError
+}
+
+/// Information supplied by the backend describing an error.
+struct WalletBackendResponseError: Decodable {
+    /// Numeric error code defined defined in the GANA gnu-taler-error-codes 
registry.
+    var talerErrorCode: Int
+    
+    /// English description of the error code.
+    var talerErrorHint: String
+    
+    /// English diagnostic message that can give details for the instance of 
the error.
+    var message: String
+    
+    /// Error details, type depends on `talerErrorCode`.
+    var details: Data?
+}
+
+extension WalletCore {
+    static func serializeRequestError() -> WalletBackendResponseError {
+        return WalletBackendResponseError(talerErrorCode: -1, talerErrorHint: 
"Could not serialize request.", message: "")
+    }
+    
+    static func parseResponseError() -> WalletBackendResponseError {
+        return WalletBackendResponseError(talerErrorCode: -2, talerErrorHint: 
"Could not parse response.", message: "")
+    }
+    
+    static func parseFailureError() -> WalletBackendResponseError {
+        return WalletBackendResponseError(talerErrorCode: -3, talerErrorHint: 
"Could not parse error detail.", message: "")
+    }
+}
diff --git a/TalerWallet1/Backend/WalletBackendRequest.swift 
b/TalerWallet1/Backend/WalletBackendRequest.swift
new file mode 100644
index 0000000..ad48879
--- /dev/null
+++ b/TalerWallet1/Backend/WalletBackendRequest.swift
@@ -0,0 +1,434 @@
+/*
+ * 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
+
+/// A request sent to the wallet backend.
+struct WalletBackendRequest: Encodable {
+    /// The operation name of the request.
+    var operation: String
+    
+    /// The body of the request as JSON.
+    var args: AnyEncodable
+}
+
+protocol WalletBackendFormattedRequest {
+    associatedtype Args: Encodable
+    associatedtype Response: Decodable
+    
+    func operation() -> String
+    func args() -> Args
+}
+// MARK: -
+
+
+/// A billing or mailing location.
+struct Location: Codable {
+    var country: String?
+    var country_subdivision: String?
+    var district: String?
+    var town: String?
+    var town_location: String?
+    var post_code: String?
+    var street: String?
+    var building_name: String?
+    var building_number: String?
+    var address_lines: [String]?
+}
+
+/// Information identifying a merchant.
+struct Merchant: Codable {
+    var name: String
+    var address: Location?
+    var jurisdiction: Location?
+}
+
+/// A tax made on a payment.
+struct Tax: Codable {
+    var name: String
+    var tax: Amount
+}
+
+/// A product being purchased from a merchant.
+struct Product: Codable {
+    var product_id: String?
+    var description: String
+    // description_i18n?
+    var quantity: Int
+    var unit: String
+    var price: Amount?
+    var image: String // URL to a product image
+    var taxes: [Tax]?
+    var delivery_date: Timestamp?
+}
+
+/// Brief information about an order.
+struct OrderShortInfo: Codable {
+    var orderId: String
+    var merchant: Merchant
+    var summary: String
+    // summary_i18n?
+    var products: [Product]
+    var fulfillmentUrl: String?
+    var fulfillmentMessage: String?
+    // fulfillmentMessage_i18n?
+}
+
+
+/// 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)
+    }
+}
+
+/// A request to process a refund.
+struct WalletBackendApplyRefundRequest: WalletBackendFormattedRequest {
+    var talerRefundUri: String
+    
+    struct Args: Encodable {
+        var talerRefundUri: String
+    }
+    
+    struct Response: Decodable {
+        var contractTermsHash: String
+        var amountEffectivePaid: Amount
+        var amountRefundGranted: Amount
+        var amountRefundGone: Amount
+        var pendingAtExchange: Bool
+        var info: OrderShortInfo
+    }
+    
+    func operation() -> String {
+        return "applyRefund"
+    }
+    
+    func args() -> Args {
+        return Args(talerRefundUri: talerRefundUri)
+    }
+}
+
+
+/// A request to force update an exchange.
+struct WalletBackendForceUpdateRequest: WalletBackendFormattedRequest {
+    var exchangeBaseUrl: String
+    
+    struct Args: Encodable {
+        var exchangeBaseUrl: String
+    }
+    
+    struct Response: Decodable {}
+    
+    func operation() -> String {
+        return "addRequest"
+    }
+    
+    func args() -> Args {
+        return Args(exchangeBaseUrl: exchangeBaseUrl)
+    }
+}
+
+
+
+
+/// 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
+    var amount: Amount
+    
+    struct Args: Encodable {
+        var depositPayToUri: String
+        var amount: Amount
+    }
+    
+    struct Response: Decodable {
+        var depositGroupId: String
+    }
+    
+    func operation() -> String {
+        return "createDepositGroup"
+    }
+    
+    func args() -> Args {
+        return Args(depositPayToUri: depositePayToUri, amount: amount)
+    }
+}
+
+/// A request to get information about a payment request.
+struct WalletBackendPreparePayRequest: WalletBackendFormattedRequest {
+    var talerPayUri: String
+    
+    struct Args: Encodable {
+        var talerPayUri: String
+    }
+    
+    struct Response: Decodable {}
+    
+    func operation() -> String {
+        return "preparePay"
+    }
+    
+    func args() -> Args {
+        return Args(talerPayUri: talerPayUri)
+    }
+}
+
+/// A request to confirm a payment.
+struct WalletBackendConfirmPayRequest: WalletBackendFormattedRequest {
+    var proposalId: String
+    
+    struct Args: Encodable {
+        var proposalId: String
+    }
+    
+    struct Response: Decodable {}
+    
+    func operation() -> String {
+        return "confirmPay"
+    }
+    
+    func args() -> Args {
+        return Args(proposalId: proposalId)
+    }
+}
+
+/// 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)
+    }
+}
+
+/// A request to accept a tip.
+struct WalletBackendAcceptTipRequest: WalletBackendFormattedRequest {
+    var walletTipId: String
+    
+    struct Args: Encodable {
+        var walletTipId: String
+    }
+    
+    struct Response: Decodable {}
+    
+    func operation() -> String {
+        return "acceptTip"
+    }
+    
+    func args() -> Args {
+        return Args(walletTipId: walletTipId)
+    }
+}
+
+/// A request to abort a failed payment.
+struct WalletBackendAbortFailedPaymentRequest: WalletBackendFormattedRequest {
+    var proposalId: String
+    
+    struct Args: Encodable {
+        var proposalId: String
+    }
+    
+    struct Response: Decodable {}
+    
+    func operation() -> String {
+        return "abortFailedPayWithRefund"
+    }
+    
+    func args() -> Args {
+        return Args(proposalId: proposalId)
+    }
+}
+
+
+struct IntegrationTestArgs: Codable {
+    var exchangeBaseUrl: String
+    var bankBaseUrl: String
+    var merchantBaseUrl: String
+    var merchantApiKey: String
+    var amountToWithdraw: String
+    var amountToSpend: String
+}
+
+/// A request to run a basic integration test.
+struct WalletBackendRunIntegrationTestRequest: WalletBackendFormattedRequest {
+    var integrationTestArgs: IntegrationTestArgs
+    
+    typealias Args = IntegrationTestArgs
+    
+    struct Response: Decodable {}
+    
+    func operation() -> String {
+        return "runIntegrationTest"
+    }
+    
+    func args() -> Args {
+        return integrationTestArgs
+    }
+}
+
+struct TestPayArgs: Codable {
+    var merchantBaseUrl: String
+    var merchantApiKey: String
+    var amount: String
+    var summary: String
+}
+
+/// A request to make a test payment.
+struct WalletBackendTestPayRequest: WalletBackendFormattedRequest {
+    var testPayArgs: TestPayArgs
+    
+    typealias Args = TestPayArgs
+    
+    struct Response: Decodable {}
+    
+    func operation() -> String {
+        return "testPay"
+    }
+    
+    func args() -> Args {
+        return testPayArgs
+    }
+}
+
+struct Coin: Codable {
+    var denom_pub: String
+    var denom_pub_hash: String
+    var denom_value: String
+    var coin_pub: String
+    var exchange_base_url: String
+    var remaining_value: String
+    var refresh_parent_coin_pub: String
+    var withdrawal_reserve_pub: String
+    var coin_suspended: Bool
+}
+
+/// A request to dump all coins to JSON.
+struct WalletBackendDumpCoinsRequest: WalletBackendFormattedRequest {
+    struct Args: Encodable {
+        
+    }
+    
+    struct Response: Decodable {
+        var coins: [Coin]
+    }
+    
+    func operation() -> String {
+        return "dumpCoins"
+    }
+    
+    func args() -> Args {
+        return Args()
+    }
+}
+
+/// A request to suspend or unsuspend a coin.
+struct WalletBackendSuspendCoinRequest: WalletBackendFormattedRequest {
+    var coinPub: String
+    var suspended: Bool
+    
+    struct Args: Encodable {
+        var coinPub: String
+        var suspended: Bool
+    }
+    
+    struct Response: Decodable {}
+    
+    func operation() -> String {
+        return "setCoinSuspended"
+    }
+    
+    func args() -> Args {
+        return Args(coinPub: coinPub, suspended: suspended)
+    }
+}
+
+
diff --git a/TalerWallet1/Backend/WalletCore.swift 
b/TalerWallet1/Backend/WalletCore.swift
new file mode 100644
index 0000000..ff67b15
--- /dev/null
+++ b/TalerWallet1/Backend/WalletCore.swift
@@ -0,0 +1,262 @@
+/*
+ * 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              // FOUNDATION has no AppStorage
+import AnyCodable
+import FTalerWalletcore
+import SymLog
+
+/// Delegate for the wallet backend.
+protocol WalletBackendDelegate {
+    /// Called when the backend interface receives a message it does not know 
how to handle.
+    func walletBackendReceivedUnknownMessage(_ walletCore: WalletCore, 
message: String)
+}
+
+/// An interface to the wallet backend.
+class WalletCore: QuickjsMessageHandler {
+    private let symLog = SymLogC()
+
+    private var quickjs: Quickjs
+    private var requestsMade: UInt          // counter for array of completion 
closures
+    private var completions: [UInt : (UInt, 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
+
+    private struct FullRequest: Encodable {
+        let operation: String
+        let id: UInt
+        let args: AnyEncodable
+    }
+    
+    private struct FullResponse: Decodable {
+        let type: String
+        let operation: String
+        let id: UInt
+        let result: AnyCodable
+    }
+    
+    struct FullError: Decodable {
+        let type: String
+        let operation: String
+        let id: UInt
+        let error: WalletBackendResponseError
+    }
+
+    var lastError: FullError?
+
+    deinit {
+        symLog.log()
+    // TODO: send shutdown message to talerWalletInstance
+//        quickjs.waitStopped()
+    }
+
+    init() throws {
+        requestsMade = 0
+        quickjs = Quickjs()
+        quickjs.messageHandler = self
+    }
+}
+// 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 {
+            do {
+                let decoded = try JSONDecoder().decode(FullError.self, from: 
messageData)
+                symLog.log(decoded)
+                completion(id, nil, decoded.error)
+            } catch {
+                symLog.log(responseDict)        // TODO: error
+                completion(id, nil, WalletCore.parseFailureError())
+            }
+        } 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())
+            }
+        }
+    }
+
+    private func handleNotification(dict responseDict: [String : Any]) 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 {
+                            try await 
Controller.shared.balancesModel.fetchBalances()
+                        } catch {
+                            // TODO: show error
+                            symLog.log(error.localizedDescription)
+                        }
+                    }
+                    break
+                case "proposal-accepted":
+                    symLog.log(payload)
+                    break
+                case "proposal-downloaded":
+                    symLog.log(payload)
+                    break
+                case "waiting-for-retry":
+                    // Bla Bla Bla
+                    break
+                case "exchange-added":
+                    symLog.log(payload)
+                    break
+                case "refresh-started":
+                    symLog.log(payload)
+                    break
+                case "refresh-melted":
+                    symLog.log(payload)
+                    break
+                case "refresh-revealed":
+                    symLog.log(payload)
+                    break
+                case "reserve-registered-with-bank":
+                    symLog.log(payload)
+                    break
+                default:
+                    symLog.log(payload)
+                    break
+            }
+        } catch let error {
+            symLog.log("Error \(error) parsing notification: \(responseDict)") 
   // TODO: .error
+        // TODO: if DevMode then should log into file for user
+        }
+    }
+
+    /// here not only responses, but also notifications from wallet-core will 
be received
+    func handleMessage(message: String) {
+        do {
+            var asyncDelay = 0
+            if let delay = developDelay {
+                if delay {
+                    asyncDelay = 2
+                }
+            }
+            if asyncDelay > 0 {
+                symLog.log(message)
+                symLog.log("...going to sleep for \(asyncDelay) seconds...")
+                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 {
+                case "error":
+                    try handleResponse(dict: responseDict, data: messageData, 
isError: true)
+                case "response":
+                    try handleResponse(dict: responseDict, data: messageData)
+                case "notification":
+                    try handleNotification(dict: responseDict)
+                case "tunnelHttp":          // TODO: Handle tunnelHttp
+                    break
+                default:
+                    symLog.log("Unknown response type: \(responseDict)")    // 
TODO: .error
+                    throw WalletBackendError.deserializationError
+            }
+        } catch {
+            delegate?.walletBackendReceivedUnknownMessage(self, message: 
message)
+        }
+    }
+    
+    private func sendRequest(request: WalletBackendRequest, completionHandler: 
@escaping (UInt, 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());
+        }
+    }
+
+    /// call this to send requests to wallet-core
+    func sendFormattedRequest<T: WalletBackendFormattedRequest>
+            (request: T, completionHandler: @escaping (T.Response?, 
WalletBackendResponseError?) -> Void)
+    {
+        let reqData = WalletBackendRequest(operation: request.operation(),
+                                                args: 
AnyEncodable(request.args()))
+        sendRequest(request: reqData) { (id: UInt, result: Data?, err: 
WalletBackendResponseError?) in
+            guard let json = result else { completionHandler(nil, err); return 
}
+            do {
+                let decoded = try JSONDecoder().decode(T.Response.self, from: 
json)
+                completionHandler(decoded, err)
+            } catch {
+                completionHandler(nil, WalletCore.parseResponseError())
+            }
+        }
+    }
+}
+// MARK: -  async / await function
+extension WalletCore {
+    /// send async requests to wallet-core
+    func sendFormattedRequest<T: WalletBackendFormattedRequest> (request: T) 
async throws -> (T.Response, UInt) {
+        let reqData = WalletBackendRequest(operation: request.operation(),
+                                           args: AnyEncodable(request.args()))
+        return try await withCheckedThrowingContinuation { continuation in
+            sendRequest(request: reqData) { id, result, error in
+                if let json = result {
+                    do {
+                        let decoded = try 
JSONDecoder().decode(T.Response.self, from: json)
+                        continuation.resume(returning: (decoded, id))
+                    } catch {
+                        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)
+                    }
+                } else {
+                    if let error = error {
+                        self.lastError = FullError(type: "error", operation: 
request.operation(), id: id, error: error)
+                    } else {
+                        self.lastError = nil
+                    }
+                    continuation.resume(throwing: 
WalletBackendError.walletCoreError)
+                }
+            }
+        }
+    }
+}
diff --git a/TalerWallet1/Controllers/Controller.swift 
b/TalerWallet1/Controllers/Controller.swift
new file mode 100644
index 0000000..513472a
--- /dev/null
+++ b/TalerWallet1/Controllers/Controller.swift
@@ -0,0 +1,128 @@
+/*
+ * 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
+
+enum BackendState {
+    case none
+    case instantiated
+    case initing
+    case ready
+    case error
+}
+
+enum UrlCommand {
+    case unknown
+    case withdraw
+    case pay
+}
+
+class Controller: ObservableObject {
+    public static var 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
+
+    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 {
+        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
+                throw error
+            }
+        } else {
+            symLog.log("Yikes\(logSymbol(-1)) wallet-core already 
initialized")     // TODO: .warning
+        }
+    }
+}
+
+// MARK: -
+extension Controller {
+    func openURL(_ url:URL) -> UrlCommand {
+        guard let scheme = url.scheme else {return UrlCommand.unknown}
+        var uncrypted = false
+        switch scheme {
+            case "taler+http":
+                uncrypted = true
+                fallthrough
+            case "taler":
+                return talerScheme(url, uncrypted)
+            case "payto":
+                messageForSheet = url.absoluteString
+                return paytoScheme(url)
+            default:
+                symLog.log("unknown scheme: <\(scheme)>")       // should 
never happen
+        }
+        return UrlCommand.unknown
+    }
+}
+// MARK: -
+extension Controller {
+    func paytoScheme(_ url:URL) -> UrlCommand {
+        let logItem = "scheme payto:// is not yet implemented"
+        // TODO: write logItem to somewhere in Debug section of SettingsView
+        symLog.log(logItem)        // TODO: symLog.error(logItem)
+        return UrlCommand.unknown
+    }
+    
+    func talerScheme(_ url:URL,_ uncrypted: Bool = false) -> UrlCommand {
+        guard let command = url.host else {return UrlCommand.unknown}
+        if uncrypted {
+            print("uncrypted")
+            // TODO: uncrypted
+        }
+        switch command {
+            case "withdraw":
+                return UrlCommand.withdraw
+            case "pay":
+                return UrlCommand.pay
+            default:
+                symLog.log("unknown command taler://\(command)")
+        }
+        return UrlCommand.unknown
+    }
+}
diff --git a/TalerWallet1/Controllers/TalerWallet1App.swift 
b/TalerWallet1/Controllers/TalerWallet1App.swift
new file mode 100644
index 0000000..11f8645
--- /dev/null
+++ b/TalerWallet1/Controllers/TalerWallet1App.swift
@@ -0,0 +1,81 @@
+/*
+ * 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/>
+ */
+import BackgroundTasks
+import SwiftUI
+import SymLog
+#if DEBUG
+let schemes: Set = ["taler", "payto", "taler+http"]
+#else
+let schemes: Set = ["taler", "payto"]
+#endif
+
+@main
+struct TalerWallet1App: App {
+    private let symLog = SymLogV(0)
+    @Environment(\.scenePhase) private var phase
+
+    // our main controller
+    @StateObject private var controller = Controller.shared
+
+    func scheduleAppRefresh() {
+        let request = BGAppRefreshTaskRequest(identifier: "net.taler.refresh")
+        request.earliestBeginDate = .now.addingTimeInterval(24 * 3600)
+        try? BGTaskScheduler.shared.submit(request)
+    }
+
+    var body: some Scene {
+        WindowGroup {
+            symLog { ContentView()
+                    .environmentObject(controller)
+                    .handlesExternalEvents(preferring: ["*"], allowing: ["*"])
+                    .task {
+                        symLog.log("task -> initWalletCore")
+                        try? await controller.initWalletCore()
+                        symLog.log("task done")
+                    }
+            }
+        }
+        .onChange(of: phase) { newPhase in
+            switch newPhase {
+                case .background: scheduleAppRefresh()
+                default: break
+            }
+        }
+//        if #available(iOS 16.0, *) {
+//            .backgroundTask(.appRefresh("net.taler.refresh")) {
+//                symLog.log("backgroundTask running")
+//#if 0
+//                let request = URLRequest(url: URL(string: "your_backend")!)
+//                guard let data = try? await URLSession.shared.data(for: 
request).0 else {
+//                    return
+//                }
+//                
+//                let decoder = JSONDecoder()
+//                guard let products = try? decoder.decode([Product].self, 
from: data) else {
+//                    return
+//                }
+//                
+//                if !products.isEmpty && !Task.isCancelled {
+//                    await notifyUser(for: products)
+//                }
+//#endif
+//            }
+//        } else {
+//            // Fallback on earlier versions
+//        }
+
+    }
+}
diff --git a/TalerWallet1/Helper/TalerDater.swift 
b/TalerWallet1/Helper/TalerDater.swift
new file mode 100644
index 0000000..5a7abdb
--- /dev/null
+++ b/TalerWallet1/Helper/TalerDater.swift
@@ -0,0 +1,102 @@
+/*
+ * 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
+
+public class TalerDater: DateFormatter {
+    public static var shared = TalerDater()
+
+    static func relativeDate(from: TimeInterval) -> String? {
+        if from > 0 {       // transactions should always be in the past
+            let minute = from / 60.0                // from is in seconds
+            if minute < 1 { return "Right now" }
+            if minute < 2 { return "1 minute ago" }
+            if minute < 55 { return "\(Int(minute)) minutes ago" }
+            if minute < 60 { return "About an hour ago" }
+            if minute < 80 { return "1 hour ago" }
+            if minute < 105 { return "About 1½ hours ago" }
+            if minute < 125 { return "About 2 hours ago" }
+            let hour = minute / 60.0
+            let calendar = Calendar.current
+            let now = Date.now
+            let currHour = Double(calendar.component(.hour, from: now))
+            let currMin = Double(calendar.component(.minute, from: now))
+            let currTime = currHour + currMin/60
+            if hour < currTime { return "\(Int(hour)) hours ago" }
+            if hour < currTime + 24 { return "Yesterday" }
+            let day = (hour - currTime) / 24.0
+            if day < 7 { return "\(Int(day+1)) days ago" }
+            if day < 14 { return "More than a week ago" }
+            // will fall thru...
+            return nil
+        } else {            // Yikes! transaction date is in the future
+            return nil
+        }
+    }
+
+    /// produces a random date string between `now` and m+h+d (edit values 
after 60x)
+    public static func randomDateStr() -> String {
+        let m =       60*15
+        let h =    60*60*9
+        let d = 24*60*60*22
+        let t = m+h+d
+        let randomTime = Int.random(in:1...t)
+        if let randomDateStr = relativeDate(from: Double(randomTime)) {
+            return randomDateStr
+        } else {    // t is too large for a relative date
+                    // return absolute date with random locale
+            let localeStr = (randomTime&1 == 1) ? "de_DE" : "en_US"
+            shared.locale = NSLocale(localeIdentifier: localeStr) as Locale
+            let randomDate = Date(timeIntervalSinceNow: Double(-t))
+            return shared.string(from: randomDate)
+        }
+    }
+
+    /// converts a timestamp into a formatted date string
+    public static func dateString(from: Timestamp, relative: Bool = false) -> 
String {
+        do {
+            let milliseconds = try from.milliseconds()
+            let date = Date(milliseconds: milliseconds)
+            if relative {
+                let now = Date.now
+                let timeInterval = now.timeIntervalSince(date)
+                if let relativeDate = relativeDate(from: timeInterval) {
+                    return relativeDate
+                }
+            }
+            return shared.string(from: date)
+        } catch {
+            return "Never"
+        }
+    }
+
+    public static func dateString() -> String {
+        return shared.string(from: Date())
+    }
+
+    private override init() {
+        super.init()
+        self.setLocalizedDateFormatFromTemplate("EEEdMMM")        // 
abbreviated day of week
+        self.dateStyle = .medium
+        self.timeStyle = .short
+//        self.timeZone = TimeZone(abbreviation: "UTC")         // UTC prints 
GMT
+//        self.dateFormat = "z yyyy-MM-dd HH:mm"                // "GMT 
2022-11-09 18:00"
+    }
+
+    required init?(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+}
diff --git a/Taler/TalerApp.swift b/TalerWallet1/Helper/TalerStrings.swift
similarity index 72%
rename from Taler/TalerApp.swift
rename to TalerWallet1/Helper/TalerStrings.swift
index 2eb3154..b642798 100644
--- a/Taler/TalerApp.swift
+++ b/TalerWallet1/Helper/TalerStrings.swift
@@ -1,6 +1,6 @@
 /*
  * This file is part of GNU Taler
- * (C) 2021 Taler Systems S.A.
+ * (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
@@ -13,14 +13,16 @@
  * 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 SwiftUI
+extension StringProtocol {
 
-@main
-struct TalerApp: App {
-    var body: some Scene {
-        WindowGroup {
-            ContentView()
+    func trimURL() -> String {
+        if let url = URL(string: String(self)) {
+            if let host = url.host {
+                return host
+            }
         }
+        return String(self)
     }
 }
diff --git a/TalerWallet1/Helper/View+dismissTop.swift 
b/TalerWallet1/Helper/View+dismissTop.swift
new file mode 100644
index 0000000..bf6721e
--- /dev/null
+++ b/TalerWallet1/Helper/View+dismissTop.swift
@@ -0,0 +1,41 @@
+/*
+ * 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
+
+/// This is just a workaround for a SwiftUI bug
+/// A presented sheet (SwiftUI view) doesn't always close when calling 
"dismiss()" provided by @Environment(\.dismiss),
+/// so we are walking the view stack to find the top presentedViewController 
(UIKit) and dismiss it.
+extension View {
+    public func dismissTop(animated: Bool = true) {
+        let windows = UIApplication.shared.connectedScenes.compactMap {
+            ($0 as? UIWindowScene)?.keyWindow       // TODO: iPad might have 
more than 1 window
+        }
+        if var topController = windows.first?.rootViewController {
+            var gotPresented = false
+            while let presentedViewController = 
topController.presentedViewController {
+                topController = presentedViewController
+                gotPresented = true
+            }
+            if gotPresented {
+                topController.dismiss(animated: animated)
+            } else {
+                print("Yikes❗️ Trying to dismiss the rootViewController!")
+            }
+        } else {
+            print("Yikes❗️ There is no window/rootViewController!")
+        }
+    }
+}
diff --git a/TalerWallet1/Model/ExchangeTestModel.swift 
b/TalerWallet1/Model/ExchangeTestModel.swift
new file mode 100644
index 0000000..f73592a
--- /dev/null
+++ b/TalerWallet1/Model/ExchangeTestModel.swift
@@ -0,0 +1,138 @@
+/*
+ * 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 EXCHANGEBASEURL = "https://exchange.demo.taler.net/";
+fileprivate let BANKBASEURL = "https://bank.demo.taler.net/";
+fileprivate let BANKACCESSAPIBASEURL = 
"https://bank.demo.taler.net/demobanks/default/access-api/";
+fileprivate let MERCHANTBASEURL = "https://backend.demo.taler.net/";
+fileprivate let MERCHANTAUTHTOKEN = "secret-token:sandbox"
+
+// MARK: -
+class ExchangeTestModel: ObservableObject {
+    private let symLog = SymLogC(0)
+
+    var walletCore: WalletCore
+    
+    @Published var loading: Bool = false
+    
+    init(walletCore: WalletCore) {
+        self.walletCore = walletCore
+    }
+}
+// MARK: -
+extension ExchangeTestModel {
+    func loadTestKudos() {
+        loading = true
+        
+        let amount = Amount(currency: "KUDOS", integer: 11, fraction: 0)
+        let req = WalletBackendWithdrawTestBalance(amount: amount, 
bankBaseUrl: BANKBASEURL,
+                        exchangeBaseUrl: EXCHANGEBASEURL, 
bankAccessApiBaseUrl: BANKACCESSAPIBASEURL)
+        symLog.log("sending: \(req)")
+        walletCore.sendFormattedRequest(request: req) { response, err in
+            DispatchQueue.main.async {
+                self.loading = false
+                if let res = response {
+                    // TODO: ?
+                    self.symLog.log("received: \(res)")
+                } else {
+                    // TODO: Handle error
+                }
+            }
+        }
+    }
+
+    func runIntegrationTest() {
+        loading = true
+
+        let amountW = Amount(currency: "KUDOS", integer: 3, fraction: 0)
+        let amountS = Amount(currency: "KUDOS", integer: 1, fraction: 0)
+        let req = WalletBackendRunIntegration(amountToWithdraw: amountW,
+                                              amountToSpend: amountS,
+                                              bankBaseUrl: 
BANKACCESSAPIBASEURL,
+                                              exchangeBaseUrl: EXCHANGEBASEURL,
+                                              merchantBaseUrl: MERCHANTBASEURL,
+                                              merchantAuthToken: 
MERCHANTAUTHTOKEN
+        )
+        symLog.log("sending: \(req)")
+        walletCore.sendFormattedRequest(request: req) { response, err in
+            DispatchQueue.main.async {
+                self.loading = false
+                if let res = response {
+                    // TODO: ?
+                    self.symLog.log("received: \(res)")
+                } else {
+                    // TODO: Handle 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/WalletInitModel.swift 
b/TalerWallet1/Model/WalletInitModel.swift
new file mode 100644
index 0000000..e254d41
--- /dev/null
+++ b/TalerWallet1/Model/WalletInitModel.swift
@@ -0,0 +1,86 @@
+/*
+ * 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")
+    }
+
+    struct Args: Encodable {
+        var persistentStoragePath: String
+        var cryptoWorkerType: 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
new file mode 100644
index 0000000..d340278
--- /dev/null
+++ b/TalerWallet1/Model/WalletModel.swift
@@ -0,0 +1,55 @@
+/*
+ * 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
+
+fileprivate let ASYNCDELAY: UInt = 0   //set e.g to 6 or 9 seconds for 
debugging
+
+/// The "virtual" base class for all models
+class WalletModel: ObservableObject {
+    static func className() -> String {"\(self)"}
+    var symLog: SymLogC?
+    var walletCore: WalletCore
+
+    @Published var loading: Bool = false                // update view
+
+    init(walletCore: WalletCore) {
+        self.symLog = SymLogC(funcName: Self.className())
+        self.walletCore = walletCore
+    }
+
+    @MainActor func sendRequest<T: WalletBackendFormattedRequest> (_ request: 
T, _ delay: UInt = 0)
+      async throws -> T.Response {
+        loading = true                                  // enter progressView
+        do {
+            symLog?.log("sending: \(request)")
+            let (response, id) = try await 
walletCore.sendFormattedRequest(request: request)
+            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 {
+            throw error
+        }
+    }
+
+}
diff --git a/Taler/Preview Content/Preview Assets.xcassets/Contents.json 
b/TalerWallet1/Preview Content/Preview Assets.xcassets/Contents.json
similarity index 100%
rename from Taler/Preview Content/Preview Assets.xcassets/Contents.json
rename to TalerWallet1/Preview Content/Preview Assets.xcassets/Contents.json
diff --git a/TalerWallet1/Quickjs/quickjs.swift 
b/TalerWallet1/Quickjs/quickjs.swift
new file mode 100644
index 0000000..040bfcb
--- /dev/null
+++ b/TalerWallet1/Quickjs/quickjs.swift
@@ -0,0 +1,81 @@
+/*
+ * 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/>
+ */
+import Foundation
+
+import FTalerWalletcore
+
+public protocol QuickjsMessageHandler: AnyObject {
+    func handleMessage(message: String)
+}
+
+func notification_callback(userdata: Optional<UnsafeMutableRawPointer>,
+                           payload: Optional<UnsafePointer<Int8>>) {
+    let native = Unmanaged<Quickjs>.fromOpaque(userdata!).takeUnretainedValue()
+    let string = String(cString: payload!)
+    native.internalOnNotify(payload: string)
+}
+
+public class Quickjs {
+    var talerWalletInstance: OpaquePointer!
+    public weak var messageHandler: QuickjsMessageHandler?
+
+    public init() {
+        self.talerWalletInstance = TALER_WALLET_create()
+        TALER_WALLET_set_message_handler(talerWalletInstance,
+                                         notification_callback,
+                                         
Unmanaged.passUnretained(self).toOpaque())
+        TALER_WALLET_run(talerWalletInstance);
+    }
+
+    deinit {
+        // FIXME: TALER_WALLET_destroy
+//        TALER_WALLET_destroy(talerWalletInstance)
+    }
+
+
+    public func internalOnNotify(payload: String) {
+        if let handler = messageHandler {
+            handler.handleMessage(message: payload)
+        }
+    }
+
+//    public func notifyNative() {
+//        __notifyNative(instance)
+//    }
+
+//    public func evalNodeCode(source: String) {
+//        scheduleNodeThreadAsync {
+//            __makeCallbackNative(self.instance, source.cString(using: .utf8))
+//        }
+//    }
+
+    public func sendMessage(message: String) {
+        TALER_WALLET_send_request(talerWalletInstance, message)
+    }
+
+    /// Note: This *must* be called before releasing the object, or else the 
thread will keep going.
+//    public func waitStopped() {
+//        scheduleNodeThreadSync {
+//            self.stopped = true
+//        }
+//        thread.cancel()
+//    }
+
+//    public func putModuleCode(modName: String, code: String) {
+//        __putModuleCodeNative(self.instance, modName.cString(using: .utf8),
+//                              code.cString(using: .utf8))
+//    }
+}
diff --git a/TalerWallet1/Views/Balances/BalanceRow.swift 
b/TalerWallet1/Views/Balances/BalanceRow.swift
new file mode 100644
index 0000000..9c8aeee
--- /dev/null
+++ b/TalerWallet1/Views/Balances/BalanceRow.swift
@@ -0,0 +1,46 @@
+/*
+ * 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/BalancesModel.swift 
b/TalerWallet1/Views/Balances/BalancesModel.swift
new file mode 100644
index 0000000..80197d9
--- /dev/null
+++ b/TalerWallet1/Views/Balances/BalancesModel.swift
@@ -0,0 +1,73 @@
+/*
+ * 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/CurrenciesListView.swift 
b/TalerWallet1/Views/Balances/CurrenciesListView.swift
new file mode 100644
index 0000000..a40b074
--- /dev/null
+++ b/TalerWallet1/Views/Balances/CurrenciesListView.swift
@@ -0,0 +1,78 @@
+/*
+ * 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 {
+                        symLog?.log("refreshing")
+                        try? await reloadAction()       // TODO: catch error
+                    }
+            }
+        }
+    }
+}
diff --git a/TalerWallet1/Views/Balances/CurrencyView.swift 
b/TalerWallet1/Views/Balances/CurrencyView.swift
new file mode 100644
index 0000000..0978de0
--- /dev/null
+++ b/TalerWallet1/Views/Balances/CurrencyView.swift
@@ -0,0 +1,58 @@
+/*
+ * 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
new file mode 100644
index 0000000..d6321d2
--- /dev/null
+++ b/TalerWallet1/Views/Balances/PendingRow.swift
@@ -0,0 +1,61 @@
+/*
+ * 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/Taler/Model/BackendManager.swift 
b/TalerWallet1/Views/Balances/WalletEmptyView.swift
similarity index 51%
copy from Taler/Model/BackendManager.swift
copy to TalerWallet1/Views/Balances/WalletEmptyView.swift
index d0c262b..72d5192 100644
--- a/Taler/Model/BackendManager.swift
+++ b/TalerWallet1/Views/Balances/WalletEmptyView.swift
@@ -13,18 +13,32 @@
  * 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 Foundation
+struct WalletEmptyView: View {
 
-class BackendManager: ObservableObject {
-    var backend: WalletBackend
-    
-    @Published var exchangeManager: ExchangeManager
-    @Published var pendingManager: PendingManager
-    
-    init() {
-        self.backend = try! WalletBackend()
-        self.exchangeManager = ExchangeManager(_backend: self.backend)
-        self.pendingManager = PendingManager(_backend: self.backend)
+    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
new file mode 100644
index 0000000..b4b8b9f
--- /dev/null
+++ b/TalerWallet1/Views/Exchange/ExchangeListView.swift
@@ -0,0 +1,103 @@
+/*
+ * 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 ExchangeListView: View {
+    private let symLog = SymLogV()
+    let navTitle = "Exchanges"
+
+    @ObservedObject var viewModel: ExchangeModel
+
+    var body: some View {
+        let reloadAction = viewModel.updateList
+        VStack {
+            if viewModel.exchanges == nil {
+                symLog { LoadingView(backButtonHidden: false) }
+            } else {
+                Content(symLog: symLog, viewModel: viewModel, reloadAction: 
reloadAction)
+                    .navigationTitle(navTitle)
+            }
+        }.task {
+            symLog.log(".task")
+            do {
+                try await reloadAction()
+            } catch {
+                // TODO: show error
+                symLog.log(error.localizedDescription)
+            }
+        }
+    }
+}
+// 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/";
+
+        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
+                }
+            }
+        }
+
+        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 {
+                            symLog.log("refreshing")
+                            do {
+                                try await reloadAction()
+                            } catch {
+                                // TODO: catch error
+                                symLog.log(error.localizedDescription)
+                            }
+                        }
+                }
+            }
+                .navigationBarItems(trailing: PlusButton(action: plusAction))
+                .textFieldAlert(isPresented: $showAlert, title: "Add Exchange",
+                                doneText: "Add", text: $newExchange, action: 
addExchange)
+        } // body
+    }
+}
diff --git a/TalerWallet1/Views/Exchange/ExchangeModel.swift 
b/TalerWallet1/Views/Exchange/ExchangeModel.swift
new file mode 100644
index 0000000..c16e720
--- /dev/null
+++ b/TalerWallet1/Views/Exchange/ExchangeModel.swift
@@ -0,0 +1,115 @@
+/*
+ * 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 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 {
+    static func == (lhs: Exchange, rhs: Exchange) -> Bool {
+        return lhs.exchangeBaseUrl == rhs.exchangeBaseUrl &&
+        lhs.exchangeStatus == rhs.exchangeStatus &&
+        lhs.permanent == rhs.permanent
+    }
+
+    var exchangeBaseUrl: String
+    var currency: String?
+    var tosStatus: String
+    var paytoUris: [String]
+    var exchangeStatus: String
+    var permanent: Bool
+    var ageRestrictionOptions: [Int]
+    var lastUpdateErrorInfo: ExchangeError?
+
+    var name: String? {
+        if let url = URL(string: exchangeBaseUrl) {
+            if let host = url.host {
+                return host
+            }
+        }
+        return nil
+    }
+}
+struct ExchangeError: Codable, Hashable {
+    var error: HTTPError
+}
+
+struct HTTPError: Codable, Hashable {
+    var code: Int
+    var requestUrl: String
+    var hint: String
+    var requestMethod: String
+    var httpStatusCode: Int?
+}
+
+// MARK: -
+extension ExchangeModel {
+    /// ask wallet-core for its list of known exchanges
+    @MainActor func updateList() async throws {
+        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
+        }
+    }
+
+    /// 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
+        }
+    }
+}
diff --git a/TalerWallet1/Views/HelperViews/AmountView.swift 
b/TalerWallet1/Views/HelperViews/AmountView.swift
new file mode 100644
index 0000000..4249876
--- /dev/null
+++ b/TalerWallet1/Views/HelperViews/AmountView.swift
@@ -0,0 +1,43 @@
+/*
+ * 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 AmountView: View {
+    let title: String
+    let value: String
+    let color: Color
+    var body: some View {
+        VStack {
+            Text(title)
+                .font(.title3)
+            Text(value)
+                .font(.largeTitle)
+                .fontWeight(.medium)
+                .foregroundColor(color)
+        }
+            .frame(maxWidth: .infinity, alignment: .center)
+            .listRowSeparator(.hidden)
+    }
+}
+
+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"))
+        }
+    }
+}
diff --git a/TalerWallet1/Views/HelperViews/Buttons.swift 
b/TalerWallet1/Views/HelperViews/Buttons.swift
new file mode 100644
index 0000000..ea509a8
--- /dev/null
+++ b/TalerWallet1/Views/HelperViews/Buttons.swift
@@ -0,0 +1,86 @@
+/*
+ * 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 HamburgerButton : View  {
+    let action: () -> Void
+
+    var body: some View {
+        Button(action: action) {
+            Image(systemName: "line.3.horizontal")
+        }
+            .font(.title)
+    }
+}
+
+struct PlusButton : View  {
+    let action: () -> Void
+
+    var body: some View {
+        Button(action: action) {
+            Image(systemName: "plus")
+        }
+            .font(.title)
+    }
+}
+
+struct ReloadButton : View  {
+    let disabled: Bool
+    let action: () -> Void
+
+    var body: some View {
+        Button(action: action) {
+            Image(systemName: "arrow.clockwise")
+        }
+            .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 Buttons_Previews: PreviewProvider {
+    static var previews: some View {
+        VStack {
+            HamburgerButton() {}
+                .padding()
+            PlusButton() {}
+                .padding()
+            HStack {
+                ReloadButton(disabled: false) {}
+                    .padding()
+                ReloadButton(disabled: true) {}
+                    .padding()
+            }
+            AwesomeButton(title: "AwesomeButton") {}
+                .padding()
+        }
+    }
+}
diff --git a/TalerWallet1/Views/HelperViews/LoadingView.swift 
b/TalerWallet1/Views/HelperViews/LoadingView.swift
new file mode 100644
index 0000000..10c245e
--- /dev/null
+++ b/TalerWallet1/Views/HelperViews/LoadingView.swift
@@ -0,0 +1,45 @@
+/*
+ * 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 LoadingView: View {
+    private let symLog = SymLogV(0)
+    let backButtonHidden: Bool
+
+    var body: some View {
+        symLog { NavigationView {
+            VStack {
+                Spacer()
+                ProgressView()
+                Spacer()
+                Spacer()
+                Spacer()
+            }
+                .navigationBarBackButtonHidden(backButtonHidden)
+                .navigationTitle("Loading...")
+        }
+            .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: 
.center)
+            .background(Color(.systemGray6))
+        }
+    }
+}
+
+struct LoadingView_Previews: PreviewProvider {
+    static var previews: some View {
+        LoadingView(backButtonHidden: true)
+    }
+}
diff --git a/TalerWallet1/Views/HelperViews/TextFieldAlert.swift 
b/TalerWallet1/Views/HelperViews/TextFieldAlert.swift
new file mode 100644
index 0000000..de2eaf6
--- /dev/null
+++ b/TalerWallet1/Views/HelperViews/TextFieldAlert.swift
@@ -0,0 +1,73 @@
+/*
+ * 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 TextFieldAlert: ViewModifier {
+    @Binding var isPresented: Bool
+    let title: String
+    let doneText: String
+    @Binding var text: String
+    let placeholder: String
+    let action: (String) -> Void
+    func body(content: Content) -> some View {
+        ZStack(alignment: .center) {
+            content
+                .disabled(isPresented)
+            if isPresented {
+                VStack {
+                    Text(title).font(.headline).padding()
+                    TextField(placeholder, text: 
$text).textFieldStyle(.roundedBorder).padding()
+                    Divider()
+                    HStack {
+                        Spacer()
+                        Button(role: .cancel) {
+                            withAnimation { isPresented.toggle() }
+                        } label: {
+                            Text("Cancel")
+                        }
+                        Spacer()
+                        Divider()
+                        Spacer()
+                        Button(doneText) {
+                            action(text)
+                            withAnimation { isPresented.toggle() }
+                        }
+                        Spacer()
+                    }
+                }
+                    .background(.background)
+                    .frame(width: 300, height: 200)
+                    .cornerRadius(20)
+                    .overlay {
+                        RoundedRectangle(cornerRadius: 20)
+                            .stroke(.quaternary, lineWidth: 1)
+                    }
+            }
+        }
+    }
+}
+
+extension View {
+    public func textFieldAlert(isPresented: Binding<Bool>,
+                                     title: String,
+                                  doneText: String,
+                                      text: Binding<String>,
+                               placeholder: String = "",
+                                    action: @escaping (String) -> Void
+    ) -> some View {
+        self.modifier(TextFieldAlert(isPresented: isPresented, title: title, 
doneText: doneText, text: text, placeholder: placeholder, action: action))
+    }
+}
diff --git a/TalerWallet1/Views/Main/ContentView.swift 
b/TalerWallet1/Views/Main/ContentView.swift
new file mode 100644
index 0000000..1609351
--- /dev/null
+++ b/TalerWallet1/Views/Main/ContentView.swift
@@ -0,0 +1,92 @@
+/*
+ * 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/Taler/Model/BackendManager.swift 
b/TalerWallet1/Views/Main/ErrorView.swift
similarity index 64%
copy from Taler/Model/BackendManager.swift
copy to TalerWallet1/Views/Main/ErrorView.swift
index d0c262b..25fc2f0 100644
--- a/Taler/Model/BackendManager.swift
+++ b/TalerWallet1/Views/Main/ErrorView.swift
@@ -13,18 +13,19 @@
  * 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
 
-import Foundation
+struct ErrorView: View {
+    private let symLog = SymLogV()
+    var body: some View {
+        
+        Text("Couldn't load Wallet-Core!")
+    }
+}
 
-class BackendManager: ObservableObject {
-    var backend: WalletBackend
-    
-    @Published var exchangeManager: ExchangeManager
-    @Published var pendingManager: PendingManager
-    
-    init() {
-        self.backend = try! WalletBackend()
-        self.exchangeManager = ExchangeManager(_backend: self.backend)
-        self.pendingManager = PendingManager(_backend: self.backend)
+struct ErrorView_Previews: PreviewProvider {
+    static var previews: some View {
+        ErrorView()
     }
 }
diff --git a/TalerWallet1/Views/Main/LaunchAnimationView.swift 
b/TalerWallet1/Views/Main/LaunchAnimationView.swift
new file mode 100644
index 0000000..cc20fa4
--- /dev/null
+++ b/TalerWallet1/Views/Main/LaunchAnimationView.swift
@@ -0,0 +1,33 @@
+
+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)
+        .autoconnect()
+
+    var body: some View {
+        ZStack {
+            Color.teal.ignoresSafeArea()
+            Image(systemName: "hurricane")
+                .resizable()
+                .scaledToFit()
+                .frame(width: 200, height: 200)
+                .rotationEffect(rotationDirection ? Angle(degrees: 0) : 
Angle(degrees: 1080))
+        }
+            .onReceive(animationTimer) { timerValue in
+                withAnimation(.easeInOut(duration: 1.9)) {
+                    rotationDirection.toggle()
+                }
+            }
+    }
+}
+struct LaunchAnimationView_Previews: PreviewProvider {
+    static var previews: some View {
+        LaunchAnimationView()
+    }
+}
diff --git a/TalerWallet1/Views/Main/SideBarView.swift 
b/TalerWallet1/Views/Main/SideBarView.swift
new file mode 100644
index 0000000..bec2b2c
--- /dev/null
+++ b/TalerWallet1/Views/Main/SideBarView.swift
@@ -0,0 +1,110 @@
+/*
+ * 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
+
+fileprivate let sidebarWidth = 250.0
+
+struct SidebarItem {
+    var name: String
+    var sysImage: String?
+    var view: AnyView
+}
+
+struct SideBarView: View {
+    private let symLog = SymLogV(0)
+    var views: [SidebarItem]
+    @Binding var currentView: Int
+    @Binding var sidebarVisible: Bool
+
+    var body: some View {
+        symLog {
+            HStack {
+                VStack {
+                    Spacer()
+                    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
+                        } label: {
+                            if let sysImage = views[i].sysImage {
+                                Label(views[i].name, systemImage: sysImage)
+                                    .frame(maxWidth: sidebarWidth, alignment: 
.leading)
+                            } else {
+                                Text(views[i].name)
+                                    .frame(maxWidth: sidebarWidth)
+                            }
+                        }
+                        .buttonStyle(.bordered)
+                        .font(.title)
+//                        .padding(.vertical)
+
+//                        Divider()
+                    }
+                    Spacer()
+                    Spacer()
+                }
+                .background(Color(.systemGray5))
+                .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
+                    }
+            }
+        }
+    }
+}
+
+#if DEBUG
+struct BindingViewContainer : View {
+    @State var currentView: Int = 0
+    @State var sidebarVisible: Bool = true
+    var views: [SidebarItem]
+
+    var body: some View {
+        ZStack(alignment: .leading) {
+            views[currentView].view
+            SideBarView(views: views, currentView: $currentView, 
sidebarVisible: $sidebarVisible)
+        }
+    }
+}
+
+struct SideBarView_Previews: PreviewProvider {
+    static var views: [SidebarItem] {[
+        SidebarItem(name: "Balances",
+                    view: AnyView(WalletEmptyView())),
+        SidebarItem(name: "Settings",
+                    view: AnyView(WalletEmptyView()))
+    ]}
+    static var previews: some View {
+        BindingViewContainer(views: views)
+    }
+}
+#endif
diff --git a/TalerWallet1/Views/Payment/PaymentAcceptView.swift 
b/TalerWallet1/Views/Payment/PaymentAcceptView.swift
new file mode 100644
index 0000000..7920b87
--- /dev/null
+++ b/TalerWallet1/Views/Payment/PaymentAcceptView.swift
@@ -0,0 +1,71 @@
+/*
+ * 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! effective - raw
+            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/PaymentURIModel.swift 
b/TalerWallet1/Views/Payment/PaymentURIModel.swift
new file mode 100644
index 0000000..8fd4142
--- /dev/null
+++ b/TalerWallet1/Views/Payment/PaymentURIModel.swift
@@ -0,0 +1,183 @@
+/*
+ * 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 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
+    let maxFee: Amount
+    let maxWireFee: Amount
+    let merchant: Merchant
+    let extra: Extra
+    let summary: String
+    let timestamp: Timestamp
+    let payDeadline: Timestamp
+    let refundDeadline: Timestamp
+    let wireTransferDeadline: Timestamp
+    let merchantBaseURL: String
+    let fulfillmentURL: String
+    let publicReorderURL: String
+    let auditors: [Auditor]
+    let exchanges: [ExchangeForPay]
+    let orderID, nonce, merchantPub: String
+    let products: [Product]
+    let hWire: String
+    let wireMethod: String
+    let wireFeeAmortization: Int
+
+    enum CodingKeys: String, CodingKey {
+        case amount
+        case maxFee = "max_fee"
+        case maxWireFee = "max_wire_fee"
+        case merchant, extra, summary
+        case timestamp
+        case payDeadline = "pay_deadline"
+        case refundDeadline = "refund_deadline"
+        case wireTransferDeadline = "wire_transfer_deadline"
+        case merchantBaseURL = "merchant_base_url"
+        case fulfillmentURL = "fulfillment_url"
+        case publicReorderURL = "public_reorder_url"
+        case auditors, exchanges
+        case orderID = "order_id"
+        case nonce
+        case merchantPub = "merchant_pub"
+        case products
+        case hWire = "h_wire"
+        case wireMethod = "wire_method"
+        case wireFeeAmortization = "wire_fee_amortization"
+    }
+}
+
+// MARK: - Auditor
+struct Auditor: Codable {
+    let name: String
+    let auditorPub: String
+    let url: String
+
+    enum CodingKeys: String, CodingKey {
+        case name
+        case auditorPub = "auditor_pub"
+        case url
+    }
+}
+
+// MARK: - Exchange
+struct ExchangeForPay: Codable {
+    let url: String
+    let masterPub: String
+
+    enum CodingKeys: String, CodingKey {
+        case url
+        case masterPub = "master_pub"
+    }
+}
+
+// MARK: - Extra
+struct Extra: Codable {
+    let articleName: String
+
+    enum CodingKeys: String, CodingKey {
+        case articleName = "article_name"
+    }
+}
+
+// MARK: -
+/// The result from PreparePayForUri
+struct PaymentDetailsForUri: Codable {
+    let status: String
+    let amountRaw: Amount
+    let amountEffective: Amount
+    let noncePriv: String
+    let proposalId: String
+    let contractTerms: ContractTerms
+    let contractTermsHash: String
+}
+/// A request to get an exchange's payment contract terms.
+fileprivate struct PreparePayForUri: WalletBackendFormattedRequest {
+    typealias Response = PaymentDetailsForUri
+    func operation() -> String { return "preparePayForUri" }
+    func args() -> Args { return Args(talerPayUri: talerPayUri) }
+
+    var talerPayUri: String
+    struct Args: Encodable {
+        var talerPayUri: String
+    }
+}
+// MARK: -
+/// The result from getPaymentDetailsForAmount
+struct ConfirmPayResult: Decodable {
+    var type: String
+    var contractTerms: ContractTerms
+    var transactionId: String
+}
+/// A request to get an exchange's payment details.
+fileprivate struct confirmPayForUri: WalletBackendFormattedRequest {
+    typealias Response = ConfirmPayResult
+    func operation() -> String { return "confirmPay" }
+    func args() -> Args { return Args(proposalId: proposalId) }
+
+    var proposalId: String
+    struct Args: Encodable {
+        var proposalId: String
+    }
+}
+// MARK: -
+extension PaymentURIModel {
+    /// 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
+        }
+    }
+    @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
+        }
+    }
+}
diff --git a/TalerWallet1/Views/Payment/PaymentURIView.swift 
b/TalerWallet1/Views/Payment/PaymentURIView.swift
new file mode 100644
index 0000000..38ad04c
--- /dev/null
+++ b/TalerWallet1/Views/Payment/PaymentURIView.swift
@@ -0,0 +1,65 @@
+/*
+ * 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 PaymentURIView: View {
+    private let symLog = SymLogV()
+    var url: URL
+    @ObservedObject var viewModel: PaymentURIModel
+
+//    @Environment(\.dismiss) var dismiss     // call dismiss() to get rid of 
the sheet
+    let navTitle = "Payment"
+    var cancelButton: some View {
+        Button("Cancel5") { dismissTop() }
+    }
+
+    @State var detailsForUri: PaymentDetailsForUri?
+
+    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
+            }
+        }
+    }
+}
diff --git a/TalerWallet1/Views/Pending/PendingModel.swift 
b/TalerWallet1/Views/Pending/PendingModel.swift
new file mode 100644
index 0000000..cb88523
--- /dev/null
+++ b/TalerWallet1/Views/Pending/PendingModel.swift
@@ -0,0 +1,82 @@
+/*
+ * 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/PendingOpView.swift 
b/TalerWallet1/Views/Pending/PendingOpView.swift
new file mode 100644
index 0000000..12c2924
--- /dev/null
+++ b/TalerWallet1/Views/Pending/PendingOpView.swift
@@ -0,0 +1,64 @@
+/*
+ * 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 PendingOpView: View {
+    var pendingOp: PendingOperation
+    @State var polling: Bool = false
+    @State var liveliness: Bool = false
+    @State var isDue: Bool = false
+
+    var body: some View {
+        Section {
+            Text(pendingOp.exchangeBaseUrl)
+            Text(pendingOp.id)
+                .font(.caption)
+            Toggle("isLongPolling", isOn: $polling)
+                .disabled(true)
+            Toggle("givesLifeness", isOn: $liveliness)
+                .disabled(true)
+            Toggle("isDue", isOn: $isDue)
+                .disabled(true)
+            let dateString = TalerDater.dateString(from: 
pendingOp.timestampDue)
+            Text("\(dateString)")
+        } header: {
+            Text(pendingOp.type)
+                .font(.title2)
+        }
+//        .textCase(nil)    // don't capitalize
+        .onAppear {
+            polling = pendingOp.isLongpolling
+            liveliness = pendingOp.givesLifeness
+            isDue = pendingOp.isDue
+        }
+    }
+}
+
+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/";,
+                                  isLongpolling: false,
+                                  givesLifeness: true,
+                                          isDue: false,
+                                   timestampDue: Timestamp(from:1669931055000))
+    static var previews: some View {
+        Form {
+            PendingOpView(pendingOp: pending1)
+        }
+    }
+}
diff --git a/TalerWallet1/Views/Pending/PendingOpsListView.swift 
b/TalerWallet1/Views/Pending/PendingOpsListView.swift
new file mode 100644
index 0000000..30a5de3
--- /dev/null
+++ b/TalerWallet1/Views/Pending/PendingOpsListView.swift
@@ -0,0 +1,65 @@
+/*
+ * 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")
+            try? await reloadAction()       // TODO: catch error
+        }
+    }
+}
+// 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 {
+                    symLog?.log("refreshing")
+                    try? await reloadAction()       // TODO: catch error
+                }
+            }
+        }
+    }
+}
diff --git a/TalerWallet1/Views/Settings/SettingsItem.swift 
b/TalerWallet1/Views/Settings/SettingsItem.swift
new file mode 100644
index 0000000..7735c97
--- /dev/null
+++ b/TalerWallet1/Views/Settings/SettingsItem.swift
@@ -0,0 +1,93 @@
+/*
+ * 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 SettingsItem<Content: View>: View {
+    var name: String
+    var description: String?
+    var content: () -> Content
+    
+    init(name: String, description: String? = nil, @ViewBuilder content: 
@escaping () -> Content) {
+        self.name = name
+        self.description = description
+        self.content = content
+    }
+    
+    var body: some View {
+        HStack {
+            VStack {
+                Text(name)
+                    .frame(maxWidth: .infinity, alignment: .leading)
+                    .font(.title2)
+                    .padding([.bottom], 0.01)
+                if let desc = description {
+                    Text(desc)
+                        .frame(maxWidth: .infinity, alignment: .leading)
+                        .font(.caption)
+                }
+            }
+            content()
+        }.padding([.bottom], 4)
+    }
+}
+
+struct SettingsToggle: View {
+    var name: String
+    @Binding var value: Bool
+    var description: String?
+
+    var body: some View {
+        VStack {
+            Toggle(name, isOn: $value.animation(.spring()))
+                .font(.title2)
+            if let desc = description {
+                Text(desc)
+                    .frame(maxWidth: .infinity, alignment: .leading)
+                    .font(.caption)
+            }
+        }.padding([.bottom], 4)
+    }
+}
+
+
+
+struct SettingsItemPreview : View {
+    @State var developerMode: Bool = false
+
+    var body: some View {
+        VStack {
+            SettingsToggle(name: "Developer Mode", value: $developerMode, 
description: "More information intended for debugging")
+        }
+    }
+}
+
+struct SettingsItem_Previews: PreviewProvider {
+    static var previews: some View {
+        List {
+            NavigationLink { } label: {
+                SettingsItem (name: "Exchanges", description: "Manage list of 
exchanges known to this wallet") {}
+            }
+            SettingsItemPreview()
+            SettingsItem(name: "Save Logfile", description: "Help debugging 
wallet-core") {
+                Button("Save") {
+                }
+                .buttonStyle(.bordered)
+                .disabled(true)
+            }
+        }
+    }
+}
diff --git a/TalerWallet1/Views/Settings/SettingsView.swift 
b/TalerWallet1/Views/Settings/SettingsView.swift
new file mode 100644
index 0000000..a2f3ea5
--- /dev/null
+++ b/TalerWallet1/Views/Settings/SettingsView.swift
@@ -0,0 +1,139 @@
+/*
+ * 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
+
+/*
+ * 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
+    @AppStorage("developerMode") var developerMode: Bool = false
+    @AppStorage("developDelay") var developDelay: Bool = false
+
+    var showSidebar: () -> Void
+    init(showSidebar: @escaping () -> Void) {
+        self.showSidebar = showSidebar
+    }
+
+    @State private var checkDisabled = false
+    @State private var withDrawDisabled = false
+
+    var body: some View {
+        symLog { NavigationView {
+            List {
+                NavigationLink {
+                    ExchangeListView(viewModel: controller.exchangeModel)
+                } label: {
+                    SettingsItem(name: "Exchanges", description: "Manage list 
of exchanges known to this wallet") {}
+                }
+                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
+                    })
+
+                    SettingsItem(name: "Withdraw KUDOS", description: "Get 
money for testing") {
+                        Button("Withdraw") {
+                            withDrawDisabled = true    // don't run twice
+                            let testModel: ExchangeTestModel = 
ExchangeTestModel(walletCore: walletCore)
+                            symLog.log("Withdrawing ")
+                            testModel.loadTestKudos()
+                        }
+                        .buttonStyle(.bordered)
+                        .disabled(withDrawDisabled)
+                    }
+                    SettingsItem(name: "Run Integration Test", description: 
"Check if wallet-core works") {
+                        Button("Check") {
+                            checkDisabled = true    // don't run twice
+                            let testModel: ExchangeTestModel = 
ExchangeTestModel(walletCore: walletCore)
+                            symLog.log("running integration test ")
+                            testModel.runIntegrationTest()
+                        }
+                        .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: "Used Bank") {
+                            Text("\(walletCore.versionInfo!.bank)")
+                        }
+                    }
+                }
+            }
+                .navigationTitle("Settings")
+                .navigationBarItems(leading: HamburgerButton(action: 
showSidebar))
+        } } // symLog
+    }
+}
+extension Bundle {
+    var releaseVersionNumber: String? {
+        return infoDictionary?["CFBundleShortVersionString"] as? String
+    }
+    var buildVersionNumber: String? {
+        return infoDictionary?["CFBundleVersion"] as? String
+    }
+    var releaseVersionNumberPretty: String {
+        return "v\(releaseVersionNumber ?? "1.0.0")"
+    }
+}
+
+struct SettingsView_Previews: PreviewProvider {
+    static var previews: some View {
+        SettingsView {
+            
+        }
+    }
+}
diff --git a/TalerWallet1/Views/Transactions/TransactionDetail.swift 
b/TalerWallet1/Views/Transactions/TransactionDetail.swift
new file mode 100644
index 0000000..84b442a
--- /dev/null
+++ b/TalerWallet1/Views/Transactions/TransactionDetail.swift
@@ -0,0 +1,79 @@
+/*
+ * 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 raw = transaction.amountRaw
+        let effective = transaction.amountEffective
+        let fee = try! Amount.diff(raw, effective)
+        let dateString = TalerDater.dateString(from: transaction.timestamp)
+
+        VStack() {
+            Spacer()
+            Text("\(dateString)")
+                .font(.title)
+                .fontWeight(.medium)
+                .padding(.bottom)
+            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: "Obtained coins:",
+                       value: effective.readableDescription, color: 
Color("Incoming"))
+                .padding(.bottom)
+            if let baseURL = transaction.exchangeBaseUrl {
+                VStack {
+                    Text("From exchange:")
+                        .font(.title3)
+                    Text("\(baseURL.trimURL())")
+                        .font(.title)
+                        .fontWeight(.medium)
+                }
+                .frame(maxWidth: .infinity, alignment: .center)
+            }
+            Spacer()
+            Button(role: .destructive, action: {
+                // TODO: delete from wallet-core
+                print("Should delete \(transaction.transactionId)")
+            }, label: {
+                HStack {
+                    Text("Delete from list" + "        ")
+                    Image(systemName: "trash")
+                }
+                    .font(.title)
+                    .frame(maxWidth: .infinity)
+            })
+                .buttonStyle(.bordered)
+                .controlSize(.large)
+//            Spacer()
+        }
+    }
+}
+
+#if DEBUG
+struct TransactionDetail_Previews: PreviewProvider {
+    static var transaction = Transaction(id:"some transActionID", time: 
Timestamp(from: 1_666_000_000_000))
+    static var previews: some View {
+        TransactionDetail(transaction: transaction)
+    }
+}
+#endif
diff --git a/TalerWallet1/Views/Transactions/TransactionRow.swift 
b/TalerWallet1/Views/Transactions/TransactionRow.swift
new file mode 100644
index 0000000..63df274
--- /dev/null
+++ b/TalerWallet1/Views/Transactions/TransactionRow.swift
@@ -0,0 +1,81 @@
+/*
+ * 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 amount = transaction.amountEffective
+        let withdraw: Bool = transaction.type == "withdrawal"
+        let payment: Bool = transaction.type == "payment"
+        let refund: Bool = transaction.type == "refund"
+        let incoming = withdraw || refund
+        let counterparty = transaction.exchangeBaseUrl
+        let dateString = TalerDater.dateString(from: transaction.timestamp, 
relative: true)
+
+        HStack {
+            Image(systemName: incoming ? "text.badge.plus" : 
"text.badge.minus")
+                .foregroundColor(incoming ? Color("Incoming")  : 
Color("Outgoing"))
+                .padding(.trailing)
+                .font(.largeTitle)
+
+            if withdraw {
+                if let baseURL = counterparty {
+                    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 transaction = Transaction(id:"some transActionID", time: 
Timestamp(from: 1_666_000_000_000))
+    static var previews: some View {
+        TransactionRow(transaction: transaction)
+    }
+}
+#endif
diff --git a/TalerWallet1/Views/Transactions/TransactionsListView.swift 
b/TalerWallet1/Views/Transactions/TransactionsListView.swift
new file mode 100644
index 0000000..8f9a6a3
--- /dev/null
+++ b/TalerWallet1/Views/Transactions/TransactionsListView.swift
@@ -0,0 +1,91 @@
+/*
+ * 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 TransactionsListView: View {
+    private let symLog = SymLogV()
+    let navTitle = "Transactions"
+
+    @ObservedObject var viewModel: TransactionsModel
+    
+    var body: some View {
+        let reloadAction = viewModel.fetchTransactions
+        VStack {
+            if viewModel.transactions == nil {
+                symLog { LoadingView(backButtonHidden: false) }
+            } else {
+                symLog { Content(symLog: symLog, viewModel: viewModel, 
reloadAction: reloadAction)
+                        .navigationTitle(navTitle)
+                }
+            }
+        }.task {
+            symLog.log(".task")
+            do {
+                try await reloadAction()
+            } catch {
+                // TODO: show error
+                symLog.log(error.localizedDescription)
+            }
+        }
+    }
+}
+// MARK: -
+extension TransactionsListView {
+    struct Content: View {
+        let symLog: SymLogV?
+        @ObservedObject var viewModel: TransactionsModel
+
+        var reloadAction: () async throws -> ()
+
+        var body: some View {
+            let transactions = viewModel.transactions!
+                List(transactions, id: \.transactionId) { transaction in
+                    NavigationLink {
+                        TransactionDetail(transaction: transaction)
+                    } label: {
+                        TransactionRow(transaction: transaction)
+                    }
+                    .swipeActions(edge: .leading, allowsFullSwipe: true) {
+                        Button {
+                            symLog?.log("bookmarked 
\(transaction.transactionId)")
+                            // TODO: Bookmark
+                        } label: {
+                            Label("Bookmark", systemImage: "bookmark")
+                        }.tint(.indigo)
+                    }
+                    .swipeActions(edge: .trailing, allowsFullSwipe: true) {
+                        Button(role: .destructive) {
+                            symLog?.log("deleted \(transaction.transactionId)")
+                            // TODO: delete from Model. SwiftUI deletes this 
row from view already :-)
+                        } label: {
+                            Label("Delete", systemImage: "trash")
+                        }
+                    }
+                }
+                    .navigationBarTitleDisplayMode(.large)      // .inline
+                    .refreshable {
+                        symLog?.log("refreshing")
+                        try? await reloadAction()       // TODO: catch error
+                    }
+        }
+    }
+}
+//struct TransactionsView_Previews: PreviewProvider {
+//    static var previews: some View {
+//        TransactionsView()
+//    }
+//}
diff --git a/TalerWallet1/Views/Transactions/TransactionsModel.swift 
b/TalerWallet1/Views/Transactions/TransactionsModel.swift
new file mode 100644
index 0000000..9786c1e
--- /dev/null
+++ b/TalerWallet1/Views/Transactions/TransactionsModel.swift
@@ -0,0 +1,69 @@
+/*
+ * 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
+}
+//extension Transaction {
+//    func exchangeBaseUrl() -> String {
+//        switch detail {
+//            case .withdrawal(let transactionWithdrawal):
+//                return transactionWithdrawal.exchangeBaseUrl
+//        }
+//    }
+//}
+
+// 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
new file mode 100644
index 0000000..1aaab6b
--- /dev/null
+++ b/TalerWallet1/Views/URLSheet.swift
@@ -0,0 +1,64 @@
+/*
+ * 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
new file mode 100644
index 0000000..becc6dd
--- /dev/null
+++ b/TalerWallet1/Views/Withdraw/WithdrawAcceptView.swift
@@ -0,0 +1,71 @@
+/*
+ * 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! raw - effective
+                    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/Taler/Model/BackendManager.swift 
b/TalerWallet1/Views/Withdraw/WithdrawProgressView.swift
similarity index 50%
rename from Taler/Model/BackendManager.swift
rename to TalerWallet1/Views/Withdraw/WithdrawProgressView.swift
index d0c262b..2c08b75 100644
--- a/Taler/Model/BackendManager.swift
+++ b/TalerWallet1/Views/Withdraw/WithdrawProgressView.swift
@@ -13,18 +13,33 @@
  * 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 Foundation
+struct WithdrawProgressView: View {
+    let message: String
+    let action: () -> Void
 
-class BackendManager: ObservableObject {
-    var backend: WalletBackend
-    
-    @Published var exchangeManager: ExchangeManager
-    @Published var pendingManager: PendingManager
-    
-    init() {
-        self.backend = try! WalletBackend()
-        self.exchangeManager = ExchangeManager(_backend: self.backend)
-        self.pendingManager = PendingManager(_backend: self.backend)
+    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
new file mode 100644
index 0000000..b1c3cf6
--- /dev/null
+++ b/TalerWallet1/Views/Withdraw/WithdrawTOSView.swift
@@ -0,0 +1,96 @@
+/*
+ * 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
new file mode 100644
index 0000000..f375a1d
--- /dev/null
+++ b/TalerWallet1/Views/Withdraw/WithdrawURIModel.swift
@@ -0,0 +1,213 @@
+/*
+ * 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
new file mode 100644
index 0000000..939c24d
--- /dev/null
+++ b/TalerWallet1/Views/Withdraw/WithdrawURIView.swift
@@ -0,0 +1,103 @@
+/*
+ * 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: 
\(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/bootstrap b/bootstrap
deleted file mode 100755
index c918f18..0000000
--- a/bootstrap
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/bin/sh
-
-set -eu
-
-curl 
'https://git.taler.net/wallet-core.git/plain/v0.9.0-dev.29/taler-wallet-embedded.js?h=prebuilt'
 --output taler-wallet-embedded.js
-
-if ! git --version >/dev/null; then
-  echo "git not installed"
-  exit 1
-fi
-
-git submodule sync --recursive
-git submodule update --init --recursive
-
-cd iono/ios-node-v8
-./taler-ios-build/x64
-./taler-ios-build/arm64
-cd ../..
-cd iono
-./bootstrap
-cd ..
diff --git a/iono b/iono
deleted file mode 160000
index a9fd187..0000000
--- a/iono
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit a9fd1879561822c032fcc486a71899e549e83bfc
diff --git a/taler-swift/Sources/taler-swift/Amount.swift 
b/taler-swift/Sources/taler-swift/Amount.swift
index 62e1d7b..d97612e 100644
--- a/taler-swift/Sources/taler-swift/Amount.swift
+++ b/taler-swift/Sources/taler-swift/Amount.swift
@@ -34,14 +34,14 @@ enum AmountError: Error {
 }
 
 /// A value of some currency.
-public class Amount: Codable, CustomStringConvertible {
+public class Amount: Codable, Hashable, CustomStringConvertible {
     /// Format that a currency must match.
     private static let currencyRegex = #"^[-_*A-Za-z0-9]{1,12}$"#
     
     /// The largest possible value that can be represented.
     private static let maxValue: UInt64 = 1 << 52
     
-    /// The size of `value` in relation to `fraction`.
+    /// The size of `integer` in relation to `fraction`.
     private static let fractionalBase: UInt32 = 100000000
     
     /// The greatest number of decimal digits that can be represented.
@@ -50,31 +50,38 @@ public class Amount: Codable, CustomStringConvertible {
     /// The currency of the amount.
     var currency: String
     
-    /// The value of the amount (number to the left of the decimal point).
-    var value: UInt64
+    /// The integer value of the amount (number to the left of the decimal 
point).
+    var integer: UInt64
     
     /// The fractional value of the amount (number to the right of the decimal 
point).
     var fraction: UInt32
-    
-    /// The string representation of the amount, formatted as 
"`currency`:`value`.`fraction`".
-    public var description: String {
+
+    public func hash(into hasher: inout Hasher) {
+        hasher.combine(currency)
+        if let normalized = try? normalizedCopy() {
+            hasher.combine(normalized.integer)
+            hasher.combine(normalized.fraction)
+        } else {
+            hasher.combine(integer)
+            hasher.combine(fraction)
+        }
+    }
+
+    /// The floating point representation of the value
+    public var value: Double {
+        let value = Double(integer)
         if fraction == 0 {
-            return "\(currency):\(value)"
+            return value
         } else {
-            var frac = fraction
-            var fracStr = ""
-            while (frac > 0) {
-                fracStr += "\(frac / (Amount.fractionalBase / 10))"
-                frac = (frac * 10) % Amount.fractionalBase
-            }
-            return "\(currency):\(value).\(fracStr)"
+            let thousandths = Double(fraction / (Amount.fractionalBase / 1000))
+            return value + (thousandths / 1000.0)
         }
     }
-    
-    /// The string representation of the amount, formatted as 
"`value`.`fraction` `currency`".
-    public var readableDescription: String {
+
+    /// The string representation of the value, formatted as 
"`integer`.`fraction`".
+    public var valueStr: String {
         if fraction == 0 {
-            return "\(value) \(currency)"
+            return "\(integer)"
         } else {
             var frac = fraction
             var fracStr = ""
@@ -82,24 +89,39 @@ public class Amount: Codable, CustomStringConvertible {
                 fracStr += "\(frac / (Amount.fractionalBase / 10))"
                 frac = (frac * 10) % Amount.fractionalBase
             }
-            return "\(value).\(fracStr) \(currency)"
+            return "\(integer).\(fracStr)"
         }
     }
+
+    /// read-only getter
+    public var currencyStr: String {
+        return currency
+    }
+
+    /// The string representation of the amount, formatted as 
"`currency`:`integer`.`fraction`".
+    public var description: String {
+        return "\(currency):\(valueStr)"
+    }
+    
+    /// The string representation of the amount, formatted as 
"`integer`.`fraction` `currency`".
+    public var readableDescription: String {
+        return "\(valueStr) \(currency)"
+    }
     
     /// Whether the value is valid. An amount is valid if and only if the 
currency is not empty and the value is less than the maximum allowed value.
     var valid: Bool {
         if currency.range(of: Amount.currencyRegex, options: 
.regularExpression) == nil {
             return false
         }
-        return (value <= Amount.maxValue && currency != "")
+        return (integer <= Amount.maxValue && currency != "")
     }
     
     /// Whether this amount is zero or not.
-    var isZero: Bool {
-        return value == 0 && fraction == 0
+    public var isZero: Bool {
+        return integer == 0 && fraction == 0
     }
     
-    /// Initializes an amount by parsing a string representing the amount. The 
string should be formatted as "`currency`:`value`.`fraction`".
+    /// Initializes an amount by parsing a string representing the amount. The 
string should be formatted as "`currency`:`integer`.`fraction`".
     /// - Parameters:
     ///   - fromString: The string to parse.
     /// - Throws:
@@ -110,13 +132,13 @@ public class Amount: Codable, CustomStringConvertible {
             self.currency = String(string[..<separatorIndex])
             let amountStr = String(string[string.index(separatorIndex, 
offsetBy: 1)...])
             if let dotIndex = amountStr.firstIndex(of: ".") {
-                let valueStr = String(amountStr[..<dotIndex])
+                let integerStr = String(amountStr[..<dotIndex])
                 let fractionStr = String(amountStr[string.index(dotIndex, 
offsetBy: 1)...])
                 if (fractionStr.count > Amount.fractionalBaseDigits) {
                     throw AmountError.invalidStringRepresentation
                 }
-                guard let _value = UInt64(valueStr) else { throw 
AmountError.invalidStringRepresentation }
-                self.value = _value
+                guard let intValue = UInt64(integerStr) else { throw 
AmountError.invalidStringRepresentation }
+                self.integer = intValue
                 self.fraction = 0
                 var digitValue = Amount.fractionalBase / 10
                 for char in fractionStr {
@@ -125,26 +147,26 @@ public class Amount: Codable, CustomStringConvertible {
                     digitValue /= 10
                 }
             } else {
-                guard let _value = UInt64(amountStr) else { throw 
AmountError.invalidStringRepresentation }
-                self.value = _value
+                guard let intValue = UInt64(amountStr) else { throw 
AmountError.invalidStringRepresentation }
+                self.integer = intValue
                 self.fraction = 0
             }
         } else {
             self.currency = string
-            self.value = 0
+            self.integer = 0
             self.fraction = 0
         }
         guard self.valid else { throw AmountError.invalidAmount }
     }
     
-    /// Initializes an amount with the specified currency, value, and fraction.
+    /// Initializes an amount with the specified currency, integer, and 
fraction.
     /// - Parameters:
     ///   - currency: The currency of the amount.
-    ///   - value: The value of the amount (number to the left of the decimal 
point).
+    ///   - integer: The integer value of the amount (number to the left of 
the decimal point).
     ///   - fraction: The fractional value of the amount (number to the right 
of the decimal point).
-    public init(currency: String, value: UInt64, fraction: UInt32) {
+    public init(currency: String, integer: UInt64, fraction: UInt32) {
         self.currency = currency
-        self.value = value
+        self.integer = integer
         self.fraction = fraction
     }
     
@@ -163,7 +185,7 @@ public class Amount: Codable, CustomStringConvertible {
     /// Copies an amount.
     /// - Returns: A copy of the amount.
     func copy() -> Amount {
-        return Amount(currency: self.currency, value: self.value, fraction: 
self.fraction)
+        return Amount(currency: currency, integer: integer, fraction: fraction)
     }
     
     /// Creates a normalized copy of an amount (the fractional part is 
strictly less than one unit of currency).
@@ -179,18 +201,18 @@ public class Amount: Codable, CustomStringConvertible {
     ///   - to: The encoder to encode the amount with.
     public func encode(to encoder: Encoder) throws {
         var container = encoder.singleValueContainer()
-        try container.encode(self.description)
+        try container.encode(description)
     }
     
-    /// Normalizes an amount by reducing `fraction` until it is less than 
`Amount.fractionalBase`, increasing `value` appropriately.
+    /// Normalizes an amount by reducing `fraction` until it is less than 
`Amount.fractionalBase`, increasing `integer` appropriately.
     /// - Throws:
     ///   - `AmountError.invalidAmount` if the amount is invalid either before 
or after normalization.
     func normalize() throws {
         if !valid {
             throw AmountError.invalidAmount
         }
-        self.value += UInt64(self.fraction / Amount.fractionalBase)
-        self.fraction = self.fraction % Amount.fractionalBase
+        integer += UInt64(fraction / Amount.fractionalBase)
+        fraction = fraction % Amount.fractionalBase
         if !valid {
             throw AmountError.invalidAmount
         }
@@ -210,7 +232,7 @@ public class Amount: Codable, CustomStringConvertible {
         let leftNormalized = try left.normalizedCopy()
         let rightNormalized = try right.normalizedCopy()
         let result: Amount = leftNormalized
-        result.value += rightNormalized.value
+        result.integer += rightNormalized.integer
         result.fraction += rightNormalized.fraction
         try result.normalize()
         return result
@@ -230,18 +252,23 @@ public class Amount: Codable, CustomStringConvertible {
         let leftNormalized = try left.normalizedCopy()
         let rightNormalized = try right.normalizedCopy()
         if (leftNormalized.fraction < rightNormalized.fraction) {
-            guard leftNormalized.value != 0 else { throw 
AmountError.negativeAmount }
-            leftNormalized.value -= 1
+            guard leftNormalized.integer != 0 else { throw 
AmountError.negativeAmount }
+            leftNormalized.integer -= 1
             leftNormalized.fraction += Amount.fractionalBase
         }
-        guard leftNormalized.value >= rightNormalized.value else { throw 
AmountError.negativeAmount }
+        guard leftNormalized.integer >= rightNormalized.integer else { throw 
AmountError.negativeAmount }
         let diff = Amount.zero(currency: left.currency)
-        diff.value = leftNormalized.value - rightNormalized.value
+        diff.integer = leftNormalized.integer - rightNormalized.integer
         diff.fraction = leftNormalized.fraction - rightNormalized.fraction
         try diff.normalize()
         return diff
     }
-    
+
+    public static func diff (_ left: Amount, _ right: Amount) throws -> Amount 
{
+        return try left > right ? left - right
+                                : right - left
+    }
+
     /// Divides an amount by a scalar, possibly introducing rounding error.
     /// - Parameters:
     ///   - dividend: The amount to divide.
@@ -253,8 +280,8 @@ public class Amount: Codable, CustomStringConvertible {
         if (divisor == 1) {
             return result
         }
-        var remainder = result.value % UInt64(divisor)
-        result.value = result.value / UInt64(divisor)
+        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
         try result.normalize()
@@ -268,9 +295,9 @@ public class Amount: Codable, CustomStringConvertible {
     /// - Returns: The product of `amount` and `factor`, normalized.
     public static func * (amount: Amount, factor: UInt32) throws -> Amount {
         let result = try amount.normalizedCopy()
-        result.value = result.value * UInt64(factor)
+        result.integer = result.integer * UInt64(factor)
         let fraction_tmp = UInt64(result.fraction) * UInt64(factor)
-        result.value += fraction_tmp / UInt64(Amount.fractionalBase)
+        result.integer += fraction_tmp / UInt64(Amount.fractionalBase)
         result.fraction = UInt32(fraction_tmp % UInt64(Amount.fractionalBase))
         return result
     }
@@ -281,16 +308,30 @@ public class Amount: Codable, CustomStringConvertible {
     ///   - right: The second amount.
     /// - Throws:
     ///   - `AmountError.incompatibleCurrency` if `left` and `right` do not 
share the same currency.
-    /// - Returns: `true` if and only if the amounts have the same `value` and 
`fraction` after normalization, `false` otherwise.
-    public static func == (left: Amount, right: Amount) throws -> Bool {
-        if left.currency != right.currency {
-            throw AmountError.incompatibleCurrency
+    /// - Returns: `true` if and only if the amounts have the same `integer` 
and `fraction` after normalization, `false` otherwise.
+//    public static func == (left: Amount, right: Amount) throws -> Bool {
+//        if left.currency != right.currency {
+//            throw AmountError.incompatibleCurrency
+//        }
+//        let leftNormalized = try left.normalizedCopy()
+//        let rightNormalized = try right.normalizedCopy()
+//        return (leftNormalized.integer == rightNormalized.integer && 
leftNormalized.fraction == rightNormalized.fraction)
+//    }
+    
+    public static func == (left: Amount, right: Amount) -> Bool {
+        do {
+            if left.currency != right.currency {
+                throw AmountError.incompatibleCurrency
+            }
+            let leftNormalized = try left.normalizedCopy()
+            let rightNormalized = try right.normalizedCopy()
+            return (leftNormalized.integer == rightNormalized.integer && 
leftNormalized.fraction == rightNormalized.fraction)
+        }
+        catch {
+            return false
         }
-        let leftNormalized = try left.normalizedCopy()
-        let rightNormalized = try right.normalizedCopy()
-        return (leftNormalized.value == rightNormalized.value && 
leftNormalized.fraction == rightNormalized.fraction)
     }
-    
+
     /// Compares two amounts.
     /// - Parameters:
     ///   - left: The amount on the left.
@@ -304,10 +345,10 @@ public class Amount: Codable, CustomStringConvertible {
         }
         let leftNormalized = try left.normalizedCopy()
         let rightNormalized = try right.normalizedCopy()
-        if (leftNormalized.value == rightNormalized.value) {
+        if (leftNormalized.integer == rightNormalized.integer) {
             return (leftNormalized.fraction < rightNormalized.fraction)
         } else {
-            return (leftNormalized.value < rightNormalized.value)
+            return (leftNormalized.integer < rightNormalized.integer)
         }
     }
     
@@ -349,6 +390,6 @@ public class Amount: Codable, CustomStringConvertible {
     ///   - currency: The currency to use.
     /// - Returns: The zero amount for `currency`.
     public static func zero(currency: String) -> Amount {
-        return Amount(currency: currency, value: 0, fraction: 0)
+        return Amount(currency: currency, integer: 0, fraction: 0)
     }
 }
diff --git a/taler-swift/Sources/taler-swift/Time.swift 
b/taler-swift/Sources/taler-swift/Time.swift
index b1d712e..1d76053 100644
--- a/taler-swift/Sources/taler-swift/Time.swift
+++ b/taler-swift/Sources/taler-swift/Time.swift
@@ -22,21 +22,29 @@ enum TimestampError: Error {
     
     /// Invalid arguments were supplied to an arithmetic operation.
     case invalidArithmeticArguments
+
+    /// The value `never` cannot be returned as UInt64.
+    case invalidUInt64Value
 }
 
-/// A point in time, represented by milliseconds from Jaunuary 1, 1970..
-public enum Timestamp: Codable, Equatable {
+/// A point in time, represented by milliseconds from January 1, 1970..
+public enum Timestamp: Codable, Hashable {
     case milliseconds(UInt64)
     case never
     
     enum CodingKeys: String, CodingKey {
         case t_s = "t_s"
+        case t_ms = "t_ms"
     }
     
     public init(from decoder: Decoder) throws {
         let container = try decoder.container(keyedBy: CodingKeys.self)
         do {
-            self = Timestamp.milliseconds(try container.decode(UInt64.self, 
forKey: .t_s) * 1000)
+            if let seconds: UInt64 = try? container.decode(UInt64.self, 
forKey: .t_s) {
+                self = Timestamp.milliseconds(seconds * 1000)
+            } else {
+                self = Timestamp.milliseconds(try 
container.decode(UInt64.self, forKey: .t_ms))
+            }
         } catch {
             let stringValue = try container.decode(String.self, forKey: .t_s)
             if stringValue == "never" {
@@ -46,11 +54,14 @@ public enum Timestamp: Codable, Equatable {
             }
         }
     }
-    
-    static func now() -> Timestamp {
-        return Timestamp.milliseconds(UInt64(Date().timeIntervalSince1970 * 
1000.0))
+    public func hash(into hasher: inout Hasher) {
+        switch self {
+            case .milliseconds(let t_ms):
+                hasher.combine(t_ms)
+            case .never:
+                hasher.combine("never")
+        }
     }
-    
     public func encode(to encoder: Encoder) throws {
         var value = encoder.container(keyedBy: CodingKeys.self)
         switch self {
@@ -62,6 +73,39 @@ public enum Timestamp: Codable, Equatable {
     }
 }
 
+extension Timestamp {
+    /// make a Timestamp for  `now`
+    public static func now() -> Timestamp {
+        return Timestamp.milliseconds(Date().millisecondsSince1970)
+    }
+
+    /// convenience initializer from UInt64 (milliseconds from January 1, 1970)
+    public init(from: UInt64) {
+        self = Timestamp.milliseconds(from)
+    }
+
+    /// switch-case the enum here so the caller function doesn't have to
+    public func milliseconds() throws -> UInt64 {
+        switch self {
+            case .milliseconds(let t_ms):
+                return t_ms
+            case .never:
+                throw TimestampError.invalidUInt64Value
+        }
+    }
+}
+
+/// extend iOS Date to work with milliseconds since 1970
+extension Date {
+    public var millisecondsSince1970: UInt64 {
+        UInt64((timeIntervalSince1970 * 1000.0).rounded())
+    }
+
+    public init(milliseconds: UInt64) {
+        self = Date(timeIntervalSince1970: TimeInterval(milliseconds) / 1000)
+    }
+}
+
 /// A duration of time, measured in milliseconds.
 public enum Duration: Equatable {
     case milliseconds(UInt64)

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