gnunet-svn
[Top][All Lists]
Advanced

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

[taler-taler-ios] branch master updated (e082595 -> e79881c)


From: gnunet
Subject: [taler-taler-ios] branch master updated (e082595 -> e79881c)
Date: Mon, 13 Nov 2023 21:27:05 +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 e082595  Bump version to 0.9.3 (24)
     new 27dd64c  Startup Chime
     new a93686d  Transaction, L10N
     new 5764915  Exchange with scopeInfo
     new 592a2ab  DD51 fractional base
     new 7950b99  fix tests
     new e0dc795  RequestPurpose
     new 93bdf94  Amount(currency:cent:)
     new d039dbd  Cleanup
     new 9ce01d5  cleanup, zero
     new 1ff1bc3  @Published currencyTicker
     new 2b0249b  Debugging
     new d02a14e  DD51 Currency rendering
     new 9da5f4b  once at very first startup
     new 73c64bd  Debugging: Delay currency info
     new 8197093  About with explicit link to taler.net
     new 23dd90a  TransactionsArraySliceV
     new 53ee8de  amountToTransfer Balances
     new 8a27e5e  amountToTransfer Transactions
     new 0d09d7d  amountToTransfer Pending
     new 1afc225  amountToTransfer Request
     new e18bc10  amountToTransfer Send
     new 3125395  amountToTransfer ManualWithdraw
     new ac45ed6  amountToTransfer Exchange
     new 04db105  Exchange needs Balance for Deposit
     new 02e3908  amountToTransfer Currency
     new b274d07  Minimalistic
     new fe47941  Debugging
     new ed0c4f3  Cleanup property wrappers
     new b6575a3  less logging
     new 4d6934b  Bugfix
     new 65b61e9  Use `te´and `ku´ for previews
     new 5f681aa  Shortcuts (50,25,10,5)
     new a5a8a71  Announce shouldn't change screen
     new e55bb04  Previews
     new a286bb9  Check Available, accessibility
     new e79881c  Bump version to 0.9.3 (25)

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


Summary of changes:
 TalerWallet.xcodeproj/project.pbxproj              |  18 +--
 TalerWallet1/Backend/WalletCore.swift              |   6 +-
 TalerWallet1/Controllers/Controller.swift          |  51 ++++--
 TalerWallet1/Controllers/DebugViewC.swift          |   2 +-
 TalerWallet1/Controllers/TalerWallet1App.swift     |   1 -
 TalerWallet1/Helper/CurrencySpecification.swift    |  40 ++++-
 TalerWallet1/Model/Model+Exchange.swift            |  17 +-
 TalerWallet1/Model/Transaction.swift               |  72 ++++++---
 TalerWallet1/Views/Balances/BalanceRowView.swift   |  23 ++-
 TalerWallet1/Views/Balances/BalancesListView.swift |  13 +-
 .../Views/Balances/BalancesSectionView.swift       |  89 ++++++-----
 TalerWallet1/Views/Balances/PendingRowView.swift   |  39 +++--
 TalerWallet1/Views/Exchange/ExchangeListView.swift |  22 +--
 TalerWallet1/Views/Exchange/ExchangeRowView.swift  |  13 +-
 .../Views/Exchange/ExchangeSectionView.swift       |  24 +--
 TalerWallet1/Views/Exchange/ManualWithdraw.swift   |  39 ++---
 .../Views/Exchange/ManualWithdrawDone.swift        |   9 +-
 TalerWallet1/Views/HelperViews/AmountRowV.swift    |   4 +-
 TalerWallet1/Views/HelperViews/BarGraph.swift      |   3 +-
 TalerWallet1/Views/HelperViews/CurrencyField.swift | 115 +++++++-------
 .../Views/HelperViews/CurrencyInputView.swift      |  76 +++++----
 .../Views/HelperViews/TransactionButton.swift      |  71 +++------
 .../Views/HelperViews/View+fitsSideBySide.swift    |   2 +-
 TalerWallet1/Views/Main/MainView.swift             |  36 +++--
 TalerWallet1/Views/Peer2peer/RequestPayment.swift  |  47 +++---
 .../{PaymentPurpose.swift => RequestPurpose.swift} |  37 ++---
 TalerWallet1/Views/Peer2peer/SendAmount.swift      |  87 +++++++----
 TalerWallet1/Views/Peer2peer/SendDoneV.swift       |   5 +-
 TalerWallet1/Views/Peer2peer/SendPurpose.swift     |  20 +--
 TalerWallet1/Views/Settings/AboutView.swift        |  71 +++++----
 TalerWallet1/Views/Settings/SettingsView.swift     |   5 +-
 .../Views/Sheets/P2P_Sheets/P2pAcceptDone.swift    |   2 +-
 .../Views/Sheets/P2P_Sheets/P2pPayURIView.swift    |   2 +-
 .../Sheets/P2P_Sheets/P2pReceiveURIView.swift      |   7 +-
 .../Views/Sheets/Payment/PayTemplateView.swift     |   5 +-
 .../Views/Sheets/Payment/PaymentView.swift         |  76 +++++----
 TalerWallet1/Views/Sheets/URLSheet.swift           |   1 +
 .../WithdrawAcceptDone.swift                       |   2 +-
 .../WithdrawBankIntegrated/WithdrawTOSView.swift   |   9 +-
 .../WithdrawBankIntegrated/WithdrawURIView.swift   |   4 +-
 .../Views/Transactions/ManualDetailsV.swift        |   4 +-
 .../Views/Transactions/ThreeAmountsV.swift         |   4 +-
 .../Views/Transactions/TransactionDetailView.swift |   2 +-
 .../Views/Transactions/TransactionRowView.swift    |  28 ++--
 .../Views/Transactions/TransactionsListView.swift  |  26 ++--
 TestFlight/WhatToTest.en-US.txt                    |  11 ++
 taler-swift/Sources/taler-swift/Amount.swift       | 172 ++++++++++++++++-----
 .../Tests/taler-swiftTests/AmountTests.swift       |  14 +-
 48 files changed, 820 insertions(+), 606 deletions(-)
 rename TalerWallet1/Views/Peer2peer/{PaymentPurpose.swift => 
RequestPurpose.swift} (75%)

diff --git a/TalerWallet.xcodeproj/project.pbxproj 
b/TalerWallet.xcodeproj/project.pbxproj
index 83e73fd..242ba39 100644
--- a/TalerWallet.xcodeproj/project.pbxproj
+++ b/TalerWallet.xcodeproj/project.pbxproj
@@ -67,7 +67,7 @@
                4E3EAE472A990778009F1BE8 /* QuiteSomeCoins.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4EBA82AC2A3F580500E5F39A /* 
QuiteSomeCoins.swift */; };
                4E3EAE482A990778009F1BE8 /* PayTemplateView.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4EBA56402A7FF5200084948B /* 
PayTemplateView.swift */; };
                4E3EAE492A990778009F1BE8 /* ManualWithdrawDone.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4EB431662A1E55C700C5690E /* 
ManualWithdrawDone.swift */; };
-               4E3EAE4A2A990778009F1BE8 /* PaymentPurpose.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4E9320462A164BC700A87B0E /* 
PaymentPurpose.swift */; };
+               4E3EAE4A2A990778009F1BE8 /* RequestPurpose.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4E9320462A164BC700A87B0E /* 
RequestPurpose.swift */; };
                4E3EAE4B2A990778009F1BE8 /* ShareSheet.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4E753A072A0B6A5F002D9328 /* ShareSheet.swift */; 
};
                4E3EAE4C2A990778009F1BE8 /* AmountView.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EB095492989CBFE0043A8A1 /* AmountView.swift */; 
};
                4E3EAE4D2A990778009F1BE8 /* P2pAcceptDone.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4E3B4BC22A42252300CC88B8 /* P2pAcceptDone.swift 
*/; };
@@ -167,7 +167,7 @@
                4E8E25332A1CD39700A27BFA /* EqualIconWidthDomain.swift in 
Sources */ = {isa = PBXBuildFile; fileRef = 4E8E25322A1CD39700A27BFA /* 
EqualIconWidthDomain.swift */; };
                4E9320432A14F6EA00A87B0E /* WalletColors.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4E9320422A14F6EA00A87B0E /* WalletColors.swift 
*/; };
                4E9320452A1645B600A87B0E /* RequestPayment.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4E9320442A1645B600A87B0E /* 
RequestPayment.swift */; };
-               4E9320472A164BC700A87B0E /* PaymentPurpose.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4E9320462A164BC700A87B0E /* 
PaymentPurpose.swift */; };
+               4E9320472A164BC700A87B0E /* RequestPurpose.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4E9320462A164BC700A87B0E /* 
RequestPurpose.swift */; };
                4E9796902A3765ED006F73BC /* AgePicker.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4E97968F2A3765ED006F73BC /* AgePicker.swift */; 
};
                4E983C292ADBDD3500FA9CC5 /* SingleAxisGeometryReader.swift in 
Sources */ = {isa = PBXBuildFile; fileRef = 4E983C282ADBDD3500FA9CC5 /* 
SingleAxisGeometryReader.swift */; };
                4E983C2A2ADBDD3500FA9CC5 /* SingleAxisGeometryReader.swift in 
Sources */ = {isa = PBXBuildFile; fileRef = 4E983C282ADBDD3500FA9CC5 /* 
SingleAxisGeometryReader.swift */; };
@@ -344,7 +344,7 @@
                4E8E25322A1CD39700A27BFA /* EqualIconWidthDomain.swift */ = 
{isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = 
EqualIconWidthDomain.swift; sourceTree = "<group>"; };
                4E9320422A14F6EA00A87B0E /* WalletColors.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= WalletColors.swift; sourceTree = "<group>"; };
                4E9320442A1645B600A87B0E /* RequestPayment.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= RequestPayment.swift; sourceTree = "<group>"; };
-               4E9320462A164BC700A87B0E /* PaymentPurpose.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= PaymentPurpose.swift; sourceTree = "<group>"; };
+               4E9320462A164BC700A87B0E /* RequestPurpose.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= RequestPurpose.swift; sourceTree = "<group>"; };
                4E97968F2A3765ED006F73BC /* AgePicker.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= AgePicker.swift; sourceTree = "<group>"; };
                4E983C282ADBDD3500FA9CC5 /* SingleAxisGeometryReader.swift */ = 
{isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = 
sourcecode.swift; path = SingleAxisGeometryReader.swift; sourceTree = 
"<group>"; };
                4E983C2B2ADC416800FA9CC5 /* View+fitsSideBySide.swift */ = {isa 
= PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; 
path = "View+fitsSideBySide.swift"; sourceTree = "<group>"; };
@@ -742,8 +742,8 @@
                                4EB095472989CBFE0043A8A1 /* Buttons.swift */,
                                4EF840A62A0B85F400EE0D47 /* CopyShare.swift */,
                                4ECB62812A0BB01D004ABBB7 /* SelectDays.swift */,
-                               4E53A33629F50B7B00830EC2 /* CurrencyField.swift 
*/,
                                4EA551242A2C923600FEC9A8 /* 
CurrencyInputView.swift */,
+                               4E53A33629F50B7B00830EC2 /* CurrencyField.swift 
*/,
                                4EEC157229F8242800D46A03 /* 
QRGeneratorView.swift */,
                                4E5A88F42A38A4FD00072618 /* 
QRCodeDetailView.swift */,
                                4E6EDD862A363D8D0031D520 /* ListStyle.swift */,
@@ -775,7 +775,7 @@
                                4E7940DD29FC307C00A9AEA1 /* SendPurpose.swift 
*/,
                                4EB3136029FEE79B007D68BC /* SendDoneV.swift */,
                                4E9320442A1645B600A87B0E /* 
RequestPayment.swift */,
-                               4E9320462A164BC700A87B0E /* 
PaymentPurpose.swift */,
+                               4E9320462A164BC700A87B0E /* 
RequestPurpose.swift */,
                        );
                        path = Peer2peer;
                        sourceTree = "<group>";
@@ -1090,7 +1090,7 @@
                                4E3EAE472A990778009F1BE8 /* 
QuiteSomeCoins.swift in Sources */,
                                4E3EAE482A990778009F1BE8 /* 
PayTemplateView.swift in Sources */,
                                4E3EAE492A990778009F1BE8 /* 
ManualWithdrawDone.swift in Sources */,
-                               4E3EAE4A2A990778009F1BE8 /* 
PaymentPurpose.swift in Sources */,
+                               4E3EAE4A2A990778009F1BE8 /* 
RequestPurpose.swift in Sources */,
                                4E3EAE4B2A990778009F1BE8 /* ShareSheet.swift in 
Sources */,
                                4EC4008F2AE8019700DF72C7 /* 
ExchangeRowView.swift in Sources */,
                                4E3EAE4C2A990778009F1BE8 /* AmountView.swift in 
Sources */,
@@ -1199,7 +1199,7 @@
                                4EBA82AD2A3F580500E5F39A /* 
QuiteSomeCoins.swift in Sources */,
                                4EBA56412A7FF5200084948B /* 
PayTemplateView.swift in Sources */,
                                4EB431672A1E55C700C5690E /* 
ManualWithdrawDone.swift in Sources */,
-                               4E9320472A164BC700A87B0E /* 
PaymentPurpose.swift in Sources */,
+                               4E9320472A164BC700A87B0E /* 
RequestPurpose.swift in Sources */,
                                4E753A082A0B6A5F002D9328 /* ShareSheet.swift in 
Sources */,
                                4EC400902AE8019700DF72C7 /* 
ExchangeRowView.swift in Sources */,
                                4EB0956C2989CBFE0043A8A1 /* AmountView.swift in 
Sources */,
@@ -1496,7 +1496,7 @@
                                CODE_SIGN_ENTITLEMENTS = 
"$(TARGET_NAME).entitlements";
                                CODE_SIGN_IDENTITY = "Apple Development";
                                CODE_SIGN_STYLE = Automatic;
-                               CURRENT_PROJECT_VERSION = 24;
+                               CURRENT_PROJECT_VERSION = 25;
                                DEVELOPMENT_TEAM = GUDDQ9428Y;
                                ENABLE_PREVIEWS = YES;
                                GENERATE_INFOPLIST_FILE = YES;
@@ -1538,7 +1538,7 @@
                                CODE_SIGN_ENTITLEMENTS = 
"$(TARGET_NAME).entitlements";
                                CODE_SIGN_IDENTITY = "Apple Development";
                                CODE_SIGN_STYLE = Automatic;
-                               CURRENT_PROJECT_VERSION = 24;
+                               CURRENT_PROJECT_VERSION = 25;
                                DEVELOPMENT_TEAM = GUDDQ9428Y;
                                ENABLE_PREVIEWS = YES;
                                GENERATE_INFOPLIST_FILE = YES;
diff --git a/TalerWallet1/Backend/WalletCore.swift 
b/TalerWallet1/Backend/WalletCore.swift
index 3a02aaf..350f194 100644
--- a/TalerWallet1/Backend/WalletCore.swift
+++ b/TalerWallet1/Backend/WalletCore.swift
@@ -190,7 +190,7 @@ extension WalletCore {
                     if let type = TransactionType(rawValue: components[1]) {
                         guard type != .refresh else { return }
                         if decoded.newTxState.major == .done {
-                            logger.log("Done: \(decoded.transactionId, 
privacy: .private(mask: .hash))")
+                            logger.info("Done: \(decoded.transactionId, 
privacy: .private(mask: .hash))")
                             Controller.shared.playSound(type.isIncoming ? 2 : 
1)
                         } else if decoded.newTxState.major == .expired {
                             logger.log("Expired: \(decoded.transactionId, 
privacy: .private(mask: .hash))")
@@ -202,7 +202,7 @@ extension WalletCore {
                 }
             } else {
                 // TODO: Same state usually means that an error is transmitted
-                logger.log("No State change: \(decoded.transactionId, privacy: 
.private(mask: .hash))")
+                logger.info("No State change: \(decoded.transactionId, 
privacy: .private(mask: .hash))")
             }
         } catch {       // rethrows
             symLog.log(jsonData)       // TODO: .error
@@ -339,7 +339,7 @@ extension WalletCore {
             sendRequest(request: reqData) { requestId, timeSent, result, error 
in
                 let timeUsed = Date.now - timeSent
                 let millisecs = timeUsed.milliseconds
-                self.logger.log("Request \(requestId) took \(millisecs) ms")
+                self.logger.info("Request \(requestId) took \(millisecs) ms")
                 var err: Error? = nil
                 if let json = result {
                     do {
diff --git a/TalerWallet1/Controllers/Controller.swift 
b/TalerWallet1/Controllers/Controller.swift
index fe55625..23964cc 100644
--- a/TalerWallet1/Controllers/Controller.swift
+++ b/TalerWallet1/Controllers/Controller.swift
@@ -33,7 +33,7 @@ class Controller: ObservableObject {
     private let symLog = SymLogC()
 
     @Published var backendState: BackendState = .none       // only used for 
launch animation
-    @Published var currencyInfos: [CurrencyInfo]
+    @Published var currencyTicker: Int = 0
     @AppStorage("useHaptics") var useHaptics: Bool = true   // extension 
mustn't define this, so it must be here
     @AppStorage("playSounds") var playSounds: Int = 1       // extension 
mustn't define this, so it must be here
     @AppStorage("talerFont") var talerFont: Int = 0         // extension 
mustn't define this, so it must be here
@@ -41,6 +41,7 @@ class Controller: ObservableObject {
     let logger = Logger(subsystem: "net.taler.gnu", category: "Controller")
     let player = AVQueuePlayer()
     let semaphore = AsyncSemaphore(value: 1)
+    var currencyInfos: [CurrencyInfo]
 
     var messageForSheet: String? = nil
 
@@ -52,36 +53,52 @@ class Controller: ObservableObject {
 //            }
 //        }
         backendState = .instantiated
+        currencyTicker = 0
         currencyInfos = []
     }
+// MARK: -
+    func hasInfo(for currency: String) -> Bool {
+        for info in currencyInfos {
+            if info.scope.currency == currency {
+                return true
+            }
+        }
+        return false
+    }
 
-    func info(for currency: String) -> CurrencyInfo? {
-        for currencyInfo in currencyInfos {
-            if currencyInfo.scope.currency == currency {
-                return currencyInfo
+    func info(for currency: String, _ ticker: Int) -> CurrencyInfo {
+        if ticker != currencyTicker {
+            print("❗️Yikes")
+        }
+        for info in currencyInfos {
+            if info.scope.currency == currency {
+                return info
             }
         }
-        return nil
+        return CurrencyInfo.zero(currency)
     }
 
     @MainActor
-    func setInfo(_ info: CurrencyInfo) async {
+    func setInfo(_ newInfo: CurrencyInfo) async {
         await semaphore.wait()
         defer { semaphore.signal() }
-        var existing: ScopeInfo? = nil
-        for currencyInfo in currencyInfos {
-            let scope = currencyInfo.scope
-            if scope.currency == info.scope.currency {
-                existing = scope
-                break
+
+        var replaced = false
+        var newInfos = currencyInfos.map { currencyInfo in
+            if currencyInfo.scope.currency == newInfo.scope.currency {
+                replaced = true
+                return newInfo
+            } else {
+                return currencyInfo
             }
         }
-        if existing != nil {
-            currencyInfos.removeAll(where: { $0.scope == existing })
+        if !replaced {
+            newInfos.append(newInfo)
         }
-        currencyInfos.append(info)
+        currencyInfos = newInfos
+        currencyTicker += 1         // triggers published view update
     }
-
+// MARK: -
     @MainActor
     func initWalletCore(_ model: WalletModel, sqlite3: Bool)
       async throws {
diff --git a/TalerWallet1/Controllers/DebugViewC.swift 
b/TalerWallet1/Controllers/DebugViewC.swift
index 50cbdca..2c18cce 100644
--- a/TalerWallet1/Controllers/DebugViewC.swift
+++ b/TalerWallet1/Controllers/DebugViewC.swift
@@ -152,7 +152,7 @@ class DebugViewC: ObservableObject {
 
     func announce(this: String) {
         if UIAccessibility.isVoiceOverRunning {
-            UIAccessibility.post(notification: .screenChanged, argument: this)
+            UIAccessibility.post(notification: .announcement, argument: this)
         }
     }
 
diff --git a/TalerWallet1/Controllers/TalerWallet1App.swift 
b/TalerWallet1/Controllers/TalerWallet1App.swift
index 9efb5be..043d646 100644
--- a/TalerWallet1/Controllers/TalerWallet1App.swift
+++ b/TalerWallet1/Controllers/TalerWallet1App.swift
@@ -22,7 +22,6 @@ struct TalerWallet1App: App {
     @State private var soundPlayed = false
 
     private let walletCore = WalletCore.shared
-    // our main controller
     private let controller = Controller.shared
     private let model = WalletModel.shared
     private let debugViewC = DebugViewC.shared
diff --git a/TalerWallet1/Helper/CurrencySpecification.swift 
b/TalerWallet1/Helper/CurrencySpecification.swift
index 63988e3..1cac315 100644
--- a/TalerWallet1/Helper/CurrencySpecification.swift
+++ b/TalerWallet1/Helper/CurrencySpecification.swift
@@ -8,11 +8,31 @@ import taler_swift
 extension Amount {
     func string(_ currencyInfo: CurrencyInfo?) -> String {
         if let currencyInfo {
-            return currencyInfo.string(for: valueAsTuple)
+            return currencyInfo.string(for: valueAsFloatTuple)
         } else {
             return valueStr
         }
     }
+
+    func inputDigits(_ currencyInfo: CurrencyInfo) -> UInt {
+        let inputDigits = currencyInfo.specs.fractionalInputDigits
+        if inputDigits < 0 { return 0 }
+        if inputDigits > 8 { return 8}
+        return UInt(inputDigits)
+    }
+
+    func addDigit(_ digit: UInt8, currencyInfo: CurrencyInfo) {
+        shiftLeft(add: digit, inputDigits(currencyInfo))
+    }
+
+    func removeDigit(_ currencyInfo: CurrencyInfo) {
+        shiftRight()                        // divide by 10
+        mask(inputDigits(currencyInfo))     // replace all digits after 
#inputDigit with 0
+    }
+
+    func plainString(_ currencyInfo: CurrencyInfo) -> String {
+        return plainString(inputDigits: inputDigits(currencyInfo))
+    }
 }
 
 public struct CurrencyInfo {
@@ -20,6 +40,18 @@ public struct CurrencyInfo {
     let specs: CurrencySpecification
     let formatter: CurrencyFormatter
 
+    public static func zero(_ currency: String) -> CurrencyInfo {
+        let scope = ScopeInfo(type: .global, currency: currency)
+        let specs = CurrencySpecification(name: currency,
+                         fractionalInputDigits: 0,
+                        fractionalNormalDigits: 0,
+                  fractionalTrailingZeroDigits: 0,
+                                  altUnitNames: [0 : "テ"])
+        return CurrencyInfo(scope: scope, specs: specs,
+                        formatter: CurrencyFormatter.formatter(scope: scope,
+                                                               specs: specs))
+    }
+
     /// returns all characters left from the decimalSeparator
     func integerPartStr(_ integerStr: String, decimalSeparator: String) -> 
String {
         if let integerIndex = integerStr.endIndex(of: decimalSeparator) {
@@ -119,18 +151,12 @@ public struct CurrencySpecification: Codable, Sendable {
     }
     /// some name for this CurrencySpecification
     let name: String
-    /// e.g. “.” for $, and “,” for €
-//    let decimalSeparator: String      taken from Locale.current
-    /// e.g. “,” for $, and “.” or “ ” for €     (France uses a narrow space 
character, Hungaria a normal one)
-//    let groupSeparator: String?       taken from Locale.current
     /// how much digits the user may enter after the decimal separator
     let fractionalInputDigits: Int
     /// €,$,£: 2;  some arabic currencies: 3,  ¥: 0
     let fractionalNormalDigits: Int
     /// usually same as numFractionalNormalDigits, but e.g. might be 2 for ¥
     let fractionalTrailingZeroDigits: Int
-    /// true for “$ 3.50”;  false for “3,50 €”
-//    let isCurrencyNameLeading: Bool
     /// map of powers of 10 to alternative currency names / symbols
     /// must always have an entry under "0" that defines the base name
     /// e.g.  "0 => €" or "3 => k€". For BTC, would be "0 => BTC, -3 => mBTC".
diff --git a/TalerWallet1/Model/Model+Exchange.swift 
b/TalerWallet1/Model/Model+Exchange.swift
index 524f462..032ab24 100644
--- a/TalerWallet1/Model/Model+Exchange.swift
+++ b/TalerWallet1/Model/Model+Exchange.swift
@@ -32,18 +32,21 @@ struct Exchange: Codable, Hashable, Identifiable {
     static func == (lhs: Exchange, rhs: Exchange) -> Bool {
         return lhs.exchangeBaseUrl == rhs.exchangeBaseUrl
         &&     lhs.tosStatus == rhs.tosStatus
-        &&     lhs.exchangeStatus == rhs.exchangeStatus                        
 // deprecated
+//        &&     lhs.exchangeStatus == rhs.exchangeStatus                      
   // deprecated
         &&     lhs.exchangeEntryStatus == rhs.exchangeEntryStatus
         &&     lhs.exchangeUpdateStatus == rhs.exchangeUpdateStatus
     }
 
     var exchangeBaseUrl: String
-    var currency: String?
+    // deprecated, use scopeInfo
+    var currency: String?           // TODO: remove this
+    var scopeInfo: ScopeInfo?
     var paytoUris: [String]
     var tosStatus: ExchangeTosStatus
-    var exchangeStatus: String?                                                
 // deprecated
-    var exchangeEntryStatus: ExchangeEntryStatus?                   // new, 
but not yet deployed in demo.taler.net
-    var exchangeUpdateStatus: ExchangeUpdateStatus?                 // new, 
but not yet deployed in demo.taler.net
+    // deprecated, use EntryStatus + UpdateStatus
+//    var exchangeStatus: String?
+    var exchangeEntryStatus: ExchangeEntryStatus
+    var exchangeUpdateStatus: ExchangeUpdateStatus
     var ageRestrictionOptions: [Int]
     var lastUpdateErrorInfo: ExchangeError?
 
@@ -188,10 +191,10 @@ extension WalletModel {
         _ = try await sendRequest(request)
     }
 
-    func getCurrencyInfo(scope: ScopeInfo)
+    func getCurrencyInfo(scope: ScopeInfo, delay: UInt = 0)
       async throws -> CurrencyInfo {
         let request = GetCurrencySpecification(scope: scope)
-        let response = try await sendRequest(request, ASYNCDELAY)
+        let response = try await sendRequest(request, ASYNCDELAY + delay)
         return CurrencyInfo(scope: scope, specs: 
response.currencySpecification,
                         formatter: CurrencyFormatter.formatter(scope: scope,
                                                                specs: 
response.currencySpecification))
diff --git a/TalerWallet1/Model/Transaction.swift 
b/TalerWallet1/Model/Transaction.swift
index ea1e599..307e548 100644
--- a/TalerWallet1/Model/Transaction.swift
+++ b/TalerWallet1/Model/Transaction.swift
@@ -23,7 +23,7 @@ enum TransactionMinorState: String, Codable {
     case aml        // AmlRequired
     case mergeKycRequired = "merge-kyc"
     case track
-    case pay
+    case submitPayment = "submit-payment"
     case rebindSession = "rebind-session"
     case refresh
     case pickup
@@ -40,17 +40,16 @@ enum TransactionMinorState: String, Codable {
     case repurchase
     case bankRegisterReserve = "bank-register-reserve"
     case bankConfirmTransfer = "bank-confirm-transfer"
-    case exchangeWaitReserve = "exchange-wait-reserve"
     case withdrawCoins = "withdraw-coins"
+    case exchangeWaitReserve = "exchange-wait-reserve"
     case abortingBank = "aborting-bank"
+    case aborting
     case refused
     case withdraw
     case merchantOrderProposed = "merchant-order-proposed"
     case proposed
     case refundAvailable = "refund-available"
     case acceptRefund = "accept-refund"
-
-    case submitPayment = "submit-payment"
 }
 
 enum TransactionMajorState: String, Codable {
@@ -67,23 +66,20 @@ enum TransactionMajorState: String, Codable {
     case expired
       // Only used for the notification, never in the transaction history
     case deleted
-      // Placeholder until D37 is fully implemented
-    case unknown
 
     var localizedState: String {
         switch self {
             case .none:      return                   "none"
-            case .pending:   return String(localized: "MajorState.Pending", 
defaultValue: "Pending", comment: "TransactionMajorState")
-            case .done:      return String(localized: "MajorState.Done", 
defaultValue: "Done", comment: "TransactionMajorState")
-            case .aborting:  return String(localized: "MajorState.Aborting", 
defaultValue: "Aborting", comment: "TransactionMajorState")
-            case .aborted:   return String(localized: "MajorState.Aborted", 
defaultValue: "Aborted", comment: "TransactionMajorState")
+            case .pending:   return String(localized: "MajorState.Pending", 
defaultValue: "Pending", comment: "TxMajorState heading")
+            case .done:      return String(localized: "MajorState.Done", 
defaultValue: "Done", comment: "TxMajorState heading")
+            case .aborting:  return String(localized: "MajorState.Aborting", 
defaultValue: "Aborting", comment: "TxMajorState heading")
+            case .aborted:   return String(localized: "MajorState.Aborted", 
defaultValue: "Aborted", comment: "TxMajorState heading")
             case .suspended: return                   "Suspended"
-            case .dialog:    return String(localized: "MajorState.Dialog", 
defaultValue: "Dialog", comment: "TransactionMajorState")
+            case .dialog:    return String(localized: "MajorState.Dialog", 
defaultValue: "Dialog", comment: "TxMajorState heading")
             case .suspendedAborting: return           "AbortingSuspended"
-            case .failed:    return String(localized: "MajorState.Failed", 
defaultValue: "Failed", comment: "TransactionMajorState")
-            case .expired:   return String(localized: "MajorState.Expired", 
defaultValue: "Expired", comment: "TransactionMajorState")
-            case .deleted:   return String(localized: "MajorState.Deleted", 
defaultValue: "Deleted", comment: "TransactionMajorState")
-            case .unknown:   return String(localized: "MajorState.Unknown", 
defaultValue: "Unknown", comment: "TransactionMajorState")
+            case .failed:    return String(localized: "MajorState.Failed", 
defaultValue: "Failed", comment: "TxMajorState heading")
+            case .expired:   return String(localized: "MajorState.Expired", 
defaultValue: "Expired", comment: "TxMajorState heading")
+            case .deleted:   return String(localized: "MajorState.Deleted", 
defaultValue: "Deleted", comment: "TxMajorState heading")
         }
     }
 }
@@ -104,12 +100,52 @@ struct TransactionTransition: Codable {             // 
Notification
 }
 
 enum TxAction: String, Codable {
-    case abort      // pending,dialog,suspended -> aborting
-//  case revive     // aborting -> pending ?? maybe post 1.0
-    case fail       // aborting -> failed
     case delete     // dialog,done,expired,aborted,failed -> ()
     case suspend    // pending -> suspended; aborting -> ab_suspended
     case resume     // suspended -> pending; ab_suspended -> aborting
+    case abort      // pending,dialog,suspended -> aborting
+//  case revive     // aborting -> pending ?? maybe post 1.0
+    case fail       // aborting -> failed
+    case retry      //
+
+    var localizedActionTitle: String {
+        return switch self {
+            case .delete:   String(localized: "TxAction.Delete", defaultValue: 
"Delete from list", comment: "TxAction button")
+            case .suspend:  String("Suspend")
+            case .resume:   String("Resume")
+            case .abort:    String(localized: "TxAction.Abort", defaultValue: 
"Abort", comment: "TxAction button")
+//            case .revive:   String(localized: "TxAction.Revive", 
defaultValue: "Revive", comment: "TxAction button")
+            case .fail:     String(localized: "TxAction.Fail", defaultValue: 
"Fail", comment: "TxAction button")
+            case .retry:    String(localized: "TxAction.Retry", defaultValue: 
"Retry", comment: "TxAction button")
+        }
+    }
+    var localizedActionImage: String? {
+        return switch self {
+            case .delete:   "trash"                     // 􀈑
+            case .suspend:
+                if #available(iOS 16.0, *) {
+                    "clock.badge.xmark"                 // 􁜒
+                } else {
+                    "clock.badge.exclamationmark"       // 􀹶
+                }
+            case .resume:   "clock.arrow.circlepath"    // 􀣔
+            case .abort:    "x.circle"                  // 􀀲
+//            case .revive:   "clock.arrow.circlepath"    // 􀣔
+            case .fail:     "play.slash"                // 􀪅
+            case .retry:    "arrow.circlepath"          // 􁹠
+        }
+    }
+    var localizedActionExecuted: String {
+        switch self {
+            case .delete:   return String(localized: "TxActionDone.Delete", 
defaultValue: "Deleted from list", comment: "TxAction button")
+            case .suspend:  return String("Suspending...")
+            case .resume:   return String("Resuming...")
+            case .abort:    return String(localized: "TxActionDone.Abort", 
defaultValue: "Abort pending...", comment: "TxAction button")
+//            case .revive:   return String(localized: "TxActionDone.Revive", 
defaultValue: "Revive", comment: "TxAction button")
+            case .fail:     return String(localized: "TxActionDone.Fail", 
defaultValue: "Failing...", comment: "TxAction button")
+            case .retry:    return String(localized: "TxActionDone.Retry", 
defaultValue: "Retrying...", comment: "TxAction button")
+        }
+    }
 }
 
 enum TransactionType: String, Codable {
diff --git a/TalerWallet1/Views/Balances/BalanceRowView.swift 
b/TalerWallet1/Views/Balances/BalanceRowView.swift
index 2fb15ae..1c78853 100644
--- a/TalerWallet1/Views/Balances/BalanceRowView.swift
+++ b/TalerWallet1/Views/Balances/BalanceRowView.swift
@@ -48,11 +48,11 @@ struct BalanceButton: View {
 /// [Send Money]  [Request Payment]
 struct BalanceRowView: View {
     let amount: Amount
-    let currencyInfo: CurrencyInfo?
     let sendAction: () -> Void
     let recvAction: () -> Void
     let rowAction: () -> Void
     @Environment(\.sizeCategory) var sizeCategory
+    @EnvironmentObject private var controller: Controller
     @AppStorage("iconOnly") var iconOnly: Bool = false
     @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
 
@@ -72,6 +72,7 @@ struct BalanceRowView: View {
     var body: some View {
         SingleAxisGeometryReader { width in
             VStack (alignment: .trailing) {
+                let currencyInfo = controller.info(for: amount.currencyStr, 
controller.currencyTicker)
                 let amountStr = amount.string(currencyInfo)
                 BalanceButton(amountStr: amountStr,
                            sizeCategory: sizeCategory,
@@ -99,28 +100,26 @@ struct BalanceRowView: View {
 // MARK: -
 #if  DEBUG
 
-struct SomeBalanceRows: View {
+struct BalanceRowView_Previews: PreviewProvider {
+    @MainActor
+  struct StateContainer: View {
     var body: some View {
-        let testInfo = PreviewCurrencyInfo(TESTCURRENCY, digits: 0)
-        let demoInfo = PreviewCurrencyInfo(TESTCURRENCY, digits: 2)
-        let test = try! Amount(fromString: TESTCURRENCY + ":1.23")
-        let demo = try! Amount(fromString: DEMOCURRENCY + ":123.12")
+        let test = Amount(currency: TESTCURRENCY, cent: 123)
+        let demo = Amount(currency: DEMOCURRENCY, cent: 123456)
 //        let testStr = test.string(testInfo)
 //        let demoStr = demo.string(demoInfo)
 
         List {
             Section {
-                BalanceRowView(amount: demo, currencyInfo: demoInfo, 
sendAction: {}, recvAction: {}, rowAction: {})
+                BalanceRowView(amount: demo, sendAction: {}, recvAction: {}, 
rowAction: {})
             }
-            BalanceRowView(amount: test, currencyInfo: testInfo, sendAction: 
{}, recvAction: {}, rowAction: {})
+            BalanceRowView(amount: test, sendAction: {}, recvAction: {}, 
rowAction: {})
         }
-
     }
-}
+  }
 
-struct BalanceRowView_Previews: PreviewProvider {
     static var previews: some View {
-        SomeBalanceRows()
+        StateContainer()
 //            .environment(\.sizeCategory, .extraExtraLarge)    Canvas Device 
Settings
     }
 }
diff --git a/TalerWallet1/Views/Balances/BalancesListView.swift 
b/TalerWallet1/Views/Balances/BalancesListView.swift
index d8401e2..f7ee719 100644
--- a/TalerWallet1/Views/Balances/BalancesListView.swift
+++ b/TalerWallet1/Views/Balances/BalancesListView.swift
@@ -14,14 +14,15 @@ struct BalancesListView: View {
     let navTitle: String
     @Binding var balances: [Balance]
     @Binding var shouldReloadBalances: Int
-    @State private var lastReloadedBalances = 0
 #if TABBAR  // Taler Wallet
 #else       // GNU Taler
     let hamburgerAction: () -> Void
 #endif
 
     @EnvironmentObject private var model: WalletModel
-    @State private var centsToTransfer: UInt64 = 0
+
+    @State private var lastReloadedBalances = 0
+    @State private var amountToTransfer = Amount.zero(currency: "")            
 // Update currency when used
     @State private var summary: String = ""
     @State private var showQRScanner: Bool = false
     @State private var showCameraAlert: Bool = false
@@ -102,7 +103,7 @@ struct BalancesListView: View {
         let hamburger: HamburgerButton = HamburgerButton(action: 
hamburgerAction)
 #endif
         Content(symLog: symLog, stack: stack.push(), balances: $balances,
-                centsToTransfer: $centsToTransfer, summary: $summary,
+                amountToTransfer: $amountToTransfer, summary: $summary,
                 reloadBalances: reloadBalances)
             .navigationTitle(navTitle)
             .navigationBarItems(leading: hamburger,
@@ -131,7 +132,7 @@ extension BalancesListView {
         let stack: CallStack
         @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
         @Binding var balances: [Balance]
-        @Binding var centsToTransfer: UInt64
+        @Binding var amountToTransfer: Amount
         @Binding var summary: String
         var reloadBalances: (_ stack: CallStack, _ invalidateCache: Bool) 
async -> Int
 
@@ -147,9 +148,9 @@ extension BalancesListView {
                 } else {
                     List(balances, id: \.self) { balance in
                         BalancesSectionView(stack: 
stack.push("\(balance.scopeInfo.currency)"),
-                                          balance: balance,
+                                          balance: balance,                    
 // this is the currency to be used
                                      sectionCount: count,
-                                  centsToTransfer: $centsToTransfer,
+                                 amountToTransfer: $amountToTransfer,          
 // does still have the wrong currency
                                           summary: $summary)
                     }
                     .onAppear() {
diff --git a/TalerWallet1/Views/Balances/BalancesSectionView.swift 
b/TalerWallet1/Views/Balances/BalancesSectionView.swift
index d0a3718..5d58670 100644
--- a/TalerWallet1/Views/Balances/BalancesSectionView.swift
+++ b/TalerWallet1/Views/Balances/BalancesSectionView.swift
@@ -17,18 +17,17 @@ import SymLog
 struct BalancesSectionView {
     private let symLog = SymLogV(0)
     let stack: CallStack
-    let balance: Balance
+    let balance: Balance                            // this is the currency to 
be used
     let sectionCount: Int
-    @Binding var centsToTransfer: UInt64
+    @Binding var amountToTransfer: Amount           // does still have the 
wrong currency
     @Binding var summary: String
 
-//    @AppStorage("moreContrast") var moreContrast: Bool = false
-    @AppStorage("iconOnly") var iconOnly: Bool = false
     @EnvironmentObject private var model: WalletModel
     @EnvironmentObject private var controller: Controller
+    @AppStorage("iconOnly") var iconOnly: Bool = false
+//    @AppStorage("moreContrast") var moreContrast: Bool = false
 
     @State private var isShowingDetailView = false
-
     @State private var transactions: [Transaction] = []
     @State private var completedTransactions: [Transaction] = []
     @State private var pendingTransactions: [Transaction] = []
@@ -70,7 +69,7 @@ extension BalancesSectionView: View {
         let _ = symLog.vlog()       // just to get the # to compare it with 
.onAppear & onDisappear
 #endif
         let currency = balance.scopeInfo.currency
-        let currencyInfo = controller.info(for: currency)
+        let currencyInfo = controller.info(for: currency, 
controller.currencyTicker)
 
         Section {
             if "KUDOS" == currency && !balance.available.isZero {
@@ -81,17 +80,15 @@ extension BalancesSectionView: View {
             }
             BalancesNavigationLinksView(stack: stack.push(),
                                       balance: balance,
-                                 currencyInfo: currencyInfo,
-                              centsToTransfer: $centsToTransfer,
+                             amountToTransfer: $amountToTransfer,           // 
does still have the wrong currency
                                       summary: $summary,
-//                             buttonSelected: $buttonSelected,
                         completedTransactions: $completedTransactions,
                               reloadAllAction: reloadCompleted,
                               reloadOneAction: reloadOneAction)
             if pendingTransactions.count > 0 {
                 BalancesPendingRowView(symLog: symLog,
                                         stack: stack.push(),
-                                 currencyInfo: currencyInfo,
+                                     currency: currency,
                           pendingTransactions: $pendingTransactions,
                                 reloadPending: reloadPending,
                               reloadOneAction: reloadOneAction)
@@ -102,7 +99,7 @@ extension BalancesSectionView: View {
                     LazyView {
                         TransactionsListView(stack: stack.push(),
                                           navTitle: String(localized: 
"Incomplete", comment: "ViewTitle of TransactionList"),
-                                      currencyInfo: currencyInfo,
+                                          currency: currency,
                                       transactions: incompleteTransactions,
                                         showUpDown: false,
                                    reloadAllAction: reloadIncomplete,
@@ -135,11 +132,11 @@ extension BalancesSectionView: View {
             Section {
                 let slice = completedTransactions.prefix(3)         // already 
sorted
                 let threeTransactions = Array(slice)
-                TransactionsRowsView(symLog: symLog,
-                                      stack: stack.push(),
-                               currencyInfo: currencyInfo,
-                               transactions: threeTransactions,
-                            reloadOneAction: reloadOneAction)
+                TransactionsArraySliceV(symLog: symLog,
+                                         stack: stack.push(),
+                                      currency: currency,
+                                  transactions: threeTransactions,
+                               reloadOneAction: reloadOneAction)
             } header: {
                 if !iconOnly {
                     Text("Recent transactions")
@@ -147,22 +144,21 @@ extension BalancesSectionView: View {
 //                        .foregroundColor(moreContrast ? .primary : 
.secondary)
                 }
             }
-        }
+        } // recent transactions
     } // body
-} // extension BalancesSectionView
+} // BalancesSectionView
 
 fileprivate struct BalancesPendingRowView: View {
     let symLog: SymLogV?
     let stack: CallStack
-    let currencyInfo: CurrencyInfo?
-//
+    let currency: String // = currencyInfo.scope.currency
     @Binding var pendingTransactions: [Transaction]
     let reloadPending: (_ stack: CallStack) async -> ()
     let reloadOneAction: ((_ transactionId: String) async throws -> 
Transaction)
 
     func computePending(currency: String) -> (Amount, Amount) {
-        var incoming = Amount(currency: currency, value: 0)
-        var outgoing = Amount(currency: currency, value: 0)
+        var incoming = Amount(currency: currency, cent: 0)
+        var outgoing = Amount(currency: currency, cent: 0)
         for transaction in pendingTransactions {
             let effective = transaction.common.amountEffective
             if currency == effective.currencyStr {
@@ -182,7 +178,6 @@ fileprivate struct BalancesPendingRowView: View {
     }
 
     var body: some View {
-        let currency: String = currencyInfo?.scope.currency ?? ""
         let (pendingIncoming, pendingOutgoing) = computePending(currency: 
currency)
 
         NavigationLink {
@@ -190,7 +185,7 @@ fileprivate struct BalancesPendingRowView: View {
             LazyView {
                 TransactionsListView(stack: stack.push(),
                                   navTitle: String(localized: "Pending", 
comment: "ViewTitle of TransactionList"),
-                              currencyInfo: currencyInfo,
+                                  currency: currency,
                               transactions: pendingTransactions,
                                 showUpDown: false,
                            reloadAllAction: reloadPending,
@@ -200,11 +195,11 @@ fileprivate struct BalancesPendingRowView: View {
             VStack(spacing: 6) {
                 var rows = 0
                 if !pendingIncoming.isZero {
-                    PendingRowView(amount: pendingIncoming, currencyInfo: 
currencyInfo, incoming: true)
+                    PendingRowView(amount: pendingIncoming, incoming: true)
                     let _ = (rows+=1)
                 }
                 if !pendingOutgoing.isZero {
-                    PendingRowView(amount: pendingOutgoing, currencyInfo: 
currencyInfo, incoming: false)
+                    PendingRowView(amount: pendingOutgoing, incoming: false)
                     let _ = (rows+=1)
                 }
                 if rows == 0 {
@@ -220,9 +215,8 @@ fileprivate struct BalancesPendingRowView: View {
 fileprivate struct BalancesNavigationLinksView: View {
     let stack: CallStack
     let balance: Balance
-    let currencyInfo: CurrencyInfo?
 //    let sectionCount: Int
-    @Binding var centsToTransfer: UInt64
+    @Binding var amountToTransfer: Amount           // does still have the 
wrong currency
     @Binding var summary: String
     @Binding var completedTransactions: [Transaction]
     let reloadAllAction: (_ stack: CallStack) async -> ()
@@ -243,21 +237,26 @@ fileprivate struct BalancesNavigationLinksView: View {
 //        }
     }
 
-    var body: some View {
+    func setCurrency() -> String {
         let currency = balance.scopeInfo.currency
+        amountToTransfer.setCurrency(currency)
+        return currency
+    }
+
+    var body: some View {
+        let currency = setCurrency()                        // update currency 
in amountToTransfer
         HStack(spacing: 0) {
             NavigationLink(destination: LazyView {
                 SendAmount(stack: stack.push(),
                  amountAvailable: balance.available,
-                 centsToTransfer: $centsToTransfer,
+                amountToTransfer: $amountToTransfer,        // with correct 
currency
                          summary: $summary)
             }, tag: 1, selection: $buttonSelected
             ) { EmptyView() }.frame(width: 0).opacity(0).hidden()           // 
SendAmount
 
             NavigationLink(destination: LazyView {
                 RequestPayment(stack: stack.push(),
-                           scopeInfo: balance.scopeInfo,
-                     centsToTransfer: $centsToTransfer,
+                    amountToTransfer: $amountToTransfer,    // with correct 
currency
                              summary: $summary)
             }, tag: 2, selection: $buttonSelected
             ) { EmptyView() }.frame(width: 0).opacity(0).hidden()           // 
RequestPayment
@@ -265,7 +264,7 @@ fileprivate struct BalancesNavigationLinksView: View {
             NavigationLink(destination: LazyView {
                 TransactionsListView(stack: stack.push(),
                                   navTitle: String(localized: "Transactions", 
comment: "ViewTitle of TransactionList"),
-                              currencyInfo: currencyInfo,
+                                  currency: currency,
                               transactions: completedTransactions,
                                 showUpDown: true,
                            reloadAllAction: reloadAllAction,
@@ -273,37 +272,37 @@ fileprivate struct BalancesNavigationLinksView: View {
             }, tag: 3, selection: $buttonSelected
             ) { EmptyView() }.frame(width: 0).opacity(0).hidden()           // 
TransactionsListView
 
-            BalanceRowView(amount: balance.available, currencyInfo: 
currencyInfo,
-                sendAction: {
-                    selectAndUpdate(1)      // will trigger SendAmount 
NavigationLink
-                }, recvAction: {
-                    selectAndUpdate(2)      // will trigger RequestPayment 
NavigationLink
-                }, rowAction: {
-                    buttonSelected = 3      // will trigger TransactionList 
NavigationLink
-                })
+            BalanceRowView(amount: balance.available,
+                       sendAction: {
+                            selectAndUpdate(1)      // trigger SendAmount 
NavigationLink
+                    }, recvAction: {
+                            selectAndUpdate(2)      // trigger RequestPayment 
NavigationLink
+                     }, rowAction: {
+                            buttonSelected = 3      // trigger TransactionList 
NavigationLink
+                     })
         }
     }
 }
 // MARK: -
 #if false   // model crashes
+struct BalancesSectionView_Previews: PreviewProvider {
 fileprivate struct BindingViewContainer: View {
-    @State var centsToTransfer: UInt64 = 333
+    @State var amountToTransfer: UInt64 = 333
     @State private var summary: String = "bla-bla"
 
     var body: some View {
         let scopeInfo = ScopeInfo(type: ScopeInfo.ScopeInfoType.exchange, url: 
DEMOEXCHANGE, currency: LONGCURRENCY)
-        let balance = Balance(available: try! Amount(fromString: LONGCURRENCY 
+ ":0.1"),
+        let balance = Balance(available: Amount(currency: LONGCURRENCY, 
cent:1),
                               scopeInfo: scopeInfo,
                       requiresUserInput: false,
                  hasPendingTransactions: true)
         BalancesSectionView(balance: balance,
                        sectionCount: 2,
-                    centsToTransfer: $centsToTransfer,
+                   amountToTransfer: $amountToTransfer,
                             summary: $summary)
     }
 }
 
-struct BalancesSectionView_Previews: PreviewProvider {
     static var previews: some View {
         List {
             BindingViewContainer()
diff --git a/TalerWallet1/Views/Balances/PendingRowView.swift 
b/TalerWallet1/Views/Balances/PendingRowView.swift
index c312006..9549c40 100644
--- a/TalerWallet1/Views/Balances/PendingRowView.swift
+++ b/TalerWallet1/Views/Balances/PendingRowView.swift
@@ -53,10 +53,10 @@ struct PendingRowContentV: View {
 /// This view shows a pending transaction row in a currency section
 struct PendingRowView: View {
     let amount: Amount
-    let currencyInfo: CurrencyInfo?
     let incoming: Bool
 
     @Environment(\.sizeCategory) var sizeCategory
+    @EnvironmentObject private var controller: Controller
     @AppStorage("iconOnly") var iconOnly: Bool = false
 
     let inTitle0 = String(localized: "TitleIncoming_Short", defaultValue: 
"Incoming",
@@ -85,6 +85,7 @@ struct PendingRowView: View {
         let pendingColor = WalletColors().pendingColor(incoming)
         SingleAxisGeometryReader { width in
             Group {
+                let currencyInfo = controller.info(for: amount.currencyStr, 
controller.currencyTicker)
                 let amountStr = amount.string(currencyInfo)
                 let amountWidth = amountStr.width(largeAmountFont: false, 
sizeCategory)
                 let inTitle = iconOnly ? (inTitle0, nil)
@@ -108,31 +109,41 @@ struct PendingRowView: View {
 }
 // MARK: -
 #if DEBUG
-
 func PreviewCurrencyInfo(_ currency: String, digits: Int) -> CurrencyInfo {
-    let unitName = digits == 0 ? "¥" : "€"
+    let unitName = digits == 0 ? "テ" : "ク"  // do not use real currency 
symbols like "¥" : "€"
     let scope = ScopeInfo(type: .global, currency: currency)
-    let specs = CurrencySpecification(name: TESTCURRENCY,
-//                        decimalSeparator: ".", groupSeparator: "'",
+    let specs = CurrencySpecification(name: currency,
                      fractionalInputDigits: digits,
                     fractionalNormalDigits: digits,
               fractionalTrailingZeroDigits: digits,
-//                   isCurrencyNameLeading: true,
                               altUnitNames: [0 : unitName])
     let formatter = CurrencyFormatter.formatter(scope: scope, specs: specs)
     return CurrencyInfo(scope: scope, specs: specs, formatter: formatter)
 }
 
-struct PendingRowView_Previews: PreviewProvider {
-    static var previews: some View {
-        let testInfo = PreviewCurrencyInfo(TESTCURRENCY, digits: 0)
-        let demoInfo = PreviewCurrencyInfo(TESTCURRENCY, digits: 2)
-        let test = try! Amount(fromString: TESTCURRENCY + ":1.23")
-        let demo = try! Amount(fromString: DEMOCURRENCY + ":1234.56")
+@MainActor
+fileprivate struct Preview_Content: View {
+    var body: some View {
+        let test = Amount(currency: TESTCURRENCY, cent: 123)
+        let demo = Amount(currency: DEMOCURRENCY, cent: 123456)
         List {
-            PendingRowView(amount: test, currencyInfo: testInfo, incoming: 
true)
-            PendingRowView(amount: demo, currencyInfo: demoInfo, incoming: 
false)
+            PendingRowView(amount: test, incoming: true)
+            PendingRowView(amount: demo, incoming: false)
+        }
+    }
+}
+fileprivate struct Previews: PreviewProvider {
+    @MainActor
+    struct StateContainer: View {
+//        @StateObject private var controller = Controller.shared
+        var body: some View {
+            Text("Hello")
+//            Preview_Content()
+//                .environmentObject(controller)
         }
     }
+    static var previews: some View {
+        StateContainer()
+    }
 }
 #endif
diff --git a/TalerWallet1/Views/Exchange/ExchangeListView.swift 
b/TalerWallet1/Views/Exchange/ExchangeListView.swift
index cc1d987..75b7156 100644
--- a/TalerWallet1/Views/Exchange/ExchangeListView.swift
+++ b/TalerWallet1/Views/Exchange/ExchangeListView.swift
@@ -10,13 +10,14 @@ import SymLog
 struct ExchangeListView: View {
     private let symLog = SymLogV(0)
     let stack: CallStack
-//    @Binding var balances: [Balance]
+    @Binding var balances: [Balance]
     let navTitle: String
 #if TABBAR  // Taler Wallet
 #else       // GNU Taler
     var hamburgerAction: () -> Void
 #endif
     @EnvironmentObject private var model: WalletModel
+
     @State var showAlert: Bool = false
     @State var newExchange: String = TESTEXCHANGE
 
@@ -46,7 +47,7 @@ struct ExchangeListView: View {
         let addTitleStr = String(localized: "Add Exchange", comment: "title of 
the addExchange alert")
         let addButtonStr = String(localized: "Add", comment: "button in the 
addExchange alert")
         if #available(iOS 16.0, *) {
-            ExchangeListCommonV(symLog: symLog, stack: stack.push())
+            ExchangeListCommonV(symLog: symLog, stack: stack.push(), balances: 
$balances)
                 .navigationTitle(navTitle)
                 .navigationBarItems(leading: hamburger, trailing: plusButton)
                 .alert(addTitleStr, isPresented: $showAlert) {
@@ -60,7 +61,7 @@ struct ExchangeListView: View {
                     Text("Please enter the exchange URL")
                 }
         } else {    // iOS 15 cannot have a textfield in an alert, so we must
-            ExchangeListCommonV(symLog: symLog, stack: stack.push())
+            ExchangeListCommonV(symLog: symLog, stack: stack.push(), balances: 
$balances)
                 .navigationTitle(navTitle)
                 .navigationBarItems(leading: hamburger, trailing: plusButton)
                 .textFieldAlert(isPresented: $showAlert,
@@ -76,14 +77,14 @@ struct ExchangeListView: View {
 struct ExchangeListCommonV: View {
     let symLog: SymLogV?
     let stack: CallStack
-//    @Binding var balances: [Balance]
+    @Binding var balances: [Balance]
 
     @EnvironmentObject private var model: WalletModel
 
     @State private var exchanges: [Exchange] = []
 
     // source of truth for the value the user enters in currencyField for 
exchange withdrawals
-    @State private var centsToTransfer: UInt64 = 0        // TODO: different 
values for different currencies?
+    @State private var amountToTransfer = Amount.zero(currency: "")     // 
TODO: Hold different values for different currencies?
 
     func reloadExchanges() async -> Void {
         exchanges = await model.listExchangesM()
@@ -97,9 +98,9 @@ struct ExchangeListCommonV: View {
         //Text("Exchanges...")
         Content(symLog: symLog,
                  stack: stack.push(),
-//              balances: $balances,
+              balances: $balances,
              exchanges: $exchanges,
-       centsToTransfer: $centsToTransfer,
+      amountToTransfer: $amountToTransfer,
        reloadExchanges: reloadExchanges)
         .overlay {
             if exchanges.isEmpty {
@@ -119,9 +120,9 @@ extension ExchangeListCommonV {
         let symLog: SymLogV?
         let stack: CallStack
         @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
-//        @Binding var balances: [Balance]
+        @Binding var balances: [Balance]
         @Binding var exchanges: [Exchange]
-        @Binding var centsToTransfer: UInt64
+        @Binding var amountToTransfer: Amount           // does still have the 
wrong currency
         var reloadExchanges: () async -> Void
 
         func currenciesDict(_ exchanges: [Exchange]) -> [String : [Exchange]] {
@@ -142,12 +143,13 @@ extension ExchangeListCommonV {
 
         var body: some View {
             let dict = currenciesDict(exchanges)
+            // TODO: Balances for amountAvailable for Deposit
             let sortedDict = dict.sorted{ $0.key < $1.key}
             Group { // necessary for .backslide transition (bug in SwiftUI)
                 List(sortedDict, id: \.key) { key, value in
                     ExchangeSectionView(stack: stack.push(),
                                      currency: key, exchanges: value,
-                              centsToTransfer: $centsToTransfer)
+                             amountToTransfer: $amountToTransfer)              
 // does still have the wrong currency
                 }
                 .refreshable {
                     symLog?.log("refreshing")
diff --git a/TalerWallet1/Views/Exchange/ExchangeRowView.swift 
b/TalerWallet1/Views/Exchange/ExchangeRowView.swift
index 0ed21cb..afea32c 100644
--- a/TalerWallet1/Views/Exchange/ExchangeRowView.swift
+++ b/TalerWallet1/Views/Exchange/ExchangeRowView.swift
@@ -8,12 +8,11 @@ import taler_swift
 struct ExchangeRowView: View {
     let stack: CallStack
     let exchange: Exchange
-//    let amount: Amount
     let currency: String
-    @Binding var centsToTransfer: UInt64
-    @AppStorage("iconOnly") var iconOnly: Bool = false
+    @Binding var amountToTransfer: Amount
 
     @Environment(\.sizeCategory) var sizeCategory
+    @AppStorage("iconOnly") var iconOnly: Bool = false
     @State private var buttonSelected: Int? = nil
 
     func selectAndUpdate(_ button: Int) {
@@ -69,7 +68,7 @@ struct ExchangeRowView: View {
             NavigationLink(destination: LazyView {
                 ManualWithdraw(stack: stack.push(),
                             exchange: exchange,
-                     centsToTransfer: $centsToTransfer)
+                    amountToTransfer: $amountToTransfer)
             }, tag: 2, selection: $buttonSelected
             ) { EmptyView() }.frame(width: 0).opacity(0)
         }.listRowSeparator(.hidden)
@@ -105,9 +104,9 @@ struct ExchangeRowView: View {
 // MARK: -
 #if DEBUG
 fileprivate struct ExchangeRow_Container : View {
-    @State private var centsToTransfer: UInt64 = 100
+    @State private var amountToTransfer = Amount(currency: LONGCURRENCY, cent: 
1234)
 
-//    let amount = try! Amount(fromString: LONGCURRENCY + ":1234.56")
+//    let amount = Amount(currency: LONGCURRENCY, cent: 123456)
     var body: some View {
         let exchange1 = Exchange(exchangeBaseUrl: ARS_AGE_EXCHANGE,
                                         currency: LONGCURRENCY,
@@ -126,7 +125,7 @@ fileprivate struct ExchangeRow_Container : View {
         ExchangeRowView(stack: CallStack("Preview"),
                      exchange: exchange1,
                      currency: LONGCURRENCY,
-              centsToTransfer: $centsToTransfer)
+             amountToTransfer: $amountToTransfer)
     }
 }
 
diff --git a/TalerWallet1/Views/Exchange/ExchangeSectionView.swift 
b/TalerWallet1/Views/Exchange/ExchangeSectionView.swift
index 6e2afab..4664d50 100644
--- a/TalerWallet1/Views/Exchange/ExchangeSectionView.swift
+++ b/TalerWallet1/Views/Exchange/ExchangeSectionView.swift
@@ -10,23 +10,27 @@ import taler_swift
 /// [Deposit Coins]  [Withdraw Coins]
 struct ExchangeSectionView: View {
     let stack: CallStack
-//    let amount: Amount
-    let currency: String          // TODO: amount.currencyStr
+    let currency: String                        // this is the currency to be 
used
     let exchanges: [Exchange]
-    @Binding var centsToTransfer: UInt64
+    @Binding var amountToTransfer: Amount       // does still have the wrong 
currency
+
+    func setCurrency() -> String {
+        amountToTransfer.setCurrency(currency)
+        return currency
+    }
 
     var body: some View {
 #if DEBUG
         let _ = Self._printChanges()
 //        let _ = symLog.vlog()       // just to get the # to compare it with 
.onAppear & onDisappear
 #endif
+        let currency2 = setCurrency()           // update currency in 
amountToTransfer
         Section {
             ForEach(exchanges) { exchange in
                 ExchangeRowView(stack: stack.push(),
                              exchange: exchange,
-//                               amount: amount,
-                             currency: currency,            // TODO: 
(balance.available) amount.isZero to disable Deposit-button
-                      centsToTransfer: $centsToTransfer)
+                             currency: currency2,            // TODO: 
(balance.available) amount.isZero to disable Deposit-button
+                     amountToTransfer: $amountToTransfer)
             }
         } header: {
             BarGraphHeader(stack: stack.push(), currency: currency)
@@ -35,10 +39,11 @@ struct ExchangeSectionView: View {
 }
 // MARK: -
 #if DEBUG
+fileprivate struct ExchangeSection_Previews: PreviewProvider {
 fileprivate struct ExchangeRow_Container : View {
-    @State private var centsToTransfer: UInt64 = 100
+    @State private var amountToTransfer = Amount(currency: LONGCURRENCY, cent: 
1234)
 
-//    let amount = try! Amount(fromString: LONGCURRENCY + ":1234.56")
+//    let amount = Amount(currency: LONGCURRENCY, cent: 123456)
     var body: some View {
         let exchange1 = Exchange(exchangeBaseUrl: ARS_AGE_EXCHANGE,
                                         currency: LONGCURRENCY,
@@ -56,11 +61,10 @@ fileprivate struct ExchangeRow_Container : View {
                            ageRestrictionOptions: [])
         ExchangeSectionView(stack: CallStack("Preview"), currency: 
LONGCURRENCY,
                             exchanges: [exchange1, exchange2],
-                            centsToTransfer: $centsToTransfer)
+                            amountToTransfer: $amountToTransfer)
     }
 }
 
-fileprivate struct ExchangeSection_Previews: PreviewProvider {
     static var previews: some View {
         List {
             ExchangeRow_Container()
diff --git a/TalerWallet1/Views/Exchange/ManualWithdraw.swift 
b/TalerWallet1/Views/Exchange/ManualWithdraw.swift
index dc98268..505b55d 100644
--- a/TalerWallet1/Views/Exchange/ManualWithdraw.swift
+++ b/TalerWallet1/Views/Exchange/ManualWithdraw.swift
@@ -12,12 +12,12 @@ struct ManualWithdraw: View {
     let stack: CallStack
 
     let exchange: Exchange
-    @Binding var centsToTransfer: UInt64
+    @Binding var amountToTransfer: Amount
 
     @EnvironmentObject private var model: WalletModel
+    @AppStorage("iconOnly") var iconOnly: Bool = false
 
     @State var withdrawalAmountDetails: WithdrawalAmountDetails? = nil
-
 //    @State var ageMenuList: [Int] = []
 //    @State var selectedAge = 0
 
@@ -28,13 +28,14 @@ struct ManualWithdraw: View {
 #endif
         let currency = exchange.currency ?? String(localized: "Unknown", 
comment: "unknown currency")
         let navTitle = String(localized: "NavTitle_Withdraw (currency)", 
defaultValue: "Withdraw \(currency)")
-        let currencyField = CurrencyField(value: $centsToTransfer, currency: 
currency) // becomeFirstResponder
 //        let agePicker = AgePicker(ageMenuList: $ageMenuList, selectedAge: 
$selectedAge)
 
         ScrollView {
             VStack {
-                CurrencyInputView(currencyField: currencyField,
-                                  title: String(localized: "Amount to 
withdraw:"))
+                CurrencyInputView(amount: $amountToTransfer,
+                                   title: iconOnly ? String(localized: "How 
much:")
+                                                   : String(localized: "Amount 
to withdraw:"),
+                           shortcutLabel: String(localized: "Withdraw", 
comment: "VoiceOver: Withdraw $50,$25,$10,$5 shortcut buttons"))
                 let someCoins = SomeCoins(details: withdrawalAmountDetails)
                 QuiteSomeCoins(someCoins: someCoins, shouldShowFee: true,
                                currency: currency, amountEffective: 
withdrawalAmountDetails?.amountEffective)
@@ -42,7 +43,7 @@ struct ManualWithdraw: View {
                     .multilineTextAlignment(.center)
                     .accessibilityFont(.body)
 
-                let disabled = (centsToTransfer == 0) || someCoins.invalid || 
someCoins.tooMany
+                let disabled = amountToTransfer.isZero || someCoins.invalid || 
someCoins.tooMany
                 if !disabled {
 //                    agePicker
 
@@ -54,7 +55,7 @@ struct ManualWithdraw: View {
                             NavigationLink(destination: LazyView {
                                 ManualWithdrawDone(stack: stack.push(),
                                                 exchange: exchange,
-                                         centsToTransfer: centsToTransfer)
+                                        amountToTransfer: amountToTransfer)
 //                                              restrictAge: restrictAge)
                             }) {
                                 Text("Confirm Withdrawal")      // 
VIEW_WITHDRAW_ACCEPT
@@ -80,11 +81,11 @@ struct ManualWithdraw: View {
             symLog.log("onAppear")
             DebugViewC.shared.setViewID(VIEW_WITHDRAWAL, stack: stack.push())
         }
-        .task(id: centsToTransfer) { // re-run this whenever centsToTransfer 
changes
-            if centsToTransfer > 0 {
-                let amount = Amount.amountFromCents(currency, centsToTransfer)
+        .task(id: amountToTransfer.value) { // re-run this whenever 
amountToTransfer changes
+            if !amountToTransfer.isZero {
                 do {
-                    withdrawalAmountDetails = try await 
model.loadWithdrawalDetailsForAmountM(exchange.exchangeBaseUrl, amount: amount)
+                    withdrawalAmountDetails = try await 
model.loadWithdrawalDetailsForAmountM(exchange.exchangeBaseUrl,
+                                                                               
               amount: amountToTransfer)
 //                  agePicker.setAges(ages: 
withdrawalAmountDetails?.ageRestrictionOptions)
                 } catch {    // TODO: error
                     symLog.log(error.localizedDescription)
@@ -96,11 +97,12 @@ struct ManualWithdraw: View {
 }
 // MARK: -
 #if DEBUG
-struct ManualWithdraw_Container : View {
-    @State private var centsToTransfer: UInt64 = 510
+struct ManualWithdraw_Previews: PreviewProvider {
+  struct StateContainer : View {
+    @State private var amountToTransfer = Amount(currency: LONGCURRENCY, cent: 
510)
     @State private var details = WithdrawalAmountDetails(tosAccepted: false,
-                                                           amountRaw: try! 
Amount(fromString: LONGCURRENCY + ":5.1"),
-                                                     amountEffective: try! 
Amount(fromString: LONGCURRENCY + ":5.0"),
+                                                           amountRaw: 
Amount(currency: LONGCURRENCY, cent: 510),
+                                                     amountEffective: 
Amount(currency: LONGCURRENCY, cent: 500),
                                                            paytoUris: [],
                                                ageRestrictionOptions: [],
                                                             numCoins: 6)
@@ -114,14 +116,13 @@ struct ManualWithdraw_Container : View {
                           ageRestrictionOptions: [])
         ManualWithdraw(stack: CallStack("Preview"),
                     exchange: exchange,
-             centsToTransfer: $centsToTransfer,
+            amountToTransfer: $amountToTransfer,
      withdrawalAmountDetails: details)
     }
-}
+  }
 
-struct ManualWithdraw_Previews: PreviewProvider {
     static var previews: some View {
-        ManualWithdraw_Container()
+        StateContainer()
     }
 }
 #endif
diff --git a/TalerWallet1/Views/Exchange/ManualWithdrawDone.swift 
b/TalerWallet1/Views/Exchange/ManualWithdrawDone.swift
index 1a182fa..039f6e4 100644
--- a/TalerWallet1/Views/Exchange/ManualWithdrawDone.swift
+++ b/TalerWallet1/Views/Exchange/ManualWithdrawDone.swift
@@ -12,7 +12,7 @@ struct ManualWithdrawDone: View {
     let navTitle = String(localized: "Wire Transfer")
 
     let exchange: Exchange
-    let centsToTransfer: UInt64
+    let amountToTransfer: Amount
 //    let restrictAge: Int?
 
     @EnvironmentObject private var model: WalletModel
@@ -53,9 +53,8 @@ struct ManualWithdrawDone: View {
             DebugViewC.shared.setViewID(VIEW_WITHDRAW_ACCEPT, stack: 
stack.push())
         }.task {
             do {
-                let amount = Amount.amountFromCents(exchange.currency!, 
centsToTransfer)
                 let result = try await 
model.sendAcceptManualWithdrawalM(exchange.exchangeBaseUrl,
-                                                                         
amount: amount, restrictAge: 0)
+                                                                         
amount: amountToTransfer, restrictAge: 0)
                 transactionId = result!.transactionId
             } catch {    // TODO: error
                 symLog.log(error.localizedDescription)
@@ -67,7 +66,7 @@ struct ManualWithdrawDone: View {
 // MARK: -
 #if DEBUG
 struct ManualWithdrawDone_Container : View {
-    @State private var centsToTransfer: UInt64 = 510
+    @State private var amountToTransfer = Amount(currency: LONGCURRENCY, cent: 
510)
 
     var body: some View {
         let exchange = Exchange(exchangeBaseUrl: DEMOEXCHANGE,
@@ -79,7 +78,7 @@ struct ManualWithdrawDone_Container : View {
                           ageRestrictionOptions: [])
         ManualWithdrawDone(stack: CallStack("Preview"),
                         exchange: exchange,
-                 centsToTransfer: centsToTransfer)
+                amountToTransfer: amountToTransfer)
     }
 }
 
diff --git a/TalerWallet1/Views/HelperViews/AmountRowV.swift 
b/TalerWallet1/Views/HelperViews/AmountRowV.swift
index ac741f9..eda77f1 100644
--- a/TalerWallet1/Views/HelperViews/AmountRowV.swift
+++ b/TalerWallet1/Views/HelperViews/AmountRowV.swift
@@ -54,8 +54,8 @@ struct SectionWithAmountRow: View {
     var body: some View {
         let testInfo = PreviewCurrencyInfo(TESTCURRENCY, digits: 0)
         let demoInfo = PreviewCurrencyInfo(DEMOCURRENCY, digits: 2)
-        let test = try! Amount(fromString: TESTCURRENCY + ":1.23")
-        let demo = try! Amount(fromString: DEMOCURRENCY + ":1234.12")
+        let test = Amount(currency: TESTCURRENCY, cent: 123)
+        let demo = Amount(currency: DEMOCURRENCY, cent: 123456)
         let testStr = test.string(testInfo)
         let demoStr = demo.string(demoInfo)
         List {
diff --git a/TalerWallet1/Views/HelperViews/BarGraph.swift 
b/TalerWallet1/Views/HelperViews/BarGraph.swift
index 9eb61dc..673f6ad 100644
--- a/TalerWallet1/Views/HelperViews/BarGraph.swift
+++ b/TalerWallet1/Views/HelperViews/BarGraph.swift
@@ -12,8 +12,9 @@ struct BarGraphHeader: View {
     let stack: CallStack
     let currency: String
 
-//    @AppStorage("moreContrast") var moreContrast: Bool = false
     @EnvironmentObject private var model: WalletModel
+//    @AppStorage("moreContrast") var moreContrast: Bool = false
+
     @State private var completedTransactions: [Transaction] = []
 
     var body: some View {
diff --git a/TalerWallet1/Views/HelperViews/CurrencyField.swift 
b/TalerWallet1/Views/HelperViews/CurrencyField.swift
index 2072b02..784c9dd 100644
--- a/TalerWallet1/Views/HelperViews/CurrencyField.swift
+++ b/TalerWallet1/Views/HelperViews/CurrencyField.swift
@@ -21,12 +21,15 @@
  */
 import SwiftUI
 import UIKit
+import taler_swift
+import SymLog
 
 @MainActor
-public struct CurrencyField: View {
-    @Binding var value: UInt64
-    var currency: String
-    var formatter: NumberFormatter
+struct CurrencyField: View {
+    private let symLog = SymLogV(0)
+    @Binding var amount: Amount         // the `value´
+    let currencyInfo: CurrencyInfo
+
     private var currencyInputField: CurrencyInputField! = nil
 
     public func becomeFirstResponder() -> Void {
@@ -37,38 +40,31 @@ public struct CurrencyField: View {
         currencyInputField.resignFirstResponder()
     }
 
-    private var label: String {
-        let mag = pow(10, formatter.maximumFractionDigits)
-        return formatter.string(for: Decimal(value) / mag) ?? ""
+    func updateText(amount: Amount) {
+        currencyInputField.updateText(amount: amount)
     }
 
-    public init(value: Binding<UInt64>, currency: String, formatter: 
NumberFormatter) {
-        self._value = value
-        self.currency = currency
-        self.formatter = formatter
-        self.currencyInputField = CurrencyInputField(value: $value, formatter: 
formatter)
+    public init(amount: Binding<Amount>, currencyInfo: CurrencyInfo) {
+        self._amount = amount
+        self.currencyInfo = currencyInfo
+        self.currencyInputField = CurrencyInputField(amount: self.$amount,
+                                               currencyInfo: currencyInfo)
     }
 
-    public init(value: Binding<UInt64>, currency: String) {
-        let formatter = NumberFormatter()
-        formatter.locale = .current
-        formatter.numberStyle = .currency
-        formatter.currencySymbol = currency
-        formatter.minimumFractionDigits = 2
-        formatter.maximumFractionDigits = 2
-
-        self.init(value: value, currency: currency, formatter: formatter)
-    }
-
-    public var body: some View {
+    var body: some View {
+#if DEBUG
+        let _ = Self._printChanges()
+        let _ = symLog.vlog(amount.description)       // just to get the # to 
compare it with .onAppear & onDisappear
+#endif
         ZStack {
             // Text view to display the formatted currency
             // Set as priority so CurrencyInputField size doesn't affect parent
-            Text(label)
+            Text(amount.string(currencyInfo))
                 .layoutPriority(1)
 
             // Input text field to handle UI
             currencyInputField
+                .accessibilityHidden(true)
 //                .textFieldStyle(.roundedBorder)
         }
     }
@@ -91,8 +87,9 @@ class NoCaretTextField: UITextField {
 
 @MainActor
 struct CurrencyInputField: UIViewRepresentable {
-    @Binding var value: UInt64
-    var formatter: NumberFormatter
+    @Binding var amount: Amount
+    let currencyInfo: CurrencyInfo
+
     private let textField = NoCaretTextField(frame: .zero)
 
     func makeCoordinator() -> Coordinator {
@@ -107,6 +104,14 @@ struct CurrencyInputField: UIViewRepresentable {
         textField.resignFirstResponder()
     }
 
+    func updateText(amount: Amount) {
+        let plain = amount.plainString(currencyInfo)
+        print("Setting textfield to: \(plain)")
+        textField.text = plain
+        let endPosition = textField.endOfDocument
+        textField.selectedTextRange = textField.textRange(from: endPosition, 
to: endPosition)
+    }
+
     func makeUIView(context: Context) -> NoCaretTextField {
         // Assign delegate
         textField.delegate = context.coordinator
@@ -127,7 +132,7 @@ struct CurrencyInputField: UIViewRepresentable {
         )
 
         // Set initial textfield text
-        context.coordinator.updateText(value, textField: textField)
+        context.coordinator.updateText(amount, textField: textField)
 
         return textField
     }
@@ -145,30 +150,35 @@ struct CurrencyInputField: UIViewRepresentable {
             self.input = currencyTextField
         }
 
-        func setValue(_ value: UInt64, textField: UITextField) {
+        func setValue(_ amount: Amount, textField: UITextField) {
+            // Update hidden textfield text
+            updateText(amount, textField: textField)
             // Update input value
-            input.value = value
-
-            // Update textfield text
-            updateText(value, textField: textField)
+//    print(input.amount.description, " := ", amount.description)
+            input.amount = amount
         }
 
-        func updateText(_ value: UInt64, textField: UITextField) {
+        func updateText(_ amount: Amount, textField: UITextField) {
             // Update field text and last valid input text
-            textField.text = String(value)
-            lastValidInput = String(value)
+            lastValidInput = amount.plainString(input.currencyInfo)
+//    print("lastValidInput: `\(lastValidInput)´")
+            textField.text = lastValidInput
+            let endPosition = textField.endOfDocument
+            textField.selectedTextRange = textField.textRange(from: 
endPosition, to: endPosition)
         }
 
         func textField(_ textField: UITextField, shouldChangeCharactersIn 
range: NSRange, replacementString string: String) -> Bool {
             // If replacement string is empty, we can assume the backspace key 
was hit
             if string.isEmpty {
                 // Resign first responder when delete is hit when value is 0
-                if input.value == 0 {
+                if input.amount.isZero {
                     textField.resignFirstResponder()
+                } else {
+                    // Remove trailing digit: divide value by 10
+                    let amount = input.amount.copy()
+                    amount.removeDigit(input.currencyInfo)
+                    setValue(amount, textField: textField)
                 }
-
-                // Remove trailing digit
-                setValue(UInt64(input.value / 10), textField: textField)
             }
             return true
         }
@@ -190,7 +200,7 @@ struct CurrencyInputField: UIViewRepresentable {
             }
 
             // Find new character and try to get an Int value from it
-            guard let char = char, let digit = Int(String(char)) else {
+            guard let char, let digit = UInt8(String(char)), digit <= 9 else {
                 // New character could not be converted to Int
                 // Revert to last valid text
                 textField.text = lastValidInput
@@ -198,27 +208,18 @@ struct CurrencyInputField: UIViewRepresentable {
             }
 
             // Multiply by 10 to shift numbers one position to the left, 
revert if an overflow occurs
-            let (multValue, multOverflow) = 
input.value.multipliedReportingOverflow(by: 10)
-            if multOverflow {
-                textField.text = lastValidInput
-                return
-            }
-
             // Add the new trailing digit, revert if an overflow occurs
-            let (addValue, addOverflow) = 
multValue.addingReportingOverflow(UInt64(digit))
-            if addOverflow {
-                textField.text = lastValidInput
-                return
-            }
+            let amount = input.amount.copy()
+            amount.addDigit(digit, currencyInfo: input.currencyInfo)
 
             // If new value has more digits than allowed by formatter, revert
-            if input.formatter.maximumFractionDigits + 
input.formatter.maximumIntegerDigits < String(addValue).count {
-                textField.text = lastValidInput
-                return
-            }
+//            if input.formatter.maximumFractionDigits + 
input.formatter.maximumIntegerDigits < String(addValue).count {
+//                textField.text = lastValidInput
+//                return
+//            }
 
             // Update new value
-            setValue(addValue, textField: textField)
+            setValue(amount, textField: textField)
         }
     }
 }
diff --git a/TalerWallet1/Views/HelperViews/CurrencyInputView.swift 
b/TalerWallet1/Views/HelperViews/CurrencyInputView.swift
index f89a702..7d6c045 100644
--- a/TalerWallet1/Views/HelperViews/CurrencyInputView.swift
+++ b/TalerWallet1/Views/HelperViews/CurrencyInputView.swift
@@ -1,33 +1,55 @@
-//
-//  CurrencyInputView.swift
-//  TalerWalletT
-//
-//  Created by Marc Stibane on 2023-06-04.
-//  Copyright © 2023 Taler. All rights reserved.
-//
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
 import SwiftUI
+import taler_swift
 
 struct CurrencyInputView: View {
-    let currencyField: CurrencyField
+    @Binding var amount: Amount         // the `value´
     let title: String
+    let shortcutLabel: String
 
+    @EnvironmentObject private var controller: Controller
+    
     @State var hasBeenShown = false
+
     var body: some View {
-        VStack (alignment: .leading) {
-            Text(title)
-//                .padding(.top)
-                .accessibilityFont(.title3)
+        let shortcuts = [50,25,10,5]
+        let currency = amount.currencyStr
+        let currencyInfo = controller.info(for: currency, 
controller.currencyTicker)
+        let currencyField = CurrencyField(amount: $amount, currencyInfo: 
currencyInfo)
+        VStack (alignment: .center) {
+            HStack {
+                Text(title)
+//                  .padding(.top)
+                    .accessibilityFont(.title3)
+                    .accessibilityAddTraits(.isHeader)
+                    .accessibilityRemoveTraits(.isStaticText)
+                Spacer()
+            }
             currencyField
                 .frame(maxWidth: .infinity, alignment: .trailing)
                 .foregroundColor(WalletColors().fieldForeground)     // text 
color
                 .background(WalletColors().fieldBackground)
                 .accessibilityFont(.title2)
                 .textFieldStyle(.roundedBorder)
+            HStack {
+                ForEach(shortcuts, id: \.self) { shortcut in
+                    let shortie = Amount(currency: currency, integer: 
UInt64(shortcut), fraction: 0)
+                    let title = shortie.string(currencyInfo)
+                    Button(title) {
+                        currencyField.updateText(amount: shortie)
+                        amount = shortie
+                    }.buttonStyle(.bordered)
+                        .accessibilityLabel("\(shortcutLabel) \(title)")
+                }
+            }
         }.onAppear {   // make CurrencyField show the keyboard after 0.4 
seconds
             if hasBeenShown {
-                print("❗️Yikes: CurrencyInputView hasBeenShown")
+//                print("❗️Yikes: CurrencyInputView hasBeenShown")
             } else {
-                print("❗️Yikes: First CurrencyInputView❗️")
+//                print("❗️Yikes: First CurrencyInputView❗️")
                 DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
                     hasBeenShown = true
                     currencyField.becomeFirstResponder()
@@ -40,21 +62,21 @@ struct CurrencyInputView: View {
 }
 // MARK: -
 #if DEBUG
-fileprivate struct BindingViewContainer : View {
-    @State var centsToTransfer: UInt64 = 0
-
-    var body: some View {
-        let currencyField = CurrencyField(value: $centsToTransfer, currency: 
LONGCURRENCY)
-        CurrencyInputView(currencyField: currencyField,
-                                  title: "Amount to withdraw:")
+fileprivate struct Previews: PreviewProvider {
+    @MainActor
+    struct StateContainer: View {
+        @StateObject private var controller = Controller.shared
+        @State var amountToTransfer = Amount(currency: LONGCURRENCY, cent: 0)
+        var body: some View {
+//            Preview_Content()
+            CurrencyInputView(amount: $amountToTransfer,
+                               title: "Amount to withdraw:",
+                       shortcutLabel: "Withdraw")
+                .environmentObject(controller)
+        }
     }
-}
-
-struct CurrencyInputView_Previews: PreviewProvider {
     static var previews: some View {
-        List {
-            BindingViewContainer()
-        }
+        StateContainer()
     }
 }
 #endif
diff --git a/TalerWallet1/Views/HelperViews/TransactionButton.swift 
b/TalerWallet1/Views/HelperViews/TransactionButton.swift
index 0ac465e..40b0c5e 100644
--- a/TalerWallet1/Views/HelperViews/TransactionButton.swift
+++ b/TalerWallet1/Views/HelperViews/TransactionButton.swift
@@ -15,58 +15,29 @@ struct TransactionButton: View {
     @State var executed: Bool = false
     var body: some View {
         let isDestructive = (command == .delete) || (command == .fail)
-        let role: ButtonRole? = (command == .abort) ? .cancel
-                              :  isDestructive ? .destructive
-                                               : nil
+        let isCancel = (command == .abort)
+        let role: ButtonRole? = isDestructive ? .destructive
+                              : isCancel      ? .cancel
+                                              : nil
         Button(role: role, action: {
-            Task { // runs on MainActor
+            if !disabled {
                 disabled = true     // don't try this more than once
-                do {
-                    try await action(transactionId)
+                Task { // runs on MainActor
+                    do {
+                        try await action(transactionId)
 //                    symLog.log("\(executed) \(transactionId)")
-                    executed = true
-                } catch {    // TODO: error
+                        executed = true
+                    } catch {    // TODO: error
 //                    symLog.log(error.localizedDescription)
+                    }
                 }
             }
         }, label: {
-            HStack {
-                if executed {
-                    switch command {
-                        case .delete:
-                            Text("Deleted from list")
-                        case .abort:
-                            Text("Abort pending...")
-                        case .fail:
-                            Text("Failing...")
-                        case .suspend:
-                            Text("Suspending...")
-                        case .resume:
-                            Text("Resuming...")
-                    }
-                } else {
-                    let spaces = "        "
-                    switch command {
-                        case .delete:
-                            Text("Delete from list" + spaces)
-                            Image(systemName: "trash")                         
 // 􀈑
-                        case .abort:
-                            Text("Abort" + spaces)
-                            Image(systemName: "x.circle")                      
 // 􀀲
-                        case .fail:
-                            Text("Fail" + spaces)
-                            Image(systemName: "play.slash")                    
 // 􀪅
-                        case .suspend:
-                            Text("Suspend" + spaces)
-                            if #available(iOS 16.0, *) {
-                                Image(systemName: "clock.badge.xmark")         
 // 􁜒
-                            } else {
-                                Image(systemName: 
"clock.badge.exclamationmark")// 􀹶
-                            }
-                        case .resume:
-                            Text("Resume" + spaces)
-                            Image(systemName: "clock.arrow.circlepath")        
 // 􀣔
-                    }
+            HStack(spacing: 50) {
+                Text(executed ? command.localizedActionExecuted
+                              : command.localizedActionTitle)
+                if let imageName = command.localizedActionImage {
+                    Image(systemName: imageName)
                 }
             }
             .accessibilityFont(.title2)
@@ -76,16 +47,18 @@ struct TransactionButton: View {
         .controlSize(.large)
         .disabled(disabled)
     }
-
 }
-
-
+// MARK: -
 #if DEBUG
 struct TransactionButton_Previews: PreviewProvider {
 
+    static func action(_ transactionId: String) async throws {
+        print(transactionId)
+    }
+
     static var previews: some View {
         List {
-            TransactionButton(transactionId: "String", command: .abort, 
action: {transactionId in })
+            TransactionButton(transactionId: "Button pressed", command: 
.abort, action: action)
         }
     }
 }
diff --git a/TalerWallet1/Views/HelperViews/View+fitsSideBySide.swift 
b/TalerWallet1/Views/HelperViews/View+fitsSideBySide.swift
index fa4ca83..baecf5a 100644
--- a/TalerWallet1/Views/HelperViews/View+fitsSideBySide.swift
+++ b/TalerWallet1/Views/HelperViews/View+fitsSideBySide.swift
@@ -9,7 +9,7 @@ extension View {
     @MainActor
     public func announce(this: String) {
         if UIAccessibility.isVoiceOverRunning {
-            UIAccessibility.post(notification: .screenChanged, argument: this)
+            UIAccessibility.post(notification: .announcement, argument: this)
         }
     }
 }
diff --git a/TalerWallet1/Views/Main/MainView.swift 
b/TalerWallet1/Views/Main/MainView.swift
index 128f214..a1639ed 100644
--- a/TalerWallet1/Views/Main/MainView.swift
+++ b/TalerWallet1/Views/Main/MainView.swift
@@ -18,15 +18,18 @@ struct MainView: View {
     private let symLog = SymLogV(0)
     let logger: Logger
     let stack: CallStack
+    @Binding var soundPlayed: Bool
+
     @EnvironmentObject private var viewState: ViewState         // 
popToRootView()
     @EnvironmentObject private var controller: Controller
+    @AppStorage("talerFont") var talerFont: Int = 0         // extension 
mustn't define this, so it must be here
+    @AppStorage("playSounds") var playSounds: Int = 1       // extension 
mustn't define this, so it must be here
+
     @State private var sheetPresented = false
     @State private var urlToOpen: URL? = nil
-    @Binding var soundPlayed: Bool
-    @AppStorage("talerFont") var talerFont: Int = 0         // extension 
mustn't define this, so it must be here
 
     func sheetDismissed() -> Void {
-        symLog.log("sheet dismiss")
+        logger.info("sheet dismiss")
     }
     var body: some View {
 #if DEBUG
@@ -35,11 +38,13 @@ struct MainView: View {
 #endif
         Group {
             if controller.backendState == .ready {
-                Content(symLog: symLog, logger: logger, stack: 
stack.push("Content"), talerFont: $talerFont)
+                Content(logger: logger, stack: stack.push("Content"), 
talerFont: $talerFont)
                 // any change to rootViewId triggers popToRootView behaviour
                     .id(viewState.rootViewId)
                     .onAppear() {
-                        controller.playSound(1008)     // Startup chime
+                        if playSounds != 0  && !soundPlayed {
+                            controller.playSound(1008)     // Startup chime
+                        }
                         soundPlayed = true
                     }
             } else if controller.backendState == .error {
@@ -75,7 +80,6 @@ enum Tab {
 // MARK: - Content
 extension MainView {
     struct Content: View {
-        let symLog: SymLogV?
         let logger: Logger
         let stack: CallStack
         @State private var shouldReloadBalances = 0
@@ -164,7 +168,9 @@ extension MainView {
 //            } else {
                 let _ = Self._printChanges()
 //            }
-            let _ = symLog?.vlog()       // just to get the identity # to 
compare with .onAppear & .onDisappear
+            let delay: UInt = 5
+#else
+            let delay: UInt = 0
 #endif
           Group {
 #if TABBAR  // Taler Wallet
@@ -186,7 +192,7 @@ extension MainView {
 
                 NavigationView {
                     ExchangeListView(stack: stack.push(exchangesTitle),
-//                                  balances: $balances,
+                                  balances: $balances,
                                   navTitle: exchangesTitle)
                 }.navigationViewStyle(.stack)
                 .tabItem {
@@ -248,8 +254,7 @@ extension MainView {
                         }
                     }
                 } else { // should never happen
-                    logger.error("Yikes! TransactionStateTransition without 
transition")
-//                    symLog.log(notification.userInfo as Any)
+                    logger.error("Yikes❗️ TransactionStateTransition without 
transition")
                 }
             }
             .alert("You need to pass a KYC procedure",
@@ -265,15 +270,16 @@ extension MainView {
             .onChange(of: balances) { newArray in
                 for balance in newArray {
                     let scope = balance.scopeInfo
-                    if controller.info(for: scope.currency) == nil {
+                    logger.info("balance changed: \(scope.currency, privacy: 
.public)")
+                    if !controller.hasInfo(for: scope.currency) {
                         Task { // runs on MainActor
-                            symLog?.log("get info for: \(scope.currency)")
+                            logger.info("Task to get info for: 
\(scope.currency, privacy: .public)")
                             do {
-                                let info = try await 
model.getCurrencyInfo(scope: scope)
-                                symLog?.log("added: \(scope.currency)")
+                                let info = try await 
model.getCurrencyInfo(scope: scope, delay: delay)
+                                logger.info("got info: \(scope.currency, 
privacy: .public)")
                                 await controller.setInfo(info)
                             } catch {    // TODO: error handling - couldn't 
get CurrencyInfo
-                                symLog?.log("error: \(error)")
+                                logger.error("Couldn't get info for: 
\(scope.currency, privacy: .public)\n\(error)")
                             }
                         }
                     }
diff --git a/TalerWallet1/Views/Peer2peer/RequestPayment.swift 
b/TalerWallet1/Views/Peer2peer/RequestPayment.swift
index 10cb313..a41b72e 100644
--- a/TalerWallet1/Views/Peer2peer/RequestPayment.swift
+++ b/TalerWallet1/Views/Peer2peer/RequestPayment.swift
@@ -11,11 +11,11 @@ struct RequestPayment: View {
     private let symLog = SymLogV(0)
     let stack: CallStack
 
-    var scopeInfo: ScopeInfo
-    @Binding var centsToTransfer: UInt64
+    @Binding var amountToTransfer: Amount
     @Binding var summary: String
 
     @EnvironmentObject private var model: WalletModel
+    @AppStorage("iconOnly") var iconOnly: Bool = false
 
     @State private var peerPullCheck: CheckPeerPullCreditResponse? = nil
     @State private var expireDays: UInt = 0
@@ -25,32 +25,32 @@ struct RequestPayment: View {
         let _ = Self._printChanges()
         let _ = symLog.vlog()       // just to get the # to compare it with 
.onAppear & onDisappear
 #endif
-        let currency = scopeInfo.currency
+        let currency = amountToTransfer.currencyStr
         let navTitle = String(localized: "Request Money", comment: "Dialog 
Title")
-        let currencyField = CurrencyField(value: $centsToTransfer, currency: 
currency)
 
         ScrollView { VStack {
-            CurrencyInputView(currencyField: currencyField,
-                              title: String(localized: "Amount to request:"))
+            CurrencyInputView(amount: $amountToTransfer,
+                               title: iconOnly ? String(localized: "How much:")
+                                               : String(localized: "Amount to 
request:"),
+                       shortcutLabel: String(localized: "Request", comment: 
"VoiceOver: Request $50,$25,$10,$5 shortcut buttons"))
 
             let someCoins = SomeCoins(details: peerPullCheck)
             QuiteSomeCoins(someCoins: someCoins, shouldShowFee: true,
-                           currency: currency, amountEffective: 
peerPullCheck?.amountEffective)
+                            currency: currency,
+                     amountEffective: peerPullCheck?.amountEffective)
 
             HStack {
-                let disabled = (centsToTransfer == 0) || someCoins.invalid || 
someCoins.tooMany
+                let disabled = amountToTransfer.isZero || someCoins.invalid || 
someCoins.tooMany
 
                 NavigationLink(destination: LazyView {
-                    PaymentPurpose(stack: stack.push(),
-                               scopeInfo: scopeInfo,
-                         centsToTransfer: centsToTransfer,
+                    RequestPurpose(stack: stack.push(),
+                        amountToTransfer: amountToTransfer,
                                      fee: someCoins.fee,
                                  summary: $summary,
                               expireDays: $expireDays)
 //                        { deactivateAction() }
                 }) {
-                    let amount = Amount.amountFromCents(currency, 
centsToTransfer)
-                    Text("Request \(amount.readableDescription)")
+                    Text("Request \(amountToTransfer.readableDescription)")    
 // TODO: formatter
                 }
                 .buttonStyle(TalerButtonStyle(type: .prominent))
                 .disabled(disabled)
@@ -68,16 +68,17 @@ struct RequestPayment: View {
         .onDisappear {
             symLog.log("❗️Yikes \(navTitle) onDisappear")
         }
-        .task(id: centsToTransfer) {
-            let amount = Amount.amountFromCents(currency, centsToTransfer)
-            do {
-                let ppCheck = try await model.checkPeerPullCreditM(amount, 
exchangeBaseUrl: nil)
-                peerPullCheck = ppCheck
-                // TODO: set from exchange
-//                agePicker.setAges(ages: peerPushCheck?.ageRestrictionOptions)
-            } catch {    // TODO: error
-                symLog.log(error.localizedDescription)
-                peerPullCheck = nil
+        .task(id: amountToTransfer.value) {
+            if !amountToTransfer.isZero {
+                do {
+                    let ppCheck = try await 
model.checkPeerPullCreditM(amountToTransfer, exchangeBaseUrl: nil)
+                    peerPullCheck = ppCheck
+                    // TODO: set from exchange
+//                  agePicker.setAges(ages: 
peerPushCheck?.ageRestrictionOptions)
+                } catch {    // TODO: error
+                    symLog.log(error.localizedDescription)
+                    peerPullCheck = nil
+                }
             }
         }
     }
diff --git a/TalerWallet1/Views/Peer2peer/PaymentPurpose.swift 
b/TalerWallet1/Views/Peer2peer/RequestPurpose.swift
similarity index 75%
rename from TalerWallet1/Views/Peer2peer/PaymentPurpose.swift
rename to TalerWallet1/Views/Peer2peer/RequestPurpose.swift
index 8b04c12..8562c52 100644
--- a/TalerWallet1/Views/Peer2peer/PaymentPurpose.swift
+++ b/TalerWallet1/Views/Peer2peer/RequestPurpose.swift
@@ -6,32 +6,27 @@ import SwiftUI
 import taler_swift
 import SymLog
 
-struct PaymentPurpose: View {
+struct RequestPurpose: View {
     private let symLog = SymLogV(0)
     let stack: CallStack
 
-    let scopeInfo: ScopeInfo
-    let centsToTransfer: UInt64
+    let amountToTransfer: Amount
     let fee: String
     @Binding var summary: String
     @Binding var expireDays: UInt
+
+    @EnvironmentObject private var controller: Controller
     @AppStorage("iconOnly") var iconOnly: Bool = false
-    let navTitle = String(localized: "NavTitle_Request_Subject", defaultValue: 
"Request", comment: "NavTitle for entering the subject for Request-Payment")
+    let navTitle = String(localized: "NavTitle_Request_Subject",
+                       defaultValue: "Request", comment: "NavTitle for 
entering the subject for Request-Payment")
 
     @State private var transactionStarted: Bool = false
     @FocusState private var isFocused: Bool
 
-    private var label: String {
-//        let mag = pow(10, formatter.maximumFractionDigits)
-//        return formatter.string(for: Decimal(centsToTransfer) / mag) ?? ""
-        return String(centsToTransfer / 100)   // TODO: based on currency
-    }
-
     var body: some View {
-        let amount = Amount.amountFromCents(scopeInfo.currency, 
centsToTransfer)
-
+        let currencyInfo = controller.info(for: amountToTransfer.currencyStr, 
controller.currencyTicker)
         VStack (spacing: 6) {
-            Text(amount.readableDescription)
+            Text(amountToTransfer.string(currencyInfo))
             Text("+ \(fee) payment fee")
                 .foregroundColor(.red)
             VStack(alignment: .leading, spacing: 6) {
@@ -65,12 +60,12 @@ struct PaymentPurpose: View {
                 NavigationLink(destination: LazyView {
                     SendDoneV(stack: stack.push(),
                        amountToSend: nil,
-                    amountToReceive: amount,
+                    amountToReceive: amountToTransfer,
                             summary: summary,
                          expireDays: expireDays,
                  transactionStarted: $transactionStarted)
                 }) {
-                    Text("Request \(label) \(scopeInfo.currency)")
+                    Text("Request \(amountToTransfer.readableDescription)")    
 // TODO: formatter
 //                        .accessibilityFont(buttonFont)
                 }
                 .buttonStyle(TalerButtonStyle(type: .prominent))
@@ -86,23 +81,23 @@ struct PaymentPurpose: View {
         .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
         .onAppear {
             DebugViewC.shared.setViewID(VIEW_REQUEST_PURPOSE, stack: 
stack.push())
-//            print("❗️ PaymentPurpose onAppear")
+//            print("❗️ RequestPurpose onAppear")
         }
         .onDisappear {
-//            print("❗️ PaymentPurpose onDisappear")
+//            print("❗️ RequestPurpose onDisappear")
         }
     }
 
 }
 // MARK: -
 #if DEBUG
-//struct PaymentPurpose_Previews: PreviewProvider {
+//struct RequestPurpose_Previews: PreviewProvider {
 //    static var previews: some View {
-//        let scopeInfo = ScopeInfo(type: ScopeInfo.ScopeInfoType.exchange, 
exchangeBaseUrl: DEMOEXCHANGE, currency: LONGCURRENCY)
+//        let scopeInfo = ScopeInfo(type: .exchange, exchangeBaseUrl: 
DEMOEXCHANGE, currency: LONGCURRENCY)
 //        @State var summary: String = "pUrPoSe"
 //        @State var expireDays: UInt = 0
-//        PaymentPurpose(scopeInfo: scopeInfo,
-//                 centsToTransfer: 5,
+//        RequestPurpose(scopeInfo: scopeInfo,
+//                 amountToReceive: 5,
 //                             fee: "fee",
 //                         summary: $summary,
 //                      expireDays: $expireDays)
diff --git a/TalerWallet1/Views/Peer2peer/SendAmount.swift 
b/TalerWallet1/Views/Peer2peer/SendAmount.swift
index 5fa6693..7437004 100644
--- a/TalerWallet1/Views/Peer2peer/SendAmount.swift
+++ b/TalerWallet1/Views/Peer2peer/SendAmount.swift
@@ -12,24 +12,30 @@ struct SendAmount: View {
     let stack: CallStack
 
     let amountAvailable: Amount // TODO: GetMaxPeerPushAmount
-    @Binding var centsToTransfer: UInt64
+    @Binding var amountToTransfer: Amount
     @Binding var summary: String
 
+    @EnvironmentObject private var controller: Controller
     @EnvironmentObject private var model: WalletModel
+    @AppStorage("iconOnly") var iconOnly: Bool = false
 
     @State var peerPushCheck: CheckPeerPushDebitResponse? = nil
     @State private var expireDays: UInt = SEVENDAYS
+    @State private var insufficient: Bool = false
+    @State private var fee: String = ""
 
-    private func fee(ppCheck: CheckPeerPushDebitResponse?) -> String {
+    private func fee(ppCheck: CheckPeerPushDebitResponse?) -> Amount? {
         do {
             if let ppCheck {
                 // Outgoing: fee = effective - raw
                 let fee = try ppCheck.amountEffective - ppCheck.amountRaw
-                return fee.readableDescription
+                return fee
             }
         } catch {}
-        return ""
+        return nil
     }
+    
+    var feeLabel: String { String(localized: " + \(fee) payment fee") }
 
     var body: some View {
 #if DEBUG
@@ -37,29 +43,33 @@ struct SendAmount: View {
         let _ = symLog.vlog()       // just to get the # to compare it with 
.onAppear & onDisappear
 #endif
         let currency = amountAvailable.currencyStr
+        let currencyInfo = controller.info(for: currency, 
controller.currencyTicker)
         let navTitle = String(localized: "Send \(currency)", comment: "Send 
currency, Dialog Title")
-        let currencyField = CurrencyField(value: $centsToTransfer, currency: 
currency)
-
-        let fee = fee(ppCheck: peerPushCheck)
+        let available = amountAvailable.string(currencyInfo)
+        let current = amountToTransfer.string(currencyInfo)
+        let insufficientLabel = String(localized: "You don't have enough 
\(currency).")
+        let insufficientLabel2 = String(localized: "but you only have 
\(available) to send.")
         ScrollView {
             VStack(alignment: .trailing) {
-                let available = amountAvailable.readableDescription
-                Text("Available: \(available)")
+//                let _ = print("available: \(available)")
+                Text("Available:\t\(available)")
                     .accessibilityFont(.title3)
                     .padding(.bottom, 2)
-                CurrencyInputView(currencyField: currencyField,
-                                  title: String(localized: "Amount to send:"))
-                Text("+ \(fee) payment fee")
+                CurrencyInputView(amount: $amountToTransfer,
+                                   title: iconOnly ? String(localized: "How 
much:")
+                                                   : String(localized: "Amount 
to send:"),
+                           shortcutLabel: String(localized: "Send", comment: 
"VoiceOver: Send $50,$25,$10,$5 shortcut buttons"))
+                let disabled = insufficient || amountToTransfer.isZero
+                Text(insufficient ? insufficientLabel
+                                  : feeLabel)
                     .accessibilityFont(.body)
                     .foregroundColor(.red)
                     .padding(4)
 
-                let disabled = centsToTransfer == 0    // TODO: check 
amountAvailable
-
                 NavigationLink(destination: LazyView {
                     SendPurpose(stack: stack.push(),
                       amountAvailable: amountAvailable,
-                      centsToTransfer: centsToTransfer,
+                     amountToTransfer: amountToTransfer,
                                   fee: fee,
                               summary: $summary,
                            expireDays: $expireDays)
@@ -81,14 +91,27 @@ struct SendAmount: View {
         .onDisappear {
             symLog.log("❗️Yikes SendAmount onDisappear")
         }
-        .task(id: centsToTransfer) {
-            if centsToTransfer > 0 {
-                let amount = Amount.amountFromCents(currency, centsToTransfer)
+        .task(id: amountToTransfer.value) {
+            do {
+                insufficient = try amountToTransfer > amountAvailable
+                print("current: \(current)")
+            } catch {
+                print("Yikes❗️ insufficient failed❗️")
+                insufficient = true
+            }
+
+            if insufficient {
+                announce(this: "\(current), \(insufficientLabel2)")
+            } else if !amountToTransfer.isZero {
                 do {
-                    let ppCheck = try await model.checkPeerPushDebitM(amount)
+                    let ppCheck = try await 
model.checkPeerPushDebitM(amountToTransfer)
                     peerPushCheck = ppCheck
                 // TODO: set from exchange
 //                agePicker.setAges(ages: peerPushCheck?.ageRestrictionOptions)
+                    if let feeAmount = fee(ppCheck: peerPushCheck) {
+                        fee = feeAmount.string(currencyInfo)
+                    } else { fee = "" }
+                    announce(this: "\(current), \(feeLabel)")
                 } catch {    // TODO: error
                     symLog.log(error.localizedDescription)
                     peerPushCheck = nil
@@ -99,22 +122,30 @@ struct SendAmount: View {
 }
 // MARK: -
 #if DEBUG
-struct SendAmount_Container : View {
-    @State private var centsToTransfer: UInt64 = 510
+fileprivate struct Preview_Content: View {
+    @State private var amountToTransfer = Amount(currency: TESTCURRENCY, cent: 
510)
     @State private var summary: String = ""
 
     var body: some View {
-        let amount = Amount(currency: LONGCURRENCY, integer: 10, fraction: 0)
+        let amount = Amount(currency: TESTCURRENCY, integer: 10, fraction: 0)
         SendAmount(stack: CallStack("Preview"),
          amountAvailable: amount,
-         centsToTransfer: $centsToTransfer,
+        amountToTransfer: $amountToTransfer,
                  summary: $summary)
     }
 }
 
-//struct SendAmount_Previews: PreviewProvider {
-//    static var previews: some View {
-//        SendAmount_Container()
-//    }
-//}
+fileprivate struct Previews: PreviewProvider {
+    @MainActor
+    struct StateContainer: View {
+        @StateObject private var controller = Controller.shared
+        var body: some View {
+            Preview_Content()
+                .environmentObject(controller)
+        }
+    }
+    static var previews: some View {
+        StateContainer()
+    }
+}
 #endif
diff --git a/TalerWallet1/Views/Peer2peer/SendDoneV.swift 
b/TalerWallet1/Views/Peer2peer/SendDoneV.swift
index 84b31ee..d7fdd45 100644
--- a/TalerWallet1/Views/Peer2peer/SendDoneV.swift
+++ b/TalerWallet1/Views/Peer2peer/SendDoneV.swift
@@ -11,6 +11,7 @@ struct SendDoneV: View {
     private let symLog = SymLogV()
     let stack: CallStack
     let navTitle = String(localized: "P2P Ready")
+    @EnvironmentObject private var model: WalletModel
 #if DEBUG
     @AppStorage("developerMode") var developerMode: Bool = true
 #else
@@ -24,8 +25,6 @@ struct SendDoneV: View {
     let expireDays: UInt
     @Binding var transactionStarted: Bool
 
-    @EnvironmentObject private var model: WalletModel
-
     @State private var transactionId: String? = nil
 
     func reloadOneAction(_ transactionId: String) async throws -> Transaction {
@@ -97,7 +96,7 @@ struct SendDoneV: View {
 //    static var previews: some View {
 //        Group {
 //            SendDoneV(stack: CallStack("Preview"),
-//              amountToSend: try! Amount(fromString: LONGCURRENCY + ":4.8"),
+//               amountToSend: Amount(currency: LONGCURRENCY, cent: 480),
 //           amountToReceive: nil,
 //                   summary: "some subject/purpose",
 //                expireDays: 0)
diff --git a/TalerWallet1/Views/Peer2peer/SendPurpose.swift 
b/TalerWallet1/Views/Peer2peer/SendPurpose.swift
index 06ba0a4..a06f25b 100644
--- a/TalerWallet1/Views/Peer2peer/SendPurpose.swift
+++ b/TalerWallet1/Views/Peer2peer/SendPurpose.swift
@@ -9,10 +9,9 @@ import SymLog
 struct SendPurpose: View {
     private let symLog = SymLogV(0)
     let stack: CallStack
-    @FocusState private var isFocused: Bool
 
     let amountAvailable: Amount
-    let centsToTransfer: UInt64
+    let amountToTransfer: Amount
     let fee: String
     @Binding var summary: String
     @Binding var expireDays: UInt
@@ -20,18 +19,11 @@ struct SendPurpose: View {
     let navTitle = String(localized: "NavTitle_Send_Subject", defaultValue: 
"Subject", comment: "NavTitle for entering the subject for Send-Money")
 
     @State private var transactionStarted: Bool = false
-
-    private var value: String {
-//        let mag = pow(10, formatter.maximumFractionDigits)
-//        return formatter.string(for: Decimal(centsToTransfer) / mag) ?? ""
-        return String(centsToTransfer / 100)    // TODO: based on currency
-    }
+    @FocusState private var isFocused: Bool
 
     var body: some View {
-        let amount = Amount.amountFromCents(amountAvailable.currencyStr, 
centsToTransfer)
-
         VStack (spacing: 6) {
-            Text(amount.readableDescription)
+            Text(amountToTransfer.readableDescription)              // TODO: 
curreny formatter
             Text("+ \(fee) payment fee")
                 .accessibilityFont(.body)
                 .foregroundColor(.red)
@@ -84,13 +76,13 @@ struct SendPurpose: View {
                 let disabled = (expireDays == 0) || (summary.count < 1)    // 
TODO: check amountAvailable
                 NavigationLink(destination: LazyView {
                     SendDoneV(stack: stack.push(),
-                       amountToSend: amount,
+                       amountToSend: amountToTransfer,
                     amountToReceive: nil,
                             summary: summary,
                          expireDays: expireDays,
                  transactionStarted: $transactionStarted)
                 }) {
-                    Text("Send \(value) \(amountAvailable.currencyStr) now", 
comment: "first is value, second currencyString")  // TODO: currency formatter
+                    Text("Send \(amountToTransfer.readableDescription) now", 
comment: "amountToTransfer")  // TODO: currency formatter
                 }
                 .buttonStyle(TalerButtonStyle(type: .prominent))
                 .disabled(disabled)
@@ -130,7 +122,7 @@ struct SendPurpose: View {
 //        @State var expireDays: UInt = 0
 //        let amount = Amount(currency: LONGCURRENCY, integer: 10, fraction: 0)
 //        SendPurpose(amountAvailable: amount,
-//                    centsToTransfer: 543,
+//                   amountToTransfer: 543,
 //                                fee: "0,43",
 //                            summary: $summary,
 //                         expireDays: $expireDays)
diff --git a/TalerWallet1/Views/Settings/AboutView.swift 
b/TalerWallet1/Views/Settings/AboutView.swift
index 2e460bf..adf768d 100644
--- a/TalerWallet1/Views/Settings/AboutView.swift
+++ b/TalerWallet1/Views/Settings/AboutView.swift
@@ -18,9 +18,9 @@ struct AboutView: View {
     @AppStorage("developerMode") var developerMode: Bool = false
 #endif
     @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
+    @AppStorage("iconOnly") var iconOnly: Bool = false
 
     @State private var rotationEnabled = false
-
     @State private var listID = UUID()
 
     var body: some View {
@@ -31,38 +31,45 @@ struct AboutView: View {
         let walletCore = WalletCore.shared
         Group {
             List {
-//                VStack {
-                    HStack {
-                        Spacer()
-                        RotatingTaler(size: 100, rotationEnabled: 
$rotationEnabled)
-                            .accessibilityHint("Will go to the taler.net 
website when long-pressed.")
-                            .onTapGesture(count: 2) {
-                                rotationEnabled.toggle()
-                            }
-                            .onLongPressGesture(minimumDuration: 0.3) {
-                                
UIApplication.shared.open(URL(string:"https://taler.net";)!, options: [:])
-                            }
-                        Spacer()
-                    }
-                    SettingsItem(name: "App Version", id1: "app") {
-                        Text(verbatim: 
"\(Bundle.main.releaseVersionNumberPretty)")
-                    }
-                    SettingsItem(name: "Wallet Core Version", id1: 
"wallet-core") {
-                        Text(verbatim: "\(walletCore.versionInfo!.version)")
+                HStack {
+                    Spacer()
+                    RotatingTaler(size: 100, rotationEnabled: $rotationEnabled)
+                        .onTapGesture(count: 2) {
+                            rotationEnabled.toggle()
+                        }
+                    Spacer()
+                }
+                SettingsItem(name: "Visit the taler.net website", id1: "web",
+                             description: iconOnly ? nil : String(localized: 
"More info about Gnu Taler in general...")) { }
+                    .accessibilityAddTraits(.isLink)
+                    .accessibilityRemoveTraits(.isStaticText)
+                    .onTapGesture() {
+                        
UIApplication.shared.open(URL(string:"https://taler.net";)!, options: [:])
                     }
+
+                SettingsItem(name: "App Version", id1: "app") {
+                    Text(verbatim: "\(Bundle.main.releaseVersionNumberPretty)")
+                }
+                SettingsItem(name: "Wallet Core Version", id1: "wallet-core") {
+                    Text(verbatim: "\(walletCore.versionInfo?.version ?? 
"unknown")")
+                }
+                if developerMode {
                     SettingsItem(name: "Wallet Core DevMode", id1: "devMode") {
-                        Text(verbatim: "\(walletCore.versionInfo!.devMode ? 
"YES" : "NO")")
+                        let modeStr = if let devMode = 
walletCore.versionInfo?.devMode {
+                            devMode ? "YES" : "NO"
+                        } else { "unknown" }
+                        Text(modeStr)
                     }
-                    SettingsItem(name: "Supported Exchange Versions", id1: 
"exchange") {
-                        Text(verbatim: "\(walletCore.versionInfo!.exchange)")
-                    }
-                    SettingsItem(name: "Supported Merchant Versions", id1: 
"merchant") {
-                        Text(verbatim: "\(walletCore.versionInfo!.merchant)")
-                    }
-                    SettingsItem(name: "Used Bank", id1: "bank") {
-                        Text(verbatim: "\(walletCore.versionInfo!.bank)")
-                    }
-//                } // App version info
+                }
+                SettingsItem(name: "Supported Exchange Versions", id1: 
"exchange") {
+                    Text(verbatim: "\(walletCore.versionInfo?.exchange ?? 
"unknown")")
+                }
+                SettingsItem(name: "Supported Merchant Versions", id1: 
"merchant") {
+                    Text(verbatim: "\(walletCore.versionInfo?.merchant ?? 
"unknown")")
+                }
+                SettingsItem(name: "Used Bank", id1: "bank") {
+                    Text(verbatim: "\(walletCore.versionInfo?.bank ?? 
"unknown")")
+                }
             }
             .id(listID)
             .listStyle(myListStyle.style).anyView
@@ -94,11 +101,7 @@ extension Bundle {
 #if DEBUG
 struct AboutView_Previews: PreviewProvider {
     static var previews: some View {
-#if TABBAR  // Taler Wallet
         AboutView(stack: CallStack("Preview"), navTitle: "About")
-#else       // GNU Taler
-        SettingsView(stack: CallStack("Preview"), navTitle: "About") { }
-#endif
     }
 }
 #endif
diff --git a/TalerWallet1/Views/Settings/SettingsView.swift 
b/TalerWallet1/Views/Settings/SettingsView.swift
index 40abcc2..ae34f61 100644
--- a/TalerWallet1/Views/Settings/SettingsView.swift
+++ b/TalerWallet1/Views/Settings/SettingsView.swift
@@ -13,8 +13,6 @@ import SymLog
  * Debug log
  * View/send internal log
  *
- * Reset Wallet (dangerous!)
- * Throws away your money
  */
 
 struct SettingsView: View {
@@ -23,6 +21,7 @@ struct SettingsView: View {
     let navTitle: String
 
     @EnvironmentObject private var controller: Controller
+    @EnvironmentObject private var model: WalletModel
 #if DEBUG
     @AppStorage("developerMode") var developerMode: Bool = true
 #else
@@ -41,8 +40,6 @@ struct SettingsView: View {
     var hamburgerAction: () -> Void
 #endif
 
-    @EnvironmentObject private var model: WalletModel
-
     @State private var checkDisabled = false
     @State private var withDrawDisabled = false
 #if DEBUG
diff --git a/TalerWallet1/Views/Sheets/P2P_Sheets/P2pAcceptDone.swift 
b/TalerWallet1/Views/Sheets/P2P_Sheets/P2pAcceptDone.swift
index b26a6d0..0088fdc 100644
--- a/TalerWallet1/Views/Sheets/P2P_Sheets/P2pAcceptDone.swift
+++ b/TalerWallet1/Views/Sheets/P2P_Sheets/P2pAcceptDone.swift
@@ -14,8 +14,8 @@ struct P2pAcceptDone: View {
     let transactionId: String
     let incoming: Bool
 
-    @EnvironmentObject private var model: WalletModel
     @EnvironmentObject private var controller: Controller
+    @EnvironmentObject private var model: WalletModel
 
     @State private var finished: Bool = false
 
diff --git a/TalerWallet1/Views/Sheets/P2P_Sheets/P2pPayURIView.swift 
b/TalerWallet1/Views/Sheets/P2P_Sheets/P2pPayURIView.swift
index 9e9ccc6..7d4b384 100644
--- a/TalerWallet1/Views/Sheets/P2P_Sheets/P2pPayURIView.swift
+++ b/TalerWallet1/Views/Sheets/P2P_Sheets/P2pPayURIView.swift
@@ -51,7 +51,7 @@ struct P2pPayURIView: View {
                     .buttonStyle(TalerButtonStyle(type: .prominent))
                     .padding(.horizontal)
             } else {
-                WithdrawProgressView(message: url.host ?? "Yikes - no valid 
URL")
+                WithdrawProgressView(message: url.host ?? "Yikes❗️ no valid 
URL")
                     .navigationTitle("Contacting Exchange")
             }
         }
diff --git a/TalerWallet1/Views/Sheets/P2P_Sheets/P2pReceiveURIView.swift 
b/TalerWallet1/Views/Sheets/P2P_Sheets/P2pReceiveURIView.swift
index d856947..4a39eca 100644
--- a/TalerWallet1/Views/Sheets/P2P_Sheets/P2pReceiveURIView.swift
+++ b/TalerWallet1/Views/Sheets/P2P_Sheets/P2pReceiveURIView.swift
@@ -12,16 +12,17 @@ struct P2pReceiveURIView: View {
     private let symLog = SymLogV(0)
     let stack: CallStack
     let navTitle = String(localized: "P2P Receive")
-    @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
 
     // the scanned URL
     let url: URL
     
     @EnvironmentObject private var model: WalletModel
+    @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
 
     @State private var peerPushCreditResponse: PreparePeerPushCreditResponse?
 
     var body: some View {
+        let badURL = "Error in URL: \(url)"
         VStack {
             if let peerPushCreditResponse {
                 List {
@@ -59,8 +60,8 @@ struct P2pReceiveURIView: View {
                 }
             } else {
                 // Yikes no details or no baseURL
-//                WithdrawProgressView(message: url.host ?? badURL)
-//                    .navigationTitle("Contacting Exchange")
+                WithdrawProgressView(message: url.host ?? badURL)
+                    .navigationTitle("Contacting Exchange")
             }
         }
         .onAppear() {
diff --git a/TalerWallet1/Views/Sheets/Payment/PayTemplateView.swift 
b/TalerWallet1/Views/Sheets/Payment/PayTemplateView.swift
index 7dda920..5a66006 100644
--- a/TalerWallet1/Views/Sheets/Payment/PayTemplateView.swift
+++ b/TalerWallet1/Views/Sheets/Payment/PayTemplateView.swift
@@ -13,13 +13,12 @@ struct PayTemplateView: View {
     let stack: CallStack
     let navTitle = String(localized: "Confirm Payment", comment:"pay merchant")
 
-    @EnvironmentObject private var controller: Controller
-    @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
-
     // the scanned URL
     let url: URL
 
+    @EnvironmentObject private var controller: Controller
     @EnvironmentObject private var model: WalletModel
+    @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
 
     func acceptAction(preparePayResult: PreparePayResult) {
         Task { // runs on MainActor
diff --git a/TalerWallet1/Views/Sheets/Payment/PaymentView.swift 
b/TalerWallet1/Views/Sheets/Payment/PaymentView.swift
index 27557b7..534a7d4 100644
--- a/TalerWallet1/Views/Sheets/Payment/PaymentView.swift
+++ b/TalerWallet1/Views/Sheets/Payment/PaymentView.swift
@@ -13,13 +13,12 @@ struct PaymentView: View {
     let stack: CallStack
     let navTitle = String(localized: "Confirm Payment", comment:"pay merchant")
 
-    @EnvironmentObject private var controller: Controller
-    @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
-
     // the scanned URL
     let url: URL
 
+    @EnvironmentObject private var controller: Controller
     @EnvironmentObject private var model: WalletModel
+    @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
 
     func acceptAction(preparePayResult: PreparePayResult) {
         Task { // runs on MainActor
@@ -119,44 +118,43 @@ struct PaymentURIView_Previews: PreviewProvider {
         let extra = Extra(articleName: "articleName")
         let product = Product(description: "description")
         let terms = MerchantContractTerms(hWire: "hWire",
-                                          wireMethod: "wireMethod",
-                                          summary: "summary",
-                                            summaryI18n: nil,
+                                     wireMethod: "wireMethod",
+                                        summary: "summary",
+                                    summaryI18n: nil,
                                           nonce: "nonce",
-                                          amount: try! Amount(fromString: 
LONGCURRENCY + ":2.2"),
-                                          payDeadline: Timestamp.tomorrow(),
-                                          maxFee: try! Amount(fromString: 
LONGCURRENCY + ":0.2"),
-                                          merchant: merchant,
-                                          merchantPub: "merchantPub",
-                                            deliveryDate: nil,
-                                            deliveryLocation: nil,
-                                          exchanges: [],
-                                          products: [product],
-                                          refundDeadline: Timestamp.tomorrow(),
-                                          wireTransferDeadline: 
Timestamp.tomorrow(),
-                                          timestamp: Timestamp.now(),
-                                          orderID: "orderID",
-                                          merchantBaseURL: "merchantBaseURL",
-                                            fulfillmentURL: "fulfillmentURL",
-                                            publicReorderURL: 
"publicReorderURL",
-                                            fulfillmentMessage: nil,
-                                            fulfillmentMessageI18n: nil,
-                                          wireFeeAmortization: 0,
-                                          maxWireFee: try! Amount(fromString: 
LONGCURRENCY + ":0.2"),
-                                          minimumAge: nil
-//                                          extra: extra,
-//                                          auditors: [],
+                                         amount: Amount(currency: 
LONGCURRENCY, cent: 220),
+                                    payDeadline: Timestamp.tomorrow(),
+                                         maxFee: Amount(currency: 
LONGCURRENCY, cent: 20),
+                                       merchant: merchant,
+                                    merchantPub: "merchantPub",
+                                   deliveryDate: nil,
+                               deliveryLocation: nil,
+                                      exchanges: [],
+                                       products: [product],
+                                 refundDeadline: Timestamp.tomorrow(),
+                           wireTransferDeadline: Timestamp.tomorrow(),
+                                      timestamp: Timestamp.now(),
+                                        orderID: "orderID",
+                                merchantBaseURL: "merchantBaseURL",
+                                 fulfillmentURL: "fulfillmentURL",
+                               publicReorderURL: "publicReorderURL",
+                             fulfillmentMessage: nil,
+                         fulfillmentMessageI18n: nil,
+                            wireFeeAmortization: 0,
+                                     maxWireFee: Amount(currency: 
LONGCURRENCY, cent: 20),
+                                     minimumAge: nil
+//                                        extra: extra,
+//                                     auditors: []
                                   )
-        let details = PreparePayResult(
-            status: PreparePayResultType.paymentPossible,
-            transactionId: "txn:payment:012345",
-            contractTerms: terms,
-            contractTermsHash: "termsHash",
-            amountRaw: try! Amount(fromString: LONGCURRENCY + ":2.2"),
-            amountEffective: try! Amount(fromString: LONGCURRENCY + ":2.4"),
-            balanceDetails: nil,
-            paid: nil
-//        ,   talerUri: "talerURI"
+        let details = PreparePayResult(status: 
PreparePayResultType.paymentPossible,
+                                transactionId: "txn:payment:012345",
+                                contractTerms: terms,
+                            contractTermsHash: "termsHash",
+                                    amountRaw: Amount(currency: LONGCURRENCY, 
cent: 220),
+                              amountEffective: Amount(currency: LONGCURRENCY, 
cent: 240),
+                               balanceDetails: nil,
+                                         paid: nil
+//                               ,   talerUri: "talerURI"
         )
         let url = URL(string: "taler://pay/some_amount")!
         
diff --git a/TalerWallet1/Views/Sheets/URLSheet.swift 
b/TalerWallet1/Views/Sheets/URLSheet.swift
index 4bb7a10..4c9f99d 100644
--- a/TalerWallet1/Views/Sheets/URLSheet.swift
+++ b/TalerWallet1/Views/Sheets/URLSheet.swift
@@ -10,6 +10,7 @@ struct URLSheet: View {
     let stack: CallStack
     let navTitle = String(localized: "Checking Link")
     var urlToOpen: URL
+
     @EnvironmentObject private var controller: Controller
 
     var body: some View {
diff --git 
a/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawAcceptDone.swift 
b/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawAcceptDone.swift
index 0799c6c..048351d 100644
--- a/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawAcceptDone.swift
+++ b/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawAcceptDone.swift
@@ -14,8 +14,8 @@ struct WithdrawAcceptDone: View {
     let exchangeBaseUrl: String?
     let url: URL
 
-    @EnvironmentObject private var model: WalletModel
     @EnvironmentObject private var controller: Controller
+    @EnvironmentObject private var model: WalletModel
 
     @State private var transactionId: String? = nil
 
diff --git 
a/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawTOSView.swift 
b/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawTOSView.swift
index bf584a5..0142d42 100644
--- a/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawTOSView.swift
+++ b/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawTOSView.swift
@@ -8,19 +8,18 @@ import SymLog
 struct WithdrawTOSView: View {
     private let symLog = SymLogV(0)
     let stack: CallStack
-    @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
 
     let navTitle = String(localized: "Terms of Service")
 
     let exchangeBaseUrl: String?
+    let viewID: Int         // either VIEW_WITHDRAW_TOS or SHEET_WITHDRAW_TOS
+    let acceptAction: (() -> Void)?
 
+    @Environment(\.presentationMode) var presentationMode
     @EnvironmentObject private var model: WalletModel
+    @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
 
     @State var exchangeTOS: ExchangeTermsOfService?
-    let viewID: Int         // either VIEW_WITHDRAW_TOS or SHEET_WITHDRAW_TOS
-
-    let acceptAction: (() -> Void)?
-    @Environment(\.presentationMode) var presentationMode
 
     var body: some View {
         VStack {
diff --git 
a/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawURIView.swift 
b/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawURIView.swift
index 2fb147e..a8e5b2d 100644
--- a/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawURIView.swift
+++ b/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawURIView.swift
@@ -71,8 +71,8 @@ struct WithdrawURIView: View {
                 }
             } else {
                 // Yikes no details or no baseURL
-//                WithdrawProgressView(message: url.host ?? badURL)
-//                    .navigationTitle("Contacting Exchange")
+                WithdrawProgressView(message: url.host ?? badURL)
+                    .navigationTitle("Contacting Exchange")
             }
         }
         .onAppear() {
diff --git a/TalerWallet1/Views/Transactions/ManualDetailsV.swift 
b/TalerWallet1/Views/Transactions/ManualDetailsV.swift
index 3adceaf..44fc7cf 100644
--- a/TalerWallet1/Views/Transactions/ManualDetailsV.swift
+++ b/TalerWallet1/Views/Transactions/ManualDetailsV.swift
@@ -85,8 +85,8 @@ struct ManualDetails_Previews: PreviewProvider {
     static var previews: some View {
         let common = TransactionCommon(type: .withdrawal,
                                     txState: TransactionState(major: .done),
-                            amountEffective: try! Amount(fromString: 
LONGCURRENCY + ":1.1"),
-                                  amountRaw: try! Amount(fromString: 
LONGCURRENCY + ":2.2"),
+                            amountEffective: Amount(currency: LONGCURRENCY, 
cent: 110),
+                                  amountRaw: Amount(currency: LONGCURRENCY, 
cent: 220),
                               transactionId: "someTxID",
                                   timestamp: Timestamp(from: 
1_666_666_000_000),
                                   txActions: [])
diff --git a/TalerWallet1/Views/Transactions/ThreeAmountsV.swift 
b/TalerWallet1/Views/Transactions/ThreeAmountsV.swift
index 2579b94..0ff8f06 100644
--- a/TalerWallet1/Views/Transactions/ThreeAmountsV.swift
+++ b/TalerWallet1/Views/Transactions/ThreeAmountsV.swift
@@ -114,8 +114,8 @@ struct ThreeAmounts_Previews: PreviewProvider {
     static var previews: some View {
         let common = TransactionCommon(type: .withdrawal,
                                     txState: TransactionState(major: .done),
-                            amountEffective: try! Amount(fromString: 
LONGCURRENCY + ":0.1"),
-                                  amountRaw: try! Amount(fromString: 
LONGCURRENCY + ":0.2"),
+                            amountEffective: Amount(currency: LONGCURRENCY, 
cent: 10),
+                                  amountRaw: Amount(currency: LONGCURRENCY, 
cent: 20),
                               transactionId: "someTxID",
                                   timestamp: Timestamp(from: 
1_666_666_000_000),
                                   txActions: [])
diff --git a/TalerWallet1/Views/Transactions/TransactionDetailView.swift 
b/TalerWallet1/Views/Transactions/TransactionDetailView.swift
index aca9ae8..31e8780 100644
--- a/TalerWallet1/Views/Transactions/TransactionDetailView.swift
+++ b/TalerWallet1/Views/Transactions/TransactionDetailView.swift
@@ -8,7 +8,7 @@ import SymLog
 
 extension Transaction {             // for Dummys
     init(dummyCurrency: String) {
-        let amount = try! Amount(fromString: "\(dummyCurrency):0")
+        let amount = Amount.zero(currency: dummyCurrency)
         let now = Timestamp.now()
         let common = TransactionCommon(type: .dummy,
                                        txState: TransactionState(major: 
.pending),
diff --git a/TalerWallet1/Views/Transactions/TransactionRowView.swift 
b/TalerWallet1/Views/Transactions/TransactionRowView.swift
index f51e1c6..e3d71ab 100644
--- a/TalerWallet1/Views/Transactions/TransactionRowView.swift
+++ b/TalerWallet1/Views/Transactions/TransactionRowView.swift
@@ -59,9 +59,10 @@ struct TransactionRowContentV: View {
 
 struct TransactionRowView: View {
     let transaction : Transaction
-    let currencyInfo: CurrencyInfo?
+    let currency: String
 
     @Environment(\.sizeCategory) var sizeCategory
+    @EnvironmentObject private var controller: Controller
 
     func needVStack(available: CGFloat, contentWidth: CGFloat, valueWidth: 
CGFloat) -> Bool {
         available < (contentWidth + valueWidth + 40)
@@ -80,7 +81,7 @@ struct TransactionRowView: View {
         let foreColor = pending ? WalletColors().pendingColor(incoming)
                       : done ? WalletColors().transactionColor(incoming)
                              : WalletColors().incompleteColor
-
+        let currencyInfo = controller.info(for: currency, 
controller.currencyTicker)
         SingleAxisGeometryReader { width in
             Group {
                 let amountStr = amount.string(currencyInfo)
@@ -114,30 +115,21 @@ struct TransactionRow_Previews: PreviewProvider {
                                            id: "some payment ID",
                                          time: Timestamp(from: 
1_666_666_000_000))
     static var previews: some View {
-        let testInfo = PreviewCurrencyInfo(TESTCURRENCY, digits: 0)
-        let demoInfo = PreviewCurrencyInfo(TESTCURRENCY, digits: 2)
-        let test = try! Amount(fromString: TESTCURRENCY + ":1.23")
-        let demo = try! Amount(fromString: DEMOCURRENCY + ":1234.56")
         List {
-            TransactionRowView(transaction: withdrawal, currencyInfo: testInfo)
-            TransactionRowView(transaction: payment, currencyInfo: demoInfo)
+            TransactionRowView(transaction: withdrawal, currency: TESTCURRENCY)
+            TransactionRowView(transaction: payment, currency: DEMOCURRENCY)
         }
     }
 }
 // MARK: -
 extension Transaction {             // for PreViews
     init(incoming: Bool, pending: Bool, id: String, time: Timestamp) {
-        let currency = LONGCURRENCY
-        let raw = currency + ":5"
-        let effective = currency + (incoming ? ":4.8"
-                                             : ":5.2")
-        let refRaw = currency + ":3"
-        let refEff = currency + ":2.8"
+        let effective = incoming ? 480 : 520
         let common = TransactionCommon(type: incoming ? .withdrawal : .payment,
                                     txState: TransactionState(major: pending ? 
TransactionMajorState.pending
                                                                              : 
TransactionMajorState.done),
-                            amountEffective: try! Amount(fromString: 
effective),
-                                  amountRaw: try! Amount(fromString: raw),
+                            amountEffective: Amount(currency: LONGCURRENCY, 
cent: UInt64(effective)),
+                                  amountRaw: Amount(currency: LONGCURRENCY, 
cent: 5),
                               transactionId: id,
                                   timestamp: time,
                                   txActions: [.abort])
@@ -159,8 +151,8 @@ extension Transaction {             // for PreViews
                                       summary: "some product summary",
                                      products: [])
             let pDetails = PaymentTransactionDetails(proposalId: "some 
proposal ID",
-                                                 totalRefundRaw: try! 
Amount(fromString: refRaw),
-                                           totalRefundEffective: try! 
Amount(fromString: refEff),
+                                                 totalRefundRaw: 
Amount(currency: LONGCURRENCY, cent: 300),
+                                           totalRefundEffective: 
Amount(currency: LONGCURRENCY, cent: 280),
                                                            info: info)
             self = .payment(PaymentTransaction(common: common, details: 
pDetails))
         }
diff --git a/TalerWallet1/Views/Transactions/TransactionsListView.swift 
b/TalerWallet1/Views/Transactions/TransactionsListView.swift
index 7160969..cce00e6 100644
--- a/TalerWallet1/Views/Transactions/TransactionsListView.swift
+++ b/TalerWallet1/Views/Transactions/TransactionsListView.swift
@@ -11,7 +11,7 @@ struct TransactionsListView: View {
     @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
     let navTitle: String
 
-    let currencyInfo: CurrencyInfo?
+    let currency: String
     let transactions: [Transaction]
     let showUpDown: Bool
     let reloadAllAction: (_ stack: CallStack) async -> ()
@@ -28,12 +28,11 @@ struct TransactionsListView: View {
         let count = transactions.count
         ScrollViewReader { scrollView in
             List {
-                TransactionsRowsView(symLog: symLog,
-                                      stack: stack.push(),
-                               currencyInfo: currencyInfo,
-                               transactions: transactions,
-//                          reloadAllAction: reloadAllAction,
-                            reloadOneAction: reloadOneAction)
+                TransactionsArraySliceV(symLog: symLog,
+                                         stack: stack.push(),
+                                      currency: currency,
+                                  transactions: transactions,
+                               reloadOneAction: reloadOneAction)
             }
             .id(viewId)
             .listStyle(myListStyle.style).anyView
@@ -69,8 +68,7 @@ struct TransactionsListView: View {
         }
         .overlay {
             if transactions.isEmpty {
-                let unknown = String(localized: "Unknown currency")
-                TransactionsEmptyView(stack: stack.push(), currency: 
currencyInfo?.scope.currency ?? unknown)
+                TransactionsEmptyView(stack: stack.push(), currency: currency)
             }
         }
         .onAppear {
@@ -79,13 +77,12 @@ struct TransactionsListView: View {
     }
 }
 // MARK: -
-// used by TransactionsListView, and by Balances to show the last 3 
transactions
-struct TransactionsRowsView: View {
+// used by TransactionsListView, and by BalancesSectionView to show the last 3 
transactions
+struct TransactionsArraySliceV: View {
     let symLog: SymLogV?
     let stack: CallStack
-    let currencyInfo: CurrencyInfo?
+    let currency: String
     let transactions: [Transaction]
-//  let reloadAllAction: (_ stack: CallStack) async -> ()
     let reloadOneAction: ((_ transactionId: String) async throws -> 
Transaction)
 
     @EnvironmentObject private var model: WalletModel
@@ -114,7 +111,8 @@ struct TransactionsRowsView: View {
                                    resumeAction: resumeAction)
                 }
             } label: {
-                TransactionRowView(transaction: transaction, currencyInfo: 
currencyInfo)
+                TransactionRowView(transaction: transaction,
+                                      currency: currency)
             }
             .id(Int(index))
         }
diff --git a/TestFlight/WhatToTest.en-US.txt b/TestFlight/WhatToTest.en-US.txt
index 4de97d4..81f22ce 100644
--- a/TestFlight/WhatToTest.en-US.txt
+++ b/TestFlight/WhatToTest.en-US.txt
@@ -1,4 +1,15 @@
 
+Version 0.9.3 (25)
+
+• New feature: Shortcuts Buttons (50,25,10,5) for P2P + Withdrawal
+
+- CurrencyFormatter for P2P + Withdrawal
+- BugFix: Accessibility announcements always did reset the focus to start of 
view
+
+A/B test result: Nobody liked GNU Taler (with SideView & Hamburger-button) 
better,
+thus we concentrate on Taler Wallet (with TabBar)
+
+
 Version 0.9.3 (24)
 
 • Sound & Haptics ON by default
diff --git a/taler-swift/Sources/taler-swift/Amount.swift 
b/taler-swift/Sources/taler-swift/Amount.swift
index 25706cf..1751b20 100644
--- a/taler-swift/Sources/taler-swift/Amount.swift
+++ b/taler-swift/Sources/taler-swift/Amount.swift
@@ -47,14 +47,32 @@ public final class Amount: Codable, Hashable, @unchecked 
Sendable, CustomStringC
     /// The largest possible value that can be represented.
     private static let maxValue: UInt64 = 1 << 52
     
-    /// The size of `integer` in relation to `fraction`.
-    private static let fractionalBase: UInt32 = 100000000
-    
     /// The greatest number of fractional digits that can be represented.
     private static let fractionalBaseDigits: UInt = 8
-    
-    /// The currency of the amount. Cannot be changed later
-    private let currency: String
+
+    /// The size of `integer` in relation to `fraction`.
+    static func fractionalBase(_ power: UInt = Amount.fractionalBaseDigits) -> 
UInt32 {
+        var exponent = power < Amount.fractionalBaseDigits
+                     ? power : Amount.fractionalBaseDigits
+        var base: UInt32 = 1
+        for _ in 0..<exponent { base *= 10 }
+        return base
+    }
+
+    /// Convenience re-definition
+    func fractionalBase(_ power: UInt = Amount.fractionalBaseDigits) -> UInt32 
{
+        Self.fractionalBase(power)
+    }
+
+    public static let decimalSeparator = "."
+
+    /// The currency of the amount. Cannot be changed later, except...
+    private var currency: String
+
+    /// ... with this function
+    public func setCurrency(_ newCurrency: String) {
+        currency = newCurrency
+    }
 
     /// The integer value of the amount (number to the left of the decimal 
point).
     var integer: UInt64
@@ -81,7 +99,7 @@ public final class Amount: Codable, Hashable, @unchecked 
Sendable, CustomStringC
     /// The floating point representation of the fraction.
     public var fracValue: Double {
         let oneThousand = 1000.0
-        let base = Double(Amount.fractionalBase) / oneThousand
+        let base = Double(fractionalBase()) / oneThousand
         let thousandths = Double(fraction) / base
         return thousandths / oneThousand
     }
@@ -95,43 +113,76 @@ public final class Amount: Codable, Hashable, @unchecked 
Sendable, CustomStringC
     }
 
     /// The tuple representation of the value.
-    public var valueAsTuple: (Double, Double) {
+    public var valueAsDecimalTuple: (UInt64, UInt32) {
+        (integer, fraction)
+    }
+    /// The tuple representation of the value.
+    public var valueAsFloatTuple: (Double, Double) {
         (intValue, fracValue)
     }
 
     /// The string representation of the value, formatted as 
"`integer`.`fraction`",
     /// no trailing zeroes, no group separator.
     public var valueStr: String {
-        var decimalSeparator = "."
-//        if let currencySpecification {      // TODO: use locale
-//            decimalSeparator = currencySpecification.decimalSeparator
-//        }
         if fraction == 0 {
-            return "\(integer)"
+            return String(integer)
         } else {
             var frac = fraction
             var fracStr = ""
             while (frac > 0) {
-                fracStr += "\(frac / (Amount.fractionalBase / 10))"
-                frac = (frac * 10) % Amount.fractionalBase
+                fracStr += String(frac / (fractionalBase() / 10))
+                frac = (frac * 10) % fractionalBase()
             }
-            return "\(integer)\(decimalSeparator)\(fracStr)"
+            return "\(integer)\(Self.decimalSeparator)\(fracStr)"
+        }
+    }
+
+    /// The string representation of the value, formatted as 
"`integer``fraction`",
+    /// no group separator, no decimalSeparator, #inputDigits digits from 
fraction, padded with trailing zeroes
+    public func plainString(inputDigits: UInt) -> String {
+        let base = fractionalBase()
+        var frac = fraction
+        var fracStr = ""
+        var i = inputDigits
+
+        var nextchar: UInt32 {
+            let fracChar = frac / (base / 10)
+            frac = (frac * 10) % base
+            return fracChar
+        }
+
+        if integer > 0 {
+            while (i > 0) {
+                fracStr += String(nextchar)
+                i -= 1
+            }
+            return "\(integer)\(fracStr)"
+        } else {
+            while (i > 0) {
+                let fracChar = nextchar
+                // skip leading zeroes
+                if fracStr.count > 0 || fracChar > 0 {
+                    fracStr += String(fracChar)
+                }
+                i -= 1
+            }
+            return fracStr.count > 0 ? fracStr : "0"
         }
     }
 
     /// read-only getter
     public var currencyStr: String {
-        return currency
+        currency
     }
 
-    /// The string representation of the amount, formatted as 
"`currency`:`integer`.`fraction`".
+    /// The string representation of the amount, formatted as 
"`currency`:`integer`.`fraction`" (without space).
     public var description: String {
-        return "\(currency):\(valueStr)"
+        "\(currency):\(valueStr)"
     }
     
-    /// The string representation of the amount, formatted as 
"`integer`.`fraction` `currency`".
+    /// The string representation of the amount, formatted as 
"`integer`.`fraction` `currency`" (with space).
     public var readableDescription: String {
-        return "\(valueStr) \(currency)"
+        "\(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.
@@ -144,7 +195,7 @@ public final class Amount: Codable, Hashable, @unchecked 
Sendable, CustomStringC
     
     /// Whether this amount is zero or not.
     public var isZero: Bool {
-        return integer == 0 && fraction == 0
+        integer == 0 && fraction == 0
     }
     
     /// Initializes an amount by parsing a string representing the amount. The 
string should be formatted as "`currency`:`integer`.`fraction`".
@@ -160,13 +211,13 @@ public final class Amount: Codable, Hashable, @unchecked 
Sendable, CustomStringC
             if let dotIndex = amountStr.firstIndex(of: ".") {
                 let integerStr = String(amountStr[..<dotIndex])
                 let fractionStr = String(amountStr[string.index(dotIndex, 
offsetBy: 1)...])
-                if (fractionStr.count > Amount.fractionalBaseDigits) {
+                if (fractionStr.count > Self.fractionalBaseDigits) {
                     throw AmountError.invalidStringRepresentation
                 }
                 guard let intValue = UInt64(integerStr) else { throw 
AmountError.invalidStringRepresentation }
                 self.integer = intValue
                 self.fraction = 0
-                var digitValue = Amount.fractionalBase / 10
+                var digitValue = fractionalBase() / 10
                 for char in fractionStr {
                     guard let digit = char.wholeNumberValue else { throw 
AmountError.invalidStringRepresentation }
                     self.fraction += digitValue * UInt32(digit)
@@ -195,10 +246,10 @@ public final class Amount: Codable, Hashable, @unchecked 
Sendable, CustomStringC
         self.integer = integer
         self.fraction = fraction
     }
-    public init(currency: String, value: UInt64) {
+    public init(currency: String, cent: UInt64) {
         self.currency = currency
-        self.integer = value / 100          // TODO: fractional digits can be 
0, 2 or 3
-        self.fraction = UInt32(value - (self.integer * 100))
+        self.integer = cent / 100   // For existing currencies, fractional 
digits could be 0, 2 or 3
+        self.fraction = UInt32(cent - (self.integer * 100))
     }
 
     /// Initializes an amount from a decoder.
@@ -215,8 +266,8 @@ public final class Amount: Codable, Hashable, @unchecked 
Sendable, CustomStringC
     
     /// Copies an amount.
     /// - Returns: A copy of the amount.
-    func copy() -> Amount {
-        return Amount(currency: currency, integer: integer, fraction: fraction)
+    public func copy() -> Amount {
+        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).
@@ -235,20 +286,67 @@ public final class Amount: Codable, Hashable, @unchecked 
Sendable, CustomStringC
         try container.encode(description)
     }
     
-    /// Normalizes an amount by reducing `fraction` until it is less than 
`Amount.fractionalBase`, increasing `integer` appropriately.
+    /// Normalizes an amount by reducing `fraction` until it is less than 
`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
         }
-        integer += UInt64(fraction / Amount.fractionalBase)
-        fraction = fraction % Amount.fractionalBase
+        integer += UInt64(fraction / fractionalBase())
+        fraction = fraction % fractionalBase()
         if !valid {
             throw AmountError.invalidAmount
         }
     }
     
+    /// Divides by ten
+    public func shiftRight() {
+        var remainder = UInt32(integer % 10)
+        self.integer = integer / 10
+
+        remainder = remainder * fractionalBase() + fraction
+        self.fraction = remainder / 10
+    }
+
+    /// Multiplies by ten, then adds digit
+    public func shiftLeft(add digit: UInt8, _ inputDigits: UInt) {
+        // how many digits to shift right (e.g. inputD=2 ==> shift:=6)
+        let shift = Self.fractionalBaseDigits - inputDigits
+        // mask to zero out fractions smaller than inputDigits
+        let shiftMask = fractionalBase(shift)
+
+        let carryMask = fractionalBase(Self.fractionalBaseDigits - 1)
+        // get biggest fractional digit
+        let carry = fraction / carryMask
+        var remainder = fraction % carryMask
+//        print("fraction: \(fraction) = \(carry) + \(remainder)")
+
+        let shiftedInt = integer * 10 + UInt64(carry)
+        if shiftedInt < Self.maxValue {
+            self.integer = shiftedInt
+//            print("remainder: \(remainder) / shiftMask \(shiftMask) = 
\(remainder / shiftMask)")
+            remainder = (remainder / shiftMask) * 10
+        } else { // will get too big
+            // Just swap the last significant digit for the one the user typed 
last
+            if shiftMask >= 10 {
+                remainder = (remainder / (shiftMask / 10)) * 10
+            } else {
+                remainder = (remainder / 10) * 10
+            }
+        }
+        let sum = remainder + UInt32(digit)
+        self.fraction = sum * shiftMask
+//        print("(remainder: \(remainder) + \(digit)) * base(shift) 
\(shiftMask) = fraction \(fraction)")
+    }
+
+    /// Sets all fractional digits after inputDigits to 0
+    public func mask(_ inputDigits: UInt) {
+        let mask = fractionalBase(Self.fractionalBaseDigits - inputDigits)
+        let remainder = fraction % mask
+        self.fraction -= remainder
+    }
+
     /// Adds two amounts together.
     /// - Parameters:
     ///   - left: The amount on the left.
@@ -285,7 +383,7 @@ public final class Amount: Codable, Hashable, @unchecked 
Sendable, CustomStringC
         if (leftNormalized.fraction < rightNormalized.fraction) {
             guard leftNormalized.integer != 0 else { throw 
AmountError.negativeAmount }
             leftNormalized.integer -= 1
-            leftNormalized.fraction += Amount.fractionalBase
+            leftNormalized.fraction += fractionalBase()
         }
         guard leftNormalized.integer >= rightNormalized.integer else { throw 
AmountError.negativeAmount }
         let diff = Amount.zero(currency: left.currency)
@@ -314,8 +412,8 @@ public final class Amount: Codable, Hashable, @unchecked 
Sendable, CustomStringC
         var remainder = result.integer % UInt64(divisor)
         result.integer = result.integer / UInt64(divisor)
 
-        let fractionalBase = UInt64(Amount.fractionalBase)
-        remainder = (remainder * fractionalBase) + UInt64(result.fraction)
+        let fractionalBase64 = UInt64(fractionalBase())
+        remainder = (remainder * fractionalBase64) + UInt64(result.fraction)
         result.fraction = UInt32(remainder / UInt64(divisor))
         try result.normalize()
         return result
@@ -330,8 +428,8 @@ public final class Amount: Codable, Hashable, @unchecked 
Sendable, CustomStringC
         let result = try amount.normalizedCopy()
         result.integer = result.integer * UInt64(factor)
         let fraction_tmp = UInt64(result.fraction) * UInt64(factor)
-        result.integer += fraction_tmp / UInt64(Amount.fractionalBase)
-        result.fraction = UInt32(fraction_tmp % UInt64(Amount.fractionalBase))
+        result.integer += fraction_tmp / UInt64(fractionalBase())
+        result.fraction = UInt32(fraction_tmp % UInt64(fractionalBase()))
         return result
     }
     
diff --git a/taler-swift/Tests/taler-swiftTests/AmountTests.swift 
b/taler-swift/Tests/taler-swiftTests/AmountTests.swift
index a0b1f27..27e5dae 100644
--- a/taler-swift/Tests/taler-swiftTests/AmountTests.swift
+++ b/taler-swift/Tests/taler-swiftTests/AmountTests.swift
@@ -10,31 +10,31 @@ class AmountTests: XCTestCase {
         var str = "TESTKUDOS:23.42"
         var amt = try! Amount(fromString: str)
         XCTAssert(str == amt.description)
-        XCTAssert("TESTKUDOS" == amt.currency)
+        XCTAssert("TESTKUDOS" == amt.currencyStr)
         XCTAssert(23 == amt.value)
         XCTAssert(UInt64(0.42 * 1e8) == amt.fraction)
         
         str = "EUR:500000000.00000001"
         amt = try! Amount(fromString: str)
         XCTAssert(str == amt.description)
-        XCTAssert("EUR" == amt.currency)
+        XCTAssert("EUR" == amt.currencyStr)
         XCTAssert(500000000 == amt.value)
         XCTAssert(1 == amt.fraction)
         
         str = "EUR:1500000000.00000003"
         amt = try! Amount(fromString: str)
         XCTAssert(str == amt.description)
-        XCTAssert("EUR" == amt.currency)
+        XCTAssert("EUR" == amt.currencyStr)
         XCTAssert(1500000000 == amt.value)
         XCTAssert(3 == amt.fraction)
         
-        let maxValue = 4503599627370496
+        let maxValue = 4503599627370496                 // 16 significant 
digits are 1 too many for double
         str = "TESTKUDOS123:\(maxValue).99999999"
         amt = try! Amount(fromString: str)
         XCTAssert(str == amt.description)
-        XCTAssert("TESTKUDOS123" == amt.currency)
-        XCTAssert(maxValue == amt.value)
-        
+        XCTAssert("TESTKUDOS123" == amt.currencyStr)
+        XCTAssert(Double(maxValue) == amt.value)
+
         XCTAssertThrowsError(try Amount(fromString: 
"TESTKUDOS1234:\(maxValue).99999999"))
         XCTAssertThrowsError(try Amount(fromString: "TESTKUDOS123:\(maxValue + 
1).99999999"))
         XCTAssertThrowsError(try Amount(fromString: 
"TESTKUDOS123:\(maxValue).999999990"))

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