gnunet-svn
[Top][All Lists]
Advanced

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

[taler-taler-android] 02/04: [wallet] update to new wallet-core with v8


From: gnunet
Subject: [taler-taler-android] 02/04: [wallet] update to new wallet-core with v8 exchange API
Date: Thu, 30 Jul 2020 22:12:22 +0200

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

torsten-grote pushed a commit to branch master
in repository taler-android.

commit e19ba096d57353db6b1f141da4bf170ef2d2d534
Author: Torsten Grote <t@grobox.de>
AuthorDate: Wed Jul 29 14:12:50 2020 -0300

    [wallet] update to new wallet-core with v8 exchange API
    
    (except payments which are still buggy)
---
 wallet/build.gradle                                |   4 +-
 .../src/main/java/net/taler/wallet/MainActivity.kt |   5 +-
 .../src/main/java/net/taler/wallet/MainFragment.kt |   2 +-
 .../main/java/net/taler/wallet/MainViewModel.kt    |  39 +---
 .../net/taler/wallet/backend/WalletBackendApi.kt   |   4 +-
 .../taler/wallet/backend/WalletBackendService.kt   |   9 +-
 .../net/taler/wallet/balances/BalanceAdapter.kt    |  11 +-
 .../net/taler/wallet/balances/BalancesFragment.kt  |   2 +-
 .../main/java/net/taler/wallet/crypto/Encoding.kt  | 134 ------------
 .../wallet/{withdraw => exchanges}/ExchangeFees.kt |   2 +-
 .../net/taler/wallet/exchanges/ExchangeManager.kt  |   3 +-
 .../SelectExchangeFragment.kt                      |  12 +-
 .../net/taler/wallet/history/DevHistoryAdapter.kt  | 110 ----------
 .../net/taler/wallet/history/DevHistoryFragment.kt |  87 --------
 .../net/taler/wallet/history/DevHistoryManager.kt  |  75 -------
 .../java/net/taler/wallet/history/HistoryEvent.kt  | 199 ------------------
 .../net/taler/wallet/history/JsonDialogFragment.kt |  57 ------
 .../net/taler/wallet/payment/PaymentManager.kt     | 105 +++++-----
 .../net/taler/wallet/payment/PaymentResponses.kt   |  62 ++++++
 .../wallet/pending/PendingOperationsManager.kt     |   4 +-
 .../wallet/transactions/TransactionManager.kt      |   8 -
 .../wallet/transactions/TransactionsFragment.kt    |   2 +-
 .../wallet/withdraw/ManualWithdrawFragment.kt      |   2 +-
 .../wallet/withdraw/PromptWithdrawFragment.kt      |  26 ++-
 .../wallet/withdraw/ReviewExchangeTosFragment.kt   |   2 +-
 .../net/taler/wallet/withdraw/WithdrawManager.kt   | 227 ++++++++++-----------
 wallet/src/main/res/drawable/ic_directions.xml     |  25 ---
 wallet/src/main/res/drawable/ic_edit.xml           |  10 +
 wallet/src/main/res/drawable/ic_history.xml        |   9 -
 .../res/drawable/transaction_payment_aborted.xml   |  25 ---
 .../main/res/drawable/transaction_tip_declined.xml |  25 ---
 wallet/src/main/res/layout/fragment_json.xml       |  40 ----
 .../main/res/layout/fragment_prompt_withdraw.xml   |   6 +-
 .../src/main/res/layout/fragment_transactions.xml  |   2 +-
 wallet/src/main/res/layout/list_item_history.xml   |  75 -------
 wallet/src/main/res/menu/activity_main_drawer.xml  |   4 -
 wallet/src/main/res/navigation/nav_graph.xml       |  12 +-
 wallet/src/main/res/values/strings.xml             |   1 -
 wallet/src/main/res/xml/settings_backup.xml        |   2 +-
 .../net/taler/wallet/crypto/Base32CrockfordTest.kt |  36 ----
 40 files changed, 295 insertions(+), 1170 deletions(-)

diff --git a/wallet/build.gradle b/wallet/build.gradle
index 5b28c6c..8cca8dc 100644
--- a/wallet/build.gradle
+++ b/wallet/build.gradle
@@ -23,7 +23,7 @@ plugins {
     id "de.undercouch.download"
 }
 
-def walletCoreVersion = "v0.7.1-dev.10"
+def walletCoreVersion = "v0.7.1-dev.14"
 
 static def versionCodeEpoch() {
     return (new Date().getTime() / 1000).toInteger()
@@ -47,7 +47,7 @@ android {
         minSdkVersion 24
         targetSdkVersion 29
         versionCode 6
-        versionName "0.7.1.dev.10"
+        versionName "0.7.1.dev.14"
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
         buildConfigField "String", "WALLET_CORE_VERSION", 
"\"$walletCoreVersion\""
     }
diff --git a/wallet/src/main/java/net/taler/wallet/MainActivity.kt 
b/wallet/src/main/java/net/taler/wallet/MainActivity.kt
index fdb8cf8..c7c31ca 100644
--- a/wallet/src/main/java/net/taler/wallet/MainActivity.kt
+++ b/wallet/src/main/java/net/taler/wallet/MainActivity.kt
@@ -81,7 +81,7 @@ class MainActivity : AppCompatActivity(), 
OnNavigationItemSelectedListener,
 
         setSupportActionBar(toolbar)
         val appBarConfiguration = AppBarConfiguration(
-            setOf(R.id.nav_main, R.id.nav_settings, 
R.id.nav_pending_operations, R.id.nav_history),
+            setOf(R.id.nav_main, R.id.nav_settings, 
R.id.nav_pending_operations),
             drawer_layout
         )
         toolbar.setupWithNavController(nav, appBarConfiguration)
@@ -122,7 +122,6 @@ class MainActivity : AppCompatActivity(), 
OnNavigationItemSelectedListener,
             R.id.nav_home -> nav.navigate(R.id.nav_main)
             R.id.nav_settings -> nav.navigate(R.id.nav_settings)
             R.id.nav_pending_operations -> 
nav.navigate(R.id.nav_pending_operations)
-            R.id.nav_history -> nav.navigate(R.id.nav_history)
         }
         drawer_layout.closeDrawer(START)
         return true
@@ -160,7 +159,7 @@ class MainActivity : AppCompatActivity(), 
OnNavigationItemSelectedListener,
                 Log.v(TAG, "navigating!")
                 // there's more than one entry point, so use global action
                 nav.navigate(R.id.action_global_promptWithdraw)
-                model.withdrawManager.getWithdrawalInfo(url)
+                model.withdrawManager.getWithdrawalDetails(url)
             }
             url.toLowerCase(ROOT).startsWith("taler://refund/") -> {
                 model.showProgressBar.value = true
diff --git a/wallet/src/main/java/net/taler/wallet/MainFragment.kt 
b/wallet/src/main/java/net/taler/wallet/MainFragment.kt
index a735987..d5bd3fc 100644
--- a/wallet/src/main/java/net/taler/wallet/MainFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/MainFragment.kt
@@ -49,7 +49,7 @@ class MainFragment : Fragment() {
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         model.balances.observe(viewLifecycleOwner, Observer {
-            onBalancesChanged(it.values.toList())
+            onBalancesChanged(it)
         })
         model.transactionsEvent.observe(viewLifecycleOwner, EventObserver { 
currency ->
             // we only need to navigate to a dedicated list, when in 
multi-currency mode
diff --git a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt 
b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
index 46f5021..3d725d0 100644
--- a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
+++ b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
@@ -27,14 +27,13 @@ import androidx.lifecycle.viewModelScope
 import 
com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
 import com.fasterxml.jackson.databind.ObjectMapper
 import com.fasterxml.jackson.module.kotlin.KotlinModule
-import net.taler.common.Amount
+import com.fasterxml.jackson.module.kotlin.readValue
 import net.taler.common.Event
 import net.taler.common.assertUiThread
 import net.taler.common.toEvent
 import net.taler.wallet.backend.WalletBackendApi
 import net.taler.wallet.balances.BalanceItem
 import net.taler.wallet.exchanges.ExchangeManager
-import net.taler.wallet.history.DevHistoryManager
 import net.taler.wallet.payment.PaymentManager
 import net.taler.wallet.pending.PendingOperationsManager
 import net.taler.wallet.refund.RefundManager
@@ -55,8 +54,8 @@ private val transactionNotifications = listOf(
 
 class MainViewModel(val app: Application) : AndroidViewModel(app) {
 
-    private val mBalances = MutableLiveData<Map<String, BalanceItem>>()
-    val balances: LiveData<Map<String, BalanceItem>> = 
mBalances.distinctUntilChanged()
+    private val mBalances = MutableLiveData<List<BalanceItem>>()
+    val balances: LiveData<List<BalanceItem>> = 
mBalances.distinctUntilChanged()
 
     val devMode = MutableLiveData(BuildConfig.DEBUG)
     val showProgressBar = MutableLiveData<Boolean>()
@@ -85,7 +84,6 @@ class MainViewModel(val app: Application) : 
AndroidViewModel(app) {
             // refresh pending ops and history with each notification
             if (devMode.value == true) {
                 pendingOperationsManager.getPending()
-                historyManager.loadHistory()
             }
         }
     }
@@ -94,12 +92,10 @@ class MainViewModel(val app: Application) : 
AndroidViewModel(app) {
         .registerModule(KotlinModule())
         .configure(FAIL_ON_UNKNOWN_PROPERTIES, false)
 
-    val withdrawManager = WithdrawManager(walletBackendApi)
+    val withdrawManager = WithdrawManager(walletBackendApi, mapper)
     val paymentManager = PaymentManager(walletBackendApi, mapper)
     val pendingOperationsManager: PendingOperationsManager =
         PendingOperationsManager(walletBackendApi)
-    val historyManager: DevHistoryManager =
-        DevHistoryManager(walletBackendApi, viewModelScope, mapper)
     val transactionManager: TransactionManager =
         TransactionManager(walletBackendApi, viewModelScope, mapper)
     val refundManager = RefundManager(walletBackendApi)
@@ -123,26 +119,13 @@ class MainViewModel(val app: Application) : 
AndroidViewModel(app) {
     @UiThread
     fun loadBalances() {
         showProgressBar.value = true
-        walletBackendApi.sendRequest("getBalances", null) { isError, result ->
+        walletBackendApi.sendRequest("getBalances") { isError, result ->
             if (isError) {
                 Log.e(TAG, "Error retrieving balances: ${result.toString(2)}")
                 return@sendRequest
             }
-            val balanceMap = HashMap<String, BalanceItem>()
-            val byCurrency = result.getJSONObject("byCurrency")
-            val currencyList = byCurrency.keys().asSequence().toList().sorted()
-            for (currency in currencyList) {
-                val jsonAmount = byCurrency.getJSONObject(currency)
-                    .getJSONObject("available")
-                val amount = Amount.fromJsonObject(jsonAmount)
-                val jsonAmountIncoming = byCurrency.getJSONObject(currency)
-                    .getJSONObject("pendingIncoming")
-                val amountIncoming = Amount.fromJsonObject(jsonAmountIncoming)
-                val hasPending = transactionManager.hasPending(currency)
-                balanceMap[currency] = BalanceItem(amount, amountIncoming, 
hasPending)
-            }
-            mBalances.postValue(balanceMap)
-            showProgressBar.postValue(false)
+            mBalances.value = mapper.readValue(result.getString("balances"))
+            showProgressBar.value = false
         }
     }
 
@@ -156,17 +139,17 @@ class MainViewModel(val app: Application) : 
AndroidViewModel(app) {
 
     @UiThread
     fun dangerouslyReset() {
-        walletBackendApi.sendRequest("reset", null)
+        walletBackendApi.sendRequest("reset")
         withdrawManager.testWithdrawalInProgress.value = false
-        mBalances.value = emptyMap()
+        mBalances.value = emptyList()
     }
 
     fun startTunnel() {
-        walletBackendApi.sendRequest("startTunnel", null)
+        walletBackendApi.sendRequest("startTunnel")
     }
 
     fun stopTunnel() {
-        walletBackendApi.sendRequest("stopTunnel", null)
+        walletBackendApi.sendRequest("stopTunnel")
     }
 
     fun tunnelResponse(resp: String) {
diff --git a/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt 
b/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt
index 3ffcd7b..51b3419 100644
--- a/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt
+++ b/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt
@@ -30,7 +30,7 @@ import android.util.Log
 import android.util.SparseArray
 import org.json.JSONObject
 import java.lang.ref.WeakReference
-import java.util.*
+import java.util.LinkedList
 
 class WalletBackendApi(
     private val app: Application,
@@ -115,7 +115,7 @@ class WalletBackendApi(
 
     fun sendRequest(
         operation: String,
-        args: JSONObject?,
+        args: JSONObject? = null,
         onResponse: (isError: Boolean, message: JSONObject) -> Unit = { _, _ 
-> }
     ) {
         val requestID = nextRequestID++
diff --git 
a/wallet/src/main/java/net/taler/wallet/backend/WalletBackendService.kt 
b/wallet/src/main/java/net/taler/wallet/backend/WalletBackendService.kt
index c810054..f39a3e7 100644
--- a/wallet/src/main/java/net/taler/wallet/backend/WalletBackendService.kt
+++ b/wallet/src/main/java/net/taler/wallet/backend/WalletBackendService.kt
@@ -30,7 +30,7 @@ import net.taler.wallet.BuildConfig.WALLET_CORE_VERSION
 import net.taler.wallet.HostCardEmulatorService
 import org.json.JSONObject
 import java.lang.ref.WeakReference
-import java.util.*
+import java.util.LinkedList
 import java.util.concurrent.ConcurrentHashMap
 import kotlin.system.exitProcess
 
@@ -56,9 +56,10 @@ class WalletBackendService : Service() {
     private val subscribers = LinkedList<Messenger>()
 
     override fun onCreate() {
-        val talerWalletAndroidCode = 
assets.open("taler-wallet-android-$WALLET_CORE_VERSION.js").use {
-            it.readBytes().toString(Charsets.UTF_8)
-        }
+        val talerWalletAndroidCode =
+            assets.open("taler-wallet-android-$WALLET_CORE_VERSION.js").use {
+                it.readBytes().toString(Charsets.UTF_8)
+            }
 
 
         Log.i(TAG, "onCreate in wallet backend service")
diff --git a/wallet/src/main/java/net/taler/wallet/balances/BalanceAdapter.kt 
b/wallet/src/main/java/net/taler/wallet/balances/BalanceAdapter.kt
index be50364..c090e75 100644
--- a/wallet/src/main/java/net/taler/wallet/balances/BalanceAdapter.kt
+++ b/wallet/src/main/java/net/taler/wallet/balances/BalanceAdapter.kt
@@ -28,7 +28,14 @@ import net.taler.common.Amount
 import net.taler.wallet.R
 import net.taler.wallet.balances.BalanceAdapter.BalanceViewHolder
 
-data class BalanceItem(val available: Amount, val pendingIncoming: Amount, val 
hasPending: Boolean)
+data class BalanceItem(
+    val available: Amount,
+    val pendingIncoming: Amount,
+    val pendingOutgoing: Amount
+) {
+    val currency: String get() = available.currency
+    val hasPending: Boolean get() = !pendingIncoming.isZero() || 
!pendingOutgoing.isZero()
+}
 
 class BalanceAdapter(private val listener: BalanceClickListener) : 
Adapter<BalanceViewHolder>() {
 
@@ -65,7 +72,7 @@ class BalanceAdapter(private val listener: 
BalanceClickListener) : Adapter<Balan
 
         fun bind(item: BalanceItem) {
             v.setOnClickListener { 
listener.onBalanceClick(item.available.currency) }
-            currencyView.text = item.available.currency
+            currencyView.text = item.currency
             amountView.text = item.available.amountStr
 
             val amountIncoming = item.pendingIncoming
diff --git a/wallet/src/main/java/net/taler/wallet/balances/BalancesFragment.kt 
b/wallet/src/main/java/net/taler/wallet/balances/BalancesFragment.kt
index 22dd992..2b4d032 100644
--- a/wallet/src/main/java/net/taler/wallet/balances/BalancesFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/balances/BalancesFragment.kt
@@ -60,7 +60,7 @@ class BalancesFragment : Fragment(),
         }
 
         model.balances.observe(viewLifecycleOwner, Observer {
-            onBalancesChanged(it.values.toList())
+            onBalancesChanged(it)
         })
     }
 
diff --git a/wallet/src/main/java/net/taler/wallet/crypto/Encoding.kt 
b/wallet/src/main/java/net/taler/wallet/crypto/Encoding.kt
deleted file mode 100644
index 25a59be..0000000
--- a/wallet/src/main/java/net/taler/wallet/crypto/Encoding.kt
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2020 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-package net.taler.wallet.crypto
-
-import java.io.ByteArrayOutputStream
-
-class EncodingException : Exception("Invalid encoding")
-
-
-object Base32Crockford {
-
-    private fun ByteArray.getIntAt(index: Int): Int {
-        val x = this[index].toInt()
-        return if (x >= 0) x else (x + 256)
-    }
-
-    private var encTable = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
-
-    fun encode(data: ByteArray): String {
-        val sb = StringBuilder()
-        val size = data.size
-        var bitBuf = 0
-        var numBits = 0
-        var pos = 0
-        while (pos < size || numBits > 0) {
-            if (pos < size && numBits < 5) {
-                val d = data.getIntAt(pos++)
-                bitBuf = (bitBuf shl 8) or d
-                numBits += 8
-            }
-            if (numBits < 5) {
-                // zero-padding
-                bitBuf = bitBuf shl (5 - numBits)
-                numBits = 5
-            }
-            val v = bitBuf.ushr(numBits - 5) and 31
-            sb.append(encTable[v])
-            numBits -= 5
-        }
-        return sb.toString()
-    }
-
-    fun decode(encoded: String, out: ByteArrayOutputStream) {
-        val size = encoded.length
-        var bitpos = 0
-        var bitbuf = 0
-        var readPosition = 0
-
-        while (readPosition < size || bitpos > 0) {
-            //println("at position $readPosition with bitpos $bitpos")
-            if (readPosition < size) {
-                val v = getValue(encoded[readPosition++])
-                bitbuf = (bitbuf shl 5) or v
-                bitpos += 5
-            }
-            while (bitpos >= 8) {
-                val d = (bitbuf ushr (bitpos - 8)) and 0xFF
-                out.write(d)
-                bitpos -= 8
-            }
-            if (readPosition == size && bitpos > 0) {
-                bitbuf = (bitbuf shl (8 - bitpos)) and 0xFF
-                bitpos = if (bitbuf == 0) 0 else 8
-            }
-        }
-    }
-
-    fun decode(encoded: String): ByteArray {
-        val out = ByteArrayOutputStream()
-        decode(encoded, out)
-        return out.toByteArray()
-    }
-
-    private fun getValue(chr: Char): Int {
-        var a = chr
-        when (a) {
-            'O', 'o' -> a = '0'
-            'i', 'I', 'l', 'L' -> a = '1'
-            'u', 'U' -> a = 'V'
-        }
-        if (a in '0'..'9')
-            return a - '0'
-        if (a in 'a'..'z')
-            a = Character.toUpperCase(a)
-        var dec = 0
-        if (a in 'A'..'Z') {
-            if ('I' < a) dec++
-            if ('L' < a) dec++
-            if ('O' < a) dec++
-            if ('U' < a) dec++
-            return a - 'A' + 10 - dec
-        }
-        throw EncodingException()
-    }
-
-    /**
-     * Compute the length of the resulting string when encoding data of the 
given size
-     * in bytes.
-     *
-     * @param dataSize size of the data to encode in bytes
-     * @return size of the string that would result from encoding
-     */
-    @Suppress("unused")
-    fun calculateEncodedStringLength(dataSize: Int): Int {
-        return (dataSize * 8 + 4) / 5
-    }
-
-    /**
-     * Compute the length of the resulting data in bytes when decoding a 
(valid) string of the
-     * given size.
-     *
-     * @param stringSize size of the string to decode
-     * @return size of the resulting data in bytes
-     */
-    @Suppress("unused")
-    fun calculateDecodedDataLength(stringSize: Int): Int {
-        return stringSize * 5 / 8
-    }
-}
-
diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/ExchangeFees.kt 
b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFees.kt
similarity index 99%
rename from wallet/src/main/java/net/taler/wallet/withdraw/ExchangeFees.kt
rename to wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFees.kt
index 9c815c9..ae90b98 100644
--- a/wallet/src/main/java/net/taler/wallet/withdraw/ExchangeFees.kt
+++ b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFees.kt
@@ -14,7 +14,7 @@
  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-package net.taler.wallet.withdraw
+package net.taler.wallet.exchanges
 
 import net.taler.common.Amount
 import net.taler.common.Timestamp
diff --git a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeManager.kt 
b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeManager.kt
index 41c8f2c..9d31b5f 100644
--- a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeManager.kt
@@ -21,7 +21,6 @@ import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
 import com.fasterxml.jackson.databind.ObjectMapper
 import com.fasterxml.jackson.module.kotlin.readValue
-import net.taler.common.Amount
 import net.taler.common.Event
 import net.taler.common.toEvent
 import net.taler.wallet.TAG
@@ -46,7 +45,7 @@ class ExchangeManager(
 
     private fun list(): LiveData<List<ExchangeItem>> {
         mProgress.value = true
-        walletBackendApi.sendRequest("listExchanges", JSONObject()) { isError, 
result ->
+        walletBackendApi.sendRequest("listExchanges") { isError, result ->
             if (isError) {
                 throw AssertionError("Wallet core failed to return exchanges!")
             } else {
diff --git 
a/wallet/src/main/java/net/taler/wallet/withdraw/SelectExchangeFragment.kt 
b/wallet/src/main/java/net/taler/wallet/exchanges/SelectExchangeFragment.kt
similarity index 94%
rename from 
wallet/src/main/java/net/taler/wallet/withdraw/SelectExchangeFragment.kt
rename to 
wallet/src/main/java/net/taler/wallet/exchanges/SelectExchangeFragment.kt
index 2ade9f2..ef4894d 100644
--- a/wallet/src/main/java/net/taler/wallet/withdraw/SelectExchangeFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/exchanges/SelectExchangeFragment.kt
@@ -14,7 +14,7 @@
  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-package net.taler.wallet.withdraw
+package net.taler.wallet.exchanges
 
 import android.os.Bundle
 import android.view.LayoutInflater
@@ -33,8 +33,8 @@ import net.taler.common.toRelativeTime
 import net.taler.common.toShortDate
 import net.taler.wallet.MainViewModel
 import net.taler.wallet.R
-import net.taler.wallet.withdraw.CoinFeeAdapter.CoinFeeViewHolder
-import net.taler.wallet.withdraw.WireFeeAdapter.WireFeeViewHolder
+import net.taler.wallet.exchanges.CoinFeeAdapter.CoinFeeViewHolder
+import net.taler.wallet.exchanges.WireFeeAdapter.WireFeeViewHolder
 
 class SelectExchangeFragment : Fragment() {
 
@@ -59,8 +59,10 @@ class SelectExchangeFragment : Fragment() {
             overheadView.visibility = GONE
         } else overheadView.setAmount(fees.overhead)
         expirationView.text = 
fees.earliestDepositExpiration.ms.toRelativeTime(requireContext())
-        coinFeesList.adapter = CoinFeeAdapter(fees.coinFees)
-        wireFeesList.adapter = WireFeeAdapter(fees.wireFees)
+        coinFeesList.adapter =
+            CoinFeeAdapter(fees.coinFees)
+        wireFeesList.adapter =
+            WireFeeAdapter(fees.wireFees)
     }
 
     private fun TextView.setAmount(amount: Amount) {
diff --git a/wallet/src/main/java/net/taler/wallet/history/DevHistoryAdapter.kt 
b/wallet/src/main/java/net/taler/wallet/history/DevHistoryAdapter.kt
deleted file mode 100644
index a2684e1..0000000
--- a/wallet/src/main/java/net/taler/wallet/history/DevHistoryAdapter.kt
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2020 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-package net.taler.wallet.history
-
-import android.content.Context
-import android.view.LayoutInflater
-import android.view.View
-import android.view.View.GONE
-import android.view.View.VISIBLE
-import android.view.ViewGroup
-import android.widget.ImageView
-import android.widget.TextView
-import androidx.recyclerview.widget.RecyclerView.Adapter
-import androidx.recyclerview.widget.RecyclerView.ViewHolder
-import net.taler.common.exhaustive
-import net.taler.common.toRelativeTime
-import net.taler.wallet.R
-import net.taler.wallet.history.DevHistoryAdapter.HistoryViewHolder
-import net.taler.wallet.transactions.AmountType
-
-internal class DevHistoryAdapter(
-    private val listener: OnEventClickListener
-) : Adapter<HistoryViewHolder>() {
-
-    private var history: List<HistoryEvent> = ArrayList()
-
-    init {
-        setHasStableIds(false)
-    }
-
-    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): 
HistoryViewHolder {
-        val view = LayoutInflater.from(parent.context)
-            .inflate(R.layout.list_item_history, parent, false)
-        return HistoryViewHolder(view)
-    }
-
-    override fun getItemCount(): Int = history.size
-
-    override fun onBindViewHolder(holder: HistoryViewHolder, position: Int) {
-        val transaction = history[position]
-        holder.bind(transaction)
-    }
-
-    fun update(updatedHistory: List<HistoryEvent>) {
-        this.history = updatedHistory
-        this.notifyDataSetChanged()
-    }
-
-    internal open inner class HistoryViewHolder(private val v: View) : 
ViewHolder(v) {
-
-        protected val context: Context = v.context
-
-        private val icon: ImageView = v.findViewById(R.id.icon)
-        protected val title: TextView = v.findViewById(R.id.title)
-        private val time: TextView = v.findViewById(R.id.time)
-        private val amount: TextView = v.findViewById(R.id.amount)
-
-        private val amountColor = amount.currentTextColor
-
-        open fun bind(historyEvent: HistoryEvent) {
-            v.setOnClickListener { listener.onTransactionClicked(historyEvent) 
}
-            icon.setImageResource(historyEvent.icon)
-            title.text = historyEvent.title
-            time.text = historyEvent.timestamp.ms.toRelativeTime(context)
-            bindAmount(historyEvent.displayAmount)
-        }
-
-        private fun bindAmount(displayAmount: DisplayAmount?) {
-            if (displayAmount == null) {
-                amount.visibility = GONE
-            } else {
-                amount.visibility = VISIBLE
-                when (displayAmount.type) {
-                    AmountType.Positive -> {
-                        amount.text = context.getString(
-                            R.string.amount_positive, 
displayAmount.amount.amountStr
-                        )
-                        amount.setTextColor(context.getColor(R.color.green))
-                    }
-                    AmountType.Negative -> {
-                        amount.text = context.getString(
-                            R.string.amount_negative, 
displayAmount.amount.amountStr
-                        )
-                        amount.setTextColor(context.getColor(R.color.red))
-                    }
-                    AmountType.Neutral -> {
-                        amount.text = displayAmount.amount.amountStr
-                        amount.setTextColor(amountColor)
-                    }
-                }.exhaustive
-            }
-        }
-
-    }
-
-}
diff --git 
a/wallet/src/main/java/net/taler/wallet/history/DevHistoryFragment.kt 
b/wallet/src/main/java/net/taler/wallet/history/DevHistoryFragment.kt
deleted file mode 100644
index c3c07a3..0000000
--- a/wallet/src/main/java/net/taler/wallet/history/DevHistoryFragment.kt
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2020 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-package net.taler.wallet.history
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.View.INVISIBLE
-import android.view.View.VISIBLE
-import android.view.ViewGroup
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.Observer
-import androidx.recyclerview.widget.DividerItemDecoration
-import androidx.recyclerview.widget.LinearLayoutManager.VERTICAL
-import kotlinx.android.synthetic.main.fragment_transactions.*
-import net.taler.common.fadeIn
-import net.taler.common.fadeOut
-import net.taler.wallet.MainViewModel
-import net.taler.wallet.R
-
-internal interface OnEventClickListener {
-    fun onTransactionClicked(historyEvent: HistoryEvent)
-}
-
-class DevHistoryFragment : Fragment(),
-    OnEventClickListener {
-
-    private val model: MainViewModel by activityViewModels()
-    private val historyManager by lazy { model.historyManager }
-    private val historyAdapter by lazy { DevHistoryAdapter(this) }
-
-    override fun onCreateView(
-        inflater: LayoutInflater, container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        return inflater.inflate(R.layout.fragment_transactions, container, 
false)
-    }
-
-    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        if (savedInstanceState == null) historyManager.loadHistory()
-
-        list.apply {
-            adapter = historyAdapter
-            addItemDecoration(DividerItemDecoration(context, VERTICAL))
-        }
-        historyManager.progress.observe(viewLifecycleOwner, Observer { show ->
-            progressBar.visibility = if (show) VISIBLE else INVISIBLE
-        })
-        historyManager.history.observe(viewLifecycleOwner, Observer { result ->
-            onHistoryResult(result)
-        })
-    }
-
-    override fun onTransactionClicked(historyEvent: HistoryEvent) {
-        JsonDialogFragment.new(historyEvent.json.toString(2))
-            .show(parentFragmentManager, null)
-    }
-
-    private fun onHistoryResult(result: HistoryResult) = when (result) {
-        HistoryResult.Error -> {
-            list.fadeOut()
-            emptyState.text = getString(R.string.transactions_error)
-            emptyState.fadeIn()
-        }
-        is HistoryResult.Success -> {
-            emptyState.visibility = if (result.history.isEmpty()) VISIBLE else 
INVISIBLE
-            historyAdapter.update(result.history)
-            list.fadeIn()
-        }
-    }
-
-}
diff --git a/wallet/src/main/java/net/taler/wallet/history/DevHistoryManager.kt 
b/wallet/src/main/java/net/taler/wallet/history/DevHistoryManager.kt
deleted file mode 100644
index 9052d6e..0000000
--- a/wallet/src/main/java/net/taler/wallet/history/DevHistoryManager.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2020 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-package net.taler.wallet.history
-
-import androidx.annotation.UiThread
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import com.fasterxml.jackson.databind.ObjectMapper
-import com.fasterxml.jackson.module.kotlin.readValue
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import net.taler.wallet.backend.WalletBackendApi
-import org.json.JSONObject
-import java.util.*
-
-sealed class HistoryResult {
-    object Error : HistoryResult()
-    class Success(val history: List<HistoryEvent>) : HistoryResult()
-}
-
-class DevHistoryManager(
-    private val walletBackendApi: WalletBackendApi,
-    private val scope: CoroutineScope,
-    private val mapper: ObjectMapper
-) {
-
-    private val mProgress = MutableLiveData<Boolean>()
-    val progress: LiveData<Boolean> = mProgress
-
-    private val mHistory = MutableLiveData<HistoryResult>()
-    val history: LiveData<HistoryResult> = mHistory
-
-    @UiThread
-    internal fun loadHistory() {
-        mProgress.value = true
-        walletBackendApi.sendRequest("getHistory", null) { isError, result ->
-            scope.launch(Dispatchers.Default) {
-                onEventsLoaded(isError, result)
-            }
-        }
-    }
-
-    private fun onEventsLoaded(isError: Boolean, result: JSONObject) {
-        if (isError) {
-            mHistory.postValue(HistoryResult.Error)
-            return
-        }
-        val history = LinkedList<HistoryEvent>()
-        val json = result.getJSONArray("history")
-        for (i in 0 until json.length()) {
-            val event: HistoryEvent = mapper.readValue(json.getString(i))
-            event.json = json.getJSONObject(i)
-            history.add(event)
-        }
-        history.reverse()  // show latest first
-        mProgress.postValue(false)
-        mHistory.postValue(HistoryResult.Success(history))
-    }
-
-}
diff --git a/wallet/src/main/java/net/taler/wallet/history/HistoryEvent.kt 
b/wallet/src/main/java/net/taler/wallet/history/HistoryEvent.kt
deleted file mode 100644
index 3cbe7d7..0000000
--- a/wallet/src/main/java/net/taler/wallet/history/HistoryEvent.kt
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2020 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-package net.taler.wallet.history
-
-import androidx.annotation.DrawableRes
-import com.fasterxml.jackson.annotation.JsonSubTypes
-import com.fasterxml.jackson.annotation.JsonSubTypes.Type
-import com.fasterxml.jackson.annotation.JsonTypeInfo
-import com.fasterxml.jackson.annotation.JsonTypeInfo.As.PROPERTY
-import com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME
-import com.fasterxml.jackson.annotation.JsonTypeName
-import net.taler.common.Amount
-import net.taler.common.Timestamp
-import net.taler.wallet.R
-import net.taler.wallet.transactions.AmountType
-import org.json.JSONObject
-
-class DisplayAmount(
-    val amount: Amount,
-    val type: AmountType
-)
-
-@JsonTypeInfo(
-    use = NAME,
-    include = PROPERTY,
-    property = "type",
-    defaultImpl = UnknownHistoryEvent::class
-)
-/** missing:
-AuditorComplaintSent = "auditor-complained-sent",
-AuditorComplaintProcessed = "auditor-complaint-processed",
-AuditorTrustAdded = "auditor-trust-added",
-AuditorTrustRemoved = "auditor-trust-removed",
-ExchangeTermsAccepted = "exchange-terms-accepted",
-ExchangePolicyChanged = "exchange-policy-changed",
-ExchangeTrustAdded = "exchange-trust-added",
-ExchangeTrustRemoved = "exchange-trust-removed",
-FundsDepositedToSelf = "funds-deposited-to-self",
-FundsRecouped = "funds-recouped",
-ReserveCreated = "reserve-created",
- */
-@JsonSubTypes(
-    Type(value = ExchangeAddedEvent::class, name = "exchange-added"),
-    Type(value = ExchangeUpdatedEvent::class, name = "exchange-updated"),
-    Type(value = ReserveBalanceUpdatedHistoryEvent::class, name = 
"reserve-balance-updated"),
-    Type(value = WithdrawHistoryEvent::class, name = "withdrawn"),
-    Type(value = OrderAcceptedHistoryEvent::class, name = "order-accepted"),
-    Type(value = OrderRefusedHistoryEvent::class, name = "order-refused"),
-    Type(value = OrderRedirectedHistoryEvent::class, name = 
"order-redirected"),
-    Type(value = PaymentHistoryEvent::class, name = "payment-sent"),
-    Type(value = PaymentAbortedHistoryEvent::class, name = "payment-aborted"),
-    Type(value = TipAcceptedHistoryEvent::class, name = "tip-accepted"),
-    Type(value = TipDeclinedHistoryEvent::class, name = "tip-declined"),
-    Type(value = RefundHistoryEvent::class, name = "refund"),
-    Type(value = RefreshHistoryEvent::class, name = "refreshed")
-)
-abstract class HistoryEvent(
-    val timestamp: Timestamp,
-    val eventId: String,
-    @get:DrawableRes
-    open val icon: Int = R.drawable.ic_account_balance
-) {
-    val title: String get() = this::class.java.simpleName
-    open val displayAmount: DisplayAmount? = null
-    lateinit var json: JSONObject
-}
-
-class UnknownHistoryEvent(timestamp: Timestamp, eventId: String) : 
HistoryEvent(timestamp, eventId)
-
-@JsonTypeName("exchange-added")
-class ExchangeAddedEvent(
-    timestamp: Timestamp,
-    eventId: String
-) : HistoryEvent(timestamp, eventId)
-
-@JsonTypeName("exchange-updated")
-class ExchangeUpdatedEvent(
-    timestamp: Timestamp,
-    eventId: String
-) : HistoryEvent(timestamp, eventId)
-
-@JsonTypeName("reserve-balance-updated")
-class ReserveBalanceUpdatedHistoryEvent(
-    timestamp: Timestamp,
-    eventId: String,
-    val reserveBalance: Amount
-) : HistoryEvent(timestamp, eventId) {
-    override val displayAmount = DisplayAmount(reserveBalance, 
AmountType.Neutral)
-}
-
-@JsonTypeName("withdrawn")
-class WithdrawHistoryEvent(
-    timestamp: Timestamp,
-    eventId: String,
-    val amountWithdrawnEffective: Amount
-) : HistoryEvent(timestamp, eventId) {
-    override val icon = R.drawable.transaction_withdrawal
-    override val displayAmount = DisplayAmount(amountWithdrawnEffective, 
AmountType.Positive)
-}
-
-@JsonTypeName("order-accepted")
-class OrderAcceptedHistoryEvent(
-    timestamp: Timestamp,
-    eventId: String
-) : HistoryEvent(timestamp, eventId) {
-    override val icon = R.drawable.ic_add_circle
-}
-
-@JsonTypeName("order-refused")
-class OrderRefusedHistoryEvent(
-    timestamp: Timestamp,
-    eventId: String
-) : HistoryEvent(timestamp, eventId) {
-    override val icon = R.drawable.ic_cancel
-}
-
-@JsonTypeName("payment-sent")
-class PaymentHistoryEvent(
-    timestamp: Timestamp,
-    eventId: String,
-    val amountPaidWithFees: Amount
-) : HistoryEvent(timestamp, eventId) {
-    override val icon = R.drawable.ic_cash_usd_outline
-    override val displayAmount = DisplayAmount(amountPaidWithFees, 
AmountType.Negative)
-}
-
-@JsonTypeName("payment-aborted")
-class PaymentAbortedHistoryEvent(
-    timestamp: Timestamp,
-    eventId: String,
-    amountLost: Amount
-) : HistoryEvent(timestamp, eventId) {
-    override val icon = R.drawable.transaction_payment_aborted
-    override val displayAmount = DisplayAmount(amountLost, AmountType.Negative)
-}
-
-@JsonTypeName("refreshed")
-class RefreshHistoryEvent(
-    timestamp: Timestamp,
-    eventId: String,
-    val amountRefreshedEffective: Amount,
-    val amountRefreshedRaw: Amount
-) : HistoryEvent(timestamp, eventId) {
-    override val icon = R.drawable.transaction_refresh
-    override val displayAmount =
-        DisplayAmount(amountRefreshedRaw - amountRefreshedEffective, 
AmountType.Negative)
-}
-
-@JsonTypeName("order-redirected")
-class OrderRedirectedHistoryEvent(
-    timestamp: Timestamp,
-    eventId: String
-) : HistoryEvent(timestamp, eventId) {
-    override val icon = R.drawable.ic_directions
-}
-
-@JsonTypeName("tip-accepted")
-class TipAcceptedHistoryEvent(
-    timestamp: Timestamp,
-    eventId: String,
-    tipRaw: Amount
-) : HistoryEvent(timestamp, eventId) {
-    override val icon = R.drawable.transaction_tip_accepted
-    override val displayAmount = DisplayAmount(tipRaw, AmountType.Positive)
-}
-
-@JsonTypeName("tip-declined")
-class TipDeclinedHistoryEvent(
-    timestamp: Timestamp,
-    eventId: String,
-    tipAmount: Amount
-) : HistoryEvent(timestamp, eventId) {
-    override val icon = R.drawable.transaction_tip_declined
-    override val displayAmount = DisplayAmount(tipAmount, AmountType.Neutral)
-}
-
-@JsonTypeName("refund")
-class RefundHistoryEvent(
-    timestamp: Timestamp,
-    eventId: String,
-    val amountRefundedEffective: Amount
-) : HistoryEvent(timestamp, eventId) {
-    override val icon = R.drawable.transaction_refund
-    override val displayAmount = DisplayAmount(amountRefundedEffective, 
AmountType.Positive)
-}
diff --git 
a/wallet/src/main/java/net/taler/wallet/history/JsonDialogFragment.kt 
b/wallet/src/main/java/net/taler/wallet/history/JsonDialogFragment.kt
deleted file mode 100644
index 31c2b93..0000000
--- a/wallet/src/main/java/net/taler/wallet/history/JsonDialogFragment.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2020 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-package net.taler.wallet.history
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.view.ViewGroup.LayoutParams.MATCH_PARENT
-import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
-import androidx.fragment.app.DialogFragment
-import kotlinx.android.synthetic.main.fragment_json.*
-import net.taler.wallet.R
-
-class JsonDialogFragment : DialogFragment() {
-
-    companion object {
-        fun new(json: String): JsonDialogFragment {
-            return JsonDialogFragment().apply {
-                arguments = Bundle().apply { putString("json", json) }
-            }
-        }
-    }
-
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        return inflater.inflate(R.layout.fragment_json, container, false)
-    }
-
-    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        val json = requireArguments().getString("json")
-        jsonView.text = json
-    }
-
-    override fun onStart() {
-        super.onStart()
-        dialog?.window?.setLayout(MATCH_PARENT, WRAP_CONTENT)
-    }
-
-}
diff --git a/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt 
b/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt
index 5c73d6c..c6351ee 100644
--- a/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt
@@ -26,11 +26,29 @@ import net.taler.common.Amount
 import net.taler.common.ContractTerms
 import net.taler.wallet.TAG
 import net.taler.wallet.backend.WalletBackendApi
+import net.taler.wallet.payment.PreparePayResponse.AlreadyConfirmedResponse
+import net.taler.wallet.payment.PreparePayResponse.InsufficientBalanceResponse
+import net.taler.wallet.payment.PreparePayResponse.PaymentPossibleResponse
 import org.json.JSONObject
 import java.net.MalformedURLException
 
 val REGEX_PRODUCT_IMAGE = 
Regex("^data:image/(jpeg|png);base64,([A-Za-z0-9+/=]+)$")
 
+sealed class PayStatus {
+    object None : PayStatus()
+    object Loading : PayStatus()
+    data class Prepared(
+        val contractTerms: ContractTerms,
+        val proposalId: String,
+        val totalFees: Amount
+    ) : PayStatus()
+
+    data class InsufficientBalance(val contractTerms: ContractTerms) : 
PayStatus()
+    object AlreadyPaid : PayStatus()
+    data class Error(val error: String) : PayStatus()
+    data class Success(val currency: String) : PayStatus()
+}
+
 class PaymentManager(
     private val walletBackendApi: WalletBackendApi,
     private val mapper: ObjectMapper
@@ -42,52 +60,27 @@ class PaymentManager(
     private val mDetailsShown = MutableLiveData<Boolean>()
     internal val detailsShown: LiveData<Boolean> = mDetailsShown
 
-    private var currentPayRequestId = 0
-
     @UiThread
     fun preparePay(url: String) {
         mPayStatus.value = PayStatus.Loading
         mDetailsShown.value = false
 
-        val args = JSONObject(mapOf("url" to url))
-
-        currentPayRequestId += 1
-        val payRequestId = currentPayRequestId
-
+        val args = JSONObject(mapOf("talerPayUri" to url))
         walletBackendApi.sendRequest("preparePay", args) { isError, result ->
-            when {
-                isError -> {
-                    Log.v(TAG, "got preparePay error result")
-                    mPayStatus.value = PayStatus.Error(result.toString())
-                }
-                payRequestId != this.currentPayRequestId -> {
-                    Log.v(TAG, "preparePay result was for old request")
-                }
-                else -> {
-                    val status = result.getString("status")
-                    try {
-                        mPayStatus.postValue(getPayStatusUpdate(status, 
result))
-                    } catch (e: Exception) {
-                        Log.e(TAG, "Error getting PayStatusUpdate", e)
-                        mPayStatus.postValue(PayStatus.Error(e.message ?: 
"unknown error"))
-                    }
-                }
+            if (isError) {
+                handleError("preparePay", result.toString(2))
+                return@sendRequest
+            }
+            val response: PreparePayResponse = 
mapper.readValue(result.toString())
+            Log.e(TAG, "PreparePayResponse $response")
+            mPayStatus.value = when (response) {
+                is PaymentPossibleResponse -> TODO()
+                is InsufficientBalanceResponse -> TODO()
+                is AlreadyConfirmedResponse -> TODO()
             }
         }
     }
 
-    private fun getPayStatusUpdate(status: String, json: JSONObject) = when 
(status) {
-        "payment-possible" -> PayStatus.Prepared(
-            contractTerms = getContractTerms(json),
-            proposalId = json.getString("proposalId"),
-            totalFees = Amount.fromJsonObject(json.getJSONObject("totalFees"))
-        )
-        "paid" -> PayStatus.AlreadyPaid(getContractTerms(json))
-        "insufficient-balance" -> 
PayStatus.InsufficientBalance(getContractTerms(json))
-        "error" -> PayStatus.Error("got some error")
-        else -> PayStatus.Error("unknown status")
-    }
-
     private fun getContractTerms(json: JSONObject): ContractTerms {
         val terms: ContractTerms = 
mapper.readValue(json.getString("contractTermsRaw"))
         // validate product images
@@ -101,16 +94,13 @@ class PaymentManager(
         return terms
     }
 
-    @UiThread
-    fun toggleDetailsShown() {
-        val oldValue = mDetailsShown.value ?: false
-        mDetailsShown.value = !oldValue
-    }
-
     fun confirmPay(proposalId: String, currency: String) {
         val args = JSONObject(mapOf("proposalId" to proposalId))
-
-        walletBackendApi.sendRequest("confirmPay", args) { _, _ ->
+        walletBackendApi.sendRequest("confirmPay", args) { isError, result ->
+            if (isError) {
+                handleError("preparePay", result.toString())
+                return@sendRequest
+            }
             mPayStatus.postValue(PayStatus.Success(currency))
         }
     }
@@ -129,8 +119,9 @@ class PaymentManager(
 
         Log.i(TAG, "aborting proposal")
 
-        walletBackendApi.sendRequest("abortProposal", args) { isError, _ ->
+        walletBackendApi.sendRequest("abortProposal", args) { isError, result 
->
             if (isError) {
+                handleError("abortProposal", result.toString(2))
                 Log.e(TAG, "received error response to abortProposal")
                 return@sendRequest
             }
@@ -138,24 +129,20 @@ class PaymentManager(
         }
     }
 
+    @UiThread
+    fun toggleDetailsShown() {
+        val oldValue = mDetailsShown.value ?: false
+        mDetailsShown.value = !oldValue
+    }
+
     @UiThread
     fun resetPayStatus() {
         mPayStatus.value = PayStatus.None
     }
 
-}
-
-sealed class PayStatus {
-    object None : PayStatus()
-    object Loading : PayStatus()
-    data class Prepared(
-        val contractTerms: ContractTerms,
-        val proposalId: String,
-        val totalFees: Amount
-    ) : PayStatus()
+    private fun handleError(operation: String, msg: String) {
+        Log.e(TAG, "got $operation error result $msg")
+        mPayStatus.value = PayStatus.Error(msg)
+    }
 
-    data class InsufficientBalance(val contractTerms: ContractTerms) : 
PayStatus()
-    data class AlreadyPaid(val contractTerms: ContractTerms) : PayStatus()
-    data class Error(val error: String) : PayStatus()
-    data class Success(val currency: String) : PayStatus()
 }
diff --git a/wallet/src/main/java/net/taler/wallet/payment/PaymentResponses.kt 
b/wallet/src/main/java/net/taler/wallet/payment/PaymentResponses.kt
new file mode 100644
index 0000000..4c5b010
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/payment/PaymentResponses.kt
@@ -0,0 +1,62 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under 
the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+ * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.wallet.payment
+
+import com.fasterxml.jackson.annotation.JsonSubTypes
+import com.fasterxml.jackson.annotation.JsonSubTypes.Type
+import com.fasterxml.jackson.annotation.JsonTypeInfo
+import com.fasterxml.jackson.annotation.JsonTypeInfo.As.PROPERTY
+import com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME
+import com.fasterxml.jackson.annotation.JsonTypeName
+import net.taler.common.ContractTerms
+import net.taler.wallet.payment.PreparePayResponse.AlreadyConfirmedResponse
+import net.taler.wallet.payment.PreparePayResponse.InsufficientBalanceResponse
+import net.taler.wallet.payment.PreparePayResponse.PaymentPossibleResponse
+
+@JsonTypeInfo(use = NAME, include = PROPERTY, property = "status")
+@JsonSubTypes(
+    Type(value = PaymentPossibleResponse::class, name = "payment-possible"),
+    Type(value = AlreadyConfirmedResponse::class, name = "already-confirmed"),
+    Type(value = InsufficientBalanceResponse::class, name = 
"insufficient-balance")
+)
+sealed class PreparePayResponse(open val proposalId: String) {
+    @JsonTypeName("payment-possible")
+    data class PaymentPossibleResponse(
+        override val proposalId: String,
+        val contractTerms: ContractTerms
+    ) : PreparePayResponse(proposalId)
+
+    @JsonTypeName("insufficient-balance")
+    data class InsufficientBalanceResponse(
+        override val proposalId: String,
+        val contractTerms: ContractTerms
+    ) : PreparePayResponse(proposalId)
+
+    @JsonTypeName("already-confirmed")
+    data class AlreadyConfirmedResponse(
+        override val proposalId: String,
+        /**
+         * Did the payment succeed?
+         */
+        val paid: Boolean,
+
+        /**
+         * Redirect URL for the fulfillment page, only given if paid==true.
+         */
+        val nextUrl: String?
+    ) : PreparePayResponse(proposalId)
+}
diff --git 
a/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsManager.kt 
b/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsManager.kt
index 6c58b81..7027687 100644
--- a/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsManager.kt
@@ -32,7 +32,7 @@ class PendingOperationsManager(private val walletBackendApi: 
WalletBackendApi) {
     val pendingOperations = MutableLiveData<List<PendingOperationInfo>>()
 
     internal fun getPending() {
-        walletBackendApi.sendRequest("getPendingOperations", null) { isError, 
result ->
+        walletBackendApi.sendRequest("getPendingOperations") { isError, result 
->
             if (isError) {
                 Log.i(TAG, "got getPending error result: $result")
                 return@sendRequest
@@ -51,7 +51,7 @@ class PendingOperationsManager(private val walletBackendApi: 
WalletBackendApi) {
     }
 
     fun retryPendingNow() {
-        walletBackendApi.sendRequest("retryPendingNow", null)
+        walletBackendApi.sendRequest("retryPendingNow")
     }
 
 }
diff --git 
a/wallet/src/main/java/net/taler/wallet/transactions/TransactionManager.kt 
b/wallet/src/main/java/net/taler/wallet/transactions/TransactionManager.kt
index d8204b6..bd37b37 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionManager.kt
@@ -102,12 +102,4 @@ class TransactionManager(
         }
     }
 
-    @UiThread
-    fun hasPending(currency: String): Boolean {
-        val result = mTransactions[currency]?.value ?: return false
-        return if (result is TransactionsResult.Success) {
-            result.transactions.any { it.pending }
-        } else false
-    }
-
 }
diff --git 
a/wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt 
b/wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt
index 526aa94..2ae58c3 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt
@@ -112,7 +112,7 @@ class TransactionsFragment : Fragment(), 
OnTransactionClickListener, ActionMode.
     override fun onActivityCreated(savedInstanceState: Bundle?) {
         super.onActivityCreated(savedInstanceState)
         model.balances.observe(viewLifecycleOwner, Observer { balances ->
-            balances[currency]?.available?.let { amount ->
+            balances.find { it.currency == currency }?.available?.let { amount 
->
                 requireActivity().title =
                     getString(R.string.transactions_detail_title_balance, 
amount)
             }
diff --git 
a/wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawFragment.kt 
b/wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawFragment.kt
index 55f931d..9788d1c 100644
--- a/wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawFragment.kt
@@ -60,7 +60,7 @@ class ManualWithdrawFragment : Fragment() {
             val amount = Amount(exchangeItem.currency, value, 0)
             amountView.hideKeyboard()
             Toast.makeText(view.context, "Not implemented: $amount", 
LENGTH_SHORT).show()
-            withdrawManager.getWithdrawalDetails(exchangeItem, amount)
+            withdrawManager.getWithdrawalDetails(exchangeItem.exchangeBaseUrl, 
amount)
         }
     }
 
diff --git 
a/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt 
b/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt
index 331554b..5a98a89 100644
--- a/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt
@@ -20,6 +20,8 @@ import android.os.Bundle
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
+import android.widget.Toast
+import android.widget.Toast.LENGTH_SHORT
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
 import androidx.lifecycle.Observer
@@ -34,7 +36,7 @@ import net.taler.wallet.MainViewModel
 import net.taler.wallet.R
 import net.taler.wallet.cleanExchange
 import net.taler.wallet.withdraw.WithdrawStatus.Loading
-import net.taler.wallet.withdraw.WithdrawStatus.TermsOfServiceReviewRequired
+import net.taler.wallet.withdraw.WithdrawStatus.TosReviewRequired
 import net.taler.wallet.withdraw.WithdrawStatus.Withdrawing
 
 class PromptWithdrawFragment : Fragment() {
@@ -59,17 +61,13 @@ class PromptWithdrawFragment : Fragment() {
 
     private fun showWithdrawStatus(status: WithdrawStatus?): Any = when 
(status) {
         is WithdrawStatus.ReceivedDetails -> {
-            showContent(status.amount, status.fee, status.exchange)
+            showContent(status.amountRaw, status.amountEffective, 
status.exchangeBaseUrl)
             confirmWithdrawButton.apply {
                 text = getString(R.string.withdraw_button_confirm)
                 setOnClickListener {
                     it.fadeOut()
                     confirmProgressBar.fadeIn()
-                    withdrawManager.acceptWithdrawal(
-                        status.talerWithdrawUri,
-                        status.exchange,
-                        status.amount.currency
-                    )
+                    withdrawManager.acceptWithdrawal()
                 }
                 isEnabled = true
             }
@@ -87,8 +85,8 @@ class PromptWithdrawFragment : Fragment() {
         is Withdrawing -> {
             model.showProgressBar.value = true
         }
-        is TermsOfServiceReviewRequired -> {
-            showContent(status.amount, status.fee, status.exchange)
+        is TosReviewRequired -> {
+            showContent(status.amountRaw, status.amountEffective, 
status.exchangeBaseUrl)
             confirmWithdrawButton.apply {
                 text = getString(R.string.withdraw_button_tos)
                 setOnClickListener {
@@ -104,20 +102,20 @@ class PromptWithdrawFragment : Fragment() {
         null -> model.showProgressBar.value = false
     }
 
-    private fun showContent(amount: Amount, fee: Amount, exchange: String) {
+    private fun showContent(amountRaw: Amount, amountEffective: Amount, 
exchange: String) {
         model.showProgressBar.value = false
         progressBar.fadeOut()
 
         introView.fadeIn()
-        effectiveAmountView.text = (amount - fee).toString()
+        effectiveAmountView.text = amountEffective.toString()
         effectiveAmountView.fadeIn()
 
         chosenAmountLabel.fadeIn()
-        chosenAmountView.text = amount.toString()
+        chosenAmountView.text = amountRaw.toString()
         chosenAmountView.fadeIn()
 
         feeLabel.fadeIn()
-        feeView.text = getString(R.string.amount_negative, fee.toString())
+        feeView.text = getString(R.string.amount_negative, (amountRaw - 
amountEffective).toString())
         feeView.fadeIn()
 
         exchangeIntroView.fadeIn()
@@ -125,7 +123,7 @@ class PromptWithdrawFragment : Fragment() {
         withdrawExchangeUrl.fadeIn()
         selectExchangeButton.fadeIn()
         selectExchangeButton.setOnClickListener {
-            
findNavController().navigate(R.id.action_promptWithdraw_to_selectExchangeFragment)
+            Toast.makeText(context, "Not yet implemented", LENGTH_SHORT).show()
         }
 
         withdrawCard.fadeIn()
diff --git 
a/wallet/src/main/java/net/taler/wallet/withdraw/ReviewExchangeTosFragment.kt 
b/wallet/src/main/java/net/taler/wallet/withdraw/ReviewExchangeTosFragment.kt
index ffaef5a..db1f326 100644
--- 
a/wallet/src/main/java/net/taler/wallet/withdraw/ReviewExchangeTosFragment.kt
+++ 
b/wallet/src/main/java/net/taler/wallet/withdraw/ReviewExchangeTosFragment.kt
@@ -55,7 +55,7 @@ class ReviewExchangeTosFragment : Fragment() {
         }
         withdrawManager.withdrawStatus.observe(viewLifecycleOwner, Observer {
             when (it) {
-                is WithdrawStatus.TermsOfServiceReviewRequired -> {
+                is WithdrawStatus.TosReviewRequired -> {
                     val sections = try {
                         // TODO remove next line once exchange delivers proper 
markdown
                         val text = it.tosText.replace("****************", 
"================")
diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt 
b/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt
index ea65e7c..e14a747 100644
--- a/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt
@@ -17,38 +17,57 @@
 package net.taler.wallet.withdraw
 
 import android.util.Log
+import androidx.annotation.UiThread
 import androidx.lifecycle.MutableLiveData
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.fasterxml.jackson.module.kotlin.readValue
 import net.taler.common.Amount
 import net.taler.wallet.TAG
 import net.taler.wallet.backend.WalletBackendApi
+import net.taler.wallet.exchanges.ExchangeFees
 import net.taler.wallet.exchanges.ExchangeItem
 import net.taler.wallet.withdraw.WithdrawStatus.ReceivedDetails
 import org.json.JSONObject
 
 sealed class WithdrawStatus {
-    data class Loading(val talerWithdrawUri: String) : WithdrawStatus()
-    data class TermsOfServiceReviewRequired(
-        val talerWithdrawUri: String,
-        val exchange: String,
+    data class Loading(val talerWithdrawUri: String? = null) : WithdrawStatus()
+    data class TosReviewRequired(
+        val talerWithdrawUri: String? = null,
+        val exchangeBaseUrl: String,
+        val amountRaw: Amount,
+        val amountEffective: Amount,
         val tosText: String,
-        val tosEtag: String,
-        val amount: Amount,
-        val fee: Amount
+        val tosEtag: String
     ) : WithdrawStatus()
 
     data class ReceivedDetails(
-        val talerWithdrawUri: String,
-        val exchange: String,
-        val amount: Amount,
-        val fee: Amount
+        val talerWithdrawUri: String? = null,
+        val exchangeBaseUrl: String,
+        val amountRaw: Amount,
+        val amountEffective: Amount
     ) : WithdrawStatus()
 
-    data class Withdrawing(val talerWithdrawUri: String) : WithdrawStatus()
+    object Withdrawing : WithdrawStatus()
     data class Success(val currency: String) : WithdrawStatus()
     data class Error(val message: String?) : WithdrawStatus()
 }
 
-class WithdrawManager(private val walletBackendApi: WalletBackendApi) {
+data class WithdrawalDetailsForUri(
+    val amount: Amount,
+    val defaultExchangeBaseUrl: String?,
+    val possibleExchanges: List<ExchangeItem>
+)
+
+data class WithdrawalDetails(
+    val tosAccepted: Boolean,
+    val amountRaw: Amount,
+    val amountEffective: Amount
+)
+
+class WithdrawManager(
+    private val walletBackendApi: WalletBackendApi,
+    private val mapper: ObjectMapper
+) {
 
     val withdrawStatus = MutableLiveData<WithdrawStatus>()
     val testWithdrawalInProgress = MutableLiveData(false)
@@ -58,149 +77,127 @@ class WithdrawManager(private val walletBackendApi: 
WalletBackendApi) {
 
     fun withdrawTestkudos() {
         testWithdrawalInProgress.value = true
-
-        walletBackendApi.sendRequest("withdrawTestkudos", null) { _, _ ->
+        walletBackendApi.sendRequest("withdrawTestkudos") { _, _ ->
             testWithdrawalInProgress.postValue(false)
         }
     }
 
-    fun getWithdrawalDetails(exchangeItem: ExchangeItem, amount: Amount) {
+    fun getWithdrawalDetails(uri: String) {
+        withdrawStatus.value = WithdrawStatus.Loading(uri)
         val args = JSONObject().apply {
-            put("exchangeBaseUrl", exchangeItem.exchangeBaseUrl)
-            put("amount", amount.toJSONString())
+            put("talerWithdrawUri", uri)
         }
-        walletBackendApi.sendRequest("getWithdrawalDetailsForAmount", args) { 
isError, result ->
-            // 
{"rawAmount":"TESTKUDOS:5","effectiveAmount":"TESTKUDOS:4.8","paytoUris":["payto:\/\/x-taler-bank\/bank.test.taler.net\/Exchange"],"tosAccepted":false}
+        walletBackendApi.sendRequest("getWithdrawalDetailsForUri", args) { 
isError, result ->
             if (isError) {
-                Log.e(TAG, "$result")
+                handleError("getWithdrawalDetailsForUri", result)
+                return@sendRequest
+            }
+            val details: WithdrawalDetailsForUri = 
mapper.readValue(result.toString())
+            if (details.defaultExchangeBaseUrl == null) {
+                // TODO go to exchange selection screen instead
+                val chosenExchange = 
details.possibleExchanges[0].exchangeBaseUrl
+                getWithdrawalDetails(chosenExchange, details.amount, uri)
             } else {
-                Log.e(TAG, "$result")
+                getWithdrawalDetails(details.defaultExchangeBaseUrl, 
details.amount, uri)
             }
         }
     }
 
-    fun getWithdrawalInfo(talerWithdrawUri: String) {
+    fun getWithdrawalDetails(exchangeBaseUrl: String, amount: Amount, uri: 
String? = null) {
+        withdrawStatus.value = WithdrawStatus.Loading(uri)
         val args = JSONObject().apply {
-            put("talerWithdrawUri", talerWithdrawUri)
+            put("exchangeBaseUrl", exchangeBaseUrl)
+            put("amount", amount.toJSONString())
         }
-        withdrawStatus.value = WithdrawStatus.Loading(talerWithdrawUri)
-
-        walletBackendApi.sendRequest("getWithdrawDetailsForUri", args) { 
isError, result ->
+        walletBackendApi.sendRequest("getWithdrawalDetailsForAmount", args) { 
isError, result ->
             if (isError) {
-                Log.e(TAG, "Error getWithdrawDetailsForUri 
${result.toString(4)}")
-                val message = if (result.has("message")) 
result.getString("message") else null
-                withdrawStatus.postValue(WithdrawStatus.Error(message))
+                handleError("getWithdrawalDetailsForAmount", result)
                 return@sendRequest
             }
-            Log.v(TAG, "got getWithdrawDetailsForUri result")
-            val status = withdrawStatus.value
-            if (status !is WithdrawStatus.Loading) {
-                Log.v(TAG, "ignoring withdrawal info result, not loading.")
-                return@sendRequest
-            }
-            val wi = result.getJSONObject("bankWithdrawDetails")
-            val suggestedExchange = wi.getString("suggestedExchange")
-            // We just use the suggested exchange, in the future there will be
-            // a selection dialog.
-            getWithdrawalInfoWithExchange(talerWithdrawUri, suggestedExchange)
+            val details: WithdrawalDetails = 
mapper.readValue(result.toString())
+            if (details.tosAccepted)
+                withdrawStatus.value = ReceivedDetails(
+                    talerWithdrawUri = uri,
+                    exchangeBaseUrl = exchangeBaseUrl,
+                    amountRaw = details.amountRaw,
+                    amountEffective = details.amountEffective
+                )
+            else getExchangeTos(exchangeBaseUrl, details, uri)
         }
     }
 
-    private fun getWithdrawalInfoWithExchange(talerWithdrawUri: String, 
selectedExchange: String) {
+    private fun getExchangeTos(exchangeBaseUrl: String, details: 
WithdrawalDetails, uri: String?) {
         val args = JSONObject().apply {
-            put("talerWithdrawUri", talerWithdrawUri)
-            put("selectedExchange", selectedExchange)
+            put("exchangeBaseUrl", exchangeBaseUrl)
         }
-
-        walletBackendApi.sendRequest("getWithdrawDetailsForUri", args) { 
isError, result ->
+        walletBackendApi.sendRequest("getExchangeTos", args) { isError, result 
->
             if (isError) {
-                Log.e(TAG, "Error getWithdrawDetailsForUri 
${result.toString(4)}")
-                val message = if (result.has("message")) 
result.getString("message") else null
-                withdrawStatus.postValue(WithdrawStatus.Error(message))
-                return@sendRequest
-            }
-            Log.v(TAG, "got getWithdrawDetailsForUri result (with exchange 
details)")
-            val status = withdrawStatus.value
-            if (status !is WithdrawStatus.Loading) {
-                Log.w(TAG, "ignoring withdrawal info result, not loading.")
+                handleError("getExchangeTos", result)
                 return@sendRequest
             }
-            val wi = result.getJSONObject("bankWithdrawDetails")
-            val amount = Amount.fromJsonObject(wi.getJSONObject("amount"))
-
-            val ei = result.getJSONObject("exchangeWithdrawDetails")
-            val termsOfServiceAccepted = 
ei.getBoolean("termsOfServiceAccepted")
-
-            exchangeFees = ExchangeFees.fromExchangeWithdrawDetailsJson(ei)
-
-            val withdrawFee = 
Amount.fromJsonObject(ei.getJSONObject("withdrawFee"))
-            val overhead = Amount.fromJsonObject(ei.getJSONObject("overhead"))
-            val fee = withdrawFee + overhead
-
-            if (!termsOfServiceAccepted) {
-                val exchange = ei.getJSONObject("exchangeInfo")
-                val tosText = exchange.getString("termsOfServiceText")
-                val tosEtag = exchange.optString("termsOfServiceLastEtag", 
"undefined")
-                withdrawStatus.postValue(
-                    WithdrawStatus.TermsOfServiceReviewRequired(
-                        status.talerWithdrawUri,
-                        selectedExchange, tosText, tosEtag,
-                        amount, fee
-                    )
-                )
-            } else {
-                withdrawStatus.postValue(
-                    ReceivedDetails(
-                        status.talerWithdrawUri,
-                        selectedExchange, amount,
-                        fee
-                    )
-                )
-            }
+            withdrawStatus.value = WithdrawStatus.TosReviewRequired(
+                talerWithdrawUri = uri,
+                exchangeBaseUrl = exchangeBaseUrl,
+                amountRaw = details.amountRaw,
+                amountEffective = details.amountEffective,
+                tosText = result.getString("tos"),
+                tosEtag = result.getString("currentEtag")
+            )
         }
     }
 
-    fun acceptWithdrawal(talerWithdrawUri: String, selectedExchange: String, 
currency: String) {
-        val args = JSONObject()
-        args.put("talerWithdrawUri", talerWithdrawUri)
-        args.put("selectedExchange", selectedExchange)
-
-        withdrawStatus.value = WithdrawStatus.Withdrawing(talerWithdrawUri)
-
-        walletBackendApi.sendRequest("acceptWithdrawal", args) { isError, 
result ->
+    /**
+     * Accept the currently displayed terms of service.
+     */
+    fun acceptCurrentTermsOfService() {
+        val s = withdrawStatus.value as WithdrawStatus.TosReviewRequired
+        val args = JSONObject().apply {
+            put("exchangeBaseUrl", s.exchangeBaseUrl)
+            put("etag", s.tosEtag)
+        }
+        walletBackendApi.sendRequest("setExchangeTosAccepted", args) { 
isError, result ->
             if (isError) {
-                Log.v(TAG, "got acceptWithdrawal error result: 
${result.toString(2)}")
-                return@sendRequest
-            }
-            Log.v(TAG, "got acceptWithdrawal result")
-            val status = withdrawStatus.value
-            if (status !is WithdrawStatus.Withdrawing) {
-                Log.w(TAG, "ignoring acceptWithdrawal result, invalid state: 
$status")
+                handleError("setExchangeTosAccepted", result)
                 return@sendRequest
             }
-            withdrawStatus.postValue(WithdrawStatus.Success(currency))
+            withdrawStatus.value = ReceivedDetails(
+                talerWithdrawUri = s.talerWithdrawUri,
+                exchangeBaseUrl = s.exchangeBaseUrl,
+                amountRaw = s.amountRaw,
+                amountEffective = s.amountEffective
+            )
         }
     }
 
-    /**
-     * Accept the currently displayed terms of service.
-     */
-    fun acceptCurrentTermsOfService() {
-        val s = withdrawStatus.value
-        check(s is WithdrawStatus.TermsOfServiceReviewRequired)
+    @UiThread
+    fun acceptWithdrawal() {
+        val status = withdrawStatus.value as ReceivedDetails
 
+        val operation = if (status.talerWithdrawUri == null)
+            "acceptManualWithdrawal" else "acceptBankIntegratedWithdrawal"
         val args = JSONObject().apply {
-            put("exchangeBaseUrl", s.exchange)
-            put("etag", s.tosEtag)
+            put("exchangeBaseUrl", status.exchangeBaseUrl)
+            if (status.talerWithdrawUri == null) {
+                put("amount", status.amountRaw)
+            } else {
+                put("talerWithdrawUri", status.talerWithdrawUri)
+            }
         }
-        walletBackendApi.sendRequest("acceptExchangeTermsOfService", args) { 
isError, result ->
+        withdrawStatus.value = WithdrawStatus.Withdrawing
+        walletBackendApi.sendRequest(operation, args) { isError, result ->
             if (isError) {
-                Log.e(TAG, "Error acceptExchangeTermsOfService 
${result.toString(4)}")
+                handleError(operation, result)
                 return@sendRequest
             }
-            val status = ReceivedDetails(s.talerWithdrawUri, s.exchange, 
s.amount, s.fee)
-            withdrawStatus.postValue(status)
+            withdrawStatus.value = 
WithdrawStatus.Success(status.amountRaw.currency)
         }
     }
 
+    @UiThread
+    private fun handleError(operation: String, result: JSONObject) {
+        Log.e(TAG, "Error $operation ${result.toString(2)}")
+        val message = if (result.has("message")) result.getString("message") 
else null
+        withdrawStatus.value = WithdrawStatus.Error(message)
+    }
+
 }
diff --git a/wallet/src/main/res/drawable/ic_directions.xml 
b/wallet/src/main/res/drawable/ic_directions.xml
deleted file mode 100644
index 229d69c..0000000
--- a/wallet/src/main/res/drawable/ic_directions.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<!--
-  ~ This file is part of GNU Taler
-  ~ (C) 2020 Taler Systems S.A.
-  ~
-  ~ GNU Taler is free software; you can redistribute it and/or modify it under 
the
-  ~ terms of the GNU General Public License as published by the Free Software
-  ~ Foundation; either version 3, or (at your option) any later version.
-  ~
-  ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT 
ANY
-  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
-  ~ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-  ~
-  ~ You should have received a copy of the GNU General Public License along 
with
-  ~ GNU Taler; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
-  -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android";
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24.0"
-    android:viewportHeight="24.0">
-    <path
-        android:fillColor="#FF000000"
-        android:pathData="M21.71,11.29l-9,-9c-0.39,-0.39 -1.02,-0.39 
-1.41,0l-9,9c-0.39,0.39 -0.39,1.02 0,1.41l9,9c0.39,0.39 1.02,0.39 
1.41,0l9,-9c0.39,-0.38 0.39,-1.01 0,-1.41zM14,14.5V12h-4v3H8v-4c0,-0.55 0.45,-1 
1,-1h5V7.5l3.5,3.5 -3.5,3.5z" />
-</vector>
diff --git a/wallet/src/main/res/drawable/ic_edit.xml 
b/wallet/src/main/res/drawable/ic_edit.xml
new file mode 100644
index 0000000..69d2830
--- /dev/null
+++ b/wallet/src/main/res/drawable/ic_edit.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android";
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?attr/colorControlNormal"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#FFFFFF"
+        
android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39
 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 
3.75,3.75 1.83,-1.83z" />
+</vector>
diff --git a/wallet/src/main/res/drawable/ic_history.xml 
b/wallet/src/main/res/drawable/ic_history.xml
deleted file mode 100644
index d9f75ea..0000000
--- a/wallet/src/main/res/drawable/ic_history.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android";
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24.0"
-    android:viewportHeight="24.0">
-    <path
-        android:fillColor="#FF000000"
-        android:pathData="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 
0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 
-3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 
9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z" />
-</vector>
diff --git a/wallet/src/main/res/drawable/transaction_payment_aborted.xml 
b/wallet/src/main/res/drawable/transaction_payment_aborted.xml
deleted file mode 100644
index 8d47c26..0000000
--- a/wallet/src/main/res/drawable/transaction_payment_aborted.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<!--
-  ~ This file is part of GNU Taler
-  ~ (C) 2020 Taler Systems S.A.
-  ~
-  ~ GNU Taler is free software; you can redistribute it and/or modify it under 
the
-  ~ terms of the GNU General Public License as published by the Free Software
-  ~ Foundation; either version 3, or (at your option) any later version.
-  ~
-  ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT 
ANY
-  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
-  ~ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-  ~
-  ~ You should have received a copy of the GNU General Public License along 
with
-  ~ GNU Taler; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
-  -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android";
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24"
-    android:viewportHeight="24">
-    <path
-        android:fillColor="#000"
-        android:pathData="M15.46 18.12L16.88 19.54L19 17.41L21.12 19.54L22.54 
18.12L20.41 16L22.54 13.88L21.12 12.46L19 14.59L16.88 12.46L15.46 13.88L17.59 
16M14.97 11.62C14.86 10.28 13.58 8.97 12 9C10.3 9.04 9 10.3 9 12C9 13.7 10.3 
14.94 12 15C12.39 15 12.77 14.92 13.14 14.77C13.41 13.67 13.86 12.63 14.97 
11.62M13 16H7C7 14.9 6.1 14 5 14V10C6.1 10 7 9.1 7 8H17C17 9.1 17.9 10 19 
10V10.05C19.67 10.06 20.34 10.18 21 10.4V6H3V18H13.32C13.1 17.33 13 16.66 13 
16Z" />
-</vector>
diff --git a/wallet/src/main/res/drawable/transaction_tip_declined.xml 
b/wallet/src/main/res/drawable/transaction_tip_declined.xml
deleted file mode 100644
index 4bd1633..0000000
--- a/wallet/src/main/res/drawable/transaction_tip_declined.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<!--
-  ~ This file is part of GNU Taler
-  ~ (C) 2020 Taler Systems S.A.
-  ~
-  ~ GNU Taler is free software; you can redistribute it and/or modify it under 
the
-  ~ terms of the GNU General Public License as published by the Free Software
-  ~ Foundation; either version 3, or (at your option) any later version.
-  ~
-  ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT 
ANY
-  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
-  ~ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-  ~
-  ~ You should have received a copy of the GNU General Public License along 
with
-  ~ GNU Taler; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
-  -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android";
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24"
-    android:viewportHeight="24">
-    <path
-        android:fillColor="#000"
-        android:pathData="M10 4A4 4 0 0 0 6 8A4 4 0 0 0 10 12A4 4 0 0 0 14 8A4 
4 0 0 0 10 4M17.5 13C15 13 13 15 13 17.5C13 20 15 22 17.5 22C20 22 22 20 22 
17.5C22 15 20 13 17.5 13M10 14C5.58 14 2 15.79 2 18V20H11.5A6.5 6.5 0 0 1 11 
17.5A6.5 6.5 0 0 1 11.95 14.14C11.32 14.06 10.68 14 10 14M17.5 14.5C19.16 14.5 
20.5 15.84 20.5 17.5C20.5 18.06 20.35 18.58 20.08 19L16 14.92C16.42 14.65 16.94 
14.5 17.5 14.5M14.92 16L19 20.08C18.58 20.35 18.06 20.5 17.5 20.5C15.84 20.5 
14.5 19.16 14.5 17.5C14. [...]
-</vector>
diff --git a/wallet/src/main/res/layout/fragment_json.xml 
b/wallet/src/main/res/layout/fragment_json.xml
deleted file mode 100644
index d9bca8f..0000000
--- a/wallet/src/main/res/layout/fragment_json.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
-  ~ This file is part of GNU Taler
-  ~ (C) 2020 Taler Systems S.A.
-  ~
-  ~ GNU Taler is free software; you can redistribute it and/or modify it under 
the
-  ~ terms of the GNU General Public License as published by the Free Software
-  ~ Foundation; either version 3, or (at your option) any later version.
-  ~
-  ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT 
ANY
-  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
-  ~ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-  ~
-  ~ You should have received a copy of the GNU General Public License along 
with
-  ~ GNU Taler; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
-  -->
-
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android";
-    xmlns:app="http://schemas.android.com/apk/res-auto";
-    xmlns:tools="http://schemas.android.com/tools";
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:orientation="vertical">
-
-    <TextView
-        android:id="@+id/jsonView"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="8dp"
-        android:layout_marginTop="8dp"
-        android:layout_marginEnd="8dp"
-        android:layout_marginBottom="8dp"
-        android:fontFamily="monospace"
-        android:textSize="12sp"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent"
-        tools:text="[JSON]" />
-
-</ScrollView>
diff --git a/wallet/src/main/res/layout/fragment_prompt_withdraw.xml 
b/wallet/src/main/res/layout/fragment_prompt_withdraw.xml
index c9c9402..6bca4ef 100644
--- a/wallet/src/main/res/layout/fragment_prompt_withdraw.xml
+++ b/wallet/src/main/res/layout/fragment_prompt_withdraw.xml
@@ -162,12 +162,12 @@
 
     <ImageButton
         android:id="@+id/selectExchangeButton"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
+        android:layout_width="48dp"
+        android:layout_height="48dp"
         android:layout_marginEnd="16dp"
         android:backgroundTint="@color/colorPrimary"
         android:contentDescription="@string/nav_exchange_fees"
-        android:src="@drawable/ic_cash_usd_outline"
+        android:src="@drawable/ic_edit"
         android:tint="?attr/colorOnPrimary"
         android:visibility="invisible"
         app:layout_constraintBottom_toBottomOf="@+id/withdrawExchangeUrl"
diff --git a/wallet/src/main/res/layout/fragment_transactions.xml 
b/wallet/src/main/res/layout/fragment_transactions.xml
index 547da24..aaf638c 100644
--- a/wallet/src/main/res/layout/fragment_transactions.xml
+++ b/wallet/src/main/res/layout/fragment_transactions.xml
@@ -27,7 +27,7 @@
         android:scrollbars="vertical"
         android:visibility="invisible"
         app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
-        tools:listitem="@layout/list_item_history"
+        tools:listitem="@layout/list_item_transaction"
         tools:visibility="visible" />
 
     <TextView
diff --git a/wallet/src/main/res/layout/list_item_history.xml 
b/wallet/src/main/res/layout/list_item_history.xml
deleted file mode 100644
index bc94738..0000000
--- a/wallet/src/main/res/layout/list_item_history.xml
+++ /dev/null
@@ -1,75 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
-  ~ This file is part of GNU Taler
-  ~ (C) 2020 Taler Systems S.A.
-  ~
-  ~ GNU Taler is free software; you can redistribute it and/or modify it under 
the
-  ~ terms of the GNU General Public License as published by the Free Software
-  ~ Foundation; either version 3, or (at your option) any later version.
-  ~
-  ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT 
ANY
-  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
-  ~ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-  ~
-  ~ You should have received a copy of the GNU General Public License along 
with
-  ~ GNU Taler; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
-  -->
-
-<androidx.constraintlayout.widget.ConstraintLayout 
xmlns:android="http://schemas.android.com/apk/res/android";
-    xmlns:app="http://schemas.android.com/apk/res-auto";
-    xmlns:tools="http://schemas.android.com/tools";
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:background="@drawable/selectable_background"
-    android:foreground="?attr/selectableItemBackground"
-    android:paddingStart="16dp"
-    android:paddingTop="8dp"
-    android:paddingEnd="16dp"
-    android:paddingBottom="8dp">
-
-    <ImageView
-        android:id="@+id/icon"
-        android:layout_width="32dp"
-        android:layout_height="32dp"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent"
-        app:tint="?android:colorControlNormal"
-        tools:ignore="ContentDescription"
-        tools:src="@drawable/ic_cash_usd_outline" />
-
-    <TextView
-        android:id="@+id/title"
-        style="@style/TransactionTitle"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="16dp"
-        android:layout_marginEnd="8dp"
-        app:layout_constraintEnd_toStartOf="@+id/amount"
-        app:layout_constraintStart_toEndOf="@+id/icon"
-        app:layout_constraintTop_toTopOf="parent"
-        tools:text="@string/payment_title" />
-
-    <TextView
-        android:id="@+id/amount"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:textSize="24sp"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintTop_toTopOf="parent"
-        tools:text="- 1337.23" />
-
-    <TextView
-        android:id="@+id/time"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="8dp"
-        android:layout_marginEnd="8dp"
-        android:textSize="14sp"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toStartOf="@+id/amount"
-        app:layout_constraintStart_toStartOf="@+id/title"
-        app:layout_constraintTop_toBottomOf="@+id/title"
-        tools:text="23 min ago" />
-
-</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/wallet/src/main/res/menu/activity_main_drawer.xml 
b/wallet/src/main/res/menu/activity_main_drawer.xml
index 62abc32..d1cc462 100644
--- a/wallet/src/main/res/menu/activity_main_drawer.xml
+++ b/wallet/src/main/res/menu/activity_main_drawer.xml
@@ -43,10 +43,6 @@
                     android:id="@+id/nav_pending_operations"
                     android:icon="@drawable/ic_sync"
                     android:title="@string/pending_operations_title" />
-                <item
-                    android:id="@+id/nav_history"
-                    android:icon="@drawable/ic_history"
-                    android:title="@string/nav_history" />
             </group>
         </menu>
     </item>
diff --git a/wallet/src/main/res/navigation/nav_graph.xml 
b/wallet/src/main/res/navigation/nav_graph.xml
index 93db557..285fac9 100644
--- a/wallet/src/main/res/navigation/nav_graph.xml
+++ b/wallet/src/main/res/navigation/nav_graph.xml
@@ -133,7 +133,7 @@
     </fragment>
     <fragment
         android:id="@+id/selectExchangeFragment"
-        android:name="net.taler.wallet.withdraw.SelectExchangeFragment"
+        android:name="net.taler.wallet.exchanges.SelectExchangeFragment"
         android:label="@string/nav_exchange_fees"
         tools:layout="@layout/fragment_select_exchange" />
 
@@ -143,12 +143,6 @@
         android:label="@string/pending_operations_title"
         tools:layout="@layout/fragment_pending_operations" />
 
-    <fragment
-        android:id="@+id/nav_history"
-        android:name="net.taler.wallet.history.DevHistoryFragment"
-        android:label="@string/nav_history"
-        tools:layout="@layout/fragment_transactions" />
-
     <fragment
         android:id="@+id/nav_uri_input"
         android:name="net.taler.wallet.UriInputFragment"
@@ -173,10 +167,6 @@
         android:id="@+id/action_global_pending_operations"
         app:destination="@id/nav_pending_operations" />
 
-    <action
-        android:id="@+id/action_global_history"
-        app:destination="@id/nav_history" />
-
     <action
         android:id="@+id/action_nav_transaction_detail"
         app:destination="@id/nav_transactions_detail" />
diff --git a/wallet/src/main/res/values/strings.xml 
b/wallet/src/main/res/values/strings.xml
index 421d4ab..1e629a6 100644
--- a/wallet/src/main/res/values/strings.xml
+++ b/wallet/src/main/res/values/strings.xml
@@ -40,7 +40,6 @@ GNU Taler is immune against many types of fraud, such as 
phishing of credit card
     <string name="nav_prompt_withdraw">Withdraw Digital Cash</string>
     <string name="nav_exchange_tos">Exchange\'s Terms of Service</string>
     <string name="nav_exchange_fees">Exchange Fees</string>
-    <string name="nav_history">Event History</string>
     <string name="nav_error">Error</string>
 
     <string name="button_back">Go Back</string>
diff --git a/wallet/src/main/res/xml/settings_backup.xml 
b/wallet/src/main/res/xml/settings_backup.xml
index 52b72ac..f8c5839 100644
--- a/wallet/src/main/res/xml/settings_backup.xml
+++ b/wallet/src/main/res/xml/settings_backup.xml
@@ -14,7 +14,7 @@
   ~ GNU Taler; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
   -->
 
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android";
+<PreferenceScreen
     xmlns:app="http://schemas.android.com/apk/res-auto";>
 
     <SwitchPreferenceCompat
diff --git 
a/wallet/src/test/java/net/taler/wallet/crypto/Base32CrockfordTest.kt 
b/wallet/src/test/java/net/taler/wallet/crypto/Base32CrockfordTest.kt
deleted file mode 100644
index 30332fc..0000000
--- a/wallet/src/test/java/net/taler/wallet/crypto/Base32CrockfordTest.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2020 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-package net.taler.wallet.crypto
-
-import org.junit.Test
-
-class Base32CrockfordTest {
-    @Test
-    fun testBasic() {
-        val inputStr = "Hello, World"
-        val data = inputStr.toByteArray(Charsets.UTF_8)
-        val enc = Base32Crockford.encode(data)
-        println(enc)
-        val dec = Base32Crockford.decode(enc)
-        val recoveredInputStr = dec.toString(Charsets.UTF_8)
-        println(recoveredInputStr)
-
-        val foo =
-            
Base32Crockford.decode("51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30H2365338E9G6RT4AH1N6H13EGHR70RK6H1S6X2M4CSP8CSK8E1G88VKJH25610KGCHR8RWM4DJ47123CH9K89334D1S8N24ACJ48CR3EH256MR3AH1R711KCE9N6S134GSN6RW46D1H6CV3CDHJ6D0KEDHR6D24CD248MWKADHJ6WT34D25712KCD2474V46EA18H2M4GHM6WTK2E216S14CD238GSK0G9G692KCDHM6RW34CT16MV3CG9P60S34C1G70SMCHHQ8CVKJG9K6CVK6GHK70R46HJ26CR4AE9M8523ADHS8RR3EE1R74S32CHP6N1K0GT38D1M6C1R84TM2E9N8MSK2C1J71248E9H6H1MCD9J70VK4GSG6124CCHR6RS4ADSH8N0M4H1G84R4CD1G
 [...]
-        println(foo.toString(Charsets.UTF_8))
-    }
-}

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