gnunet-svn
[Top][All Lists]
Advanced

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

[taler-taler-ios] branch master updated (adfc919 -> a0c6bdb)


From: gnunet
Subject: [taler-taler-ios] branch master updated (adfc919 -> a0c6bdb)
Date: Sat, 21 Oct 2023 21:09:58 +0200

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

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

    from adfc919  disable some debug output
     new 28e5f78  CurrencySpecification
     new 49524f0  HTTPError
     new 0ebc3a8  ExchangeListItem
     new 3e2da08  cleanup
     new 1dfae88  getCurrencyInfo(scope)
     new fa93599  listExchangesForScopedCurrency
     new cefa3ed  cache currency infos
     new fba7fcf  AboutView
     new 0762407  Call AboutView
     new aec0b49  Use CurrencyFormatter for Balance
     new a0c6bdb  Minimalistic

The 11 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              |  20 ++-
 TalerWallet1/Backend/WalletBackendRequest.swift    | 138 ++++++------------
 TalerWallet1/Controllers/Controller.swift          |  30 ++++
 TalerWallet1/Controllers/DebugViewC.swift          |   7 +-
 TalerWallet1/Helper/CurrencyFormatter.swift        |  30 ----
 TalerWallet1/Helper/CurrencySpecification.swift    | 158 +++++++++++++++++++++
 TalerWallet1/Helper/TalerStrings.swift             |  22 +++
 TalerWallet1/Model/Model+Balances.swift            |   4 +-
 TalerWallet1/Model/Model+Exchange.swift            |  90 ++++++++----
 TalerWallet1/Model/Model+P2P.swift                 |  38 ++---
 TalerWallet1/Model/Model+Payment.swift             |  12 +-
 TalerWallet1/Model/Model+Pending.swift             |   2 +-
 TalerWallet1/Model/Model+Settings.swift            |  33 ++---
 TalerWallet1/Model/Model+Transactions.swift        |  26 ++--
 TalerWallet1/Model/Model+Withdraw.swift            |  41 ++----
 TalerWallet1/Model/WalletModel.swift               |  20 ++-
 TalerWallet1/Views/Balances/BalanceRowView.swift   |   7 +-
 TalerWallet1/Views/Balances/BalancesListView.swift |   2 +-
 .../Views/Balances/BalancesSectionView.swift       |  29 ++--
 TalerWallet1/Views/Main/MainView.swift             |  19 +++
 TalerWallet1/Views/Peer2peer/PaymentPurpose.swift  |   8 +-
 TalerWallet1/Views/Peer2peer/SendPurpose.swift     |   8 +-
 TalerWallet1/Views/Settings/AboutView.swift        | 111 +++++++++++++++
 TalerWallet1/Views/Settings/SettingsItem.swift     |  24 +++-
 TalerWallet1/Views/Settings/SettingsView.swift     | 105 ++++++--------
 taler-swift/Sources/taler-swift/Amount.swift       |  37 +----
 26 files changed, 640 insertions(+), 381 deletions(-)
 delete mode 100644 TalerWallet1/Helper/CurrencyFormatter.swift
 create mode 100644 TalerWallet1/Helper/CurrencySpecification.swift
 create mode 100644 TalerWallet1/Views/Settings/AboutView.swift

diff --git a/TalerWallet.xcodeproj/project.pbxproj 
b/TalerWallet.xcodeproj/project.pbxproj
index dbec31f..cbefbd1 100644
--- a/TalerWallet.xcodeproj/project.pbxproj
+++ b/TalerWallet.xcodeproj/project.pbxproj
@@ -7,7 +7,7 @@
        objects = {
 
 /* Begin PBXBuildFile section */
-               4E16E12329F3BB99008B9C86 /* CurrencyFormatter.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4E16E12229F3BB99008B9C86 /* 
CurrencyFormatter.swift */; };
+               4E16E12329F3BB99008B9C86 /* CurrencySpecification.swift in 
Sources */ = {isa = PBXBuildFile; fileRef = 4E16E12229F3BB99008B9C86 /* 
CurrencySpecification.swift */; };
                4E2254972A822B8100E41D29 /* payment_received.m4a in Resources 
*/ = {isa = PBXBuildFile; fileRef = 4E2254952A822B8100E41D29 /* 
payment_received.m4a */; };
                4E2254982A822B8100E41D29 /* payment_sent.m4a in Resources */ = 
{isa = PBXBuildFile; fileRef = 4E2254962A822B8100E41D29 /* payment_sent.m4a */; 
};
                4E3327BA2AD1635100BF5AD6 /* AsyncSemaphore.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4E3327B92AD1635100BF5AD6 /* 
AsyncSemaphore.swift */; };
@@ -74,7 +74,7 @@
                4E3EAE502A990778009F1BE8 /* Model+Transactions.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4EB095322989CBFE0043A8A1 /* 
Model+Transactions.swift */; };
                4E3EAE512A990778009F1BE8 /* Controller+playSound.swift in 
Sources */ = {isa = PBXBuildFile; fileRef = 4E578E912A481D8600F21F1C /* 
Controller+playSound.swift */; };
                4E3EAE522A990778009F1BE8 /* WalletEmptyView.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4EB095392989CBFE0043A8A1 /* 
WalletEmptyView.swift */; };
-               4E3EAE532A990778009F1BE8 /* CurrencyFormatter.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4E16E12229F3BB99008B9C86 /* 
CurrencyFormatter.swift */; };
+               4E3EAE532A990778009F1BE8 /* CurrencySpecification.swift in 
Sources */ = {isa = PBXBuildFile; fileRef = 4E16E12229F3BB99008B9C86 /* 
CurrencySpecification.swift */; };
                4E3EAE542A990778009F1BE8 /* TalerDater.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EB095062989CB7C0043A8A1 /* TalerDater.swift */; 
};
                4E3EAE552A990778009F1BE8 /* Model+Balances.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4E3B4BC42A428AF700CC88B8 /* 
Model+Balances.swift */; };
                4E3EAE562A990778009F1BE8 /* LocalizedAlertError.swift in 
Sources */ = {isa = PBXBuildFile; fileRef = 4E363CC12A2621C200D7E98C /* 
LocalizedAlertError.swift */; };
@@ -231,6 +231,8 @@
                4EBA56412A7FF5200084948B /* PayTemplateView.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4EBA56402A7FF5200084948B /* 
PayTemplateView.swift */; };
                4EBA82AB2A3EB2CA00E5F39A /* TransactionButton.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4EBA82AA2A3EB2CA00E5F39A /* 
TransactionButton.swift */; };
                4EBA82AD2A3F580500E5F39A /* QuiteSomeCoins.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4EBA82AC2A3F580500E5F39A /* 
QuiteSomeCoins.swift */; };
+               4EC400892AE3E7E800DF72C7 /* AboutView.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EC400882AE3E7E800DF72C7 /* AboutView.swift */; 
};
+               4EC4008A2AE3E7E800DF72C7 /* AboutView.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EC400882AE3E7E800DF72C7 /* AboutView.swift */; 
};
                4EC90C782A1B528B0071DC58 /* ExchangeSectionView.swift in 
Sources */ = {isa = PBXBuildFile; fileRef = 4EC90C772A1B528B0071DC58 /* 
ExchangeSectionView.swift */; };
                4ECB62802A0BA6DF004ABBB7 /* Model+P2P.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4ECB627F2A0BA6DF004ABBB7 /* Model+P2P.swift */; 
};
                4ECB62822A0BB01D004ABBB7 /* SelectDays.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4ECB62812A0BB01D004ABBB7 /* SelectDays.swift */; 
};
@@ -287,7 +289,7 @@
 /* End PBXCopyFilesBuildPhase section */
 
 /* Begin PBXFileReference section */
-               4E16E12229F3BB99008B9C86 /* CurrencyFormatter.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= CurrencyFormatter.swift; sourceTree = "<group>"; };
+               4E16E12229F3BB99008B9C86 /* CurrencySpecification.swift */ = 
{isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = 
sourcecode.swift; path = CurrencySpecification.swift; sourceTree = "<group>"; };
                4E2254952A822B8100E41D29 /* payment_received.m4a */ = {isa = 
PBXFileReference; lastKnownFileType = file; path = payment_received.m4a; 
sourceTree = "<group>"; };
                4E2254962A822B8100E41D29 /* payment_sent.m4a */ = {isa = 
PBXFileReference; lastKnownFileType = file; path = payment_sent.m4a; sourceTree 
= "<group>"; };
                4E3327B92AD1635100BF5AD6 /* AsyncSemaphore.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= AsyncSemaphore.swift; sourceTree = "<group>"; };
@@ -399,6 +401,7 @@
                4EBA56402A7FF5200084948B /* PayTemplateView.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= PayTemplateView.swift; sourceTree = "<group>"; };
                4EBA82AA2A3EB2CA00E5F39A /* TransactionButton.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= TransactionButton.swift; sourceTree = "<group>"; };
                4EBA82AC2A3F580500E5F39A /* QuiteSomeCoins.swift */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.swift; path = 
QuiteSomeCoins.swift; sourceTree = "<group>"; };
+               4EC400882AE3E7E800DF72C7 /* AboutView.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= AboutView.swift; sourceTree = "<group>"; };
                4EC90C772A1B528B0071DC58 /* ExchangeSectionView.swift */ = {isa 
= PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; 
path = ExchangeSectionView.swift; sourceTree = "<group>"; };
                4ECB627F2A0BA6DF004ABBB7 /* Model+P2P.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= "Model+P2P.swift"; sourceTree = "<group>"; };
                4ECB62812A0BB01D004ABBB7 /* SelectDays.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= SelectDays.swift; sourceTree = "<group>"; };
@@ -569,7 +572,7 @@
                                4E363CBD2A23CB2100D7E98C /* 
AnyTransition+backslide.swift */,
                                4E3327B92AD1635100BF5AD6 /* 
AsyncSemaphore.swift */,
                                4EDBDCD82AB787CB00925C02 /* CallStack.swift */,
-                               4E16E12229F3BB99008B9C86 /* 
CurrencyFormatter.swift */,
+                               4E16E12229F3BB99008B9C86 /* 
CurrencySpecification.swift */,
                                4EAD117529F672FA008EDD0B /* 
KeyboardResponder.swift */,
                                4E363CC12A2621C200D7E98C /* 
LocalizedAlertError.swift */,
                                4E578E912A481D8600F21F1C /* 
Controller+playSound.swift */,
@@ -643,6 +646,7 @@
                        isa = PBXGroup;
                        children = (
                                4EB095252989CBFE0043A8A1 /* SettingsView.swift 
*/,
+                               4EC400882AE3E7E800DF72C7 /* AboutView.swift */,
                                4EB095262989CBFE0043A8A1 /* SettingsItem.swift 
*/,
                                4EB0954B2989CBFE0043A8A1 /* Pending */,
                        );
@@ -794,7 +798,7 @@
                D14AFD1E24D232B300C51073 /* Products */ = {
                        isa = PBXGroup;
                        children = (
-                4E3EAE892A990778009F1BE8 /* GNU_Taler.app */,
+                               4E3EAE892A990778009F1BE8 /* GNU_Taler.app */,
                                D14AFD1D24D232B300C51073 /* Taler_Wallet.app */,
                                D14AFD3324D232B500C51073 /* TalerTests.xctest 
*/,
                                D14AFD3E24D232B500C51073 /* TalerUITests.xctest 
*/,
@@ -1037,6 +1041,7 @@
                                4E605DB72AB05E48002FB9A7 /* 
View+flippedDirection.swift in Sources */,
                                4E983C2C2ADC416800FA9CC5 /* 
View+needVStack.swift in Sources */,
                                4E3EAE272A990778009F1BE8 /* WalletColors.swift 
in Sources */,
+                               4EC400892AE3E7E800DF72C7 /* AboutView.swift in 
Sources */,
                                4E3EAE282A990778009F1BE8 /* 
BalancesListView.swift in Sources */,
                                4E3EAE292A990778009F1BE8 /* 
WalletBackendError.swift in Sources */,
                                4E3EAE2A2A990778009F1BE8 /* 
PendingRowView.swift in Sources */,
@@ -1084,7 +1089,7 @@
                                4E3EAE502A990778009F1BE8 /* 
Model+Transactions.swift in Sources */,
                                4E3EAE512A990778009F1BE8 /* 
Controller+playSound.swift in Sources */,
                                4E3EAE522A990778009F1BE8 /* 
WalletEmptyView.swift in Sources */,
-                               4E3EAE532A990778009F1BE8 /* 
CurrencyFormatter.swift in Sources */,
+                               4E3EAE532A990778009F1BE8 /* 
CurrencySpecification.swift in Sources */,
                                4E3EAE542A990778009F1BE8 /* TalerDater.swift in 
Sources */,
                                4E3EAE552A990778009F1BE8 /* 
Model+Balances.swift in Sources */,
                                4E3EAE562A990778009F1BE8 /* 
LocalizedAlertError.swift in Sources */,
@@ -1142,6 +1147,7 @@
                                4E605DB82AB05E48002FB9A7 /* 
View+flippedDirection.swift in Sources */,
                                4E983C2D2ADC416800FA9CC5 /* 
View+needVStack.swift in Sources */,
                                4E9320432A14F6EA00A87B0E /* WalletColors.swift 
in Sources */,
+                               4EC4008A2AE3E7E800DF72C7 /* AboutView.swift in 
Sources */,
                                4EB0955D2989CBFE0043A8A1 /* 
BalancesListView.swift in Sources */,
                                4EB095212989CBCB0043A8A1 /* 
WalletBackendError.swift in Sources */,
                                4EB0955E2989CBFE0043A8A1 /* 
PendingRowView.swift in Sources */,
@@ -1189,7 +1195,7 @@
                                4EB095592989CBFE0043A8A1 /* 
Model+Transactions.swift in Sources */,
                                4E578E922A481D8600F21F1C /* 
Controller+playSound.swift in Sources */,
                                4EB0955F2989CBFE0043A8A1 /* 
WalletEmptyView.swift in Sources */,
-                               4E16E12329F3BB99008B9C86 /* 
CurrencyFormatter.swift in Sources */,
+                               4E16E12329F3BB99008B9C86 /* 
CurrencySpecification.swift in Sources */,
                                4EB095092989CB7C0043A8A1 /* TalerDater.swift in 
Sources */,
                                4E3B4BC52A428AF700CC88B8 /* 
Model+Balances.swift in Sources */,
                                4E363CC22A2621C200D7E98C /* 
LocalizedAlertError.swift in Sources */,
diff --git a/TalerWallet1/Backend/WalletBackendRequest.swift 
b/TalerWallet1/Backend/WalletBackendRequest.swift
index 3f4cedc..293269c 100644
--- a/TalerWallet1/Backend/WalletBackendRequest.swift
+++ b/TalerWallet1/Backend/WalletBackendRequest.swift
@@ -111,12 +111,15 @@ struct OrderShortInfo: Codable {
 // MARK: -
 /// A request to process a refund.
 struct WalletBackendApplyRefundRequest: WalletBackendFormattedRequest {
+    func operation() -> String { "applyRefund" }
+    func args() -> Args { Args(talerRefundUri: talerRefundUri) }
+
     var talerRefundUri: String
-    
+
     struct Args: Encodable {
         var talerRefundUri: String
     }
-    
+
     struct Response: Decodable {
         var contractTermsHash: String
         var amountEffectivePaid: Amount
@@ -125,38 +128,27 @@ struct WalletBackendApplyRefundRequest: 
WalletBackendFormattedRequest {
         var pendingAtExchange: Bool
         var info: OrderShortInfo
     }
-    
-    func operation() -> String {
-        return "applyRefund"
-    }
-    
-    func args() -> Args {
-        return Args(talerRefundUri: talerRefundUri)
-    }
 }
 
 
 /// A request to force update an exchange.
 struct WalletBackendForceUpdateRequest: WalletBackendFormattedRequest {
+    func operation() -> String { "addRequest" }
+    func args() -> Args { Args(exchangeBaseUrl: exchangeBaseUrl) }
+
     var exchangeBaseUrl: String
-    
     struct Args: Encodable {
         var exchangeBaseUrl: String
     }
-    
+
     struct Response: Decodable {}
-    
-    func operation() -> String {
-        return "addRequest"
-    }
-    
-    func args() -> Args {
-        return Args(exchangeBaseUrl: exchangeBaseUrl)
-    }
 }
 
 /// A request to deposit funds.
 struct WalletBackendCreateDepositGroupRequest: WalletBackendFormattedRequest {
+    func operation() -> String { "createDepositGroup" }
+    func args() -> Args { Args(depositPayToUri: depositePayToUri, amount: 
amount) }
+
     var depositePayToUri: String
     var amount: Amount
     
@@ -168,52 +160,32 @@ struct WalletBackendCreateDepositGroupRequest: 
WalletBackendFormattedRequest {
     struct Response: Decodable {
         var depositGroupId: String
     }
-    
-    func operation() -> String {
-        return "createDepositGroup"
-    }
-    
-    func args() -> Args {
-        return Args(depositPayToUri: depositePayToUri, amount: amount)
-    }
 }
 
 /// A request to get information about a payment request.
 struct WalletBackendPreparePayRequest: WalletBackendFormattedRequest {
+    func operation() -> String { "preparePay" }
+    func args() -> Args { Args(talerPayUri: talerPayUri) }
     var talerPayUri: String
-    
+
     struct Args: Encodable {
         var talerPayUri: String
     }
-    
+
     struct Response: Decodable {}
-    
-    func operation() -> String {
-        return "preparePay"
-    }
-    
-    func args() -> Args {
-        return Args(talerPayUri: talerPayUri)
-    }
 }
 
 /// A request to confirm a payment.
 struct WalletBackendConfirmPayRequest: WalletBackendFormattedRequest {
+    func operation() -> String { "confirmPay" }
+    func args() -> Args { Args(proposalId: proposalId) }
     var proposalId: String
-    
+
     struct Args: Encodable {
         var proposalId: String
     }
-    
+
     struct Response: Decodable {}
-    
-    func operation() -> String {
-        return "confirmPay"
-    }
-    
-    func args() -> Args {
-        return Args(proposalId: proposalId)
-    }
 }
 
 // MARK: -
@@ -229,8 +201,8 @@ struct PrepareRewardResponse: Decodable {
 /// A request to prepare a reward.
 struct PrepareRewardRequest: WalletBackendFormattedRequest {
     typealias Response = PrepareRewardResponse
-    func operation() -> String { return "prepareReward" }
-    func args() -> Args { return Args(talerRewardUri: talerRewardUri) }
+    func operation() -> String { "prepareReward" }
+    func args() -> Args { Args(talerRewardUri: talerRewardUri) }
 
     var talerRewardUri: String
     struct Args: Encodable {
@@ -241,8 +213,8 @@ struct PrepareRewardRequest: WalletBackendFormattedRequest {
 /// A request to accept a reward.
 struct AcceptRewardRequest: WalletBackendFormattedRequest {
     struct Response: Decodable {}
-    func operation() -> String { return "acceptReward" }
-    func args() -> Args { return Args(walletRewardId: walletRewardId) }
+    func operation() -> String { "acceptReward" }
+    func args() -> Args { Args(walletRewardId: walletRewardId) }
 
     var walletRewardId: String
     struct Args: Encodable {
@@ -252,21 +224,16 @@ struct AcceptRewardRequest: WalletBackendFormattedRequest 
{
 // MARK: -
 /// A request to abort a failed payment.
 struct WalletBackendAbortFailedPaymentRequest: WalletBackendFormattedRequest {
+    func operation() -> String { "abortFailedPayWithRefund" }
+    func args() -> Args { Args(proposalId: proposalId) }
+
     var proposalId: String
-    
     struct Args: Encodable {
         var proposalId: String
     }
     
     struct Response: Decodable {}
     
-    func operation() -> String {
-        return "abortFailedPayWithRefund"
-    }
-    
-    func args() -> Args {
-        return Args(proposalId: proposalId)
-    }
 }
 // MARK: -
 struct IntegrationTestArgs: Codable {
@@ -280,19 +247,13 @@ struct IntegrationTestArgs: Codable {
 
 /// A request to run a basic integration test.
 struct WalletBackendRunIntegrationTestRequest: WalletBackendFormattedRequest {
+    func operation() -> String { "runIntegrationTest" }
+    func args() -> Args { integrationTestArgs }
     var integrationTestArgs: IntegrationTestArgs
     
     typealias Args = IntegrationTestArgs
     
     struct Response: Decodable {}
-    
-    func operation() -> String {
-        return "runIntegrationTest"
-    }
-    
-    func args() -> Args {
-        return integrationTestArgs
-    }
 }
 
 struct TestPayArgs: Codable {
@@ -304,19 +265,13 @@ struct TestPayArgs: Codable {
 
 /// A request to make a test payment.
 struct WalletBackendTestPayRequest: WalletBackendFormattedRequest {
+    func operation() -> String { "testPay" }
+    func args() -> Args { testPayArgs }
+
     var testPayArgs: TestPayArgs
-    
     typealias Args = TestPayArgs
     
     struct Response: Decodable {}
-    
-    func operation() -> String {
-        return "testPay"
-    }
-    
-    func args() -> Args {
-        return testPayArgs
-    }
 }
 
 struct Coin: Codable {
@@ -333,25 +288,22 @@ struct Coin: Codable {
 
 /// A request to dump all coins to JSON.
 struct WalletBackendDumpCoinsRequest: WalletBackendFormattedRequest {
-    struct Args: Encodable {
-        
-    }
-    
+    func operation() -> String { "dumpCoins" }
+    func args() -> Args { Args() }
+    struct Args: Encodable { }
+
     struct Response: Decodable {
         var coins: [Coin]
     }
     
-    func operation() -> String {
-        return "dumpCoins"
-    }
-    
-    func args() -> Args {
-        return Args()
-    }
 }
 
 /// A request to suspend or unsuspend a coin.
 struct WalletBackendSuspendCoinRequest: WalletBackendFormattedRequest {
+    struct Response: Decodable {}
+    func operation() -> String { "setCoinSuspended" }
+    func args() -> Args { Args(coinPub: coinPub, suspended: suspended) }
+
     var coinPub: String
     var suspended: Bool
     
@@ -359,16 +311,6 @@ struct WalletBackendSuspendCoinRequest: 
WalletBackendFormattedRequest {
         var coinPub: String
         var suspended: Bool
     }
-    
-    struct Response: Decodable {}
-    
-    func operation() -> String {
-        return "setCoinSuspended"
-    }
-    
-    func args() -> Args {
-        return Args(coinPub: coinPub, suspended: suspended)
-    }
 }
 
 
diff --git a/TalerWallet1/Controllers/Controller.swift 
b/TalerWallet1/Controllers/Controller.swift
index f408084..33169ee 100644
--- a/TalerWallet1/Controllers/Controller.swift
+++ b/TalerWallet1/Controllers/Controller.swift
@@ -33,12 +33,14 @@ class Controller: ObservableObject {
     private let symLog = SymLogC()
 
     @Published var backendState: BackendState = .none       // only used for 
launch animation
+    @Published var currencyInfos: [CurrencyInfo]
     @AppStorage("useHaptics") var useHaptics: Bool = false  // extension 
mustn't define this, so it must be here
     @AppStorage("playSounds") var playSounds: Int = 0       // 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
     let hapticCapability = CHHapticEngine.capabilitiesForHardware()
     let logger = Logger (subsystem: "net.taler.gnu", category: "Controller")
     let player = AVQueuePlayer()
+    let semaphore = AsyncSemaphore(value: 1)
 
     var messageForSheet: String? = nil
 
@@ -50,6 +52,34 @@ class Controller: ObservableObject {
 //            }
 //        }
         backendState = .instantiated
+        currencyInfos = []
+    }
+
+    func info(for currency: String) -> CurrencyInfo? {
+        for currencyInfo in currencyInfos {
+            if currencyInfo.scope.currency == currency {
+                return currencyInfo
+            }
+        }
+        return nil
+    }
+
+    @MainActor
+    func setInfo(_ info: 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
+            }
+        }
+        if existing != nil {
+            currencyInfos.removeAll(where: { $0.scope == existing })
+        }
+        currencyInfos.append(info)
     }
 
     @MainActor
diff --git a/TalerWallet1/Controllers/DebugViewC.swift 
b/TalerWallet1/Controllers/DebugViewC.swift
index 78ef45b..990f53a 100644
--- a/TalerWallet1/Controllers/DebugViewC.swift
+++ b/TalerWallet1/Controllers/DebugViewC.swift
@@ -27,9 +27,10 @@ import os.log
 // MARK: - Main View
 public let VIEW_EMPTY = 10                                          // 10 
WalletEmptyView
 public let VIEW_BALANCES = VIEW_EMPTY + 1                           // 11 
BalancesListView
-public let VIEW_SETTINGS = VIEW_BALANCES + 1                        // 12 
SettingsView
-public let VIEW_PENDING = VIEW_SETTINGS + 1                         // 13 
PendingOpsListView
-public let VIEW_EXCHANGES = VIEW_PENDING + 1                        // 14 
ExchangeListView
+public let VIEW_EXCHANGES = VIEW_BALANCES + 1                       // 12 
ExchangeListView
+public let VIEW_SETTINGS = VIEW_EXCHANGES + 1                       // 13 
SettingsView
+public let VIEW_ABOUT = VIEW_SETTINGS + 1                           // 14 
AboutView
+public let VIEW_PENDING = VIEW_ABOUT + 1                            // 15 
PendingOpsListView
 
 // MARK: Transactions
 public let VIEW_TRANSACTIONLIST = VIEW_EMPTY + 10                   // 20 
TransactionsListView
diff --git a/TalerWallet1/Helper/CurrencyFormatter.swift 
b/TalerWallet1/Helper/CurrencyFormatter.swift
deleted file mode 100644
index 2a56f33..0000000
--- a/TalerWallet1/Helper/CurrencyFormatter.swift
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
- * See LICENSE.md
- */
-import Foundation
-import taler_swift
-
-public class CurrencyFormatter: NumberFormatter {
-    public static let shared = CurrencyFormatter()
-
-    public override init() {
-        super.init()
-        self.locale = Locale.current
-        self.numberStyle = .currency            // currencyISOCode, 
currencyPlural, (currencyAccounting)
-        self.numberStyle = .currencyISOCode
-//        self.numberStyle = .spellOut
-
-//        self.currencyCode = code              // EUR, USD, JPY, GBP
-//        self.minimumFractionDigits = fractionDigits
-//        self.maximumFractionDigits = fractionDigits
-//        self.groupingSize = 3                 // thousands
-//        self.groupingSeparator = ","
-//        self.usesGroupingSeparator = true
-//        self.decimalSeparator = "."
-    }
-
-    required init?(coder aDecoder: NSCoder) {
-        fatalError("init(coder:) has not been implemented")
-    }
-}
diff --git a/TalerWallet1/Helper/CurrencySpecification.swift 
b/TalerWallet1/Helper/CurrencySpecification.swift
new file mode 100644
index 0000000..6b8a059
--- /dev/null
+++ b/TalerWallet1/Helper/CurrencySpecification.swift
@@ -0,0 +1,158 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import Foundation
+import taler_swift
+
+public struct CurrencyInfo {
+    let scope: ScopeInfo
+    let specs: CurrencySpecification
+    let formatter: CurrencyFormatter
+
+    func string(for value: Double, useSymbol: Bool = true) -> String {
+        formatter.setUseSymbol(useSymbol)
+        if let valueStr = formatter.string(for: value) {
+            let decimalSeparator = formatter.decimalSeparator ?? 
specs.decimalSeparator
+            if let decimalIndex = valueStr.endIndex(of: decimalSeparator) {
+                var fraction = 1
+                var integerStr = String(valueStr[..<decimalIndex])
+                let fractionStr = valueStr[decimalIndex...]
+                for character in fractionStr {
+                    let charStr = String(character)
+                    if let digit = Int(charStr) {
+                        let digitStr = fraction > specs.fractionalNormalDigits 
?
+                                                    SuperScriptDigits(charStr) 
: charStr
+                        integerStr += digitStr
+                    } else { integerStr += charStr }
+                    fraction += 1
+                }
+                return integerStr
+            }
+            return valueStr
+        } else {        // formatter doesn't work - we need to format ourselves
+            var madeUpStr = ""
+            var currencyName = scope.currency
+            var hasSymbol = false
+            if useSymbol && formatter.hasAltUnitName0 {
+                if let symbol = specs.altUnitNames?[0] {
+                    currencyName = symbol
+                    hasSymbol = true
+                }
+            }
+            if specs.isCurrencyNameLeading {
+                madeUpStr = currencyName
+                if !hasSymbol {
+                    madeUpStr += " "
+                }
+            }
+            let integerPart = Int(value)
+            madeUpStr += String(integerPart)
+            madeUpStr += specs.decimalSeparator
+            if !specs.isCurrencyNameLeading {
+                if !hasSymbol {
+                    madeUpStr += " "
+                }
+                madeUpStr += currencyName
+            }
+            return madeUpStr
+        } // DIY
+    }
+}
+
+public struct CurrencySpecification2: Codable, Sendable {
+    let currencySpecification: CurrencySpecification
+}
+
+public struct CurrencySpecification: Codable, Sendable {
+    enum CodingKeys: String, CodingKey {
+        case name = "name"
+        case decimalSeparator = "decimal_separator"
+        case groupSeparator = "group_separator"
+        case fractionalInputDigits = "num_fractional_input_digits"
+        case fractionalNormalDigits = "num_fractional_normal_digits"
+        case fractionalTrailingZeroDigits = 
"num_fractional_trailing_zero_digits"
+        case isCurrencyNameLeading = "is_currency_name_leading"
+        case altUnitNames = "alt_unit_names"
+    }
+    /// some name for this CurrencySpecification
+    let name: String
+    /// e.g. “.” for $, and “,” for €
+    let decimalSeparator: String
+    /// e.g. “,” for $, and “.” or “ ” for €     (France uses a narrow space 
character)
+    let groupSeparator: String?
+    /// 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".
+    /// This way, we can also communicate the currency symbol to be used.
+    let altUnitNames: [Int : String]?
+}
+
+
+public class CurrencyFormatter: NumberFormatter {
+
+    var hasAltUnitName0: Bool   // specs.altUnitNames[0] should have the 
Symbol ($,€,¥)
+    /// factory
+    static func formatter(scope: ScopeInfo, specs: CurrencySpecification) -> 
CurrencyFormatter {
+        let formatter = CurrencyFormatter()
+        formatter.setCode(to: scope.currency)
+        formatter.setMinimumFractionDigits(specs.fractionalTrailingZeroDigits)
+        if let symbol = specs.altUnitNames?[0] {
+            formatter.setSymbol(to: symbol)
+            formatter.hasAltUnitName0 = true
+        }
+        return formatter
+    }
+
+    public override init() {
+        self.hasAltUnitName0 = false
+        super.init()
+        self.locale = Locale.current
+        self.usesGroupingSeparator = true
+        self.numberStyle = .currencyISOCode         // .currency
+        self.maximumFractionDigits = 8              // ensure that formatter 
will not round
+
+//        self.currencyCode = code              // EUR, USD, JPY, GBP
+//        self.currencySymbol = symbol
+//        self.internationalCurrencySymbol =
+//        self.minimumFractionDigits = fractionDigits
+//        self.maximumFractionDigits = fractionDigits
+//        self.groupingSize = 3                 // thousands
+//        self.groupingSeparator = ","
+//        self.decimalSeparator = "."
+    }
+
+    func setUseSymbol(_ useSymbol: Bool) {
+        numberStyle = useSymbol ? .currency : .currencyISOCode
+    }
+
+    func setCode(to code:String) {
+        currencyCode = code
+    }
+
+    func setSymbol(to symbol:String) {
+        currencySymbol = symbol
+    }
+
+    func setMinimumFractionDigits(_ digits: Int) {
+        minimumFractionDigits = digits
+    }
+
+    func setLocale(to newLocale: String) {
+        locale = Locale(identifier: newLocale)
+        maximumFractionDigits = 8               // ensure that formatter will 
not round
+//        NumberFormatter.RoundingMode
+    }
+
+    required init?(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+}
diff --git a/TalerWallet1/Helper/TalerStrings.swift 
b/TalerWallet1/Helper/TalerStrings.swift
index 6c39f67..70b7330 100644
--- a/TalerWallet1/Helper/TalerStrings.swift
+++ b/TalerWallet1/Helper/TalerStrings.swift
@@ -14,6 +14,28 @@ extension StringProtocol {
         }
         return String(self)
     }
+
+    func index<S: StringProtocol>(of string: S, options: String.CompareOptions 
= []) -> Index? {
+        range(of: string, options: options)?.lowerBound
+    }
+    func endIndex<S: StringProtocol>(of string: S, options: 
String.CompareOptions = []) -> Index? {
+        range(of: string, options: options)?.upperBound
+    }
+    func indices<S: StringProtocol>(of string: S, options: 
String.CompareOptions = []) -> [Index] {
+        ranges(of: string, options: options).map(\.lowerBound)
+    }
+    func ranges<S: StringProtocol>(of string: S, options: 
String.CompareOptions = []) -> [Range<Index>] {
+        var result: [Range<Index>] = []
+        var startIndex = self.startIndex
+        while startIndex < endIndex,
+              let range = self[startIndex...]
+            .range(of: string, options: options) {
+            result.append(range)
+            startIndex = range.lowerBound < range.upperBound ? 
range.upperBound :
+            index(range.lowerBound, offsetBy: 1, limitedBy: endIndex) ?? 
endIndex
+        }
+        return result
+    }
 }
 
 extension String {
diff --git a/TalerWallet1/Model/Model+Balances.swift 
b/TalerWallet1/Model/Model+Balances.swift
index c5fa095..1bab4ca 100644
--- a/TalerWallet1/Model/Model+Balances.swift
+++ b/TalerWallet1/Model/Model+Balances.swift
@@ -31,8 +31,8 @@ struct Balance: Decodable, Hashable, Sendable {
 // MARK: -
 /// A request to get the balances held in the wallet.
 fileprivate struct Balances: WalletBackendFormattedRequest {
-    func operation() -> String { return "getBalances" }
-    func args() -> Args { return Args() }
+    func operation() -> String { "getBalances" }
+    func args() -> Args { Args() }
 
     struct Args: Encodable {}                           // no arguments needed
 
diff --git a/TalerWallet1/Model/Model+Exchange.swift 
b/TalerWallet1/Model/Model+Exchange.swift
index 7ac8d72..524f462 100644
--- a/TalerWallet1/Model/Model+Exchange.swift
+++ b/TalerWallet1/Model/Model+Exchange.swift
@@ -7,6 +7,10 @@ import taler_swift
 import SymLog
 fileprivate let ASYNCDELAY: UInt = 0   //set e.g to 6 or 9 seconds for 
debugging
 
+struct ExchangeError: Codable, Hashable {
+    var error: HTTPError
+}
+
 enum ExchangeEntryStatus: String, Codable {
     case preset
     case ephemeral
@@ -43,9 +47,7 @@ struct Exchange: Codable, Hashable, Identifiable {
     var ageRestrictionOptions: [Int]
     var lastUpdateErrorInfo: ExchangeError?
 
-    var id: String {
-        exchangeBaseUrl
-    }
+    var id: String { exchangeBaseUrl }
     var name: String? {
         if let url = URL(string: exchangeBaseUrl) {
             if let host = url.host {
@@ -56,24 +58,48 @@ struct Exchange: Codable, Hashable, Identifiable {
     }
 }
 
-struct ExchangeError: Codable, Hashable {
-    var error: HTTPError
+struct ExchangeListItem: Codable, Hashable {
+    var exchangeBaseUrl: String
+    var currency: String
+    var paytoUris: [String]
+
+    public static func == (lhs: ExchangeListItem, rhs: ExchangeListItem) -> 
Bool {
+        return lhs.exchangeBaseUrl == rhs.exchangeBaseUrl &&
+        lhs.currency == rhs.currency &&
+        lhs.paytoUris == rhs.paytoUris
+    }
+
+    public func hash(into hasher: inout Hasher) {
+        hasher.combine(exchangeBaseUrl)
+        hasher.combine(currency)
+        hasher.combine(paytoUris)
+    }
 }
 
-struct HTTPError: Codable, Hashable {
-    var code: Int
-    var requestUrl: String?
-    var hint: String
-    var requestMethod: String?
-    var httpStatusCode: Int?
-    var when: Timestamp?
-    var stack: String?
+struct ExchangeUrlItem: Codable, Hashable, Identifiable {
+    var exchangeBaseUrl: String
+    var id: String { exchangeBaseUrl }
 }
 // MARK: -
+/// A request to list exchanges names for a currency
+fileprivate struct ListExchangesForScopedCurrency: 
WalletBackendFormattedRequest {
+    func operation() -> String { "listExchangesForScopedCurrency" }
+    func args() -> Args { Args(scope: scope) }
+
+    var scope: ScopeInfo
+
+    struct Args: Encodable {
+        var scope: ScopeInfo
+    }
+    struct Response: Decodable, Sendable {
+        var exchanges: [ExchangeUrlItem]              // list of Exchange names
+    }
+}
+
 /// A request to list exchanges.
 fileprivate struct ListExchanges: WalletBackendFormattedRequest {
-    func operation() -> String { return "listExchanges" }
-    func args() -> Args { return Args() }
+    func operation() -> String { "listExchanges" }
+    func args() -> Args { Args() }
 
     struct Args: Encodable {}           // no arguments needed
 
@@ -85,8 +111,8 @@ fileprivate struct ListExchanges: 
WalletBackendFormattedRequest {
 /// A request to update a single exchange.
 fileprivate struct UpdateExchange: WalletBackendFormattedRequest {
     struct Response: Decodable {}   // no result - getting no error back means 
success
-    func operation() -> String { return "updateExchangeEntry" }
-    func args() -> Args { return Args(scopeInfo: scopeInfo) }
+    func operation() -> String { "updateExchangeEntry" }
+    func args() -> Args { Args(scopeInfo: scopeInfo) }
 
     var scopeInfo: ScopeInfo
 
@@ -98,8 +124,8 @@ fileprivate struct UpdateExchange: 
WalletBackendFormattedRequest {
 /// A request to add an exchange.
 fileprivate struct AddExchange: WalletBackendFormattedRequest {
     struct Response: Decodable {}   // no result - getting no error back means 
success
-    func operation() -> String { return "addExchange" }     // addExchangeEntry
-    func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl) }
+    func operation() -> String { "addExchange" }     // addExchangeEntry
+    func args() -> Args { Args(exchangeBaseUrl: exchangeBaseUrl) }
 
     var exchangeBaseUrl: String
 
@@ -110,9 +136,9 @@ fileprivate struct AddExchange: 
WalletBackendFormattedRequest {
 
 /// A request to get info about a currency
 fileprivate struct GetCurrencySpecification: WalletBackendFormattedRequest {
-    typealias Response = CurrencySpecification
-    func operation() -> String { return "getCurrencySpecification" }
-    func args() -> Args { return Args(scope: scope) }
+    typealias Response = CurrencySpecification2
+    func operation() -> String { "getCurrencySpecification" }
+    func args() -> Args { Args(scope: scope) }
 
     var scope: ScopeInfo
 
@@ -122,6 +148,18 @@ fileprivate struct GetCurrencySpecification: 
WalletBackendFormattedRequest {
 }
 // MARK: -
 extension WalletModel {
+    /// ask wallet-core for its list of known exchanges
+    @MainActor func listExchangesForScopedCurrencyM(scope: ScopeInfo)
+      async -> [ExchangeUrlItem] {            // M for MainActor
+        do {
+            let request = ListExchangesForScopedCurrency(scope: scope)
+            let response = try await sendRequest(request, ASYNCDELAY)
+            return response.exchanges
+        } catch {
+            return []               // empty, but not nil
+        }
+    }
+
     /// ask wallet-core for its list of known exchanges
     @MainActor func listExchangesM()
       async -> [Exchange] {   // M for MainActor
@@ -150,11 +188,13 @@ extension WalletModel {
         _ = try await sendRequest(request)
     }
 
-    @MainActor func getCurrencySpecificationM(scope: ScopeInfo)
-      async throws -> CurrencySpecification {   // M for MainActor
+    func getCurrencyInfo(scope: ScopeInfo)
+      async throws -> CurrencyInfo {
         let request = GetCurrencySpecification(scope: scope)
         let response = try await sendRequest(request, ASYNCDELAY)
-        return response
+        return CurrencyInfo(scope: scope, specs: 
response.currencySpecification,
+                        formatter: CurrencyFormatter.formatter(scope: scope,
+                                                               specs: 
response.currencySpecification))
     }
 
 }
diff --git a/TalerWallet1/Model/Model+P2P.swift 
b/TalerWallet1/Model/Model+P2P.swift
index 3cafc8a..82e5bc4 100644
--- a/TalerWallet1/Model/Model+P2P.swift
+++ b/TalerWallet1/Model/Model+P2P.swift
@@ -22,8 +22,8 @@ struct AmountResponse: Codable {
 }
 fileprivate struct GetMaxPeerPushAmount: WalletBackendFormattedRequest {
     typealias Response = AmountResponse
-    func operation() -> String { return "GetMaxPeerPushAmount" }
-    func args() -> Args { return Args(currency: currency) }
+    func operation() -> String { "GetMaxPeerPushAmount" }
+    func args() -> Args { Args(currency: currency) }
 
     var currency: String
     struct Args: Encodable {
@@ -48,8 +48,8 @@ struct CheckPeerPushDebitResponse: Codable {
 }
 fileprivate struct CheckPeerPushDebit: WalletBackendFormattedRequest {
     typealias Response = CheckPeerPushDebitResponse
-    func operation() -> String { return "checkPeerPushDebit" }
-    func args() -> Args { return Args(amount: amount) }
+    func operation() -> String { "checkPeerPushDebit" }
+    func args() -> Args { Args(amount: amount) }
 
     var amount: Amount
     struct Args: Encodable {
@@ -77,9 +77,9 @@ struct InitiatePeerPushDebitResponse: Codable {
 }
 fileprivate struct InitiatePeerPushDebit: WalletBackendFormattedRequest {
     typealias Response = InitiatePeerPushDebitResponse
-    func operation() -> String { return "initiatePeerPushDebit" }
-    func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl,
-                                      partialContractTerms: 
partialContractTerms) }
+    func operation() -> String { "initiatePeerPushDebit" }
+    func args() -> Args { Args(exchangeBaseUrl: exchangeBaseUrl,
+                          partialContractTerms: partialContractTerms) }
 
     var exchangeBaseUrl: String?
     var partialContractTerms: PeerContractTerms
@@ -109,8 +109,8 @@ struct CheckPeerPullCreditResponse: Codable {
 }
 fileprivate struct CheckPeerPullCredit: WalletBackendFormattedRequest {
     typealias Response = CheckPeerPullCreditResponse
-    func operation() -> String { return "checkPeerPullCredit" }
-    func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl, 
scopeInfo: scopeInfo, amount: amount) }
+    func operation() -> String { "checkPeerPullCredit" }
+    func args() -> Args { Args(exchangeBaseUrl: exchangeBaseUrl, scopeInfo: 
scopeInfo, amount: amount) }
 
     var exchangeBaseUrl: String?
     var scopeInfo: ScopeInfo?
@@ -138,8 +138,8 @@ struct InitiatePeerPullCreditResponse: Codable {
 }
 fileprivate struct InitiatePeerPullCredit: WalletBackendFormattedRequest {
     typealias Response = InitiatePeerPullCreditResponse
-    func operation() -> String { return "initiatePeerPullCredit" }
-    func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl,
+    func operation() -> String { "initiatePeerPullCredit" }
+    func args() -> Args { Args(exchangeBaseUrl: exchangeBaseUrl,
                                  partialContractTerms: partialContractTerms) }
 
     var exchangeBaseUrl: String?
@@ -170,8 +170,8 @@ struct PreparePeerPushCreditResponse: Codable {
 }
 fileprivate struct PreparePeerPushCredit: WalletBackendFormattedRequest {
     typealias Response = PreparePeerPushCreditResponse
-    func operation() -> String { return "preparePeerPushCredit" }
-    func args() -> Args { return Args(talerUri: talerUri) }
+    func operation() -> String { "preparePeerPushCredit" }
+    func args() -> Args { Args(talerUri: talerUri) }
 
     var talerUri: String
     struct Args: Encodable {
@@ -191,8 +191,8 @@ extension WalletModel {
 /// Accept an incoming peer push payment
 fileprivate struct AcceptPeerPushCredit: WalletBackendFormattedRequest {
     struct Response: Decodable {}   // no result - getting no error back means 
success
-    func operation() -> String { return "confirmPeerPushCredit" } // should be 
"acceptPeerPushCredit"
-    func args() -> Args { return Args(transactionId: transactionId) }
+    func operation() -> String { "confirmPeerPushCredit" } // should be 
"acceptPeerPushCredit"
+    func args() -> Args { Args(transactionId: transactionId) }
 
     var transactionId: String
     struct Args: Encodable {
@@ -219,8 +219,8 @@ struct PreparePeerPullDebitResponse: Codable {
 }
 fileprivate struct PreparePeerPullDebit: WalletBackendFormattedRequest {
     typealias Response = PreparePeerPullDebitResponse
-    func operation() -> String { return "preparePeerPullDebit" }
-    func args() -> Args { return Args(talerUri: talerUri) }
+    func operation() -> String { "preparePeerPullDebit" }
+    func args() -> Args { Args(talerUri: talerUri) }
 
     var talerUri: String
     struct Args: Encodable {
@@ -240,8 +240,8 @@ extension WalletModel {
 /// Confirm incoming peer push request(invoice) and pay
 fileprivate struct ConfirmPeerPullDebit: WalletBackendFormattedRequest {
     struct Response: Decodable {}   // no result - getting no error back means 
success
-    func operation() -> String { return "confirmPeerPullDebit" }
-    func args() -> Args { return Args(transactionId: transactionId) }
+    func operation() -> String { "confirmPeerPullDebit" }
+    func args() -> Args { Args(transactionId: transactionId) }
 
     var transactionId: String
     struct Args: Encodable {
diff --git a/TalerWallet1/Model/Model+Payment.swift 
b/TalerWallet1/Model/Model+Payment.swift
index b183d36..b211c51 100644
--- a/TalerWallet1/Model/Model+Payment.swift
+++ b/TalerWallet1/Model/Model+Payment.swift
@@ -139,8 +139,8 @@ struct PreparePayResult: Codable {
 /// A request to get an exchange's payment contract terms.
 fileprivate struct PreparePayForUri: WalletBackendFormattedRequest {
     typealias Response = PreparePayResult
-    func operation() -> String { return "preparePayForUri" }
-    func args() -> Args { return Args(talerPayUri: talerPayUri) }
+    func operation() -> String { "preparePayForUri" }
+    func args() -> Args { Args(talerPayUri: talerPayUri) }
 
     var talerPayUri: String
     struct Args: Encodable {
@@ -150,8 +150,8 @@ fileprivate struct PreparePayForUri: 
WalletBackendFormattedRequest {
 /// A request to get an exchange's payment contract terms.
 fileprivate struct PreparePayForTemplate: WalletBackendFormattedRequest {
     typealias Response = PreparePayResult
-    func operation() -> String { return "preparePayForTemplate" }
-    func args() -> Args { return Args(talerPayTemplateUri: 
talerPayTemplateUri) }
+    func operation() -> String { "preparePayForTemplate" }
+    func args() -> Args { Args(talerPayTemplateUri: talerPayTemplateUri) }
 
     var talerPayTemplateUri: String
     struct Args: Encodable {
@@ -168,8 +168,8 @@ struct ConfirmPayResult: Decodable {
 /// A request to get an exchange's payment details.
 fileprivate struct confirmPayForUri: WalletBackendFormattedRequest {
     typealias Response = ConfirmPayResult
-    func operation() -> String { return "confirmPay" }
-    func args() -> Args { return Args(transactionId: transactionId) }
+    func operation() -> String { "confirmPay" }
+    func args() -> Args { Args(transactionId: transactionId) }
 
     var transactionId: String
     struct Args: Encodable {
diff --git a/TalerWallet1/Model/Model+Pending.swift 
b/TalerWallet1/Model/Model+Pending.swift
index eb9f447..e2dec9f 100644
--- a/TalerWallet1/Model/Model+Pending.swift
+++ b/TalerWallet1/Model/Model+Pending.swift
@@ -11,7 +11,7 @@ fileprivate let ASYNCDELAY: UInt = 0   //set e.g to 6 or 9 
seconds for debugging
 // MARK: -
 /// A request to list the backend's currently pending operations.
 fileprivate struct GetPendingOperations: WalletBackendFormattedRequest {
-    func operation() -> String { return "getPendingOperations" }
+    func operation() -> String { "getPendingOperations" }
     func args() -> Args { Args() }
 
     struct Args: Encodable {}
diff --git a/TalerWallet1/Model/Model+Settings.swift 
b/TalerWallet1/Model/Model+Settings.swift
index 5dff9f8..288e22b 100644
--- a/TalerWallet1/Model/Model+Settings.swift
+++ b/TalerWallet1/Model/Model+Settings.swift
@@ -13,13 +13,12 @@ fileprivate let MERCHANTAUTHTOKEN = "secret-token:sandbox"
 /// A request to add a test balance to the wallet.
 fileprivate struct WithdrawTestBalanceRequest: WalletBackendFormattedRequest {
     struct Response: Decodable {}   // no result - getting no error back means 
success
-    func operation() -> String { return "withdrawTestBalance" }
-    func args() -> Args {
-        return Args(amount: amount,
-//                    bankBaseUrl: bankBaseUrl,
-                    corebankApiBaseUrl: corebankApiBaseUrl,
-                    exchangeBaseUrl: exchangeBaseUrl)
-    }
+    func operation() -> String { "withdrawTestBalance" }
+    func args() -> Args { Args(amount: amount,
+//                        bankBaseUrl: bankBaseUrl,
+                   corebankApiBaseUrl: corebankApiBaseUrl,
+                      exchangeBaseUrl: exchangeBaseUrl)
+                        }
 
     var amount: Amount
 //    var bankBaseUrl: String
@@ -48,17 +47,15 @@ extension WalletModel {
 /// A request to add a test balance to the wallet.
 fileprivate struct RunIntegrationTest: WalletBackendFormattedRequest {
     struct Response: Decodable {}   // no result - getting no error back means 
success
-    func operation() -> String { return newVersion ? "runIntegrationTestV2" : 
"runIntegrationTest" }
-    func args() -> Args {
-        return Args(exchangeBaseUrl: exchangeBaseUrl,
-//                    bankBaseUrl: bankBaseUrl,
-                    corebankApiBaseUrl: corebankApiBaseUrl,
-                    merchantBaseUrl: merchantBaseUrl,
-                    merchantAuthToken: merchantAuthToken,
-                    amountToWithdraw: amountToWithdraw,
-                    amountToSpend: amountToSpend
-        )
-    }
+    func operation() -> String { newVersion ? "runIntegrationTestV2" : 
"runIntegrationTest" }
+    func args() -> Args { Args(exchangeBaseUrl: exchangeBaseUrl,
+//                                 bankBaseUrl: bankBaseUrl,
+                            corebankApiBaseUrl: corebankApiBaseUrl,
+                               merchantBaseUrl: merchantBaseUrl,
+                             merchantAuthToken: merchantAuthToken,
+                              amountToWithdraw: amountToWithdraw,
+                                 amountToSpend: amountToSpend)
+                        }
 
     let newVersion: Bool
 
diff --git a/TalerWallet1/Model/Model+Transactions.swift 
b/TalerWallet1/Model/Model+Transactions.swift
index 5865ea3..2e59bfb 100644
--- a/TalerWallet1/Model/Model+Transactions.swift
+++ b/TalerWallet1/Model/Model+Transactions.swift
@@ -35,9 +35,9 @@ extension WalletModel {
 // MARK: -
 /// A request to get the transactions in the wallet's history.
 fileprivate struct GetTransactions: WalletBackendFormattedRequest {
-    func operation() -> String { return "getTransactions" }
-//    func operation() -> String { return "testingGetSampleTransactions" }
-    func args() -> Args { return Args(currency: currency, search: search, 
sort: sort) }
+    func operation() -> String { "getTransactions" }
+//    func operation() -> String { "testingGetSampleTransactions" }
+    func args() -> Args { Args(currency: currency, search: search, sort: sort) 
}
 
     var currency: String?
     var search: String?
@@ -55,8 +55,8 @@ fileprivate struct GetTransactions: 
WalletBackendFormattedRequest {
 /// A request to abort a wallet transaction by ID.
 struct AbortTransaction: WalletBackendFormattedRequest {
     struct Response: Decodable {}   // no result - getting no error back means 
success
-    func operation() -> String { return "abortTransaction" }
-    func args() -> Args { return Args(transactionId: transactionId) }
+    func operation() -> String { "abortTransaction" }
+    func args() -> Args { Args(transactionId: transactionId) }
 
     var transactionId: String
     struct Args: Encodable {
@@ -66,8 +66,8 @@ struct AbortTransaction: WalletBackendFormattedRequest {
 /// A request to delete a wallet transaction by ID.
 struct DeleteTransaction: WalletBackendFormattedRequest {
     struct Response: Decodable {}   // no result - getting no error back means 
success
-    func operation() -> String { return "deleteTransaction" }
-    func args() -> Args { return Args(transactionId: transactionId) }
+    func operation() -> String { "deleteTransaction" }
+    func args() -> Args { Args(transactionId: transactionId) }
 
     var transactionId: String
     struct Args: Encodable {
@@ -77,8 +77,8 @@ struct DeleteTransaction: WalletBackendFormattedRequest {
 /// A request to delete a wallet transaction by ID.
 struct FailTransaction: WalletBackendFormattedRequest {
     struct Response: Decodable {}   // no result - getting no error back means 
success
-    func operation() -> String { return "failTransaction" }
-    func args() -> Args { return Args(transactionId: transactionId) }
+    func operation() -> String { "failTransaction" }
+    func args() -> Args { Args(transactionId: transactionId) }
 
     var transactionId: String
     struct Args: Encodable {
@@ -88,8 +88,8 @@ struct FailTransaction: WalletBackendFormattedRequest {
 /// A request to suspend a wallet transaction by ID.
 struct SuspendTransaction: WalletBackendFormattedRequest {
     struct Response: Decodable {}   // no result - getting no error back means 
success
-    func operation() -> String { return "suspendTransaction" }
-    func args() -> Args { return Args(transactionId: transactionId) }
+    func operation() -> String { "suspendTransaction" }
+    func args() -> Args { Args(transactionId: transactionId) }
 
     var transactionId: String
     struct Args: Encodable {
@@ -99,8 +99,8 @@ struct SuspendTransaction: WalletBackendFormattedRequest {
 /// A request to suspend a wallet transaction by ID.
 struct ResumeTransaction: WalletBackendFormattedRequest {
     struct Response: Decodable {}   // no result - getting no error back means 
success
-    func operation() -> String { return "resumeTransaction" }
-    func args() -> Args { return Args(transactionId: transactionId) }
+    func operation() -> String { "resumeTransaction" }
+    func args() -> Args { Args(transactionId: transactionId) }
 
     var transactionId: String
     struct Args: Encodable {
diff --git a/TalerWallet1/Model/Model+Withdraw.swift 
b/TalerWallet1/Model/Model+Withdraw.swift
index f0084eb..5b5fe8f 100644
--- a/TalerWallet1/Model/Model+Withdraw.swift
+++ b/TalerWallet1/Model/Model+Withdraw.swift
@@ -14,28 +14,11 @@ struct WithdrawUriInfoResponse: Decodable {
     var defaultExchangeBaseUrl: String?             // TODO: might be nil 
❗️Yikes
     var possibleExchanges: [ExchangeListItem]       // TODO: query these for 
fees?
 }
-struct ExchangeListItem: Codable, Hashable {
-    var exchangeBaseUrl: String
-    var currency: String
-    var paytoUris: [String]
-
-    public static func == (lhs: ExchangeListItem, rhs: ExchangeListItem) -> 
Bool {
-        return lhs.exchangeBaseUrl == rhs.exchangeBaseUrl &&
-        lhs.currency == rhs.currency &&
-        lhs.paytoUris == rhs.paytoUris
-    }
-
-    public func hash(into hasher: inout Hasher) {
-        hasher.combine(exchangeBaseUrl)
-        hasher.combine(currency)
-        hasher.combine(paytoUris)
-    }
-}
 /// A request to get an exchange's withdrawal details.
 fileprivate struct GetWithdrawalDetailsForURI: WalletBackendFormattedRequest {
     typealias Response = WithdrawUriInfoResponse
-    func operation() -> String { return "getWithdrawalDetailsForUri" }
-    func args() -> Args { return Args(talerWithdrawUri: talerWithdrawUri) }
+    func operation() -> String { "getWithdrawalDetailsForUri" }
+    func args() -> Args { Args(talerWithdrawUri: talerWithdrawUri) }
 
     var talerWithdrawUri: String
     struct Args: Encodable {
@@ -55,8 +38,8 @@ struct WithdrawalAmountDetails: Decodable {
 /// A request to get an exchange's withdrawal details.
 fileprivate struct GetWithdrawalDetailsForAmount: 
WalletBackendFormattedRequest {
     typealias Response = WithdrawalAmountDetails
-    func operation() -> String { return "getWithdrawalDetailsForAmount" }
-    func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl, 
amount: amount) }
+    func operation() -> String { "getWithdrawalDetailsForAmount" }
+    func args() -> Args { Args(exchangeBaseUrl: exchangeBaseUrl, amount: 
amount) }
 
     var exchangeBaseUrl: String
     var amount: Amount
@@ -86,8 +69,8 @@ struct ExchangeTermsOfService: Decodable {
 /// A request to query an exchange's terms of service.
 fileprivate struct GetExchangeTermsOfService: WalletBackendFormattedRequest {
     typealias Response = ExchangeTermsOfService
-    func operation() -> String { return "getExchangeTos" }
-    func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl, 
acceptedFormat: acceptedFormat) }
+    func operation() -> String { "getExchangeTos" }
+    func args() -> Args { Args(exchangeBaseUrl: exchangeBaseUrl, 
acceptedFormat: acceptedFormat) }
 
     var exchangeBaseUrl: String
     var acceptedFormat: [String]?
@@ -99,8 +82,8 @@ fileprivate struct GetExchangeTermsOfService: 
WalletBackendFormattedRequest {
 /// A request to mark an exchange's terms of service as accepted.
 fileprivate struct SetExchangeTOSAccepted: WalletBackendFormattedRequest {
     struct Response: Decodable {}   // no result - getting no error back means 
success
-    func operation() -> String { return "setExchangeTosAccepted" }
-    func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl, etag: 
etag) }
+    func operation() -> String { "setExchangeTosAccepted" }
+    func args() -> Args { Args(exchangeBaseUrl: exchangeBaseUrl, etag: etag) }
 
     var exchangeBaseUrl: String
     var etag: String
@@ -119,8 +102,8 @@ struct AcceptWithdrawalResponse: Decodable {
 /// A request to accept a bank-integrated withdrawl.
 fileprivate struct AcceptBankIntegratedWithdrawal: 
WalletBackendFormattedRequest {
     typealias Response = AcceptWithdrawalResponse
-    func operation() -> String { return "acceptBankIntegratedWithdrawal" }
-    func args() -> Args { return Args(talerWithdrawUri: talerWithdrawUri, 
exchangeBaseUrl: exchangeBaseUrl) }
+    func operation() -> String { "acceptBankIntegratedWithdrawal" }
+    func args() -> Args { Args(talerWithdrawUri: talerWithdrawUri, 
exchangeBaseUrl: exchangeBaseUrl) }
 
     var talerWithdrawUri: String
     var exchangeBaseUrl: String
@@ -139,8 +122,8 @@ struct AcceptManualWithdrawalResult: Decodable {
 /// A request to accept a manual withdrawl.
 fileprivate struct AcceptManualWithdrawal: WalletBackendFormattedRequest {
     typealias Response = AcceptManualWithdrawalResult
-    func operation() -> String { return "acceptManualWithdrawal" }
-    func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl, 
amount: amount, restrictAge: restrictAge) }
+    func operation() -> String { "acceptManualWithdrawal" }
+    func args() -> Args { Args(exchangeBaseUrl: exchangeBaseUrl, amount: 
amount, restrictAge: restrictAge) }
 
     var exchangeBaseUrl: String
     var amount: Amount
diff --git a/TalerWallet1/Model/WalletModel.swift 
b/TalerWallet1/Model/WalletModel.swift
index 6a24d8e..1bc239f 100644
--- a/TalerWallet1/Model/WalletModel.swift
+++ b/TalerWallet1/Model/WalletModel.swift
@@ -3,12 +3,22 @@
  * See LICENSE.md
  */
 import Foundation
+import taler_swift
 import SymLog
 import os.log
 
 fileprivate let DATABASE = "talerwalletdb-v30"
 fileprivate let ASYNCDELAY: UInt = 0   //set e.g to 6 or 9 seconds for 
debugging
 
+struct HTTPError: Codable, Hashable {
+    var code: Int
+    var requestUrl: String?
+    var hint: String
+    var requestMethod: String?
+    var httpStatusCode: Int?
+    var when: Timestamp?
+    var stack: String?
+}
 // MARK: -
 /// The "virtual" base class for all models
 class WalletModel: ObservableObject {
@@ -58,8 +68,8 @@ class WalletModel: ObservableObject {
 /// A request to get a wallet transaction by ID.
 fileprivate struct GetTransactionById: WalletBackendFormattedRequest {
     typealias Response = Transaction
-    func operation() -> String { return "getTransactionById" }
-    func args() -> Args { return Args(transactionId: transactionId) }
+    func operation() -> String { "getTransactionById" }
+    func args() -> Args { Args(transactionId: transactionId) }
 
     var transactionId: String
 
@@ -80,7 +90,7 @@ struct VersionInfo: Decodable {
 // MARK: -
 ///  A request to initialize Wallet-core
 fileprivate struct InitRequest: WalletBackendFormattedRequest {
-    func operation() -> String { return "init" }
+    func operation() -> String { "init" }
     func args() -> Args {
         return Args(persistentStoragePath: persistentStoragePath,
 //                       cryptoWorkerType: "sync",
@@ -148,8 +158,8 @@ extension WalletModel {
 // MARK: -
 ///  A request to initialize Wallet-core
 fileprivate struct ResetRequest: WalletBackendFormattedRequest {
-    func operation() -> String { return "reset" }
-    func args() -> Args { return Args() }
+    func operation() -> String { "reset" }
+    func args() -> Args { Args() }
 
     struct Args: Encodable {}                           // no arguments needed
     struct Response: Decodable {}
diff --git a/TalerWallet1/Views/Balances/BalanceRowView.swift 
b/TalerWallet1/Views/Balances/BalanceRowView.swift
index 6a35992..fcff95e 100644
--- a/TalerWallet1/Views/Balances/BalanceRowView.swift
+++ b/TalerWallet1/Views/Balances/BalanceRowView.swift
@@ -74,6 +74,7 @@ struct BalanceButton: View {
 /// [Send Coins]  [Receive Coins]     Balance
 struct BalanceRowView: View {
     let amount: Amount
+    let currencyInfo: CurrencyInfo?
     let sendAction: () -> Void
     let recvAction: () -> Void
     let rowAction: () -> Void
@@ -86,9 +87,11 @@ struct BalanceRowView: View {
     let requestTitle2 = String(localized: "Payment", comment: "Bottom of 
button <Request Payment>")
 
     var body: some View {
+
         SingleAxisGeometryReader { width in
             VStack (alignment: .trailing) {
-                BalanceButton(amountStr: amount.valueStr,        // TODO: 
CurrencyFormatter?
+                let amountStr = currencyInfo?.string(for: amount.value)
+                BalanceButton(amountStr: amountStr ?? amount.valueStr,
                               rowAction: rowAction)
                 let uiFont = TalerFont.uiFont(.title3)
                 let titles = [(requestTitle1, uiFont), (requestTitle2, 
uiFont), (sendTitle1, uiFont), (sendTitle2, uiFont)]
@@ -109,7 +112,7 @@ struct BalanceRowView: View {
     }
 }
 // MARK: -
-#if DEBUG
+#if false // DEBUG
 struct BalanceRowView_Previews: PreviewProvider {
     static var previews: some View {
         let test = try! Amount(fromString: TESTCURRENCY + ":1.23")
diff --git a/TalerWallet1/Views/Balances/BalancesListView.swift 
b/TalerWallet1/Views/Balances/BalancesListView.swift
index 2ab8f93..d8401e2 100644
--- a/TalerWallet1/Views/Balances/BalancesListView.swift
+++ b/TalerWallet1/Views/Balances/BalancesListView.swift
@@ -146,7 +146,7 @@ extension BalancesListView {
                     WalletEmptyView(stack: stack.push("isEmpty"))
                 } else {
                     List(balances, id: \.self) { balance in
-                        BalancesSectionView(stack: 
stack.push("\(balance.available.currencyStr)"),
+                        BalancesSectionView(stack: 
stack.push("\(balance.scopeInfo.currency)"),
                                           balance: balance,
                                      sectionCount: count,
                                   centsToTransfer: $centsToTransfer,
diff --git a/TalerWallet1/Views/Balances/BalancesSectionView.swift 
b/TalerWallet1/Views/Balances/BalancesSectionView.swift
index dfc5996..80e446d 100644
--- a/TalerWallet1/Views/Balances/BalancesSectionView.swift
+++ b/TalerWallet1/Views/Balances/BalancesSectionView.swift
@@ -23,6 +23,7 @@ struct BalancesSectionView {
     @Binding var summary: String
 
     @EnvironmentObject private var model: WalletModel
+    @EnvironmentObject private var controller: Controller
 
     @State private var isShowingDetailView = false
 
@@ -39,21 +40,21 @@ struct BalancesSectionView {
     @State private var shownSectionID = UUID()  // guaranteed to be different 
the first time
 
     func reloadCompleted(_ stack: CallStack) async -> () {
-        let currency = balance.available.currencyStr
+        let currency = balance.scopeInfo.currency
         transactions = await model.transactionsT(stack.push(), currency: 
currency)
         completedTransactions = WalletModel.completedTransactions(transactions)
 //        sectionID = UUID()
     }
 
     func reloadPending(_ stack: CallStack) async -> () {
-        let currency = balance.available.currencyStr
+        let currency = balance.scopeInfo.currency
         transactions = await model.transactionsT(stack.push(), currency: 
currency)
         pendingTransactions = WalletModel.pendingTransactions(transactions)
 //        sectionID = UUID()
     }
 
     func reloadUncompleted(_ stack: CallStack) async -> () {
-        let currency = balance.available.currencyStr
+        let currency = balance.scopeInfo.currency
         transactions = await model.transactionsT(stack.push(), currency: 
currency)
         uncompletedTransactions = 
WalletModel.uncompletedTransactions(transactions)
 //        sectionID = UUID()
@@ -66,7 +67,8 @@ extension BalancesSectionView: View {
         let _ = Self._printChanges()
         let _ = symLog.vlog()       // just to get the # to compare it with 
.onAppear & onDisappear
 #endif
-        let currency = balance.available.currencyStr
+        let currency = balance.scopeInfo.currency
+        let currencyInfo = controller.info(for: currency)
 
         Section {
             if "KUDOS" == currency && !balance.available.isZero {
@@ -76,6 +78,7 @@ extension BalancesSectionView: View {
             }
             BalancesNavigationLinksView(stack: stack.push(),
                                       balance: balance,
+                                 currencyInfo: currencyInfo,
                               centsToTransfer: $centsToTransfer,
                                       summary: $summary,
 //                             buttonSelected: $buttonSelected,
@@ -221,6 +224,7 @@ 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 summary: String
@@ -244,7 +248,7 @@ fileprivate struct BalancesNavigationLinksView: View {
     }
 
     var body: some View {
-        let currency = balance.available.currencyStr
+        let currency = balance.scopeInfo.currency
         HStack(spacing: 0) {
             NavigationLink(destination: LazyView {
                 SendAmount(stack: stack.push(),
@@ -273,13 +277,14 @@ fileprivate struct BalancesNavigationLinksView: View {
             }, tag: 3, selection: $buttonSelected
             ) { EmptyView() }.frame(width: 0).opacity(0).hidden()           // 
TransactionsListView
 
-            BalanceRowView(amount: balance.available, 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, currencyInfo: 
currencyInfo,
+                sendAction: {
+                    selectAndUpdate(1)      // will trigger SendAmount 
NavigationLink
+                }, recvAction: {
+                    selectAndUpdate(2)      // will trigger RequestPayment 
NavigationLink
+                }, rowAction: {
+                    buttonSelected = 3      // will trigger TransactionList 
NavigationLink
+                })
         }
     }
 }
diff --git a/TalerWallet1/Views/Main/MainView.swift 
b/TalerWallet1/Views/Main/MainView.swift
index 7d70698..a0abc93 100644
--- a/TalerWallet1/Views/Main/MainView.swift
+++ b/TalerWallet1/Views/Main/MainView.swift
@@ -79,6 +79,8 @@ extension MainView {
         @State private var balances: [Balance] = []
         @Binding var talerFont: Int
         @AppStorage("iconOnly") var iconOnly: Bool = false
+        @EnvironmentObject private var controller: Controller
+        @EnvironmentObject private var model: WalletModel
         let balancesTitle = String(localized: "Balances")
         let exchangesTitle = String(localized: "Exchanges")
         let settingsTitle = String(localized: "Settings")
@@ -213,6 +215,23 @@ extension MainView {
                 symLog?.log(".onNotification(.BalanceChange) ==> reload")
                 shouldReloadBalances += 1
             }
+            .onChange(of: balances) { newArray in
+                for balance in newArray {
+                    let scope = balance.scopeInfo
+                    if controller.info(for: scope.currency) == nil {
+                        Task { // runs on MainActor
+                            symLog?.log("get info for: \(scope.currency)")
+                            do {
+                                let info = try await 
model.getCurrencyInfo(scope: scope)
+                                symLog?.log("added: \(scope.currency)")
+                                await controller.setInfo(info)
+                            } catch {    // TODO: error handling - couldn't 
get CurrencyInfo
+                                symLog?.log("error: \(error)")
+                            }
+                        }
+                    }
+                }
+            }
         } // body
     } // Content
 }
diff --git a/TalerWallet1/Views/Peer2peer/PaymentPurpose.swift 
b/TalerWallet1/Views/Peer2peer/PaymentPurpose.swift
index 339a8f6..a203c4f 100644
--- a/TalerWallet1/Views/Peer2peer/PaymentPurpose.swift
+++ b/TalerWallet1/Views/Peer2peer/PaymentPurpose.swift
@@ -19,12 +19,14 @@ struct PaymentPurpose: View {
 
     @FocusState private var isFocused: Bool
 
-    let formatter = CurrencyFormatter.shared    // TODO: based on currency
+//    let formatter = CurrencyFormatter.shared    // TODO: based on currency
 //    let buttonFont: Font = .talerTitle2
 
     private var label: String {
-        let mag = pow(10, formatter.maximumFractionDigits)
-        return formatter.string(for: Decimal(centsToTransfer) / mag) ?? ""
+//        let mag = pow(10, formatter.maximumFractionDigits)
+//        return formatter.string(for: Decimal(centsToTransfer) / mag) ?? ""
+
+        return String(centsToTransfer / 100)
     }
 
     var body: some View {
diff --git a/TalerWallet1/Views/Peer2peer/SendPurpose.swift 
b/TalerWallet1/Views/Peer2peer/SendPurpose.swift
index b0c4673..3bbb947 100644
--- a/TalerWallet1/Views/Peer2peer/SendPurpose.swift
+++ b/TalerWallet1/Views/Peer2peer/SendPurpose.swift
@@ -18,11 +18,13 @@ struct SendPurpose: View {
     @Binding var expireDays: UInt
 //    var deactivateAction: () -> Void
 
-    let formatter = CurrencyFormatter.shared    // TODO: based on currency
+//    let formatter = CurrencyFormatter.shared    // TODO: based on currency
 
     private var value: String {
-        let mag = pow(10, formatter.maximumFractionDigits)
-        return formatter.string(for: Decimal(centsToTransfer) / mag) ?? ""
+//        let mag = pow(10, formatter.maximumFractionDigits)
+//        return formatter.string(for: Decimal(centsToTransfer) / mag) ?? ""
+
+        return String(centsToTransfer / 100)
     }
 
     var body: some View {
diff --git a/TalerWallet1/Views/Settings/AboutView.swift 
b/TalerWallet1/Views/Settings/AboutView.swift
new file mode 100644
index 0000000..e6de724
--- /dev/null
+++ b/TalerWallet1/Views/Settings/AboutView.swift
@@ -0,0 +1,111 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+import SymLog
+
+struct AboutView: View {
+    private let symLog = SymLogV(0)
+    let stack: CallStack
+    let navTitle: String
+
+    @EnvironmentObject private var controller: Controller
+#if DEBUG
+    @AppStorage("developerMode") var developerMode: Bool = true
+#else
+    @AppStorage("developerMode") var developerMode: Bool = false
+#endif
+    @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
+    @AppStorage("iconOnly") var iconOnly: Bool = false
+
+#if TABBAR  // Taler Wallet
+#else       // GNU Taler
+    var hamburgerAction: () -> Void
+#endif
+
+    @EnvironmentObject private var model: WalletModel
+
+    @State private var rotationEnabled = false
+
+    @State private var listID = UUID()
+
+    var body: some View {
+#if DEBUG
+        let _ = Self._printChanges()
+        let _ = symLog.vlog()       // just to get the # to compare it with 
.onAppear & onDisappear
+#endif
+        let walletCore = WalletCore.shared
+        Group {
+            List {
+//                VStack {
+                    HStack {
+                        Spacer()
+                        RotatingTaler(size: 100, rotationEnabled: 
$rotationEnabled)
+                            .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)")
+                    }
+                    SettingsItem(name: "Wallet Core DevMode", id1: "devMode") {
+                        Text(verbatim: "\(walletCore.versionInfo!.devMode ? 
"YES" : "NO")")
+                    }
+                    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
+            }
+            .id(listID)
+            .listStyle(myListStyle.style).anyView
+        }
+        .navigationTitle(navTitle)
+        .onAppear() {
+            DebugViewC.shared.setViewID(VIEW_ABOUT, stack: stack.push())
+        }
+        .onDisappear() {
+        }
+        .task {
+            try? await Task.sleep(nanoseconds: 1_000_000_000 * UInt64(5))
+            rotationEnabled.toggle()
+        }
+    }
+}
+extension Bundle {
+    var releaseVersionNumber: String? {
+        return infoDictionary?["CFBundleShortVersionString"] as? String
+    }
+    var buildVersionNumber: String? {
+        return infoDictionary?["CFBundleVersion"] as? String
+    }
+    var releaseVersionNumberPretty: String {
+        return "v\(releaseVersionNumber ?? "1.0.0")"
+    }
+}
+// MARK: -
+#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/SettingsItem.swift 
b/TalerWallet1/Views/Settings/SettingsItem.swift
index ae61cf2..76220e0 100644
--- a/TalerWallet1/Views/Settings/SettingsItem.swift
+++ b/TalerWallet1/Views/Settings/SettingsItem.swift
@@ -6,11 +6,13 @@ import SwiftUI
 
 struct SettingsItem<Content: View>: View {
     var name: String
+    var id1: String? = nil
     var description: String?
     var content: () -> Content
     
-    init(name: String, description: String? = nil, @ViewBuilder content: 
@escaping () -> Content) {
+    init(name: String, id1: String, description: String? = nil, @ViewBuilder 
content: @escaping () -> Content) {
         self.name = name
+        self.id1 = id1
         self.description = description
         self.content = content
     }
@@ -19,30 +21,35 @@ struct SettingsItem<Content: View>: View {
         HStack {
             VStack {
                 Text(name)
+                    .id(id1)
                     .frame(maxWidth: .infinity, alignment: .leading)
                     .accessibilityFont(.title2)
                     .padding([.bottom], 0.01)
                 if let desc = description {
                     Text(desc)
+                        .id(id1 == nil ? nil : id1! + "_T")
                         .frame(maxWidth: .infinity, alignment: .leading)
                         .accessibilityFont(.caption)
                 }
-            }
+            }.id(id1 == nil ? nil : id1! + "_V")
             content()
                 .accessibilityFont(.body)
-        }.padding([.bottom], 4)
+        }.id(id1 == nil ? nil : id1! + "_H")
+        .padding([.bottom], 4)
     }
 }
 // MARK: -
 struct SettingsToggle: View {
     var name: String
     @Binding var value: Bool
+    var id1: String? = nil
     var description: String?
     var action: () -> Void = {}
 
     var body: some View {
         VStack {
             Toggle(name, isOn: $value.animation())
+                .id(id1)
                 .accessibilityFont(.title2)
                 .onChange(of: value) { value in
                     action()
@@ -50,10 +57,13 @@ struct SettingsToggle: View {
 
             if let desc = description {
                 Text(desc)
+                    .id(id1 == nil ? nil : id1! + "_T")
                     .frame(maxWidth: .infinity, alignment: .leading)
                     .accessibilityFont(.caption)
             }
-        }.padding([.bottom], 4)
+        }
+        .padding([.bottom], 4)
+        .id(id1 == nil ? nil : id1! + "_V")
     }
 }
 // MARK: -
@@ -157,7 +167,7 @@ struct SettingsItemPreview : View {
 
     var body: some View {
         VStack {
-            SettingsToggle(name: "Developer Mode", value: $developerMode,
+            SettingsToggle(name: "Developer Mode", value: $developerMode, id1: 
"dev1",
                     description: "More information intended for debugging")
             SettingsSpeaker(name: String(localized: "Play Payment Sounds"), 
value: $playSounds,
                      description: String(localized: "After a transaction 
finished"))
@@ -169,9 +179,9 @@ struct SettingsItemPreview : View {
 struct SettingsItem_Previews: PreviewProvider {
     static var previews: some View {
         List {
-            SettingsItem (name: "Exchanges", description: "Manage list of 
exchanges known to this wallet") {}
+            SettingsItem (name: "Exchanges", id1: "list", description: "Manage 
list of exchanges known to this wallet") {}
             SettingsItemPreview()
-            SettingsItem(name: "Save Logfile", description: "Help debugging 
wallet-core") {
+            SettingsItem(name: "Save Logfile", id1: "save", description: "Help 
debugging wallet-core") {
                 Button("Save") {
                 }
                 .buttonStyle(.bordered)
diff --git a/TalerWallet1/Views/Settings/SettingsView.swift 
b/TalerWallet1/Views/Settings/SettingsView.swift
index 3bf5b69..af94378 100644
--- a/TalerWallet1/Views/Settings/SettingsView.swift
+++ b/TalerWallet1/Views/Settings/SettingsView.swift
@@ -50,6 +50,7 @@ struct SettingsView: View {
     @State private var diagnosticModeEnabled = 
UserDefaults.standard.bool(forKey: "diagnostic_mode_enabled")
 #endif
     @State private var showDevelopItems = false
+    @State private var hideDescriptions = false
     @State private var showResetAlert: Bool = false
     @State private var didReset: Bool = false
 
@@ -93,35 +94,44 @@ struct SettingsView: View {
         let walletCore = WalletCore.shared
         Group {
             List {
+                let aboutStr = String(localized: "About GNU Taler")
+                NavigationLink {        // whole row like in a tableView
+                    LazyView { AboutView(stack: stack.push(), navTitle: 
aboutStr) }
+                } label: {
+                    SettingsItem(name: aboutStr, id1: "about",
+                          description: hideDescriptions ? nil : 
String(localized: "More info about this app...")) {}
+                }
                 if controller.hapticCapability.supportsHaptics {
-                    SettingsToggle(name: String(localized: "Haptics"), value: 
$useHaptics,
-                                   description: String(localized: "Vibration 
Feedback"))
+                    SettingsToggle(name: String(localized: "Haptics"), value: 
$useHaptics, id1: "haptics",
+                            description: hideDescriptions ? nil : 
String(localized: "Vibration Feedback"))
                 }
                 SettingsSpeaker(name: String(localized: "Play Payment 
Sounds"), value: $playSounds,
-                                description: String(localized: "When a 
transaction finished"))
+                         description: hideDescriptions ? nil : 
String(localized: "When a transaction finished"))
+                .id("playSounds")
                 SettingsFont(title: String(localized: "Font:"), value: 
talerFont, action: redraw)
                 SettingsStyle(title: String(localized: "Liststyle:"), 
myListStyle: $myListStyle)
-                SettingsToggle(name: String(localized: "Minimalistic"), value: 
$iconOnly,
-                               description: String(localized: "Omit text where 
possible"))
+                SettingsToggle(name: String(localized: "Minimalistic"), value: 
$iconOnly, id1: "minimal",
+                        description: hideDescriptions ? nil : 
String(localized: "Omit text where possible")) {
+                    hideDescriptions = iconOnly //withAnimation { 
hideDescriptions = iconOnly }
+                }
                 if diagnosticModeEnabled {
-                    SettingsToggle(name: String(localized: "Developer Mode"), 
value: $developerMode,
-                            description: String(localized: "More information 
intended for debugging")) {
-                        DebugViewC.shared.setViewID(VIEW_SETTINGS, stack: 
stack.push())
+                    SettingsToggle(name: String(localized: "Developer Mode"), 
value: $developerMode, id1: "devMode",
+                            description: hideDescriptions ? nil : 
String(localized: "More information intended for debugging")) {
                         withAnimation { showDevelopItems = developerMode }
                     }
                     if showDevelopItems {  // show or hide the following items
                         NavigationLink {        // whole row like in a 
tableView
                             LazyView { PendingOpsListView(stack: stack.push()) 
}
                         } label: {
-                            SettingsItem(name: String(localized: "Pending 
Operations"),
-                                  description: String(localized: "Exchange not 
yet ready...")) {}
-                        }
+                            SettingsItem(name: String(localized: "Pending 
Operations"), id1: "pending",
+                                  description: hideDescriptions ? nil : 
String(localized: "Exchange not yet ready...")) {}
+                        }.id("pending_L")
                         SettingsToggle(name: String(localized: "Set 2 seconds 
delay"),
                                       value: $developDelay.onChange({ delay in
-                                        walletCore.developDelay = delay}),
-                                description: String(localized: "After each 
wallet-core action"))
-                        SettingsItem(name: String(localized: "Withdraw 
\(DEMOCURRENCY)"),
-                              description: String(localized: "Get money for 
testing")) {
+                                        walletCore.developDelay = delay}), 
id1: "delay",
+                                description: hideDescriptions ? nil : 
String(localized: "After each wallet-core action"))
+                        SettingsItem(name: String(localized: "Withdraw 
\(DEMOCURRENCY)"), id1: "demo1with",
+                              description: hideDescriptions ? nil : 
String(localized: "Get money for testing")) {
                             Button("Withdraw") {
                                 withDrawDisabled = true    // don't run twice
                                 Task { // runs on MainActor
@@ -135,9 +145,9 @@ struct SettingsView: View {
                             }
                             .buttonStyle(.bordered)
                             .disabled(withDrawDisabled)
-                        }
-                        SettingsItem(name: String(localized: "Withdraw 
\(TESTCURRENCY)"),
-                              description: String(localized: "Get money for 
testing")) {
+                        }.id("demoWithdraw")
+                        SettingsItem(name: String(localized: "Withdraw 
\(TESTCURRENCY)"), id1: "test1with",
+                              description: hideDescriptions ? nil : 
String(localized: "Get money for testing")) {
                             Button("Withdraw") {
                                 withDrawDisabled = true    // don't run twice
                                 Task { // runs on MainActor
@@ -151,9 +161,9 @@ struct SettingsView: View {
                             }
                             .buttonStyle(.bordered)
                             .disabled(withDrawDisabled)
-                        }
-                        SettingsItem(name: String(localized: "Run Integration 
Test"),
-                              description: String(localized: "Perform basic 
test transactions")) {
+                        }.id("testWithdraw")
+                        SettingsItem(name: String(localized: "Run Integration 
Test"), id1: "demo1test",
+                              description: hideDescriptions ? nil : 
String(localized: "Perform basic test transactions")) {
                             Button("Demo 1") {
                                 checkDisabled = true    // don't run twice
                                 Task { // runs on MainActor
@@ -168,8 +178,8 @@ struct SettingsView: View {
                             .buttonStyle(.bordered)
                             .disabled(checkDisabled)
                         }
-                        SettingsItem(name: String(localized: "Run Integration 
Test"),
-                              description: String(localized: "Perform basic 
test transactions")) {
+                        SettingsItem(name: String(localized: "Run Integration 
Test"), id1: "test1test",
+                              description: hideDescriptions ? nil : 
String(localized: "Perform basic test transactions")) {
                             Button("Test 1") {
                                 checkDisabled = true    // don't run twice
                                 Task { // runs on MainActor
@@ -184,8 +194,8 @@ struct SettingsView: View {
                             .buttonStyle(.bordered)
                             .disabled(checkDisabled)
                         }
-                        SettingsItem(name: String(localized: "Run Integration 
Test V2"),
-                              description: String(localized: "Perform more 
test transactions")) {
+                        SettingsItem(name: String(localized: "Run Integration 
Test V2"), id1: "demo2test",
+                              description: hideDescriptions ? nil : 
String(localized: "Perform more test transactions")) {
                             Button("Demo 2") {
                                 checkDisabled = true    // don't run twice
                                 Task { // runs on MainActor
@@ -200,8 +210,8 @@ struct SettingsView: View {
                             .buttonStyle(.bordered)
                             .disabled(checkDisabled)
                         }
-                        SettingsItem(name: String(localized: "Run Integration 
Test V2"),
-                              description: String(localized: "Perform more 
test transactions")) {
+                        SettingsItem(name: String(localized: "Run Integration 
Test V2"), id1: "test2test",
+                              description: hideDescriptions ? nil : 
String(localized: "Perform more test transactions")) {
                             Button("Test 2") {
                                 checkDisabled = true    // don't run twice
                                 Task { // runs on MainActor
@@ -216,8 +226,8 @@ struct SettingsView: View {
                             .buttonStyle(.bordered)
                             .disabled(checkDisabled)
                         }
-                        SettingsItem(name: String(localized: "Save Logfile"),
-                              description: String(localized: "Help debugging 
wallet-core")) {
+                        SettingsItem(name: String(localized: "Save Logfile"), 
id1: "save",
+                              description: hideDescriptions ? nil : 
String(localized: "Help debugging wallet-core")) {
                             Button("Save") {
                                 symLog.log("Saving Log")
                                 // FIXME: Save Logfile
@@ -225,8 +235,8 @@ struct SettingsView: View {
                             .buttonStyle(.bordered)
                             .disabled(true)
                         }
-                        SettingsItem(name: String(localized: "Reset Wallet"),
-                              description: String(localized: "Throw away all 
your money")) {
+                        SettingsItem(name: String(localized: "Reset Wallet"), 
id1: "reset",
+                              description: hideDescriptions ? nil : 
String(localized: "Throw away all your money")) {
                             Button("Reset") {
                                 showResetAlert = true
                             }
@@ -235,27 +245,6 @@ struct SettingsView: View {
                         }
                     }
                 }
-
-                VStack {
-                    SettingsItem(name: "App Version") {
-                        Text(verbatim: 
"\(Bundle.main.releaseVersionNumberPretty)")
-                    }
-                    SettingsItem(name: "Wallet Core Version") {
-                        Text(verbatim: "\(walletCore.versionInfo!.version)")
-                    }
-                    SettingsItem(name: "Wallet Core DevMode") {
-                        Text(verbatim: "\(walletCore.versionInfo!.devMode ? 
"YES" : "NO")")
-                    }
-                    SettingsItem(name: "Supported Exchange Versions") {
-                        Text(verbatim: "\(walletCore.versionInfo!.exchange)")
-                    }
-                    SettingsItem(name: "Supported Merchant Versions") {
-                        Text(verbatim: "\(walletCore.versionInfo!.merchant)")
-                    }
-                    SettingsItem(name: "Used Bank") {
-                        Text(verbatim: "\(walletCore.versionInfo!.bank)")
-                    }
-                } // App version info
             }
             .id(listID)
             .listStyle(myListStyle.style).anyView
@@ -264,6 +253,7 @@ struct SettingsView: View {
         .navigationBarItems(leading: hamburger)
         .onAppear() {
             showDevelopItems = developerMode
+            hideDescriptions = iconOnly
             DebugViewC.shared.setViewID(VIEW_SETTINGS, stack: stack.push())
         }
         .onDisappear() {
@@ -287,17 +277,6 @@ struct SettingsView: View {
 #endif
     }
 }
-extension Bundle {
-    var releaseVersionNumber: String? {
-        return infoDictionary?["CFBundleShortVersionString"] as? String
-    }
-    var buildVersionNumber: String? {
-        return infoDictionary?["CFBundleVersion"] as? String
-    }
-    var releaseVersionNumberPretty: String {
-        return "v\(releaseVersionNumber ?? "1.0.0")"
-    }
-}
 // MARK: -
 #if DEBUG
 struct SettingsView_Previews: PreviewProvider {
diff --git a/taler-swift/Sources/taler-swift/Amount.swift 
b/taler-swift/Sources/taler-swift/Amount.swift
index 088e237..ea952a8 100644
--- a/taler-swift/Sources/taler-swift/Amount.swift
+++ b/taler-swift/Sources/taler-swift/Amount.swift
@@ -38,34 +38,6 @@ enum AmountError: Error {
     case divideByZero
 }
 
-public struct CurrencySpecification: Codable, Sendable {
-    enum CodingKeys: String, CodingKey {
-        case decimalSeparator = "decimal_separator"
-        case name = "name"
-        case fractionalInputDigits = "num_fractional_input_digits"
-        case fractionalNormalDigits = "num_fractional_normal_digits"
-        case fractionalTrailingZeroDigits = 
"num_fractional_trailing_zero_digits"
-        case isCurrencyNameLeading = "is_currency_name_leading"
-        case altUnitNames = "alt_unit_names"
-    }
-    /// e.g. “.” for $ and ¥;  “,” for €
-    let decimalSeparator: String
-    /// some name for this CurrencySpecification
-    let name: String
-    /// 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".
-    /// This way, we can also communicate the currency symbol to be used.
-    let altUnitNames: [Int : String]
-}
 
 /// A value of some currency.
 public final class Amount: Codable, Hashable, @unchecked Sendable, 
CustomStringConvertible {        // TODO: @unchecked
@@ -90,9 +62,6 @@ public final class Amount: Codable, Hashable, @unchecked 
Sendable, CustomStringC
     /// The fractional value of the amount (number to the right of the decimal 
point).
     var fraction: UInt32
 
-    /// Additional info for formatting currency strings
-    var currencySpecification: CurrencySpecification?
-
     public func hash(into hasher: inout Hasher) {
         hasher.combine(currency)
         if let normalized = try? normalizedCopy() {
@@ -118,9 +87,9 @@ public final class Amount: Codable, Hashable, @unchecked 
Sendable, CustomStringC
     /// The string representation of the value, formatted as 
"`integer`.`fraction`".
     public var valueStr: String {
         var decimalSeparator = "."
-        if let currencySpecification {      // TODO: use locale
-            decimalSeparator = currencySpecification.decimalSeparator
-        }
+//        if let currencySpecification {      // TODO: use locale
+//            decimalSeparator = currencySpecification.decimalSeparator
+//        }
         if fraction == 0 {
             return "\(integer)"
         } else {

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