gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated: cashout creation


From: gnunet
Subject: [taler-wallet-core] branch master updated: cashout creation
Date: Tue, 21 Nov 2023 21:10:37 +0100

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

sebasjm pushed a commit to branch master
in repository wallet-core.

The following commit(s) were added to refs/heads/master by this push:
     new 32182fb1b cashout creation
32182fb1b is described below

commit 32182fb1b912e1136ba933c4a4f204e6e2f33de2
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Tue Nov 21 17:10:07 2023 -0300

    cashout creation
---
 packages/demobank-ui/README.md                     |   4 +-
 packages/demobank-ui/dev.mjs                       |   2 +-
 ...demobank-ui-settings.js => bank-ui-settings.js} |   4 +-
 packages/demobank-ui/src/hooks/circuit.ts          |  99 +++--
 packages/demobank-ui/src/hooks/settings.ts         |   4 +-
 packages/demobank-ui/src/index.html                |   6 +-
 packages/demobank-ui/src/pages/BankFrame.tsx       |   1 +
 .../demobank-ui/src/pages/OperationState/views.tsx |  53 ---
 .../src/pages/PaytoWireTransferForm.tsx            |  16 +-
 .../demobank-ui/src/pages/ProfileNavigation.tsx    |   4 +-
 .../src/pages/WithdrawalConfirmationQuestion.tsx   |  53 ---
 .../demobank-ui/src/pages/admin/AccountForm.tsx    |  20 +-
 .../demobank-ui/src/pages/admin/AccountList.tsx    |   2 +-
 .../src/pages/admin/CashoutListForAccount.tsx      |  14 +-
 .../src/pages/business/CreateCashout.tsx           | 485 +++++++++------------
 packages/demobank-ui/src/settings.ts               |   6 +-
 packages/demobank-ui/src/stories.test.ts           |   4 +-
 .../taler-harness/src/http-client/bank-core.ts     |  20 +-
 .../taler-util/src/http-client/bank-conversion.ts  | 113 +++++
 packages/taler-util/src/http-client/bank-core.ts   |  98 +----
 packages/taler-util/src/http-client/types.ts       | 249 ++++++++---
 packages/taler-util/src/index.ts                   |   1 +
 packages/taler-util/src/taler-types.ts             |  12 -
 .../taler-wallet-core/src/operations/withdraw.ts   |  92 ++--
 .../src/wallet/AddExchange/index.ts                |   7 +-
 .../src/wallet/AddExchange/state.ts                |   8 +-
 .../src/wallet/AddExchange/views.tsx               |   6 +-
 27 files changed, 725 insertions(+), 658 deletions(-)

diff --git a/packages/demobank-ui/README.md b/packages/demobank-ui/README.md
index 877799748..109b6196b 100644
--- a/packages/demobank-ui/README.md
+++ b/packages/demobank-ui/README.md
@@ -26,7 +26,7 @@ localStorage.setItem("bank-base-url", OTHER_URL);
 ## Customizing Per-Deployment Settings
 
 To customize per-deployment settings, make sure that the
-`demobank-ui-settings.js` file is served alongside the UI.
+`bank-ui-settings.js` file is served alongside the UI.
 
 This file is loaded before the SPA and can do customizations by
 changing `globalThis.`.
@@ -35,7 +35,7 @@ For example, the following settings would correspond
 to the default settings:
 
 ```
-globalThis.talerDemobankSettings = {
+globalThis.talerBankSettings = {
   // location of libeufin server
   backendBaseURL: "https://bank.demo.taler.net/";,
   allowRegistrations: true,
diff --git a/packages/demobank-ui/dev.mjs b/packages/demobank-ui/dev.mjs
index f29a05e49..8b04155f4 100755
--- a/packages/demobank-ui/dev.mjs
+++ b/packages/demobank-ui/dev.mjs
@@ -18,7 +18,7 @@
 import { serve } from "@gnu-taler/web-util/node";
 import { initializeDev } from "@gnu-taler/web-util/build";
 
-const devEntryPoints = ["src/stories.tsx", "src/index.tsx", 
"src/demobank-ui-settings.js"];
+const devEntryPoints = ["src/stories.tsx", "src/index.tsx", 
"src/bank-ui-settings.js"];
 
 const build = initializeDev({
   type: "development",
diff --git a/packages/demobank-ui/src/demobank-ui-settings.js 
b/packages/demobank-ui/src/bank-ui-settings.js
similarity index 86%
rename from packages/demobank-ui/src/demobank-ui-settings.js
rename to packages/demobank-ui/src/bank-ui-settings.js
index 827f207f8..397fa28c0 100644
--- a/packages/demobank-ui/src/demobank-ui-settings.js
+++ b/packages/demobank-ui/src/bank-ui-settings.js
@@ -1,9 +1,9 @@
 // Values for development environment
 
 /**
- * Global settings for the demobank UI.
+ * Global settings for the bank UI.
  */
-globalThis.talerDemobankSettings = {
+globalThis.talerBankSettings = {
   backendBaseURL: "http://bank.taler.test:1180/";,
   allowRegistrations: true,
   showDemoNav: true,
diff --git a/packages/demobank-ui/src/hooks/circuit.ts 
b/packages/demobank-ui/src/hooks/circuit.ts
index 44edb4f8a..d0d180a53 100644
--- a/packages/demobank-ui/src/hooks/circuit.ts
+++ b/packages/demobank-ui/src/hooks/circuit.ts
@@ -18,7 +18,7 @@ import { useState } from "preact/hooks";
 import { MAX_RESULT_SIZE, PAGE_SIZE } from "../utils.js";
 import { useBackendState } from "./backend.js";
 
-import { AccessToken, AmountJson, AmountString, Amounts, OperationOk, 
TalerCoreBankErrorsByMethod, TalerCoreBankResultByMethod, TalerCorebankApi, 
TalerError, TalerHttpError } from "@gnu-taler/taler-util";
+import { AccessToken, AmountJson, AmountString, Amounts, OperationOk, 
TalerBankConversionResultByMethod, TalerCoreBankErrorsByMethod, 
TalerCoreBankResultByMethod, TalerCorebankApi, TalerError, TalerHttpError } 
from "@gnu-taler/taler-util";
 import _useSWR, { SWRHook } from "swr";
 import { useBankCoreApiContext } from "../context/config.js";
 import { assertUnreachable } from "../pages/WithdrawalOperationPage.js";
@@ -34,6 +34,7 @@ export type TransferCalculation = {
 };
 type EstimatorFunction = (
   amount: AmountJson,
+  currency: string,
   sellFee: AmountJson,
   sellRate: number,
 ) => Promise<TransferCalculation>;
@@ -43,50 +44,74 @@ type CashoutEstimators = {
   estimateByDebit: EstimatorFunction;
 };
 
+export function useConversionInfo() {
+  const { api, config } = useBankCoreApiContext()
+
+  async function fetcher() {
+    return await api.getConversionInfoAPI().getConfig()
+  }
+  const { data, error } = 
useSWR<TalerBankConversionResultByMethod<"getConfig">, TalerHttpError>(
+    !config.allow_conversion ? undefined : ["getConversionInfoAPI"], fetcher, {
+    refreshInterval: 0,
+    refreshWhenHidden: false,
+    revalidateOnFocus: false,
+    revalidateOnReconnect: false,
+    refreshWhenOffline: false,
+    errorRetryCount: 0,
+    errorRetryInterval: 1,
+    shouldRetryOnError: false,
+    keepPreviousData: true,
+  });
+
+  if (data) return data
+  if (error) return error;
+  return undefined;
+
+}
+
 export function useEstimator(): CashoutEstimators {
   const { state } = useBackendState();
   const { api } = useBankCoreApiContext();
   return {
-    estimateByCredit: async (amount, fee, rate) => {
-      const resp = await api.getCashoutRate({
-        credit: amount
-      });
-      if (resp.type === "fail") {
-        // can't happen
-        // not-supported: it should not be able to call this function
-        // wrong-calculation: we are using just one parameter
-        throw TalerError.fromDetail(resp.detail.code, {}, resp.detail.hint)
-      }
-      const credit = amount;
-      const _credit = { ...credit, currency: fee.currency };
-      const beforeFee = Amounts.sub(_credit, fee).amount;
+    estimateByCredit: async (fiatAmount, regionalCurrency, fee, rate) => {
+      // const resp = await api.getConversionInfoAPI().getCashoutRate({
+      //   credit: amount
+      // });
+      // if (resp.type === "fail") {
+      //   // can't happen
+      //   // not-supported: it should not be able to call this function
+      //   // wrong-calculation: we are using just one parameter
+      //   throw TalerError.fromDetail(resp.detail.code, {}, resp.detail.hint)
+      // }
+      const credit = fiatAmount;
+      const beforeFee = Amounts.sub(credit, fee).amount;
+
+      // const debit = Amounts.parseOrThrow(resp.body.amount_debit);
+      //FIXME: remove this when endpoint works
+      const debit = Amounts.add(
+        Amounts.zeroOfCurrency(regionalCurrency),
+        beforeFee
+      ).amount;
 
-      const debit = Amounts.parseOrThrow(resp.body.amount_debit);
       return {
         debit,
         beforeFee,
         credit,
       };
     },
-    estimateByDebit: async (amount, fee, rate) => {
-      const zeroBalance = Amounts.zeroOfCurrency(fee.currency);
-      const zeroFiat = Amounts.zeroOfCurrency(fee.currency);
-      const zeroCalc = {
-        debit: zeroBalance,
-        credit: zeroFiat,
-        beforeFee: zeroBalance,
-      };
-      const resp = await api.getCashoutRate({ debit: amount });
-      if (resp.type === "fail") {
-        // can't happen
-        // not-supported: it should not be able to call this function
-        // wrong-calculation: we are using just one parameter
-        throw TalerError.fromDetail(resp.detail.code, {}, resp.detail.hint)
-      }
-      const credit = Amounts.parseOrThrow(resp.body.amount_credit);
-      const _credit = { ...credit, currency: fee.currency };
-      const debit = amount;
-      const beforeFee = Amounts.sub(_credit, fee).amount;
+    estimateByDebit: async (regionalAmount, fiatCurrency, fee, rate) => {
+      // const resp = await api.getConversionInfoAPI().getCashoutRate({ debit: 
amount });
+      // if (resp.type === "fail") {
+      //   // can't happen
+      //   // not-supported: it should not be able to call this function
+      //   // wrong-calculation: we are using just one parameter
+      //   throw TalerError.fromDetail(resp.detail.code, {}, resp.detail.hint)
+      // }
+      // const credit = Amounts.parseOrThrow(resp.body.amount_credit);
+      const debit = regionalAmount;
+      const _credit = Amounts.parseOrThrow(regionalAmount);
+      const beforeFee = { ..._credit, currency: fiatCurrency };
+      const credit = Amounts.sub(beforeFee, fee).amount;
       return {
         debit,
         beforeFee,
@@ -178,7 +203,7 @@ export function useCashouts(account: string) {
   }
 
   const { data, error } = useSWR<OperationOk<{ cashouts: CashoutWithId[] }> | 
TalerCoreBankErrorsByMethod<"getAccountCashouts">, TalerHttpError>(
-    !config.have_cashout ? false : [account, token, "getAccountCashouts"], 
fetcher, {
+    !config.allow_conversion ? false : [account, token, "getAccountCashouts"], 
fetcher, {
     refreshInterval: 0,
     refreshWhenHidden: false,
     revalidateOnFocus: false,
@@ -280,7 +305,7 @@ export function useLastMonitorInfo(time: Date, timeframe: 
TalerCorebankApi.Monit
       }
     }
 
-    const previous: TalerCoreBankResultByMethod<"getMonitor">  = {
+    const previous: TalerCoreBankResultByMethod<"getMonitor"> = {
       type: "ok" as const,
       body: {
         type: "with-conversions" as const,
@@ -304,7 +329,7 @@ export function useLastMonitorInfo(time: Date, timeframe: 
TalerCorebankApi.Monit
   }
 
   const { data, error } = useSWR<LastMonitor, TalerHttpError>(
-    config.have_cashout || true ? ["useLastMonitorInfo"] : false, fetcher, {
+    config.allow_conversion || true ? ["useLastMonitorInfo"] : false, fetcher, 
{
     refreshInterval: 0,
     refreshWhenHidden: false,
     revalidateOnFocus: false,
diff --git a/packages/demobank-ui/src/hooks/settings.ts 
b/packages/demobank-ui/src/hooks/settings.ts
index 1e656b3ba..bd48ca680 100644
--- a/packages/demobank-ui/src/hooks/settings.ts
+++ b/packages/demobank-ui/src/hooks/settings.ts
@@ -73,7 +73,7 @@ const defaultSettings: Settings = {
   showDebugInfo: false,
 };
 
-const DEMOBANK_SETTINGS_KEY = buildStorageKey(
+const BANK_SETTINGS_KEY = buildStorageKey(
   "bank-settings",
   codecForSettings(),
 );
@@ -83,7 +83,7 @@ export function useSettings(): [
   <T extends keyof Settings>(key: T, value: Settings[T]) => void,
 ] {
   const { value, update } = useLocalStorage(
-    DEMOBANK_SETTINGS_KEY,
+    BANK_SETTINGS_KEY,
     defaultSettings,
   );
 
diff --git a/packages/demobank-ui/src/index.html 
b/packages/demobank-ui/src/index.html
index 315985648..f702f30ea 100644
--- a/packages/demobank-ui/src/index.html
+++ b/packages/demobank-ui/src/index.html
@@ -28,10 +28,10 @@
   <link rel="icon"
     
href="data:;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAABILAAASCwAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////7//v38//78/P/+/fz//vz7///+/v/+/f3//vz7///+/v/+/fz//v38///////////////////////+/v3///7+/////////////////////////////////////////////////////////v3//v79///////+/v3///////r28v/ct5//06SG/9Gffv/Xqo7/7N/V/9e2nf/bsJb/6uDW/9Sskf/euKH/+/j2///////+/v3//////+3azv+/eE3/2rWd/9Kkhv/Vr5T/48i2/8J+VP/Qn
 [...]
   <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon" />
-  <title>Demobank</title>
+  <title>Bank</title>
   <!-- Optional customization script.  -->
-  <script src="demobank-ui-settings.js"></script>
-  <!-- Entry point for the demobank SPA. -->
+  <script src="bank-ui-settings.js"></script>
+  <!-- Entry point for the bank SPA. -->
   <script type="module" src="index.js"></script>
   <link rel="stylesheet" href="index.css" />
 </head>
diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx 
b/packages/demobank-ui/src/pages/BankFrame.tsx
index 70d8cb4f4..f0baae3a3 100644
--- a/packages/demobank-ui/src/pages/BankFrame.tsx
+++ b/packages/demobank-ui/src/pages/BankFrame.tsx
@@ -45,6 +45,7 @@ export function BankFrame({
     if (error) {
       const desc = (error instanceof Error ? error.stack : String(error)) as 
TranslatedString
       if (error instanceof Error) {
+        console.log(error)
         notifyException(i18n.str`Internal error, please report.`, error)
       } else {
         notifyError(i18n.str`Internal error, please report.`, String(error) as 
TranslatedString)
diff --git a/packages/demobank-ui/src/pages/OperationState/views.tsx 
b/packages/demobank-ui/src/pages/OperationState/views.tsx
index 916a2bd98..e7db566ea 100644
--- a/packages/demobank-ui/src/pages/OperationState/views.tsx
+++ b/packages/demobank-ui/src/pages/OperationState/views.tsx
@@ -147,59 +147,6 @@ export function NeedConfirmationView({ error, onAbort: 
doAbort, onConfirm: doCon
         <h3 class="text-base font-semibold text-gray-900">
           <i18n.Translate>Confirm the withdrawal operation</i18n.Translate>
         </h3>
-        <div class="mt-2 max-w-xl text-sm text-gray-500">
-          <div class="px-4 mt-4 grid grid-cols-1 gap-y-6 sm:grid-cols-4 
sm:gap-x-3">
-
-            <label class={"relative sm:col-span-2 flex cursor-pointer 
rounded-lg border bg-white p-4 shadow-sm focus:outline-noneborder-indigo-600 
ring-2 ring-indigo-600"}>
-              <input type="radio" name="project-type" value="Newsletter" 
class="sr-only" aria-labelledby="project-type-0-label" 
aria-describedby="project-type-0-description-0 project-type-0-description-1" />
-              <span class="flex flex-1">
-                <span class="flex flex-col">
-                  <span id="project-type-0-label" class="block text-sm 
font-medium text-gray-900 ">
-                    <i18n.Translate>challenge response test</i18n.Translate>
-                  </span>
-                </span>
-              </span>
-              <svg class="h-5 w-5 text-indigo-600" viewBox="0 0 20 20" 
fill="currentColor" aria-hidden="true">
-                <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 
16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 
1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" />
-              </svg>
-            </label>
-
-
-            <label class="relative flex cursor-pointer rounded-lg border 
bg-gray-100  p-4 shadow-sm focus:outline-none border-gray-300">
-              <input type="radio" name="project-type" value="Existing 
Customers" class="sr-only" aria-labelledby="project-type-1-label" 
aria-describedby="project-type-1-description-0 project-type-1-description-1" />
-              <span class="flex flex-1">
-                <span class="flex flex-col">
-                  <span id="project-type-1-label" class="block text-sm 
font-medium text-gray-900">
-                    <i18n.Translate>using SMS</i18n.Translate>
-                  </span>
-                  <span id="project-type-1-description-0" class="mt-1 flex 
items-center text-sm text-gray-500">
-                    <i18n.Translate>not available</i18n.Translate>
-                  </span>
-                </span>
-              </span>
-              <svg class="h-5 w-5 text-indigo-600 hidden" viewBox="0 0 20 20" 
fill="currentColor" aria-hidden="true">
-                <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 
16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 
1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" />
-              </svg>
-            </label>
-
-            <label class="relative flex cursor-pointer rounded-lg border 
bg-gray-100 p-4 shadow-sm focus:outline-none border-gray-300">
-              <input type="radio" name="project-type" value="Existing 
Customers" class="sr-only" aria-labelledby="project-type-1-label" 
aria-describedby="project-type-1-description-0 project-type-1-description-1" />
-              <span class="flex flex-1">
-                <span class="flex flex-col">
-                  <span id="project-type-1-label" class="block text-sm 
font-medium text-gray-900">
-                    <i18n.Translate>one time password</i18n.Translate>
-                  </span>
-                  <span id="project-type-1-description-0" class="mt-1 flex 
items-center text-sm text-gray-500">
-                    <i18n.Translate>not available</i18n.Translate>
-                  </span>
-                </span>
-              </span>
-              <svg class="h-5 w-5 text-indigo-600 hidden" viewBox="0 0 20 20" 
fill="currentColor" aria-hidden="true">
-                <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 
16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 
1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" />
-              </svg>
-            </label>
-          </div>
-        </div>
         <div class="mt-3 text-sm leading-6">
 
           <form
diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx 
b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
index 4f7b25f6d..e9d254332 100644
--- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
+++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
@@ -458,8 +458,8 @@ export function InputAmount(
             if (!onChange) return;
             const l = e.currentTarget.value.length
             const sep_pos = e.currentTarget.value.indexOf(FRAC_SEPARATOR)
-            if (sep_pos !== -1 && l - sep_pos - 1 > 
config.currency.num_fractional_input_digits) {
-              e.currentTarget.value = e.currentTarget.value.substring(0, 
sep_pos + config.currency.num_fractional_input_digits + 1)
+            if (sep_pos !== -1 && l - sep_pos - 1 > 
config.currency_specification.num_fractional_input_digits) {
+              e.currentTarget.value = e.currentTarget.value.substring(0, 
sep_pos + config.currency_specification.num_fractional_input_digits + 1)
             }
             onChange(e.currentTarget.value);
           }}
@@ -470,21 +470,21 @@ export function InputAmount(
   );
 }
 
-export function RenderAmount({ value, negative, noCurrency }: { value: 
AmountJson, negative?: boolean, noCurrency?: boolean }): VNode {
+export function RenderAmount({ value, negative }: { value: AmountJson, 
negative?: boolean }): VNode {
   const { config } = useBankCoreApiContext()
   const str = Amounts.stringifyValue(value)
   const sep_pos = str.indexOf(FRAC_SEPARATOR)
-  if (sep_pos !== -1 && str.length - sep_pos - 1 > 
config.currency.num_fractional_normal_digits) {
-    const limit = sep_pos + config.currency.num_fractional_normal_digits + 1
+  if (sep_pos !== -1 && str.length - sep_pos - 1 > 
config.currency_specification.num_fractional_normal_digits) {
+    const limit = sep_pos + 
config.currency_specification.num_fractional_normal_digits + 1
     const normal = str.substring(0, limit)
     const small = str.substring(limit)
-    return <span class="whitespace-nowrap">
+    return <span data-negative={negative} class="whitespace-nowrap 
data-[negative=true]:bg-red-400">
       {negative ? "-" : undefined}
-      {noCurrency ? undefined : value.currency} {normal} <sup 
class="-ml-2">{small}</sup>
+      {value.currency} {normal} <sup class="-ml-1">{small}</sup>
     </span>
   }
   return <span class="whitespace-nowrap">
     {negative ? "-" : undefined}
-    {noCurrency ? undefined : value.currency} {str}
+    {value.currency} {str}
   </span>
 }
\ No newline at end of file
diff --git a/packages/demobank-ui/src/pages/ProfileNavigation.tsx 
b/packages/demobank-ui/src/pages/ProfileNavigation.tsx
index 20a1ececd..1a4b4b865 100644
--- a/packages/demobank-ui/src/pages/ProfileNavigation.tsx
+++ b/packages/demobank-ui/src/pages/ProfileNavigation.tsx
@@ -29,7 +29,7 @@ export function ProfileNavigation({ current }: { current: 
"details" | "credentia
       }}>
         <option value="details" selected={current == 
"details"}><i18n.Translate>Details</i18n.Translate></option>
         <option value="credentials" selected={current == 
"credentials"}><i18n.Translate>Credentials</i18n.Translate></option>
-        {config.have_cashout ?
+        {config.allow_conversion ?
           <option value="cashouts" selected={current == 
"cashouts"}><i18n.Translate>Cashouts</i18n.Translate></option>
           : undefined}
       </select>
@@ -44,7 +44,7 @@ export function ProfileNavigation({ current }: { current: 
"details" | "credentia
           <span><i18n.Translate>Credentials</i18n.Translate></span>
           <span aria-hidden="true" data-selected={current == "credentials"} 
class="bg-transparent data-[selected=true]:bg-indigo-500 absolute inset-x-0 
bottom-0 h-0.5"></span>
         </a>
-        {config.have_cashout ?
+        {config.allow_conversion ?
           <a href="#/my-cashouts" data-selected={current == "cashouts"} 
class="rounded-r-lg text-gray-500 hover:text-gray-700 
data-[selected=true]:text-gray-900 group relative min-w-0 flex-1 
overflow-hidden bg-white py-4 px-4 text-center text-sm font-medium 
hover:bg-gray-50 focus:z-10">
             <span>Cashouts</span>
             <span aria-hidden="true" data-selected={current == "cashouts"} 
class="bg-transparent data-[selected=true]:bg-indigo-500 absolute inset-x-0 
bottom-0 h-0.5"></span>
diff --git a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx 
b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
index 0b339030e..7fec76d2f 100644
--- a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
@@ -174,59 +174,6 @@ export function WithdrawalConfirmationQuestion({
           <h3 class="text-base font-semibold text-gray-900">
             <i18n.Translate>Confirm the withdrawal operation</i18n.Translate>
           </h3>
-          <div class="mt-2 max-w-xl text-sm text-gray-500">
-            <div class="px-4 mt-4 grid grid-cols-1 gap-y-6 sm:grid-cols-3 
sm:gap-x-3">
-
-              <label class={"relative flex cursor-pointer rounded-lg border 
bg-white p-4 shadow-sm focus:outline-noneborder-indigo-600 ring-2 
ring-indigo-600"}>
-                <input type="radio" name="project-type" value="Newsletter" 
class="sr-only" aria-labelledby="project-type-0-label" 
aria-describedby="project-type-0-description-0 project-type-0-description-1" />
-                <span class="flex flex-1">
-                  <span class="flex flex-col">
-                    <span id="project-type-0-label" class="block text-sm 
font-medium text-gray-900 ">
-                      <i18n.Translate>challenge response test</i18n.Translate>
-                    </span>
-                  </span>
-                </span>
-                <svg class="h-5 w-5 text-indigo-600" viewBox="0 0 20 20" 
fill="currentColor" aria-hidden="true">
-                  <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 
16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 
1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" />
-                </svg>
-              </label>
-
-
-              <label class="relative flex cursor-pointer rounded-lg border 
bg-gray-100  p-4 shadow-sm focus:outline-none border-gray-300">
-                <input type="radio" name="project-type" value="Existing 
Customers" class="sr-only" aria-labelledby="project-type-1-label" 
aria-describedby="project-type-1-description-0 project-type-1-description-1" />
-                <span class="flex flex-1">
-                  <span class="flex flex-col">
-                    <span id="project-type-1-label" class="block text-sm 
font-medium text-gray-900">
-                      <i18n.Translate>using SMS</i18n.Translate>
-                    </span>
-                    <span id="project-type-1-description-0" class="mt-1 flex 
items-center text-sm text-gray-500">
-                      <i18n.Translate>not available</i18n.Translate>
-                    </span>
-                  </span>
-                </span>
-                <svg class="h-5 w-5 text-indigo-600 hidden" viewBox="0 0 20 
20" fill="currentColor" aria-hidden="true">
-                  <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 
16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 
1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" />
-                </svg>
-              </label>
-
-              <label class="relative flex cursor-pointer rounded-lg border 
bg-gray-100 p-4 shadow-sm focus:outline-none border-gray-300">
-                <input type="radio" name="project-type" value="Existing 
Customers" class="sr-only" aria-labelledby="project-type-1-label" 
aria-describedby="project-type-1-description-0 project-type-1-description-1" />
-                <span class="flex flex-1">
-                  <span class="flex flex-col">
-                    <span id="project-type-1-label" class="block text-sm 
font-medium text-gray-900">
-                      <i18n.Translate>one time password</i18n.Translate>
-                    </span>
-                    <span id="project-type-1-description-0" class="mt-1 flex 
items-center text-sm text-gray-500">
-                      <i18n.Translate>not available</i18n.Translate>
-                    </span>
-                  </span>
-                </span>
-                <svg class="h-5 w-5 text-indigo-600 hidden" viewBox="0 0 20 
20" fill="currentColor" aria-hidden="true">
-                  <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 
16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 
1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" />
-                </svg>
-              </label>
-            </div>
-          </div>
           <div class="mt-3 text-sm leading-6">
 
             <div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-10 md:grid-cols-3 
bg-gray-100 my-4 px-4 pb-4 rounded-lg">
diff --git a/packages/demobank-ui/src/pages/admin/AccountForm.tsx 
b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
index 7311d826e..4fcc32484 100644
--- a/packages/demobank-ui/src/pages/admin/AccountForm.tsx
+++ b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
@@ -3,7 +3,7 @@ import { ShowInputErrorLabel } from 
"@gnu-taler/web-util/browser";
 import { PartialButDefined, RecursivePartial, WithIntermediate, 
undefinedIfEmpty, validateIBAN } from "../../utils.js";
 import { useEffect, useRef, useState } from "preact/hooks";
 import { useTranslationContext } from "@gnu-taler/web-util/browser";
-import { PaytoString, TalerCorebankApi, buildPayto, parsePaytoUri } from 
"@gnu-taler/taler-util";
+import { PaytoString, TalerCorebankApi, buildPayto, parsePaytoUri, 
stringifyPaytoUri } from "@gnu-taler/taler-util";
 import { doAutoFocus } from "../PaytoWireTransferForm.js";
 import { CopyButton } from "@gnu-taler/web-util/browser";
 import { assertUnreachable } from "../WithdrawalOperationPage.js";
@@ -80,7 +80,16 @@ export function AccountForm({
     });
     setErrors(errors);
     setForm(newForm);
-    onChange(errors === undefined ? (newForm as any) : undefined);
+    if (errors) {
+      onChange(undefined)
+    } else {
+      const cashout = buildPayto("iban", newForm.cashout_payto_uri!, undefined)
+      const account: AccountFormData = {
+        ...newForm as any, 
+        cashout_payto_uri: stringifyPaytoUri(cashout)
+      }
+      onChange(account);
+    }
   }
 
   return (
@@ -296,6 +305,13 @@ function initializeFromTemplate(
   if (typeof initial.contact_data === "undefined") {
     initial.contact_data = emptyContact;
   }
+  if (initial.cashout_payto_uri) {
+    const ac = parsePaytoUri(initial.cashout_payto_uri)
+    if (ac?.isKnown && ac.targetType === "iban") {
+      // we are using the cashout field for the iban number
+      initial.cashout_payto_uri = ac.targetPath as any
+    }
+  }
   const result: WithIntermediate<AccountFormData> = initial as any // FIXME: 
check types
   result.username = username
 
diff --git a/packages/demobank-ui/src/pages/admin/AccountList.tsx 
b/packages/demobank-ui/src/pages/admin/AccountList.tsx
index 2aefde715..8c018120d 100644
--- a/packages/demobank-ui/src/pages/admin/AccountList.tsx
+++ b/packages/demobank-ui/src/pages/admin/AccountList.tsx
@@ -119,7 +119,7 @@ export function AccountList({ onRemoveAccount, 
onShowAccountDetails, onUpdateAcc
                           change password
                         </a>
                         <br />
-                        {config.have_cashout ?
+                        {config.allow_conversion ?
                           <Fragment>
 
                             <a href="#" class="text-indigo-600 
hover:text-indigo-900" onClick={(e) => {
diff --git a/packages/demobank-ui/src/pages/admin/CashoutListForAccount.tsx 
b/packages/demobank-ui/src/pages/admin/CashoutListForAccount.tsx
index 466dc1a4b..3aefb32af 100644
--- a/packages/demobank-ui/src/pages/admin/CashoutListForAccount.tsx
+++ b/packages/demobank-ui/src/pages/admin/CashoutListForAccount.tsx
@@ -3,6 +3,8 @@ import { Fragment, VNode, h } from "preact";
 import { Cashouts } from "../../components/Cashouts/index.js";
 import { useBackendState } from "../../hooks/backend.js";
 import { ProfileNavigation } from "../ProfileNavigation.js";
+import { CreateNewAccount } from "./CreateNewAccount.js";
+import { CreateCashout } from "../business/CreateCashout.js";
 
 interface Props {
   account: string,
@@ -27,20 +29,22 @@ export function CashoutListForAccount({ account, 
onSelected, onClose }: Props):
         <i18n.Translate>Cashout for account {account}</i18n.Translate>
       </h1>
     }
+
+    <CreateCashout onCancel={() => {}} onComplete={() => {}} account={account} 
/>
     <Cashouts
       account={account}
       onSelected={onSelected}
     />
     <p>
-      <input
-        class="pure-button"
-        type="submit"
-        value={i18n.str`Close`}
+      <button
+        class="flex items-center justify-between gap-x-6 border-t 
border-gray-900/10 px-4 py-4 sm:px-8"
         onClick={async (e) => {
           e.preventDefault();
           onClose();
         }}
-      />
+      >
+        {i18n.str`Close`}
+        </button>
     </p>
   </Fragment>
 }
diff --git a/packages/demobank-ui/src/pages/business/CreateCashout.tsx 
b/packages/demobank-ui/src/pages/business/CreateCashout.tsx
index 525a170bc..771004ec6 100644
--- a/packages/demobank-ui/src/pages/business/CreateCashout.tsx
+++ b/packages/demobank-ui/src/pages/business/CreateCashout.tsx
@@ -36,6 +36,7 @@ import { useBankCoreApiContext } from 
"../../context/config.js";
 import { useAccountDetails } from "../../hooks/access.js";
 import { useBackendState } from "../../hooks/backend.js";
 import {
+  useConversionInfo,
   useEstimator
 } from "../../hooks/circuit.js";
 import {
@@ -43,11 +44,12 @@ import {
   undefinedIfEmpty
 } from "../../utils.js";
 import { LoginForm } from "../LoginForm.js";
-import { InputAmount } from "../PaytoWireTransferForm.js";
+import { InputAmount, RenderAmount, doAutoFocus } from 
"../PaytoWireTransferForm.js";
 import { assertUnreachable } from "../WithdrawalOperationPage.js";
 
 interface Props {
   account: string;
+  focus?: boolean,
   onComplete: (id: string) => void;
   onCancel: () => void;
 }
@@ -66,6 +68,7 @@ type ErrorFrom<T> = {
 export function CreateCashout({
   account: accountName,
   onComplete,
+  focus,
   onCancel,
 }: Props): VNode {
   const { i18n } = useTranslationContext();
@@ -77,20 +80,15 @@ export function CreateCashout({
   const { state } = useBackendState()
   const creds = state.status !== "loggedIn" ? undefined : state
   const { api, config } = useBankCoreApiContext()
-  const [form, setForm] = useState<Partial<FormType>>({ isDebit: true });
+  const [form, setForm] = useState<Partial<FormType>>({ isDebit: true, 
amount:"2" });
   const [notification, notify, handleError] = useLocalNotification()
+  const info = useConversionInfo();
 
-  if (!config.have_cashout) {
+  if (!config.allow_conversion) {
     return <Attention type="warning" title={i18n.str`Unable to create a 
cashout`} onClose={onCancel}>
       <i18n.Translate>The bank configuration does not support cashout 
operations.</i18n.Translate>
     </Attention>
   }
-  if (!config.fiat_currency) {
-    return <Attention type="warning" title={i18n.str`Unable to create a 
cashout`} onClose={onCancel}>
-      <i18n.Translate>The bank configuration support cashout operations but 
there is no fiat currency.</i18n.Translate>
-    </Attention>
-  }
-
   if (!resultAccount) {
     return <Loading />
   }
@@ -104,15 +102,13 @@ export function CreateCashout({
       default: assertUnreachable(resultAccount)
     }
   }
+  if (!info) {
+    return <Loading />
+  }
 
-  // if (resultRatios.type === "fail") {
-  //   switch (resultRatios.case) {
-  //     case "not-supported": return <div>cashout operations are not 
supported</div>
-  //     default: assertUnreachable(resultRatios.case)
-  //   }
-  // }
-
-  // const ratio = resultRatios.body
+  if (info instanceof TalerError) {
+    return <ErrorLoading error={info} />
+  }
 
   const account = {
     balance: Amounts.parseOrThrow(resultAccount.body.balance.amount),
@@ -120,37 +116,46 @@ export function CreateCashout({
     debitThreshold: Amounts.parseOrThrow(resultAccount.body.debit_threshold)
   }
 
-  const zero = Amounts.zeroOfCurrency(account.balance.currency);
+  const {fiat_currency, regional_currency, cashout_ratio, cashout_fee} = 
info.body
+  const regionalZero = Amounts.zeroOfCurrency(regional_currency);
+  const fiatZero = Amounts.zeroOfCurrency(fiat_currency);
   const limit = account.balanceIsDebit
     ? Amounts.sub(account.debitThreshold, account.balance).amount
     : Amounts.add(account.balance, account.debitThreshold).amount;
 
-  const zeroCalc = { debit: zero, credit: zero, beforeFee: zero };
+  const zeroCalc = { debit: regionalZero, credit: fiatZero, beforeFee: 
regionalZero };
   const [calc, setCalc] = useState(zeroCalc);
 
-  const sellRate = config.conversion_info?.sell_at_ratio;
-  const sellFee = !config.conversion_info?.sell_out_fee
-    ? zero
-    : Amounts.parseOrThrow(
-      `${account.balance.currency}:${config.conversion_info.sell_out_fee}`,
-    );
+  const sellRate = Number.parseFloat(cashout_ratio);
+  const sellFee = !cashout_fee
+    ? fiatZero
+    : Amounts.parseOrThrow(cashout_fee);
 
-  if (sellRate === undefined || sellRate < 0) return <div>error rate</div>;
+  if (sellRate === undefined || sellRate < 0) return <div>error rate d
+    <pre>
+      {JSON.stringify(info.body, undefined, 2)}
+    </pre>
+  </div>;
 
   const safeSellRate = sellRate
 
-  const amount = Amounts.parseOrThrow(
-    `${!form.isDebit ? config.fiat_currency.name : 
account.balance.currency}:${!form.amount ? "0" : form.amount
-    }`,
+  /**
+   * can be in regional currency or fiat currency
+   * depending on the isDebit flag
+   */
+  const inputAmount = Amounts.parseOrThrow(
+    `${form.isDebit ? regional_currency : fiat_currency}:${!form.amount ? "0" 
: form.amount}`,
   );
 
   useEffect(() => {
     async function doAsync() {
       await handleError(async () => {
-        const resp = await (form.isDebit ?
-          calculateFromDebit(amount, sellFee, safeSellRate) :
-          calculateFromCredit(amount, sellFee, safeSellRate));
-        setCalc(resp)
+        if (Amounts.isNonZero(inputAmount)) {
+          const resp = await (form.isDebit ?
+            calculateFromDebit(inputAmount, fiat_currency, sellFee, 
safeSellRate) :
+            calculateFromCredit(inputAmount, regional_currency, sellFee, 
safeSellRate));
+          setCalc(resp)
+        }
       })
     }
     doAsync()
@@ -164,256 +169,202 @@ export function CreateCashout({
   const errors = undefinedIfEmpty<ErrorFrom<typeof form>>({
     amount: !form.amount
       ? i18n.str`required`
-      : !amount
+      : !inputAmount
         ? i18n.str`could not be parsed`
         : Amounts.cmp(limit, calc.debit) === -1
           ? i18n.str`balance is not enough`
-          : Amounts.cmp(calc.beforeFee, sellFee) === -1
+          : Amounts.cmp(calc.credit, sellFee) === -1
             ? i18n.str`the total amount to transfer does not cover the fees`
             : Amounts.isZero(calc.credit)
               ? i18n.str`the total transfer at destination will be zero`
               : undefined,
     channel: !form.channel ? i18n.str`required` : undefined,
   });
+  const trimmedAmountStr = form.amount?.trim();
 
   return (
     <div>
       <LocalNotificationBanner notification={notification} />
-      <h1>New cashout</h1>
-      <form class="pure-form">
-        <fieldset>
-          <label>{i18n.str`Subject`}</label>
-          <input
-            value={form.subject ?? ""}
-            onChange={(e) => {
-              form.subject = e.currentTarget.value;
-              updateForm(structuredClone(form));
-            }}
-          />
-          <ShowInputErrorLabel
-            message={errors?.subject}
-            isDirty={form.subject !== undefined}
-          />
-        </fieldset>
-        <fieldset>
-          <label for="amount">
-            {form.isDebit
-              ? i18n.str`Amount to send`
-              : i18n.str`Amount to receive`}
-
-          </label>
-          <div style={{ display: "flex" }}>
-            <InputAmount
-              name="amount"
-              currency={amount.currency}
-              value={form.amount}
-              onChange={(v) => {
-                form.amount = v;
-                updateForm(structuredClone(form));
-              }}
-              error={errors?.amount}
-            />
-            <label class="toggle" style={{ marginLeft: 4, marginTop: 0 }}>
-              <input
-                class="toggle-checkbox"
-                type="checkbox"
-                name="asd"
-                onChange={(e): void => {
-                  form.isDebit = !form.isDebit;
-                  updateForm(structuredClone(form));
-                }}
-              />
-              <div class="toggle-switch"></div>
-            </label>
+
+      <div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-10 md:grid-cols-3 
bg-gray-100 my-4 px-4 pb-4 rounded-lg">
+
+        <section class="mt-4 rounded-sm px-4 py-6 p-8 ">
+          <h2 id="summary-heading" class="font-medium text-lg">Cashout</h2>
+
+          <dl class="mt-4 space-y-4">
+            <div class="justify-between items-center flex">
+              <dt class="text-sm text-gray-600">Convertion rate</dt>
+              <dd class="text-sm text-gray-900">{sellRate}</dd>
+            </div>
+
+
+            <div class="flex items-center justify-between border-t-2 afu pt-4">
+              <dt class="flex items-center text-sm text-gray-600">
+                <span>Current balance</span>
+              </dt>
+              <dd class="text-sm text-gray-900">
+                <RenderAmount value={account.balance} />
+              </dd>
+            </div>
+            <div class="flex items-center justify-between border-t-2 afu pt-4">
+              <dt class="flex items-center text-sm text-gray-600">
+                <span>Cashout fee</span>
+              </dt>
+              <dd class="text-sm text-gray-900">
+                <RenderAmount value={sellFee} />
+              </dd>
+            </div>
+          </dl>
+
+        </section>
+        <form
+          class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl 
md:col-span-2"
+          autoCapitalize="none"
+          autoCorrect="off"
+          onSubmit={e => {
+            e.preventDefault()
+          }}
+        >
+          <div class="px-4 py-6 sm:p-8">
+            <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 
sm:grid-cols-6">
+              {/* subject */}
+
+              <div class="sm:col-span-5">
+                <label
+                  class="block text-sm font-medium leading-6 text-gray-900"
+                  for="subject"
+                >
+                  {i18n.str`Subject`}
+                </label>
+                <div class="mt-2">
+                  <input
+                    ref={focus ? doAutoFocus : undefined}
+                    type="text"
+                    class="block w-full rounded-md border-0 py-1.5 
text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 
data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 
focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
+                    name="subject"
+                    id="subject"
+                    data-error={!!errors?.subject && form.subject !== 
undefined}
+                    value={form.subject ?? ""}
+                    onChange={(e) => {
+                      form.subject = e.currentTarget.value;
+                      updateForm(structuredClone(form));
+                    }}
+                    autocomplete="off"
+                  />
+                  <ShowInputErrorLabel
+                    message={errors?.subject}
+                    isDirty={form.subject !== undefined}
+                  />
+                </div>
+
+              </div>
+
+              {/* amount */}
+              <div class="sm:col-span-5">
+                <label
+                  class="block text-sm font-medium leading-6 text-gray-900"
+                  for="amount"
+                >
+                  {form.isDebit
+                    ? i18n.str`Amount to send`
+                    : i18n.str`Amount to receive`}
+                </label>
+                <div class="mt-2">
+                  <InputAmount
+                    name="amount"
+                    left
+                    currency={limit.currency}
+                    value={trimmedAmountStr}
+                    onChange={(value) => {
+                      form.amount = value;
+                      updateForm(structuredClone(form));
+                    }}
+                  />
+                  <ShowInputErrorLabel
+                    message={errors?.amount}
+                    isDirty={form.amount !== undefined}
+                  />
+                </div>
+
+              </div>
+
+              {Amounts.isZero(calc.credit) ? undefined : (
+                <div class="sm:col-span-5">
+                  <dl class="mt-4 space-y-4">
+
+                    <div class="justify-between items-center flex">
+                      <dt class="text-sm text-gray-600">Total cost</dt>
+                      <dd class="text-sm text-gray-900">
+                        <RenderAmount value={calc.debit} negative />
+                      </dd>
+                    </div>
+
+
+                    <div class="flex items-center justify-between border-t-2 
afu pt-4">
+                      <dt class="flex items-center text-sm text-gray-600">
+                        <span>Balance after</span>
+                        {/* <a href="#" class="ml-2 shrink-0 text-gray-400 
bkx">
+                  <span class="sr-only">Learn more about how shipping is 
calculated</span>
+                  <svg xmlns="http://www.w3.org/2000/svg"; viewBox="0 0 20 20" 
fill="currentColor" aria-hidden="true"
+                    class="w-5 h-5"><path fill-rule="evenodd" d="M18 10a8 8 0 
11-16 0 8 8 0 0116 0zM8.94 6.94a.75.75 0 11-1.061-1.061 3 3 0 112.871 
5.026v.345a.75.75 0 01-1.5 0v-.5c0-.72.57-1.172 1.081-1.287A1.5 1.5 0 108.94 
6.94zM10 15a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd"></path></svg>
+                </a> */}
+                      </dt>
+                      <dd class="text-sm text-gray-900">
+                        <RenderAmount value={balanceAfter} />
+                      </dd>
+                    </div>
+                    {Amounts.isZero(sellFee) || Amounts.isZero(calc.beforeFee) 
? undefined : (
+                      <div class="flex items-center justify-between border-t-2 
afu pt-4">
+                        <dt class="flex items-center text-sm text-gray-600">
+                          <span>Amount after conversion</span>
+                          {/* <a href="#" class="ml-2 shrink-0 text-gray-400 
bkx">
+                  <span class="sr-only">Learn more about how shipping is 
calculated</span>
+                  <svg xmlns="http://www.w3.org/2000/svg"; viewBox="0 0 20 20" 
fill="currentColor" aria-hidden="true"
+                    class="w-5 h-5"><path fill-rule="evenodd" d="M18 10a8 8 0 
11-16 0 8 8 0 0116 0zM8.94 6.94a.75.75 0 11-1.061-1.061 3 3 0 112.871 
5.026v.345a.75.75 0 01-1.5 0v-.5c0-.72.57-1.172 1.081-1.287A1.5 1.5 0 108.94 
6.94zM10 15a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd"></path></svg>
+                </a> */}
+                        </dt>
+                        <dd class="text-sm text-gray-900">
+                          <RenderAmount value={calc.beforeFee} />
+                        </dd>
+                      </div>
+                    )}
+                    <div class="flex justify-between items-center border-t-2 
afu pt-4">
+                      <dt class="text-lg text-gray-900 font-medium">Total 
cashout transfer</dt>
+                      <dd class="text-lg text-gray-900 font-medium">
+                        <RenderAmount value={calc.credit} />
+                      </dd>
+                    </div>
+                  </dl>
+                </div>
+              )}
+
+              {/* channel */}
+            </div>
           </div>
-        </fieldset>
-        <fieldset>
-          <label>{i18n.str`Conversion rate`}</label>
-          <input value={sellRate} disabled />
-        </fieldset>
-        <fieldset>
-          <label for="balance-now">{i18n.str`Balance now`}</label>
-          <InputAmount
-            name="banace-now"
-            currency={account.balance.currency}
-            value={Amounts.stringifyValue(account.balance)}
-          />
-        </fieldset>
-        <fieldset>
-          <label for="total-cost"
-            style={{ fontWeight: "bold", color: "red" }}
-          >{i18n.str`Total cost`}</label>
-          <InputAmount
-            name="total-cost"
-            currency={account.balance.currency}
-            value={Amounts.stringifyValue(calc.debit)}
-          />
-        </fieldset>
-        <fieldset>
-          <label for="balance-after">{i18n.str`Balance after`}</label>
-          <InputAmount
-            name="balance-after"
-            currency={account.balance.currency}
-            value={balanceAfter ? Amounts.stringifyValue(balanceAfter) : ""}
-          />
-        </fieldset>{" "}
-        {Amounts.isZero(sellFee) ? undefined : (
-          <Fragment>
-            <fieldset>
-              <label for="amount-conversiojn">{i18n.str`Amount after 
conversion`}</label>
-              <InputAmount
-                name="amount-conversion"
-                currency={config.fiat_currency.name}
-                value={Amounts.stringifyValue(calc.beforeFee)}
-              />
-            </fieldset>
-
-            <fieldset>
-              <label form="cashout-fee">{i18n.str`Cashout fee`}</label>
-              <InputAmount
-                name="cashout-fee"
-                currency={config.fiat_currency.name}
-                value={Amounts.stringifyValue(sellFee)}
-              />
-            </fieldset>
-          </Fragment>
-        )}
-        <fieldset>
-          <label for="total"
-            style={{ fontWeight: "bold", color: "green" }}
-          >{i18n.str`Total cashout transfer`}</label>
-          <InputAmount
-            name="total"
-            currency={config.fiat_currency.name}
-            value={Amounts.stringifyValue(calc.credit)}
-          />
-        </fieldset>
-        <fieldset>
-          <label>{i18n.str`Confirmation channel`}</label>
-
-          <div class="channel">
-            <input
-              class={
-                "pure-button content " +
-                (form.channel === TanChannel.EMAIL
-                  ? "pure-button-primary"
-                  : "pure-button-secondary")
-              }
-              type="submit"
-              value={i18n.str`Email`}
-              onClick={async (e) => {
-                e.preventDefault();
-                form.channel = TanChannel.EMAIL;
-                updateForm(structuredClone(form));
-              }}
-            />
-            <input
-              class={
-                "pure-button content " +
-                (form.channel === TanChannel.SMS
-                  ? "pure-button-primary"
-                  : "pure-button-secondary")
-              }
-              type="submit"
-              value={i18n.str`SMS`}
-              onClick={async (e) => {
-                e.preventDefault();
-                form.channel = TanChannel.SMS;
-                updateForm(structuredClone(form));
+
+          <div class="flex items-center justify-between gap-x-6 border-t 
border-gray-900/10 px-4 py-4 sm:px-8">
+            {onCancel ?
+              <button type="button" class="text-sm font-semibold leading-6 
text-gray-900"
+                onClick={onCancel}
+              >
+                <i18n.Translate>Cancel</i18n.Translate>
+              </button>
+              : <div />
+            }
+            <button type="submit"
+              class="disabled:opacity-50 disabled:cursor-default 
cursor-pointer rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold 
text-white shadow-sm hover:bg-indigo-500 focus-visible:outline 
focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-indigo-600"
+              disabled={!!errors}
+              onClick={(e) => {
+                e.preventDefault()
+                // doChangePassword()
               }}
-            />
+            >
+              <i18n.Translate>Change</i18n.Translate>
+            </button>
           </div>
-          <ShowInputErrorLabel
-            message={errors?.channel}
-            isDirty={form.channel !== undefined}
-          />
-        </fieldset>
-        <br />
-        <div style={{ display: "flex", justifyContent: "space-between" }}>
-          <button
-            class="pure-button pure-button-secondary btn-cancel"
-            onClick={(e) => {
-              e.preventDefault();
-              onCancel();
-            }}
-          >
-            {i18n.str`Cancel`}
-          </button>
-
-          <button
-            class="pure-button pure-button-primary btn-register"
-            type="submit"
-            disabled={!!errors}
-            onClick={async (e) => {
-              e.preventDefault();
-
-              if (errors || !creds) return;
-              await handleError(async () => {
-                const request_uid = encodeCrock(getRandomBytes(16))
-                const resp = await api.createCashout(creds, {
-                  request_uid,
-                  amount_credit: Amounts.stringify(calc.credit),
-                  amount_debit: Amounts.stringify(calc.debit),
-                  subject: form.subject,
-                  tan_channel: form.channel,
-                });
-                if (resp.type === "ok") {
-                  mutate(() => true)// clean cashout list
-                  onComplete(resp.body.cashout_id);
-                } else {
-                  switch (resp.case) {
-                    case "incorrect-exchange-rate": return notify({
-                      type: "error",
-                      title: i18n.str`The exchange rate was incorrectly 
applied`,
-                      description: resp.detail.hint as TranslatedString,
-                      debug: resp.detail,
-                    });
-                    case "no-contact-info": return notify({
-                      type: "error",
-                      title: i18n.str`Need a contact data where to send the 
TAN`,
-                      description: resp.detail.hint as TranslatedString,
-                      debug: resp.detail,
-                    });
-                    case "no-enough-balance": return notify({
-                      type: "error",
-                      title: i18n.str`The account does not have sufficient 
funds`,
-                      description: resp.detail.hint as TranslatedString,
-                      debug: resp.detail,
-                    });
-                    case "account-not-found": return notify({
-                      type: "error",
-                      title: i18n.str`Account not found`,
-                      description: resp.detail.hint as TranslatedString,
-                      debug: resp.detail,
-                    });
-                    case "cashout-not-supported": return notify({
-                      type: "error",
-                      title: i18n.str`The bank does not support cashout`,
-                      description: resp.detail.hint as TranslatedString,
-                      debug: resp.detail,
-                    });
-                    case "request-already-used": return notify({
-                      type: "error",
-                      title: i18n.str`Duplicated request found, try again.`,
-                      description: resp.detail.hint as TranslatedString,
-                      debug: resp.detail,
-                    });
-                    case "tan-failed": return notify({
-                      type: "error",
-                      title: i18n.str`Server couldn't send the confirmation 
request.`,
-                      description: resp.detail.hint as TranslatedString,
-                      debug: resp.detail,
-                    });
-                    default: assertUnreachable(resp)
-                  }
-                }
-              })
-            }}
-          >
-            {i18n.str`Create`}
-          </button>
-        </div>
-      </form>
+        </form>
+      </div>
+
     </div>
   );
 }
diff --git a/packages/demobank-ui/src/settings.ts 
b/packages/demobank-ui/src/settings.ts
index 44a016de6..f17d1d511 100644
--- a/packages/demobank-ui/src/settings.ts
+++ b/packages/demobank-ui/src/settings.ts
@@ -26,7 +26,7 @@ export interface BankUiSettings {
 }
 
 /**
- * Global settings for the demobank UI.
+ * Global settings for the bank UI.
  */
 const defaultSettings: BankUiSettings = {
   backendBaseURL: "https://bank.demo.taler.net/demobanks/default/";,
@@ -46,6 +46,6 @@ const defaultSettings: BankUiSettings = {
 };
 
 export const bankUiSettings: BankUiSettings =
-  "talerDemobankSettings" in globalThis
-    ? (globalThis as any).talerDemobankSettings
+  "talerBankSettings" in globalThis
+    ? (globalThis as any).talerBankSettings
     : defaultSettings;
diff --git a/packages/demobank-ui/src/stories.test.ts 
b/packages/demobank-ui/src/stories.test.ts
index 09de227b8..fac363e5b 100644
--- a/packages/demobank-ui/src/stories.test.ts
+++ b/packages/demobank-ui/src/stories.test.ts
@@ -65,7 +65,9 @@ function DefaultTestingContext({
     name: "libeufin-bank",
     allow_deletions: true,
     allow_registrations: true,
-    currency: {
+    allow_conversion: true,
+    currency: "ASR",
+    currency_specification: {
       name: "ARS",
       alt_unit_names: {},
       num_fractional_input_digits: 2,
diff --git a/packages/taler-harness/src/http-client/bank-core.ts 
b/packages/taler-harness/src/http-client/bank-core.ts
index 22a10580d..ecd65d32a 100644
--- a/packages/taler-harness/src/http-client/bank-core.ts
+++ b/packages/taler-harness/src/http-client/bank-core.ts
@@ -40,22 +40,6 @@ export function createTestForBankCore(api: 
TalerCoreBankHttpClient, adminToken:
       success: undefined,
       "not-found": undefined,
     },
-    test_getCashoutRate: {
-      "cashout-not-supported": undefined,
-      "wrong-calculation": undefined,
-      "amount-too-small": undefined,
-      "missing-params": undefined,
-      "wrong-currency": undefined,
-      success: undefined,
-    },
-    test_getCashinRate: {
-      "cashout-not-supported": undefined,
-      "wrong-calculation": undefined,
-      "amount-too-small": undefined,
-      "missing-params": undefined,
-      "wrong-currency": undefined,
-      success: undefined,
-    },
     test_getGlobalCashouts: {
       "cashout-not-supported": undefined,
       success: undefined,
@@ -877,7 +861,7 @@ export function createTestForBankRevenue(bank: 
TalerCoreBankHttpClient, adminTok
         account.params["message"] = "all"
 
         const amount = Amounts.stringify({
-          currency: config.currency.name,
+          currency: config.currency,
           fraction: 0,
           value: 1
         })
@@ -953,7 +937,7 @@ export function createTestForBankWireGateway(bank: 
TalerCoreBankHttpClient, admi
         account.params["message"] = "all"
 
         const amount = Amounts.stringify({
-          currency: config.currency.name,
+          currency: config.currency,
           fraction: 0,
           value: 1
         })
diff --git a/packages/taler-util/src/http-client/bank-conversion.ts 
b/packages/taler-util/src/http-client/bank-conversion.ts
new file mode 100644
index 000000000..f53b6a661
--- /dev/null
+++ b/packages/taler-util/src/http-client/bank-conversion.ts
@@ -0,0 +1,113 @@
+import { AmountJson, Amounts } from "../amounts.js";
+import { HttpRequestLibrary } from "../http-common.js";
+import { HttpStatusCode } from "../http-status-codes.js";
+import { createPlatformHttpLib } from "../http.js";
+import { FailCasesByMethod, ResultByMethod, opKnownFailure, opSuccess, 
opUnknownFailure } from "../operation.js";
+import { TalerErrorCode } from "../taler-error-codes.js";
+import { codecForTalerErrorDetail } from "../wallet-types.js";
+import {
+  codecForCashinConversionResponse,
+  codecForCashoutConversionResponse,
+  codecForConversionBankConfig
+} from "./types.js";
+
+export type TalerBankConversionResultByMethod<prop extends keyof 
TalerBankConversionHttpClient> = ResultByMethod<TalerBankConversionHttpClient, 
prop>
+export type TalerBankConversionErrorsByMethod<prop extends keyof 
TalerBankConversionHttpClient> = 
FailCasesByMethod<TalerBankConversionHttpClient, prop>
+
+/**
+ * The API is used by the wallets.
+ */
+export class TalerBankConversionHttpClient {
+  httpLib: HttpRequestLibrary;
+
+  constructor(
+    readonly baseUrl: string,
+    httpClient?: HttpRequestLibrary,
+  ) {
+    this.httpLib = httpClient ?? createPlatformHttpLib();
+  }
+
+  /**
+   * https://docs.taler.net/core/api-bank-conversion-info.html#get--config
+   * 
+   */
+  async getConfig() {
+    const url = new URL(`config`, this.baseUrl);
+    const resp = await this.httpLib.fetch(url.href, {
+      method: "GET"
+    });
+    switch (resp.status) {
+      case HttpStatusCode.Ok: return opSuccess(resp, 
codecForConversionBankConfig())
+      default: return opUnknownFailure(resp, await resp.text())
+    }
+  }
+
+  /**
+   * https://docs.taler.net/core/api-bank-conversion-info.html#get--cashin-rate
+   * 
+   */
+  async getCashinRate(conversion: { debit?: AmountJson, credit?: AmountJson }) 
{
+    const url = new URL(`cashin-rate`, this.baseUrl);
+    if (conversion.debit) {
+      url.searchParams.set("amount_debit", Amounts.stringify(conversion.debit))
+    }
+    if (conversion.credit) {
+      url.searchParams.set("amount_credit", 
Amounts.stringify(conversion.credit))
+    }
+    const resp = await this.httpLib.fetch(url.href, {
+      method: "GET",
+    });
+    switch (resp.status) {
+      case HttpStatusCode.Ok: return opSuccess(resp, 
codecForCashinConversionResponse())
+      case HttpStatusCode.BadRequest: {
+        const body = await resp.json()
+        const details = codecForTalerErrorDetail().decode(body)
+        switch (details.code) {
+          case TalerErrorCode.GENERIC_PARAMETER_MISSING: return 
opKnownFailure("missing-params", resp);
+          case TalerErrorCode.GENERIC_PARAMETER_MALFORMED: return 
opKnownFailure("wrong-calculation", resp);
+          case TalerErrorCode.GENERIC_CURRENCY_MISMATCH: return 
opKnownFailure("wrong-currency", resp);
+          default: return opUnknownFailure(resp, body)
+        }
+      }
+      case HttpStatusCode.Conflict: return opKnownFailure("amount-too-small", 
resp);
+      case HttpStatusCode.NotImplemented: return 
opKnownFailure("cashout-not-supported", resp);
+      default: return opUnknownFailure(resp, await resp.text())
+    }
+  }
+
+  /**
+   * 
https://docs.taler.net/core/api-bank-conversion-info.html#get--cashout-rate
+   * 
+   */
+  async getCashoutRate(conversion: { debit?: AmountJson, credit?: AmountJson 
}) {
+    const url = new URL(`cashout-rate`, this.baseUrl);
+    if (conversion.debit) {
+      url.searchParams.set("amount_debit", Amounts.stringify(conversion.debit))
+    }
+    if (conversion.credit) {
+      url.searchParams.set("amount_credit", 
Amounts.stringify(conversion.credit))
+    }
+    const resp = await this.httpLib.fetch(url.href, {
+      method: "GET",
+    });
+    switch (resp.status) {
+      case HttpStatusCode.Ok: return opSuccess(resp, 
codecForCashoutConversionResponse())
+      case HttpStatusCode.BadRequest: {
+        const body = await resp.json()
+        const details = codecForTalerErrorDetail().decode(body)
+        switch (details.code) {
+          case TalerErrorCode.GENERIC_PARAMETER_MISSING: return 
opKnownFailure("missing-params", resp);
+          case TalerErrorCode.GENERIC_PARAMETER_MALFORMED: return 
opKnownFailure("wrong-calculation", resp);
+          case TalerErrorCode.GENERIC_CURRENCY_MISMATCH: return 
opKnownFailure("wrong-currency", resp);
+          default: return opUnknownFailure(resp, body)
+        }
+      }
+      case HttpStatusCode.Conflict: return opKnownFailure("amount-too-small", 
resp);
+      case HttpStatusCode.NotImplemented: return 
opKnownFailure("cashout-not-supported", resp);
+      default: return opUnknownFailure(resp, await resp.text())
+    }
+  }
+
+
+}
+
diff --git a/packages/taler-util/src/http-client/bank-core.ts 
b/packages/taler-util/src/http-client/bank-core.ts
index 0b99943a3..d7bf6be29 100644
--- a/packages/taler-util/src/http-client/bank-core.ts
+++ b/packages/taler-util/src/http-client/bank-core.ts
@@ -33,6 +33,7 @@ import { TalerRevenueHttpClient } from "./bank-revenue.js";
 import { TalerWireGatewayHttpClient } from "./bank-wire.js";
 import { AccessToken, PaginationParams, TalerCorebankApi, UserAndToken, 
codecForAccountData, codecForBankAccountCreateWithdrawalResponse, 
codecForBankAccountGetWithdrawalResponse, codecForBankAccountTransactionInfo, 
codecForBankAccountTransactionsResponse, codecForCashinConversionResponse, 
codecForCashoutConversionResponse, codecForCashoutPending, 
codecForCashoutStatusResponse, codecForCashouts, codecForCoreBankConfig, 
codecForGlobalCashouts, codecForListBankAccountsResponse, codecForMon [...]
 import { addPaginationParams, makeBearerTokenAuthHeader } from "./utils.js";
+import { TalerBankConversionHttpClient } from "./bank-conversion.js";
 
 
 export type TalerCoreBankResultByMethod<prop extends keyof 
TalerCoreBankHttpClient> = ResultByMethod<TalerCoreBankHttpClient, prop>
@@ -518,12 +519,11 @@ export class TalerCoreBankHttpClient {
   }
 
   /**
-   * 
https://docs.taler.net/core/api-corebank.html#get--accounts-$USERNAME-cashouts
+   * 
https://docs.taler.net/core/api-corebank.html#get--accounts-$USERNAME-cashouts-$CASHOUT_ID
    * 
    */
-  async getAccountCashouts(auth: UserAndToken, pagination?: PaginationParams) {
-    const url = new URL(`accounts/${auth.username}/cashouts`, this.baseUrl);
-    addPaginationParams(url, pagination)
+  async getCashoutById(auth: UserAndToken, cid: string) {
+    const url = new URL(`accounts/${auth.username}/cashouts/${cid}`, 
this.baseUrl);
     const resp = await this.httpLib.fetch(url.href, {
       method: "GET",
       headers: {
@@ -531,20 +531,20 @@ export class TalerCoreBankHttpClient {
       },
     });
     switch (resp.status) {
-      case HttpStatusCode.Ok: return opSuccess(resp, codecForCashouts())
-      case HttpStatusCode.NoContent: return opFixedSuccess({ cashouts: [] });
-      case HttpStatusCode.NotFound: return opKnownFailure("account-not-found", 
resp);;
+      case HttpStatusCode.Ok: return opSuccess(resp, 
codecForCashoutStatusResponse())
+      case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp);
       case HttpStatusCode.NotImplemented: return 
opKnownFailure("cashout-not-supported", resp);
       default: return opUnknownFailure(resp, await resp.text())
     }
   }
 
   /**
-   * 
https://docs.taler.net/core/api-corebank.html#get--accounts-$USERNAME-cashouts-$CASHOUT_ID
+   * 
https://docs.taler.net/core/api-corebank.html#get--accounts-$USERNAME-cashouts
    * 
    */
-  async getCashoutById(auth: UserAndToken, cid: string) {
-    const url = new URL(`accounts/${auth.username}/cashouts/${cid}`, 
this.baseUrl);
+  async getAccountCashouts(auth: UserAndToken, pagination?: PaginationParams) {
+    const url = new URL(`accounts/${auth.username}/cashouts`, this.baseUrl);
+    addPaginationParams(url, pagination)
     const resp = await this.httpLib.fetch(url.href, {
       method: "GET",
       headers: {
@@ -552,8 +552,9 @@ export class TalerCoreBankHttpClient {
       },
     });
     switch (resp.status) {
-      case HttpStatusCode.Ok: return opSuccess(resp, 
codecForCashoutStatusResponse())
-      case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp);
+      case HttpStatusCode.Ok: return opSuccess(resp, codecForCashouts())
+      case HttpStatusCode.NoContent: return opFixedSuccess({ cashouts: [] });
+      case HttpStatusCode.NotFound: return opKnownFailure("account-not-found", 
resp);;
       case HttpStatusCode.NotImplemented: return 
opKnownFailure("cashout-not-supported", resp);
       default: return opUnknownFailure(resp, await resp.text())
     }
@@ -580,71 +581,6 @@ export class TalerCoreBankHttpClient {
     }
   }
 
-  /**
-   * 
https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-cashouts-$CASHOUT_ID-confirm
-   * 
-   */
-  async getCashoutRate(conversion: { debit?: AmountJson, credit?: AmountJson 
}) {
-    const url = new URL(`cashout-rate`, this.baseUrl);
-    if (conversion.debit) {
-      url.searchParams.set("amount_debit", Amounts.stringify(conversion.debit))
-    }
-    if (conversion.credit) {
-      url.searchParams.set("amount_credit", 
Amounts.stringify(conversion.credit))
-    }
-    const resp = await this.httpLib.fetch(url.href, {
-      method: "GET",
-    });
-    switch (resp.status) {
-      case HttpStatusCode.Ok: return opSuccess(resp, 
codecForCashoutConversionResponse())
-      case HttpStatusCode.BadRequest: {
-        const body = await resp.json()
-        const details = codecForTalerErrorDetail().decode(body)
-        switch (details.code) {
-          case TalerErrorCode.GENERIC_PARAMETER_MISSING: return 
opKnownFailure("missing-params", resp);
-          case TalerErrorCode.GENERIC_PARAMETER_MALFORMED : return 
opKnownFailure("wrong-calculation", resp);
-          case TalerErrorCode.GENERIC_CURRENCY_MISMATCH: return 
opKnownFailure("wrong-currency", resp);
-          default: return opUnknownFailure(resp, body)
-        }
-      }
-      case HttpStatusCode.Conflict: return opKnownFailure("amount-too-small", 
resp);
-      case HttpStatusCode.NotImplemented: return 
opKnownFailure("cashout-not-supported", resp);
-      default: return opUnknownFailure(resp, await resp.text())
-    }
-  }
-
-  /**
-   * https://docs.taler.net/core/api-corebank.html#get--cashin-rate
-   * 
-   */
-  async getCashinRate(conversion: { debit?: AmountJson, credit?: AmountJson }) 
{
-    const url = new URL(`cashin-rate`, this.baseUrl);
-    if (conversion.debit) {
-      url.searchParams.set("amount_debit", Amounts.stringify(conversion.debit))
-    }
-    if (conversion.credit) {
-      url.searchParams.set("amount_credit", 
Amounts.stringify(conversion.credit))
-    }
-    const resp = await this.httpLib.fetch(url.href, {
-      method: "GET",
-    });
-    switch (resp.status) {
-      case HttpStatusCode.Ok: return opSuccess(resp, 
codecForCashinConversionResponse())
-      case HttpStatusCode.BadRequest: {
-        const body = await resp.json()
-        const details = codecForTalerErrorDetail().decode(body)
-        switch (details.code) {
-          case TalerErrorCode.GENERIC_PARAMETER_MISSING: return 
opKnownFailure("missing-params", resp);
-          case TalerErrorCode.GENERIC_PARAMETER_MALFORMED : return 
opKnownFailure("wrong-calculation", resp);
-          case TalerErrorCode.GENERIC_CURRENCY_MISMATCH: return 
opKnownFailure("wrong-currency", resp);
-          default: return opUnknownFailure(resp, body)
-        }
-      }
-      case HttpStatusCode.Conflict: return opKnownFailure("amount-too-small", 
resp);
-      case HttpStatusCode.NotImplemented: return 
opKnownFailure("cashout-not-supported", resp);
-      default: return opUnknownFailure(resp, await resp.text())
-    }
-  }
   //
   // MONITOR
   //
@@ -718,4 +654,12 @@ export class TalerCoreBankHttpClient {
     return new TalerAuthenticationHttpClient(url.href, username, this.httpLib,)
   }
 
+  /**
+   * 
https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-token
+  * 
+  */
+  getConversionInfoAPI(): TalerBankConversionHttpClient {
+    const url = new URL(`conversion-info/`, this.baseUrl);
+    return new TalerBankConversionHttpClient(url.href, this.httpLib)
+  }
 }
diff --git a/packages/taler-util/src/http-client/types.ts 
b/packages/taler-util/src/http-client/types.ts
index f6542abcd..d50a0ea90 100644
--- a/packages/taler-util/src/http-client/types.ts
+++ b/packages/taler-util/src/http-client/types.ts
@@ -257,22 +257,19 @@ export const codecForIntegrationBankConfig =
     buildCodecForObject<TalerCorebankApi.IntegrationConfig>()
       .property("name", codecForConstString("taler-bank-integration"))
       .property("version", codecForString())
-      .property("currency", codecForString())
-      .property("currency_specification", codecForCurrencySpecificiation())
+      .property("currency", codecForCurrencySpecificiation())
       .build("TalerCorebankApi.IntegrationConfig")
 
 export const codecForCoreBankConfig =
   (): Codec<TalerCorebankApi.Config> =>
     buildCodecForObject<TalerCorebankApi.Config>()
       .property("name", codecForConstString("libeufin-bank"))
-      // .property("name", codecForConstString("taler-corebank"))
       .property("version", codecForString())
+      .property("allow_conversion", codecForBoolean())
       .property("allow_deletions", codecForBoolean())
       .property("allow_registrations", codecForBoolean())
-      .property("have_cashout", codecOptional(codecForBoolean()))
-      .property("currency", codecForCurrencySpecificiation())
-      .property("fiat_currency", 
codecOptional(codecForCurrencySpecificiation()))
-      .property("conversion_info", 
codecOptional(codecForConversionRatesResponse()))
+      .property("currency_specification", codecForCurrencySpecificiation())
+      .property("currency", codecForString())
       .build("TalerCorebankApi.Config")
 
 export const codecForMerchantConfig =
@@ -391,15 +388,15 @@ export const codecForCashoutPending =
       .build("TalerCorebankApi.CashoutPending");
 
 export const codecForCashoutConversionResponse =
-  (): Codec<TalerCorebankApi.CashoutConversionResponse> =>
-    buildCodecForObject<TalerCorebankApi.CashoutConversionResponse>()
+  (): Codec<TalerBankConversionApi.CashoutConversionResponse> =>
+    buildCodecForObject<TalerBankConversionApi.CashoutConversionResponse>()
       .property("amount_credit", codecForAmountString())
       .property("amount_debit", codecForAmountString())
       .build("TalerCorebankApi.CashoutConversionResponse");
 
 export const codecForCashinConversionResponse =
-  (): Codec<TalerCorebankApi.CashinConversionResponse> =>
-    buildCodecForObject<TalerCorebankApi.CashinConversionResponse>()
+  (): Codec<TalerBankConversionApi.CashinConversionResponse> =>
+    buildCodecForObject<TalerBankConversionApi.CashinConversionResponse>()
       .property("amount_credit", codecForAmountString())
       .property("amount_debit", codecForAmountString())
       .build("TalerCorebankApi.CashinConversionResponse");
@@ -661,6 +658,107 @@ export const codecForAmlDecision =
       .build("TalerExchangeApi.AmlDecision");
 
 
+// version: string;
+
+// // Name of the API.
+// name: "taler-conversion-info";
+
+// // Currency used by this bank.
+// regional_currency: string;
+
+// // How the bank SPA should render this currency.
+// regional_currency_specification: CurrencySpecification;
+
+// // External currency used during conversion.
+// fiat_currency: string;
+
+// // How the bank SPA should render this currency.
+// fiat_currency_specification: CurrencySpecification;
+
+// Extra conversion rate information.
+// // Only present if server opts in to report the static conversion rate.
+// conversion_info?: {
+
+//   // Fee to subtract after applying the cashin ratio.
+//   cashin_fee: AmountString;
+
+//   // Fee to subtract after applying the cashout ratio.
+//   cashout_fee: AmountString;
+
+//   // Minimum amount authorised for cashin, in fiat before conversion
+//   cashin_min_amount: AmountString;
+
+//   // Minimum amount authorised for cashout, in regional before conversion
+//   cashout_min_amount: AmountString;
+
+//   // Smallest possible regional amount, converted amount is rounded to this 
amount
+//   cashin_tiny_amount: AmountString;
+
+//   // Smallest possible fiat amount, converted amount is rounded to this 
amount
+//   cashout_tiny_amount: AmountString;
+
+//   // Rounding mode used during cashin conversion
+//   cashin_rounding_mode: "zero" | "up" | "nearest";
+
+//   // Rounding mode used during cashout conversion
+//   cashout_rounding_mode: "zero" | "up" | "nearest";
+// }
+export const codecForConversionInfo =
+  (): Codec<TalerBankConversionApi.ConversionInfo> =>
+    buildCodecForObject<TalerBankConversionApi.ConversionInfo>()
+      .property("cashin_fee", codecForAmountString())
+      .property("cashin_min_amount", codecForAmountString())
+      .property("cashin_ratio", codecForString())
+      // .property("cashin_ratio", codecForDecimalNumber())
+      .property("cashin_rounding_mode", codecForEither(
+        codecForConstString("zero"),
+        codecForConstString("up"),
+        codecForConstString("nearest")
+      ))
+      .property("cashin_tiny_amount", codecForAmountString())
+      .property("cashout_fee", codecForAmountString())
+      .property("cashout_min_amount", codecForAmountString())
+      .property("cashout_ratio", codecForString())
+      // .property("cashout_ratio", codecForDecimalNumber())
+      .property("cashout_rounding_mode", codecForEither(
+        codecForConstString("zero"),
+        codecForConstString("up"),
+        codecForConstString("nearest")
+      ))
+      .property("cashout_tiny_amount", codecForAmountString())
+      .build("ConversionBankConfig.ConversionInfo")
+
+export const codecForConversionBankConfig =
+  (): Codec<TalerBankConversionApi.IntegrationConfig> =>
+    buildCodecForObject<TalerBankConversionApi.IntegrationConfig>()
+      .property("name", codecForConstString("taler-conversion-info"))
+      .property("version", codecForString())
+      .property("regional_currency", codecForString())
+      .property("regional_currency_specification", 
codecForCurrencySpecificiation())
+      .property("fiat_currency", codecForString())
+      .property("fiat_currency_specification", 
codecForCurrencySpecificiation())
+      // .property("conversion_info", codecOptional(codecForConversionInfo()))
+      ////////////////////////// remove this
+      .property("cashin_fee", codecForAmountString())
+      .property("cashin_min_amount", codecForAmountString())
+      .property("cashin_ratio", codecForString())
+      .property("cashin_rounding_mode", codecForEither(
+        codecForConstString("zero"),
+        codecForConstString("up"),
+        codecForConstString("nearest")
+      ))
+      .property("cashin_tiny_amount", codecForAmountString())
+      .property("cashout_fee", codecForAmountString())
+      .property("cashout_min_amount", codecForAmountString())
+      .property("cashout_ratio", codecForString())
+      .property("cashout_rounding_mode", codecForEither(
+        codecForConstString("zero"),
+        codecForConstString("up"),
+        codecForConstString("nearest")
+      ))
+      .property("cashout_tiny_amount", codecForAmountString())
+      //////////////////////////
+      .build("ConversionBankConfig.IntegrationConfig")
 // export const codecFor =
 //   (): Codec<TalerWireGatewayApi.PublicAccountsResponse> =>
 //     buildCodecForObject<TalerWireGatewayApi.PublicAccountsResponse>()
@@ -920,6 +1018,87 @@ export namespace TalerRevenueApi {
   }
 }
 
+export namespace TalerBankConversionApi {
+
+  export interface ConversionInfo {
+    // Exchange rate to buy regional currency from fiat
+    // cashin_ratio: DecimalNumber;
+    cashin_ratio: string;
+
+    // Exchange rate to sell regional currency for fiat
+    // cashout_ratio: DecimalNumber;
+    cashout_ratio: string;
+
+    // Fee to subtract after applying the cashin ratio.
+    cashin_fee: AmountString;
+
+    // Fee to subtract after applying the cashout ratio.
+    cashout_fee: AmountString;
+
+    // Minimum amount authorised for cashin, in fiat before conversion
+    cashin_min_amount: AmountString;
+
+    // Minimum amount authorised for cashout, in regional before conversion
+    cashout_min_amount: AmountString;
+
+    // Smallest possible regional amount, converted amount is rounded to this 
amount
+    cashin_tiny_amount: AmountString;
+
+    // Smallest possible fiat amount, converted amount is rounded to this 
amount
+    cashout_tiny_amount: AmountString;
+
+    // Rounding mode used during cashin conversion
+    cashin_rounding_mode: "zero" | "up" | "nearest";
+
+    // Rounding mode used during cashout conversion
+    cashout_rounding_mode: "zero" | "up" | "nearest";
+  }
+
+  export interface IntegrationConfig extends ConversionInfo {
+    // libtool-style representation of the Bank protocol version, see
+    // 
https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning
+    // The format is "current:revision:age".
+    version: string;
+
+    // Name of the API.
+    name: "taler-conversion-info";
+
+    // Currency used by this bank.
+    regional_currency: string;
+
+    // How the bank SPA should render this currency.
+    regional_currency_specification: CurrencySpecification;
+
+    // External currency used during conversion.
+    fiat_currency: string;
+
+    // How the bank SPA should render this currency.
+    fiat_currency_specification: CurrencySpecification;
+
+    // Extra conversion rate information.
+    // Only present if server opts in to report the static conversion rate.
+    // conversion_info?: ConversionInfo
+  }
+
+  export interface CashinConversionResponse {
+    // Amount that the user will get deducted from their fiat
+    // bank account, according to the 'amount_credit' value.
+    amount_debit: AmountString;
+    // Amount that the user will receive in their regional
+    // bank account, according to 'amount_debit'.
+    amount_credit: AmountString;
+  }
+
+  export interface CashoutConversionResponse {
+    // Amount that the user will get deducted from their regional
+    // bank account, according to the 'amount_credit' value.
+    amount_debit: AmountString;
+    // Amount that the user will receive in their fiat
+    // bank account, according to 'amount_debit'.
+    amount_credit: AmountString;
+  }
+
+}
 export namespace TalerBankIntegrationApi {
   export interface BankVersion {
     // libtool-style representation of the Bank protocol version, see
@@ -1003,11 +1182,8 @@ export namespace TalerCorebankApi {
     // The format is "current:revision:age".
     version: string;
 
-    // Currency used by this bank.
-    currency: string;
-
     // How the bank SPA should render this currency.
-    currency_specification: CurrencySpecification;
+    currency: CurrencySpecification;
 
     // Name of the API.
     name: "taler-bank-integration";
@@ -1021,6 +1197,10 @@ export namespace TalerCorebankApi {
     // API version in the form $n:$n:$n
     version: string;
 
+    // If 'true' the server provides local currency conversion support
+    // If 'false' some parts of the API are not supported and return 501
+    allow_conversion: boolean;
+
     // If 'true' anyone can register
     // If 'false' only the admin can
     allow_registrations: boolean;
@@ -1029,24 +1209,11 @@ export namespace TalerCorebankApi {
     // If 'false' only the admin can delete accounts
     allow_deletions: boolean;
 
-    // If 'true', the server provides local currency
-    // conversion support.
-    // If missing or false, some parts of the API
-    // are not supported and return 404.
-    have_cashout?: boolean;
-
-    // How the bank SPA should render the currency.
-    currency: CurrencySpecification;
-
-    // Fiat currency.  That is the currency in which
-    // cash-out operations ultimately wire money.
-    // Only applicable if have_cashout=true.
-    fiat_currency?: CurrencySpecification;
+    // Currency used by this bank.
+    currency: string;
 
-    // Extra conversion rate information.
-    // Only present if conversion is supported and the server opts in
-    // to report the static conversion rate.
-    conversion_info?: ConversionRatesResponse
+    // How the bank SPA should render this currency.
+    currency_specification: CurrencySpecification;
   }
 
   export interface BankAccountCreateWithdrawalRequest {
@@ -1309,24 +1476,6 @@ export namespace TalerCorebankApi {
     tan: string;
   }
 
-  export interface CashoutConversionResponse {
-    // Amount that the user will get deducted from their regional
-    // bank account, according to the 'amount_credit' value.
-    amount_debit: AmountString;
-    // Amount that the user will receive in their fiat
-    // bank account, according to 'amount_debit'.
-    amount_credit: AmountString;
-  }
-
-  export interface CashinConversionResponse {
-    // Amount that the user will get deducted from their fiat
-    // bank account, according to the 'amount_credit' value.
-    amount_debit: AmountString;
-    // Amount that the user will receive in their regional
-    // bank account, according to 'amount_debit'.
-    amount_credit: AmountString;
-  }
-
   export interface Cashouts {
     // Every string represents a cash-out operation ID.
     cashouts: CashoutInfo[];
diff --git a/packages/taler-util/src/index.ts b/packages/taler-util/src/index.ts
index 8db266620..53d3e137a 100644
--- a/packages/taler-util/src/index.ts
+++ b/packages/taler-util/src/index.ts
@@ -49,6 +49,7 @@ export * from "./http-client/merchant.js";
 export * from "./http-client/officer-account.js";
 export * from "./http-client/bank-integration.js";
 export * from "./http-client/bank-revenue.js";
+export * from "./http-client/bank-conversion.js";
 export * from "./http-client/bank-wire.js";
 export * from "./http-client/types.js";
 export * from "./operation.js";
diff --git a/packages/taler-util/src/taler-types.ts 
b/packages/taler-util/src/taler-types.ts
index 5774f09f7..f21efc516 100644
--- a/packages/taler-util/src/taler-types.ts
+++ b/packages/taler-util/src/taler-types.ts
@@ -1756,18 +1756,6 @@ export interface MerchantAbortPayRefundSuccessStatus {
   exchange_pub: string;
 }
 
-export interface TalerConfigResponse {
-  name: string;
-  version: string;
-  currency?: string;
-}
-
-export const codecForTalerConfigResponse = (): Codec<TalerConfigResponse> =>
-  buildCodecForObject<TalerConfigResponse>()
-    .property("name", codecForString())
-    .property("version", codecForString())
-    .property("currency", codecOptional(codecForString()))
-    .build("TalerConfigResponse");
 
 export interface FutureKeysResponse {
   future_denoms: any[];
diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts 
b/packages/taler-wallet-core/src/operations/withdraw.ts
index 9819ae6a9..275d0aaf0 100644
--- a/packages/taler-wallet-core/src/operations/withdraw.ts
+++ b/packages/taler-wallet-core/src/operations/withdraw.ts
@@ -21,53 +21,62 @@ import {
   AbsoluteTime,
   AcceptManualWithdrawalResult,
   AcceptWithdrawalResponse,
-  addPaytoQueryParams,
   AgeRestriction,
   AmountJson,
   AmountLike,
   Amounts,
   BankWithdrawDetails,
   CancellationToken,
-  canonicalizeBaseUrl,
-  codecForBankWithdrawalOperationPostResponse,
-  codecForReserveStatus,
-  codecForTalerConfigResponse,
-  codecForWalletKycUuid,
-  codecForExchangeWithdrawBatchResponse,
-  codecForWithdrawOperationStatusResponse,
-  codecForWithdrawResponse,
   CoinStatus,
   DenomKeyType,
   DenomSelectionState,
   Duration,
-  encodeCrock,
+  ExchangeBatchWithdrawRequest,
   ExchangeListItem,
-  ExchangeWithdrawalDetails,
+  ExchangeWithdrawBatchResponse,
   ExchangeWithdrawRequest,
+  ExchangeWithdrawResponse,
+  ExchangeWithdrawalDetails,
   ForcedDenomSel,
-  getRandomBytes,
   HttpStatusCode,
-  j2s,
   LibtoolVersion,
   Logger,
   NotificationType,
-  parseWithdrawUri,
+  TalerError,
   TalerErrorCode,
   TalerErrorDetail,
+  TalerPreciseTimestamp,
   TalerProtocolTimestamp,
+  TransactionAction,
+  TransactionMajorState,
+  TransactionMinorState,
+  TransactionState,
   TransactionType,
-  UnblindedSignature,
   URL,
-  ExchangeWithdrawBatchResponse,
-  ExchangeWithdrawResponse,
+  UnblindedSignature,
   WithdrawUriInfoResponse,
-  ExchangeBatchWithdrawRequest,
-  TransactionState,
-  TransactionMajorState,
-  TransactionMinorState,
-  TalerPreciseTimestamp,
-  TransactionAction,
+  addPaytoQueryParams,
+  canonicalizeBaseUrl,
+  codecForBankWithdrawalOperationPostResponse,
+  codecForExchangeWithdrawBatchResponse,
+  codecForIntegrationBankConfig,
+  codecForReserveStatus,
+  codecForWalletKycUuid,
+  codecForWithdrawOperationStatusResponse,
+  encodeCrock,
+  getErrorDetailFromException,
+  getRandomBytes,
+  j2s,
+  makeErrorDetail,
+  parseWithdrawUri
 } from "@gnu-taler/taler-util";
+import {
+  HttpRequestLibrary,
+  HttpResponse,
+  readSuccessResponseJsonOrErrorCode,
+  readSuccessResponseJsonOrThrow,
+  throwUnexpectedRequestError,
+} from "@gnu-taler/taler-util/http";
 import { EddsaKeypair } from "../crypto/cryptoImplementation.js";
 import {
   CoinRecord,
@@ -84,28 +93,28 @@ import {
   WithdrawalRecordType,
 } from "../db.js";
 import {
-  getErrorDetailFromException,
-  makeErrorDetail,
-  TalerError,
-} from "@gnu-taler/taler-util";
+  ExchangeDetailsRecord,
+  ExchangeEntryDbRecordStatus,
+  PendingTaskType,
+  isWithdrawableDenom,
+  timestampPreciseToDb
+} from "../index.js";
 import { InternalWalletState } from "../internal-wallet-state.js";
 import {
+  TaskIdentifiers,
   TaskRunResult,
   TaskRunResultType,
-  TaskIdentifiers,
   constructTaskIdentifier,
   makeCoinAvailable,
   makeCoinsVisible,
   makeExchangeListItem,
   runLongpollAsync,
 } from "../operations/common.js";
+import { assertUnreachable } from "../util/assertUnreachable.js";
 import {
-  HttpRequestLibrary,
-  HttpResponse,
-  readSuccessResponseJsonOrErrorCode,
-  readSuccessResponseJsonOrThrow,
-  throwUnexpectedRequestError,
-} from "@gnu-taler/taler-util/http";
+  selectForcedWithdrawalDenominations,
+  selectWithdrawalDenominations,
+} from "../util/coinSelection.js";
 import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js";
 import {
   DbAccess,
@@ -121,25 +130,12 @@ import {
   getExchangePaytoUri,
   updateExchangeFromUrl,
 } from "./exchanges.js";
-import {
-  selectForcedWithdrawalDenominations,
-  selectWithdrawalDenominations,
-} from "../util/coinSelection.js";
-import {
-  ExchangeDetailsRecord,
-  ExchangeEntryDbRecordStatus,
-  ExchangeEntryDbUpdateStatus,
-  PendingTaskType,
-  isWithdrawableDenom,
-  timestampPreciseToDb,
-} from "../index.js";
 import {
   TransitionInfo,
   constructTransactionIdentifier,
   notifyTransition,
   stopLongpolling,
 } from "./transactions.js";
-import { assertUnreachable } from "../util/assertUnreachable.js";
 
 /**
  * Logger for this file.
@@ -559,7 +555,7 @@ export async function getBankWithdrawalInfo(
   const configResp = await http.fetch(configReqUrl.href);
   const config = await readSuccessResponseJsonOrThrow(
     configResp,
-    codecForTalerConfigResponse(),
+    codecForIntegrationBankConfig(),
   );
 
   const versionRes = LibtoolVersion.compare(
diff --git a/packages/taler-wallet-webextension/src/wallet/AddExchange/index.ts 
b/packages/taler-wallet-webextension/src/wallet/AddExchange/index.ts
index cece582e9..f16d3929d 100644
--- a/packages/taler-wallet-webextension/src/wallet/AddExchange/index.ts
+++ b/packages/taler-wallet-webextension/src/wallet/AddExchange/index.ts
@@ -14,15 +14,14 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { TalerConfigResponse } from "@gnu-taler/taler-util";
+import { HttpResponse } from "@gnu-taler/web-util/browser";
 import { ErrorAlertView } from "../../components/CurrentAlerts.js";
 import { Loading } from "../../components/Loading.js";
 import { ErrorAlert } from "../../context/alert.js";
+import { TextFieldHandler } from "../../mui/handlers.js";
 import { compose, StateViewMap } from "../../utils/index.js";
 import { useComponentState } from "./state.js";
 import { ConfirmView, VerifyView } from "./views.js";
-import { HttpResponse, InputFieldHandler } from "@gnu-taler/web-util/browser";
-import { TextFieldHandler } from "../../mui/handlers.js";
 
 export interface Props {
   currency?: string;
@@ -65,7 +64,7 @@ export namespace State {
 
     url: TextFieldHandler,
     knownExchanges: URL[],
-    result: HttpResponse<TalerConfigResponse, unknown> | undefined,
+    result: HttpResponse<{ currency_specification: {currency: string}, 
version: string}, unknown> | undefined,
     expectedCurrency: string | undefined,
   }
 }
diff --git a/packages/taler-wallet-webextension/src/wallet/AddExchange/state.ts 
b/packages/taler-wallet-webextension/src/wallet/AddExchange/state.ts
index fc1762331..61f4308f4 100644
--- a/packages/taler-wallet-webextension/src/wallet/AddExchange/state.ts
+++ b/packages/taler-wallet-webextension/src/wallet/AddExchange/state.ts
@@ -16,7 +16,7 @@
 
 import { useState, useEffect, useCallback } from "preact/hooks";
 import { Props, State } from "./index.js";
-import { ExchangeEntryStatus, TalerConfigResponse, TranslatedString, 
canonicalizeBaseUrl } from "@gnu-taler/taler-util";
+import { ExchangeEntryStatus, TalerCorebankApi, TalerExchangeApi, 
canonicalizeBaseUrl } from "@gnu-taler/taler-util";
 import { useBackendContext } from "../../context/backend.js";
 import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
 import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
@@ -27,7 +27,7 @@ import { withSafe } from "../../mui/handlers.js";
 
 export function useComponentState({ onBack, currency, noDebounce }: Props): 
RecursiveState<State> {
   const [verified, setVerified] = useState<
-    { url: string; config: TalerConfigResponse } | undefined
+    { url: string; config: { currency_specification: {currency: string}, 
version: string} } | undefined
   >(undefined);
 
   const api = useBackendContext();
@@ -48,10 +48,10 @@ export function useComponentState({ onBack, currency, 
noDebounce }: Props): Recu
         if (found !== -1) {
           throw Error("This exchange is already active")
         }
-        const result = await request<TalerConfigResponse>(c, "/keys")
+        const result = await request<{ currency_specification: {currency: 
string}, version: string}>(c, "/keys")
         return result
       }, [used])
-      const { result, value: url, update, error: requestError } = 
useDebounce<HttpResponse<TalerConfigResponse, unknown>>(ccc, noDebounce ?? 
false)
+      const { result, value: url, update, error: requestError } = 
useDebounce<HttpResponse<{ currency_specification: {currency: string}, version: 
string}, unknown>>(ccc, noDebounce ?? false)
       const [inputError, setInputError] = useState<string>()
 
       return {
diff --git 
a/packages/taler-wallet-webextension/src/wallet/AddExchange/views.tsx 
b/packages/taler-wallet-webextension/src/wallet/AddExchange/views.tsx
index e1bc7f0f6..87ea5eae3 100644
--- a/packages/taler-wallet-webextension/src/wallet/AddExchange/views.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/AddExchange/views.tsx
@@ -60,7 +60,7 @@ export function VerifyView({
             </i18n.Translate>
           </LightText>
         )}
-        {result && result.ok && expectedCurrency && expectedCurrency !== 
result.data.currency && (
+        {result && result.ok && expectedCurrency && expectedCurrency !== 
result.data.currency_specification.currency && (
           <WarningBox>
             <i18n.Translate>
               This exchange doesn&apos;t match the expected currency
@@ -105,7 +105,7 @@ export function VerifyView({
                 <label>
                   <i18n.Translate>Currency</i18n.Translate>
                 </label>
-                <input type="text" disabled value={result.data.currency} />
+                <input type="text" disabled 
value={result.data.currency_specification.currency} />
               </Input>
             </Fragment>
           )}
@@ -127,7 +127,7 @@ export function VerifyView({
             !result ||
             result.loading ||
             !result.ok ||
-            (!!expectedCurrency && expectedCurrency !== result.data.currency)
+            (!!expectedCurrency && expectedCurrency !== 
result.data.currency_specification.currency)
           }
           onClick={onAccept}
         >

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