gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated (9bd94fc64 -> 1039689b5)


From: gnunet
Subject: [taler-wallet-core] branch master updated (9bd94fc64 -> 1039689b5)
Date: Fri, 03 Nov 2023 23:13:50 +0100

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

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

    from 9bd94fc64 show error inplace instead of notification
     new 5823d9606 fix spaces
     new f7910c5d6 do not show details on kyc or aml
     new 368a87a36 update to gana 34f20d8be618409730e02f0078896ec129d8a404
     new 08898c7aa update to the new bank api breaking changes
     new 11b99b925 test for every error code in the spec
     new 954557e84 add error messages for new error cases
     new 1039689b5 update to dd51 and removing unused EC

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


Summary of changes:
 .../demobank-ui/src/components/Cashouts/index.ts   |   12 +-
 .../demobank-ui/src/components/Cashouts/state.ts   |    6 +
 .../demobank-ui/src/components/Cashouts/views.tsx  |   20 +
 packages/demobank-ui/src/hooks/circuit.ts          |   13 +-
 .../demobank-ui/src/pages/AccountPage/state.ts     |    5 -
 .../demobank-ui/src/pages/OperationState/views.tsx |    6 +
 .../src/pages/PaytoWireTransferForm.tsx            |   20 +-
 .../demobank-ui/src/pages/RegistrationPage.tsx     |   24 +-
 .../demobank-ui/src/pages/ShowAccountDetails.tsx   |    3 +-
 .../src/pages/WithdrawalConfirmationQuestion.tsx   |    8 +-
 packages/demobank-ui/src/pages/admin/Account.tsx   |    3 +-
 .../demobank-ui/src/pages/admin/AccountList.tsx    |    5 +-
 packages/demobank-ui/src/pages/admin/AdminHome.tsx |   41 +-
 .../src/pages/admin/CreateNewAccount.tsx           |   28 +-
 .../demobank-ui/src/pages/admin/RemoveAccount.tsx  |    5 +-
 .../src/pages/business/CreateCashout.tsx           |   13 +-
 .../src/pages/business/ShowCashoutDetails.tsx      |    2 +-
 .../taler-harness/src/http-client/bank-core.ts     | 1375 ++++++++++++--------
 packages/taler-harness/src/index.ts                |  111 +-
 packages/taler-util/src/errors.ts                  |    2 +-
 packages/taler-util/src/http-client/bank-core.ts   |  207 +--
 packages/taler-util/src/http-client/types.ts       |   86 +-
 packages/taler-util/src/operation.ts               |   23 +-
 packages/taler-util/src/taler-error-codes.ts       |  268 +++-
 packages/taler-util/src/wallet-types.ts            |    2 +-
 .../taler-wallet-core/src/operations/reward.ts     |    4 +-
 .../taler-wallet-core/src/operations/withdraw.ts   |    6 +-
 packages/taler-wallet-core/src/wallet.ts           |   16 +-
 .../src/wallet/DeveloperPage.tsx                   |    9 +-
 .../src/wallet/Transaction.tsx                     |   28 +-
 30 files changed, 1497 insertions(+), 854 deletions(-)

diff --git a/packages/demobank-ui/src/components/Cashouts/index.ts 
b/packages/demobank-ui/src/components/Cashouts/index.ts
index ae020cef6..09839e753 100644
--- a/packages/demobank-ui/src/components/Cashouts/index.ts
+++ b/packages/demobank-ui/src/components/Cashouts/index.ts
@@ -18,16 +18,16 @@ import { HttpError, utils } from 
"@gnu-taler/web-util/browser";
 import { Loading } from "../Loading.js";
 // import { compose, StateViewMap } from "../../utils/index.js";
 // import { wxApi } from "../../wxApi.js";
-import { AbsoluteTime, AmountJson, TalerCorebankApi, TalerError } from 
"@gnu-taler/taler-util";
+import { AbsoluteTime, AmountJson, TalerCoreBankErrorsByMethod, 
TalerCorebankApi, TalerError } from "@gnu-taler/taler-util";
 import { useComponentState } from "./state.js";
-import { LoadingUriView, ReadyView } from "./views.js";
+import { FailedView, LoadingUriView, ReadyView } from "./views.js";
 
 export interface Props {
   account: string;
   onSelected: (id: string) => void;
 }
 
-export type State = State.Loading | State.LoadingUriError | State.Ready;
+export type State = State.Loading | State.Failed | State.LoadingUriError | 
State.Ready;
 
 export namespace State {
   export interface Loading {
@@ -40,6 +40,11 @@ export namespace State {
     error: TalerError;
   }
 
+  export interface Failed {
+    status: "failed";
+    error: TalerCoreBankErrorsByMethod<"getAccountCashouts">;
+  }
+
   export interface BaseInfo {
     error: undefined;
   }
@@ -62,6 +67,7 @@ export interface Transaction {
 const viewMapping: utils.StateViewMap<State> = {
   loading: Loading,
   "loading-error": LoadingUriView,
+  "failed": FailedView,
   ready: ReadyView,
 };
 
diff --git a/packages/demobank-ui/src/components/Cashouts/state.ts 
b/packages/demobank-ui/src/components/Cashouts/state.ts
index 47ad0a297..814755541 100644
--- a/packages/demobank-ui/src/components/Cashouts/state.ts
+++ b/packages/demobank-ui/src/components/Cashouts/state.ts
@@ -32,6 +32,12 @@ export function useComponentState({ account, onSelected }: 
Props): State {
       error: result,
     };
   }
+  if (result.type === "fail") {
+    return {
+      status: "failed",
+      error: result
+    }
+  }
 
   return {
     status: "ready",
diff --git a/packages/demobank-ui/src/components/Cashouts/views.tsx 
b/packages/demobank-ui/src/components/Cashouts/views.tsx
index 32fe0aa9e..89f173b0d 100644
--- a/packages/demobank-ui/src/components/Cashouts/views.tsx
+++ b/packages/demobank-ui/src/components/Cashouts/views.tsx
@@ -20,6 +20,8 @@ import { State } from "./index.js";
 import { format } from "date-fns";
 import { Amounts } from "@gnu-taler/taler-util";
 import { RenderAmount } from "../../pages/PaytoWireTransferForm.js";
+import { assertUnreachable } from "../Routing.js";
+import { Attention } from "../Attention.js";
 
 export function LoadingUriView({ error }: State.LoadingUriError): VNode {
   const { i18n } = useTranslationContext();
@@ -30,6 +32,24 @@ export function LoadingUriView({ error }: 
State.LoadingUriError): VNode {
     </div>
   );
 }
+export function FailedView({ error }: State.Failed) {
+  const { i18n } = useTranslationContext();
+  switch (error.case) {
+    case "cashout-not-supported": return <Attention type="danger"
+      title={i18n.str`Cashout not supported.`}>
+      <div class="mt-2 text-sm text-red-700">
+        {error.detail.hint}
+      </div>
+    </Attention>
+    case "account-not-found": return <Attention type="danger"
+      title={i18n.str`Account not found.`}>
+      <div class="mt-2 text-sm text-red-700">
+        {error.detail.hint}
+      </div>
+    </Attention>
+    default: assertUnreachable(error)
+  }
+}
 
 export function ReadyView({ cashouts, onSelected }: State.Ready): VNode {
   const { i18n } = useTranslationContext();
diff --git a/packages/demobank-ui/src/hooks/circuit.ts 
b/packages/demobank-ui/src/hooks/circuit.ts
index e7a28bd98..845f37822 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, 
TalerCoreBankResultByMethod, TalerCorebankApi, TalerError, TalerHttpError } 
from "@gnu-taler/taler-util";
+import { AccessToken, AmountJson, AmountString, Amounts, OperationOk, 
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";
@@ -105,7 +105,7 @@ export function useBusinessAccounts() {
 
   function fetcher([token, offset]: [AccessToken, string]) {
     //FIXME: add account name filter
-    return api.getAccounts(token,{}, {
+    return api.getAccounts(token, {}, {
       limit: MAX_RESULT_SIZE,
       offset,
       order: "asc"
@@ -164,10 +164,7 @@ export function useCashouts(account: string) {
   async function fetcher([username, token]: [string, AccessToken]) {
     const list = await api.getAccountCashouts({ username, token })
     if (list.type !== "ok") {
-      if (list.case === "cashout-not-supported") {
-        throw Error("cashout is not supported")
-      }
-      assertUnreachable(list.case)
+      return list;
     }
     const all: Array<CashoutWithId | undefined> = await 
Promise.all(list.body.cashouts.map(c => {
       return api.getCashoutById({ username, token }, c.cashout_id).then(r => {
@@ -180,7 +177,7 @@ export function useCashouts(account: string) {
     return { type: "ok" as const, body: { cashouts } }
   }
 
-  const { data, error } = useSWR<OperationOk<{ cashouts: CashoutWithId[] }>, 
TalerHttpError>(
+  const { data, error } = useSWR<OperationOk<{ cashouts: CashoutWithId[] }> | 
TalerCoreBankErrorsByMethod<"getAccountCashouts">, TalerHttpError>(
     !config.have_cashout ? false : [account, token, "getAccountCashouts"], 
fetcher, {
     refreshInterval: 0,
     refreshWhenHidden: false,
@@ -269,6 +266,7 @@ export function useLastMonitorInfo(time: Date, timeframe: 
TalerCorebankApi.Monit
     const current: TalerCoreBankResultByMethod<"getMonitor"> = {
       type: "ok" as const,
       body: {
+        type: "with-cashout" as const,
         cashinCount: 1,
         cashinExternalVolume: "LOCAL:1234" as AmountString,
         cashoutCount: 2,
@@ -281,6 +279,7 @@ export function useLastMonitorInfo(time: Date, timeframe: 
TalerCorebankApi.Monit
     const previous = {
       type: "ok" as const,
       body: {
+        type: "with-cashout" as const,
         cashinCount: 1,
         cashinExternalVolume: "LOCAL:2345" as AmountString,
         cashoutCount: 2,
diff --git a/packages/demobank-ui/src/pages/AccountPage/state.ts 
b/packages/demobank-ui/src/pages/AccountPage/state.ts
index 6da066d77..d28aba7bf 100644
--- a/packages/demobank-ui/src/pages/AccountPage/state.ts
+++ b/packages/demobank-ui/src/pages/AccountPage/state.ts
@@ -48,11 +48,6 @@ export function useComponentState({ account, 
goToConfirmOperation }: Props): Sta
         status: "login",
         reason: "not-found",
       }
-      case "no-rights": return {
-        //users are forbiden to see others account
-        status: "login",
-        reason: "not-found",
-      }
       default: {
         assertUnreachable(result)
       }
diff --git a/packages/demobank-ui/src/pages/OperationState/views.tsx 
b/packages/demobank-ui/src/pages/OperationState/views.tsx
index ea38525b9..e623b0dc2 100644
--- a/packages/demobank-ui/src/pages/OperationState/views.tsx
+++ b/packages/demobank-ui/src/pages/OperationState/views.tsx
@@ -129,6 +129,12 @@ export function NeedConfirmationView({ error, onAbort: 
doAbort, onConfirm: doCon
           description: hasError.detail.hint as TranslatedString,
           debug: hasError.detail,
         });
+        case "insufficient-funds": return notify({
+          type: "error",
+          title: i18n.str`Your balance is not enough.`,
+          description: hasError.detail.hint as TranslatedString,
+          debug: hasError.detail,
+        });
         default: assertUnreachable(hasError)
       }
     })
diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx 
b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
index 97e38d75e..31592039f 100644
--- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
+++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
@@ -160,12 +160,30 @@ export function PaytoWireTransferForm({
             description: res.detail.hint as TranslatedString,
             debug: res.detail,
           })
-          case "account-not-found": return notify({
+          case "creditor-not-found": return notify({
             type: "error",
             title: i18n.str`The destination account "${puri}" was not found.`,
             description: res.detail.hint as TranslatedString,
             debug: res.detail,
           })
+          case "creditor-same": return notify({
+            type: "error",
+            title: i18n.str`The origin and the destination of the transfer 
can't be the same.`,
+            description: res.detail.hint as TranslatedString,
+            debug: res.detail,
+          })
+          case "insufficient-funds": return notify({
+            type: "error",
+            title: i18n.str`Your balance is not enough.`,
+            description: res.detail.hint as TranslatedString,
+            debug: res.detail,
+          })
+          case "not-found": return notify({
+            type: "error",
+            title: i18n.str`The origin account "${puri}" was not found.`,
+            description: res.detail.hint as TranslatedString,
+            debug: res.detail,
+          })
           default: assertUnreachable(res)
         }
       }
diff --git a/packages/demobank-ui/src/pages/RegistrationPage.tsx 
b/packages/demobank-ui/src/pages/RegistrationPage.tsx
index fdf2c0e9d..c2eca25e8 100644
--- a/packages/demobank-ui/src/pages/RegistrationPage.tsx
+++ b/packages/demobank-ui/src/pages/RegistrationPage.tsx
@@ -99,15 +99,15 @@ function RegistrationForm({ onComplete, onCancel }: { 
onComplete: () => void, on
       const creationResponse = await api.createAccount("" as AccessToken, { 
name: name ?? "", username, password });
       if (creationResponse.type === "fail") {
         switch (creationResponse.case) {
-          case "invalid-input": return notify({
+          case "invalid-phone-or-email": return notify({
             type: "error",
-            title: i18n.str`Some of the input fields are invalid.`,
+            title: i18n.str`Server replied with invalid phone or email.`,
             description: creationResponse.detail.hint as TranslatedString,
             debug: creationResponse.detail,
           })
-          case "unable-to-create": return notify({
+          case "insufficient-funds": return notify({
             type: "error",
-            title: i18n.str`Unable to create that account.`,
+            title: i18n.str`Registration is disabled because the bank ran out 
of bonus credit.`,
             description: creationResponse.detail.hint as TranslatedString,
             debug: creationResponse.detail,
           })
@@ -117,9 +117,21 @@ function RegistrationForm({ onComplete, onCancel }: { 
onComplete: () => void, on
             description: creationResponse.detail.hint as TranslatedString,
             debug: creationResponse.detail,
           })
-          case "already-exist": return notify({
+          case "payto-already-exists": return notify({
             type: "error",
-            title: i18n.str`That username is already taken`,
+            title: i18n.str`That account id is already taken.`,
+            description: creationResponse.detail.hint as TranslatedString,
+            debug: creationResponse.detail,
+          })
+          case "username-already-exists": return notify({
+            type: "error",
+            title: i18n.str`That username is already taken.`,
+            description: creationResponse.detail.hint as TranslatedString,
+            debug: creationResponse.detail,
+          })
+          case "username-reserved": return notify({
+            type: "error",
+            title: i18n.str`That username can't be used because is reserved.`,
             description: creationResponse.detail.hint as TranslatedString,
             debug: creationResponse.detail,
           })
diff --git a/packages/demobank-ui/src/pages/ShowAccountDetails.tsx 
b/packages/demobank-ui/src/pages/ShowAccountDetails.tsx
index eb8ea8f20..c07802273 100644
--- a/packages/demobank-ui/src/pages/ShowAccountDetails.tsx
+++ b/packages/demobank-ui/src/pages/ShowAccountDetails.tsx
@@ -45,7 +45,6 @@ export function ShowAccountDetails({
     switch (result.case) {
       case "not-found": return <LoginForm reason="not-found" />
       case "unauthorized": return <LoginForm reason="forbidden" />
-      case "no-rights": return <LoginForm reason="forbidden" />
       default: assertUnreachable(result)
     }
   }
@@ -95,7 +94,7 @@ export function ShowAccountDetails({
 
   return (
     <Fragment>
-          <ShowLocalNotification notification={notification} />
+      <ShowLocalNotification notification={notification} />
       {accountIsTheCurrentUser ?
         <ProfileNavigation current="details" />
         :
diff --git a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx 
b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
index 89538e305..b548c0d16 100644
--- a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
@@ -119,6 +119,12 @@ export function WithdrawalConfirmationQuestion({
             description: resp.detail.hint as TranslatedString,
             debug: resp.detail,
           })
+          case "insufficient-funds": return notify({
+            type: "error",
+            title: i18n.str`Your balance is not enough for the operation.`,
+            description: resp.detail.hint as TranslatedString,
+            debug: resp.detail,
+          })
           default: assertUnreachable(resp)
         }
       }
@@ -161,7 +167,7 @@ export function WithdrawalConfirmationQuestion({
 
   return (
     <Fragment>
-          <ShowLocalNotification notification={notification} />
+      <ShowLocalNotification notification={notification} />
 
       <div class="bg-white shadow sm:rounded-lg">
         <div class="px-4 py-5 sm:p-6">
diff --git a/packages/demobank-ui/src/pages/admin/Account.tsx 
b/packages/demobank-ui/src/pages/admin/Account.tsx
index 7109b082f..19189bec4 100644
--- a/packages/demobank-ui/src/pages/admin/Account.tsx
+++ b/packages/demobank-ui/src/pages/admin/Account.tsx
@@ -23,9 +23,8 @@ export function WireTransfer({ toAccount, onRegister, 
onCancel, onSuccess }: { o
   }
   if (result.type === "fail") {
     switch (result.case) {
-      case "unauthorized": return <LoginForm reason="forbidden"  />
+      case "unauthorized": return <LoginForm reason="forbidden" />
       case "not-found": return <LoginForm reason="not-found" />
-      case "no-rights": return <LoginForm reason="not-found" />
       default: assertUnreachable(result)
     }
   }
diff --git a/packages/demobank-ui/src/pages/admin/AccountList.tsx 
b/packages/demobank-ui/src/pages/admin/AccountList.tsx
index 056035750..be5194e6d 100644
--- a/packages/demobank-ui/src/pages/admin/AccountList.tsx
+++ b/packages/demobank-ui/src/pages/admin/AccountList.tsx
@@ -30,9 +30,8 @@ export function AccountList({ onRemoveAccount, 
onShowAccountDetails, onUpdateAcc
   }
   if (result.data.type === "fail") {
     switch (result.data.case) {
-      case "unauthorized": return <Fragment/>
-      case "no-rights": return <Fragment/>
-      default: assertUnreachable(result.data)
+      case "unauthorized": return <Fragment />
+      default: assertUnreachable(result.data.case)
     }
   }
 
diff --git a/packages/demobank-ui/src/pages/admin/AdminHome.tsx 
b/packages/demobank-ui/src/pages/admin/AdminHome.tsx
index 1e3d3d748..a30cae547 100644
--- a/packages/demobank-ui/src/pages/admin/AdminHome.tsx
+++ b/packages/demobank-ui/src/pages/admin/AdminHome.tsx
@@ -124,25 +124,28 @@ function Metrics(): VNode {
     </div>
     <dl class="mt-5 grid grid-cols-1 divide-y divide-gray-200 overflow-hidden 
rounded-lg bg-white shadow-lg md:grid-cols-3 md:divide-x md:divide-y-0">
 
-      <div class="px-4 py-5 sm:p-6">
-        <dt class="text-base font-normal text-gray-900">
-          <i18n.Translate>Cashin</i18n.Translate>
-        </dt>
-        <MetricValue
-          current={resp.current.body.cashinExternalVolume}
-          previous={resp.previous.body.cashinExternalVolume}
-        />
-      </div>
-
-      <div class="px-4 py-5 sm:p-6">
-        <dt class="text-base font-normal text-gray-900">
-          <i18n.Translate>Cashout</i18n.Translate>
-        </dt>
-        <MetricValue
-          current={resp.current.body.cashoutExternalVolume}
-          previous={resp.previous.body.cashoutExternalVolume}
-        />
-      </div>
+      {resp.current.body.type !== "with-cashout" || resp.previous.body.type 
!== "with-cashout" ? undefined :
+        <Fragment>
+          <div class="px-4 py-5 sm:p-6">
+            <dt class="text-base font-normal text-gray-900">
+              <i18n.Translate>Cashin</i18n.Translate>
+            </dt>
+            <MetricValue
+              current={resp.current.body.cashinExternalVolume}
+              previous={resp.previous.body.cashinExternalVolume}
+            />
+          </div>
+          <div class="px-4 py-5 sm:p-6">
+            <dt class="text-base font-normal text-gray-900">
+              <i18n.Translate>Cashout</i18n.Translate>
+            </dt>
+            <MetricValue
+              current={resp.current.body.cashoutExternalVolume}
+              previous={resp.previous.body.cashoutExternalVolume}
+            />
+          </div>
+        </Fragment>
+      }
       <div class="px-4 py-5 sm:p-6">
         <dt class="text-base font-normal text-gray-900">
           <i18n.Translate>Payout</i18n.Translate>
diff --git a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx 
b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
index f2c1d5456..3f4364c16 100644
--- a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
+++ b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
@@ -49,27 +49,39 @@ export function CreateNewAccount({
         onCreateSuccess();
       } else {
         switch (resp.case) {
-          case "invalid-input": return notify({
+          case "invalid-phone-or-email": return notify({
             type: "error",
-            title: i18n.str`Server replied that input data was invalid`,
+            title: i18n.str`Server replied with invalid phone or email`,
             description: resp.detail.hint as TranslatedString,
             debug: resp.detail,
           })
-          case "unable-to-create": return notify({
+          case "unauthorized": return notify({
             type: "error",
-            title: i18n.str`The account name is registered.`,
+            title: i18n.str`The rights to perform the operation are not 
sufficient`,
             description: resp.detail.hint as TranslatedString,
             debug: resp.detail,
           })
-          case "unauthorized": return notify({
+          case "username-already-exists": return notify({
             type: "error",
-            title: i18n.str`The rights to perform the operation are not 
sufficient`,
+            title: i18n.str`Account username is already taken`,
+            description: resp.detail.hint as TranslatedString,
+            debug: resp.detail,
+          })
+          case "payto-already-exists": return notify({
+            type: "error",
+            title: i18n.str`Account id is already taken`,
+            description: resp.detail.hint as TranslatedString,
+            debug: resp.detail,
+          })
+          case "insufficient-funds": return notify({
+            type: "error",
+            title: i18n.str`Bank ran out of bonus credit.`,
             description: resp.detail.hint as TranslatedString,
             debug: resp.detail,
           })
-          case "already-exist": return notify({
+          case "username-reserved": return notify({
             type: "error",
-            title: i18n.str`Account name is already taken`,
+            title: i18n.str`Account username can't be used because is 
reserved`,
             description: resp.detail.hint as TranslatedString,
             debug: resp.detail,
           })
diff --git a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx 
b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
index 1a5255595..fa9693941 100644
--- a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
+++ b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
@@ -45,7 +45,6 @@ export function RemoveAccount({
     switch (result.case) {
       case "unauthorized": return <LoginForm reason="forbidden" />
       case "not-found": return <LoginForm reason="not-found" />
-      case "no-rights": return <LoginForm reason="not-found" />
       default: assertUnreachable(result)
     }
   }
@@ -82,9 +81,9 @@ export function RemoveAccount({
             description: resp.detail.hint as TranslatedString,
             debug: resp.detail,
           })
-          case "unable-to-delete": return notify({
+          case "username-reserved": return notify({
             type: "error",
-            title: i18n.str`The administrator specified a institutional 
username.`,
+            title: i18n.str`Can't delete a reserved username.`,
             description: resp.detail.hint as TranslatedString,
             debug: resp.detail,
           })
diff --git a/packages/demobank-ui/src/pages/business/CreateCashout.tsx 
b/packages/demobank-ui/src/pages/business/CreateCashout.tsx
index 8d90e9205..5c284be24 100644
--- a/packages/demobank-ui/src/pages/business/CreateCashout.tsx
+++ b/packages/demobank-ui/src/pages/business/CreateCashout.tsx
@@ -100,7 +100,6 @@ export function CreateCashout({
     switch (resultAccount.case) {
       case "unauthorized": return <LoginForm reason="forbidden" />
       case "not-found": return <LoginForm reason="not-found" />
-      case "no-rights": return <LoginForm reason="not-found" />
       default: assertUnreachable(resultAccount)
     }
   }
@@ -178,7 +177,7 @@ export function CreateCashout({
 
   return (
     <div>
-          <ShowLocalNotification notification={notification} />
+      <ShowLocalNotification notification={notification} />
       <h1>New cashout</h1>
       <form class="pure-form">
         <fieldset>
@@ -393,9 +392,15 @@ export function CreateCashout({
                       description: resp.detail.hint as TranslatedString,
                       debug: resp.detail,
                     });
-                    case "cashout-or-tan-not-supported": return notify({
+                    case "account-not-found": return notify({
                       type: "error",
-                      title: i18n.str`The bank does not support the TAN 
channel for this operation`,
+                      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,
                     });
diff --git a/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx 
b/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
index 7e7ed21cb..a8b57b90c 100644
--- a/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
+++ b/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
@@ -65,7 +65,7 @@ export function ShowCashoutDetails({
   }
   if (result.type === "fail") {
     switch (result.case) {
-      case "already-aborted": return <Attention type="warning" 
title={i18n.str`This cashout has already been aborted.`}>
+      case "not-found": return <Attention type="warning" title={i18n.str`This 
cashout not found. Maybe already aborted.`}>
       </Attention>
       case "cashout-not-supported": return <Attention type="warning" 
title={i18n.str`Cashouts are not supported`}>
       </Attention>
diff --git a/packages/taler-harness/src/http-client/bank-core.ts 
b/packages/taler-harness/src/http-client/bank-core.ts
index 6f9641dc9..ccefd2bfe 100644
--- a/packages/taler-harness/src/http-client/bank-core.ts
+++ b/packages/taler-harness/src/http-client/bank-core.ts
@@ -1,542 +1,841 @@
-import { AccessToken, Amounts, TalerCoreBankHttpClient, TalerCorebankApi, 
buildPayto, encodeCrock, failOrThrow, getRandomBytes, parsePaytoUri, 
stringifyPaytoUri, succeedOrThrow } from "@gnu-taler/taler-util"
-
-export class BankCoreSmokeTest {
-  constructor(readonly api: TalerCoreBankHttpClient) {
-
-  }
-
-  async testConfig() {
-    const config = await this.api.getConfig()
-    if (!this.api.isCompatible(config.body.version)) {
-      throw Error(`not compatible with server ${config.body.version}`)
-    }
-    return config.body
-  }
-
-  async testCashouts(adminPassword: string) {
-  }
-  async testMonitor(adminPassword: string) {
-    const { access_token: adminToken } = await succeedOrThrow(() =>
-      this.api.getAuthenticationAPI("admin").createAccessToken(adminPassword, {
-        scope: "readwrite"
-      })
-    )
-
-    await succeedOrThrow(() => (
-      this.api.getMonitor()
-    ))
-
-    await succeedOrThrow(() => (
-      this.api.getMonitor({
-        timeframe: TalerCorebankApi.MonitorTimeframeParam.day,
-        which: (new Date()).getDate() - 1
-      })
-    ))
-  }
-
-  async testAccountManagement(adminPassword: string) {
-
-    const { access_token: adminToken } = await succeedOrThrow(() =>
-      this.api.getAuthenticationAPI("admin").createAccessToken(adminPassword, {
-        scope: "readwrite"
-      })
-    )
-
-    /**
-     * Create account
-    */
-    {
-      const username = "harness-" + 
encodeCrock(getRandomBytes(10)).toLowerCase();
-
-      // await failOrThrow("invalid-input",() =>
-      //   this.api.createAccount(adminToken, {
-      //     name: username,
-      //     username, password: "123",
-      //     challenge_contact_data: {
-      //       email: "invalid email",
-      //       phone: "invalid phone",
-      //     }
-      //   })
-      // )
-
-      // await failOrThrow("unable-to-create",() =>
-      //   this.api.createAccount(adminToken, {
-      //     name: "admin",
-      //     username, password: "123"
-      //   })
-      // )
-
-      // await failOrThrow("unable-to-create",() =>
-      //   this.api.createAccount(adminToken, {
-      //     name: "bank",
-      //     username, password: "123"
-      //   })
-      // )
-
-      await succeedOrThrow(() =>
-        this.api.createAccount(adminToken, {
-          name: username,
-          username, password: "123"
-        })
-      )
-
-      await failOrThrow("already-exist", () =>
-        this.api.createAccount(adminToken, {
-          name: username,
-          username, password: "123"
-        })
-      );
-    }
-
-    /**
-     * Delete account
-     */
-    {
-      const { username, token } = await createRandomTestUser(this.api, 
adminToken)
-
-      await failOrThrow("not-found", () =>
-        this.api.deleteAccount({ username: "not-found", token: adminToken })
-      )
-      await failOrThrow("unable-to-delete", () =>
-        this.api.deleteAccount({ username: "admin", token: adminToken })
-      )
-      await failOrThrow("unable-to-delete", () =>
-        this.api.deleteAccount({ username: "bank", token: adminToken })
-      )
-
-      await failOrThrow("balance-not-zero", () =>
-        this.api.deleteAccount({ username, token: adminToken })
-      )
-
-      const userInfo = await succeedOrThrow(() =>
-        this.api.getAccount({ username, token })
-      )
-
-      const adminInfo = await succeedOrThrow(() =>
-        this.api.getAccount({ username: "admin", token: adminToken })
-      )
-
-      const adminAccount = parsePaytoUri(adminInfo.payto_uri)!
-      adminAccount.params["message"] = "all my money"
-      const withSubject = stringifyPaytoUri(adminAccount)
-
-      await succeedOrThrow(() =>
-        this.api.createTransaction({ username, token }, {
-          payto_uri: withSubject,
-          amount: userInfo.balance.amount
-        })
-      )
-
-
-      const otherUsername = "harness-" + 
encodeCrock(getRandomBytes(10)).toLowerCase();
-
-      await succeedOrThrow(() =>
-        this.api.createAccount(adminToken, {
-          name: otherUsername,
-          username: otherUsername, password: "123"
-        })
-      )
-
-      await failOrThrow("unauthorized", () =>
-        this.api.deleteAccount({ username: otherUsername, token })
-      )
-
-      await succeedOrThrow(() =>
-        this.api.deleteAccount({ username, token: adminToken })
-      )
-    }
-
-    /**
-     * Update account
-     */
-    {
-      const { username, token } = await createRandomTestUser(this.api, 
adminToken)
-
-      await failOrThrow("cant-change-legal-name-or-admin", () =>
-        this.api.updateAccount({ username, token }, {
-          name: "something else",
-        })
-      )
-
-      // await failOrThrow("not-found", () =>
-      //   this.api.updateAccount({ username: "notfound", token }, {
-      //     challenge_contact_data: {
-      //       email: "asd@Aasd.com"
-      //     }
-      //   })
-      // )
-
-      await failOrThrow("unauthorized", () =>
-        this.api.updateAccount({ username: "notfound", token: "wrongtoken" as 
AccessToken }, {
-          challenge_contact_data: {
-            email: "asd@Aasd.com"
-          }
-        })
-      )
-
-      await succeedOrThrow(() =>
-        this.api.updateAccount({ username, token }, {
-          challenge_contact_data: {
-            email: "asd@Aasd.com"
-          }
-        })
-      )
-    }
-
-    /**
-     * Update password
-     */
-    {
-      const { username, token } = await createRandomTestUser(this.api, 
adminToken)
-
-      await succeedOrThrow(() =>
-        this.api.updatePassword({ username, token }, {
-          old_password: "123",
-          new_password: "234"
-        })
-      )
-      // await failOrThrow("not-found",() =>
-      //   this.api.updatePassword({ username:"notfound", token: userTempToken 
}, {
-      //     old_password: "123",
-      //     new_password: "234"
-      //   })
-      // )
-      await failOrThrow("unauthorized", () =>
-        this.api.updatePassword({ username: "admin", token }, {
-          old_password: "123",
-          new_password: "234"
-        })
-      )
-      // await failOrThrow("old-password-invalid-or-not-allowed",() =>
-      //   this.api.updatePassword({ username, token: userTempToken }, {
-      //     old_password: "123",
-      //     new_password: "234"
-      //   })
-      // )
-
-    }
-
-    /**
-     * public accounts
-     */
-    {
-      const acs = await succeedOrThrow(() => this.api.getPublicAccounts())
-
-    }
-    /**
-     * get accounts
-     */
-    {
-      const { username, token } = await createRandomTestUser(this.api, 
adminToken)
-      // await failOrThrow("no-rights",() => 
-      //   this.api.getAccounts(token)
-      // )
-      await failOrThrow("unauthorized", () =>
-        this.api.getAccounts("ASDASD" as AccessToken)
-      )
-
-      const acs = await succeedOrThrow(() =>
-        this.api.getAccounts(adminToken)
-      )
-    }
-
-  }
-
-  async testWithdrawals(adminPassword: string) {
-    const { access_token: adminToken } = await succeedOrThrow(() =>
-      this.api.getAuthenticationAPI("admin").createAccessToken(adminPassword, {
-        scope: "readwrite"
-      })
-    )
-    /**
-     * create withdrawals
-     */
-    {
-      const { username, token } = await createRandomTestUser(this.api, 
adminToken)
-
-      const userInfo = await succeedOrThrow(() =>
-        this.api.getAccount({ username, token })
-      )
-
-      const balance = Amounts.parseOrThrow(userInfo.balance.amount)
-      const moreThanBalance = Amounts.stringify(Amounts.mult(balance, 
5).amount)
-      await failOrThrow("insufficient-funds", () =>
-        this.api.createWithdrawal({ username, token }, {
-          amount: moreThanBalance
-        })
-      )
-
-      await failOrThrow("unauthorized", () =>
-        this.api.createWithdrawal({ username, token: "wrongtoken" as 
AccessToken }, {
-          amount: userInfo.balance.amount
-        })
-      )
-
-      await succeedOrThrow(() =>
-        this.api.createWithdrawal({ username, token }, {
-          amount: userInfo.balance.amount
-        })
-      )
-    }
-
-    /**
-     * get withdrawal
-     */
-    {
-      const { username, token } = await createRandomTestUser(this.api, 
adminToken)
-
-      const userInfo = await succeedOrThrow(() =>
-        this.api.getAccount({ username, token })
-      )
-
-      const { withdrawal_id } = await succeedOrThrow(() =>
-        this.api.createWithdrawal({ username, token }, {
-          amount: userInfo.balance.amount
-        })
-      )
-
-      await succeedOrThrow(() =>
-        this.api.getWithdrawalById(withdrawal_id)
-      )
-
-      await failOrThrow("invalid-id", () =>
-        this.api.getWithdrawalById("invalid")
-      )
-      await failOrThrow("not-found", () =>
-        this.api.getWithdrawalById("11111111-1111-1111-1111-111111111111")
-      )
-    }
-
-    /**
-     * abort withdrawal
-     */
-    {
-      const { username: exchangeUser, token: exchangeToken } = await 
createRandomTestUser(this.api, adminToken, { is_taler_exchange: true })
-      const { username, token } = await createRandomTestUser(this.api, 
adminToken)
-
-      const userInfo = await succeedOrThrow(() =>
-        this.api.getAccount({ username, token })
-      )
-      const exchangeInfo = await succeedOrThrow(() =>
-        this.api.getAccount({ username: exchangeUser, token: exchangeToken })
-      )
-
-      await failOrThrow("invalid-id", () =>
-        this.api.abortWithdrawalById("invalid")
-      )
-      await failOrThrow("not-found", () =>
-        this.api.abortWithdrawalById("11111111-1111-1111-1111-111111111111")
-      )
-
-      const { withdrawal_id: firstWithdrawal } = await succeedOrThrow(() =>
-        this.api.createWithdrawal({ username, token }, {
-          amount: userInfo.balance.amount
-        })
-      )
-
-      await succeedOrThrow(() =>
-        this.api.abortWithdrawalById(firstWithdrawal)
-      )
-
-      const { taler_withdraw_uri: uri, withdrawal_id: secondWithdrawal } = 
await succeedOrThrow(() =>
-        this.api.createWithdrawal({ username, token }, {
-          amount: userInfo.balance.amount
-        })
-      )
-
-      await succeedOrThrow(() =>
-        
this.api.getIntegrationAPI().completeWithdrawalOperationById(secondWithdrawal, {
-          reserve_pub: encodeCrock(getRandomBytes(32)),
-          selected_exchange: exchangeInfo.payto_uri,
-        })
-      )
-      await succeedOrThrow(() =>
-        this.api.confirmWithdrawalById(secondWithdrawal)
-      )
-      await failOrThrow("previously-confirmed", () =>
-        this.api.abortWithdrawalById(secondWithdrawal)
-      )
-    }
-
-    /**
-     * confirm withdrawal
-     */
-    {
-      const { username: exchangeUser, token: exchangeToken } = await 
createRandomTestUser(this.api, adminToken, { is_taler_exchange: true })
-      const { username, token } = await createRandomTestUser(this.api, 
adminToken)
-
-      const userInfo = await succeedOrThrow(() =>
-        this.api.getAccount({ username, token })
-      )
-      const exchangeInfo = await succeedOrThrow(() =>
-        this.api.getAccount({ username: exchangeUser, token: exchangeToken })
-      )
-
-      await failOrThrow("invalid-id", () =>
-        this.api.confirmWithdrawalById("invalid")
-      )
-      await failOrThrow("not-found", () =>
-        this.api.confirmWithdrawalById("11111111-1111-1111-1111-111111111111")
-      )
-
-      const { withdrawal_id: firstWithdrawal } = await succeedOrThrow(() =>
-        this.api.createWithdrawal({ username, token }, {
-          amount: userInfo.balance.amount
-        })
-      )
-
-      await failOrThrow("no-exchange-or-reserve-selected", () =>
-        this.api.confirmWithdrawalById(firstWithdrawal)
-      )
-
-      await succeedOrThrow(() =>
-        
this.api.getIntegrationAPI().completeWithdrawalOperationById(firstWithdrawal, {
-          reserve_pub: encodeCrock(getRandomBytes(32)),
-          selected_exchange: exchangeInfo.payto_uri,
-        })
-      )
-
-      await succeedOrThrow(() =>
-        this.api.confirmWithdrawalById(firstWithdrawal)
-      )
-
-      const { withdrawal_id: secondWithdrawal } = await succeedOrThrow(() =>
-        this.api.createWithdrawal({ username, token }, {
-          amount: userInfo.balance.amount
-        })
-      )
-
-      await succeedOrThrow(() =>
-        this.api.abortWithdrawalById(secondWithdrawal)
-      )
-      //FIXME: skip
-      // await failOrThrow("previously-aborted", () =>
-      //   this.api.confirmWithdrawalById(secondWithdrawal)
-      // )
-    }
+import { AccessToken, Amounts, TalerCoreBankHttpClient, TalerCorebankApi, 
TestForApi, buildPayto, encodeCrock, failOrThrow, getRandomBytes, 
parsePaytoUri, stringifyPaytoUri, succeedOrThrow } from "@gnu-taler/taler-util"
+
+
+
+export function createTestForBankCore(adminToken: AccessToken): 
TestForApi<TalerCoreBankHttpClient> {
+  return {
+    test_abortCashoutById: {
+      success: undefined,
+      "already-confirmed": undefined,
+      "cashout-not-supported": undefined,
+      "not-found": undefined,
+    },
+    test_createCashout: {
+      "account-not-found": undefined,
+      "incorrect-exchange-rate": undefined,
+      "no-contact-info": undefined,
+      "no-enough-balance": undefined,
+      "cashout-not-supported": undefined,
+      success: undefined,
+    },
+    test_confirmCashoutById: {
+      "cashout-address-changed": undefined,
+      "cashout-not-supported": undefined,
+      "not-found": undefined,
+      "wrong-tan-or-credential": undefined,
+      success: undefined,
+    },
+    test_getAccountCashouts: {
+      "cashout-not-supported": undefined,
+      "account-not-found": undefined,
+      success: undefined,
+    },
+    test_getCashoutById: {
+      "cashout-not-supported": undefined,
+      success: undefined,
+      "not-found": undefined,
+    },
+    test_getCashoutRate: {
+      "cashout-not-supported": undefined,
+      "not-supported": undefined,
+      "wrong-calculation": undefined,
+      success: undefined,
+    },
+    test_getGlobalCashouts: {
+      "cashout-not-supported": undefined,
+      success: undefined,
+    },
+    test_abortWithdrawalById: {
+      "invalid-id": async (api) => {
+        await failOrThrow("invalid-id", () =>
+          api.abortWithdrawalById("invalid")
+        )
+      },
+      "not-found": async (api) => {
+        await failOrThrow("not-found", () =>
+          api.abortWithdrawalById("11111111-1111-1111-1111-111111111111")
+        )
+      },
+      "previously-confirmed": async (api) => {
+        const { username: exchangeUser, token: exchangeToken } = await 
createRandomTestUser(api, adminToken, { is_taler_exchange: true })
+        const { username, token } = await createRandomTestUser(api, adminToken)
+
+        const userInfo = await succeedOrThrow(() =>
+          api.getAccount({ username, token })
+        )
+        const exchangeInfo = await succeedOrThrow(() =>
+          api.getAccount({ username: exchangeUser, token: exchangeToken })
+        )
+
+        const { withdrawal_id } = await succeedOrThrow(() =>
+          api.createWithdrawal({ username, token }, {
+            amount: userInfo.balance.amount
+          })
+        )
+
+        await succeedOrThrow(() =>
+          
api.getIntegrationAPI().completeWithdrawalOperationById(withdrawal_id, {
+            reserve_pub: encodeCrock(getRandomBytes(32)),
+            selected_exchange: exchangeInfo.payto_uri,
+          })
+        )
+        await succeedOrThrow(() =>
+          api.confirmWithdrawalById(withdrawal_id)
+        )
+        await failOrThrow("previously-confirmed", () =>
+          api.abortWithdrawalById(withdrawal_id)
+        )
+      },
+      success: async (api) => {
+        const { username, token } = await createRandomTestUser(api, adminToken)
+
+        const userInfo = await succeedOrThrow(() =>
+          api.getAccount({ username, token })
+        )
+
+        const { withdrawal_id: firstWithdrawal } = await succeedOrThrow(() =>
+          api.createWithdrawal({ username, token }, {
+            amount: userInfo.balance.amount
+          })
+        )
+
+        await succeedOrThrow(() =>
+          api.abortWithdrawalById(firstWithdrawal)
+        )
+      },
+    },
+    test_confirmWithdrawalById: {
+      "insufficient-funds": async (api) => {
+
+      },
+      "invalid-id": async (api) => {
+        await failOrThrow("invalid-id", () =>
+          api.confirmWithdrawalById("invalid")
+        )
+      },
+      "no-exchange-or-reserve-selected": async (api) => {
+        const { username, token } = await createRandomTestUser(api, adminToken)
+
+        const userInfo = await succeedOrThrow(() =>
+          api.getAccount({ username, token })
+        )
+
+        const { withdrawal_id } = await succeedOrThrow(() =>
+          api.createWithdrawal({ username, token }, {
+            amount: userInfo.balance.amount
+          })
+        )
+
+        await failOrThrow("no-exchange-or-reserve-selected", () =>
+          api.confirmWithdrawalById(withdrawal_id)
+        )
+      },
+      "not-found": async (api) => {
+        await failOrThrow("not-found", () =>
+          api.confirmWithdrawalById("11111111-1111-1111-1111-111111111111")
+        )
+      },
+      "previously-aborted": async (api) => {
+        const { username, token } = await createRandomTestUser(api, adminToken)
+        const userInfo = await succeedOrThrow(() =>
+          api.getAccount({ username, token })
+        )
+        const { withdrawal_id } = await succeedOrThrow(() =>
+          api.createWithdrawal({ username, token }, {
+            amount: userInfo.balance.amount
+          })
+        )
+
+        await succeedOrThrow(() =>
+          api.abortWithdrawalById(withdrawal_id)
+        )
+        await failOrThrow("previously-aborted", () =>
+          api.confirmWithdrawalById(withdrawal_id)
+        )
+      },
+      success: async (api) => {
+        const { username: exchangeUser, token: exchangeToken } = await 
createRandomTestUser(api, adminToken, { is_taler_exchange: true })
+        const { username, token } = await createRandomTestUser(api, adminToken)
+
+        const userInfo = await succeedOrThrow(() =>
+          api.getAccount({ username, token })
+        )
+        const exchangeInfo = await succeedOrThrow(() =>
+          api.getAccount({ username: exchangeUser, token: exchangeToken })
+        )
+
+        const { withdrawal_id } = await succeedOrThrow(() =>
+          api.createWithdrawal({ username, token }, {
+            amount: userInfo.balance.amount
+          })
+        )
+
+        await succeedOrThrow(() =>
+          
api.getIntegrationAPI().completeWithdrawalOperationById(withdrawal_id, {
+            reserve_pub: encodeCrock(getRandomBytes(32)),
+            selected_exchange: exchangeInfo.payto_uri,
+          })
+        )
+
+        await succeedOrThrow(() =>
+          api.confirmWithdrawalById(withdrawal_id)
+        )
+
+      },
+    },
+    test_createAccount: {
+      "insufficient-funds": undefined,
+      "payto-already-exists": async (api) => {
+        const { username, token } = await createRandomTestUser(api, adminToken)
+
+        const userInfo = await succeedOrThrow(() =>
+          api.getAccount({ username, token })
+        )
+
+        const anotherUsername = "harness-" + 
encodeCrock(getRandomBytes(10)).toLowerCase();
+        await failOrThrow("payto-already-exists", () =>
+          api.createAccount(adminToken, {
+            name: anotherUsername,
+            username: anotherUsername,
+            password: "123",
+            internal_payto_uri: userInfo.payto_uri,
+          })
+        );
+
+      },
+      "username-reserved": async (api) => {
+        await failOrThrow("username-reserved", () =>
+          api.createAccount(adminToken, {
+            name: "admin",
+            username: "admin", password: "123"
+          })
+        )
+      },
+      "username-already-exists": async (api) => {
+        const username = "harness-" + 
encodeCrock(getRandomBytes(10)).toLowerCase();
+        await succeedOrThrow(() =>
+          api.createAccount(adminToken, {
+            name: username,
+            username, password: "123"
+          })
+        )
+
+        await failOrThrow("username-already-exists", () =>
+          api.createAccount(adminToken, {
+            name: username,
+            username, password: "123"
+          })
+        );
+      },
+      "invalid-phone-or-email": async (api) => {
+        const username = "harness-" + 
encodeCrock(getRandomBytes(10)).toLowerCase();
+        await failOrThrow("invalid-input", () =>
+          api.createAccount(adminToken, {
+            name: username,
+            username, password: "123",
+            challenge_contact_data: {
+              email: "invalid email",
+              phone: "invalid phone",
+            }
+          })
+        )
+      },
+      success: async (api) => {
+        const username = "harness-" + 
encodeCrock(getRandomBytes(10)).toLowerCase();
+
+        await succeedOrThrow(() =>
+          api.createAccount(adminToken, {
+            name: username,
+            username, password: "123"
+          })
+        )
+      },
+      unauthorized: async (api) => {
+        const username = "harness-" + 
encodeCrock(getRandomBytes(10)).toLowerCase();
+
+        await succeedOrThrow(() =>
+          api.createAccount(adminToken, {
+            name: username,
+            username, password: "123"
+          })
+        )
+
+        const { access_token } = await succeedOrThrow(() =>
+          api.getAuthenticationAPI(username).createAccessToken("123", {
+            scope: "readwrite"
+          })
+        )
+
+        const anotherUser = "harness-" + 
encodeCrock(getRandomBytes(10)).toLowerCase();
+        await failOrThrow("unauthorized", () =>
+          api.createAccount(access_token, {
+            name: anotherUser,
+            username: anotherUser, password: "123"
+          })
+        )
+
+      },
+
+    },
+    test_createTransaction: {
+      "creditor-not-found": async (api) => {
+        const { username, token } = await createRandomTestUser(api, adminToken)
+
+        const userInfo = await succeedOrThrow(() =>
+          api.getAccount({ username, token })
+        )
+
+        const notFoundAccount = buildPayto("iban", "DE1231231231", undefined)
+        notFoundAccount.params["message"] = "not-found"
+        await failOrThrow("creditor-not-found", () =>
+          api.createTransaction({ username, token }, {
+            payto_uri: stringifyPaytoUri(notFoundAccount),
+            amount: userInfo.balance.amount
+          })
+        )
+      },
+      "creditor-same": async (api) => {
+        const { username, token } = await createRandomTestUser(api, adminToken)
+
+        const userInfo = await succeedOrThrow(() =>
+          api.getAccount({ username, token })
+        )
+        const account = parsePaytoUri(userInfo.payto_uri)!
+        account.params["message"] = "myaccount"
+
+        await failOrThrow("creditor-same", () =>
+          api.createTransaction({ username, token }, {
+            payto_uri: stringifyPaytoUri(account),
+            amount: userInfo.balance.amount
+          })
+        )
+
+      },
+      "insufficient-funds": async (api) => {
+        const { username, token } = await createRandomTestUser(api, adminToken)
+        const { username: otherUser, token: otherToken } = await 
createRandomTestUser(api, adminToken)
+
+        const userInfo = await succeedOrThrow(() =>
+          api.getAccount({ username, token })
+        )
+        const otherInfo = await succeedOrThrow(() =>
+          api.getAccount({ username: otherUser, token: otherToken })
+        )
+        const otherAccount = parsePaytoUri(otherInfo.payto_uri)!
+        otherAccount.params["message"] = "all"
+
+        await failOrThrow("insufficient-funds", () =>
+          api.createTransaction({ username, token }, {
+            payto_uri: stringifyPaytoUri(otherAccount),
+            amount: Amounts.stringify(Amounts.mult(userInfo.balance.amount, 
20).amount)
+          })
+        )
+      },
+      "not-found": async (api) => {
+        const { username, token } = await createRandomTestUser(api, adminToken)
+        const { username: otherUser, token: otherToken } = await 
createRandomTestUser(api, adminToken)
+
+        const userInfo = await succeedOrThrow(() =>
+          api.getAccount({ username, token })
+        )
+        const otherInfo = await succeedOrThrow(() =>
+          api.getAccount({ username: otherUser, token: otherToken })
+        )
+        const otherAccount = parsePaytoUri(otherInfo.payto_uri)!
+        otherAccount.params["message"] = "all"
+
+        await succeedOrThrow(() =>
+          api.createTransaction({ username: "notfound", token }, {
+            payto_uri: stringifyPaytoUri(otherAccount),
+            amount: userInfo.balance.amount
+          })
+        )
+      },
+      "invalid-input": async (api) => {
+        const { username, token } = await createRandomTestUser(api, adminToken)
+        const { username: otherUser, token: otherToken } = await 
createRandomTestUser(api, adminToken)
+
+        const userInfo = await succeedOrThrow(() =>
+          api.getAccount({ username, token })
+        )
+        const otherInfo = await succeedOrThrow(() =>
+          api.getAccount({ username: otherUser, token: otherToken })
+        )
+        const otherAccount = parsePaytoUri(otherInfo.payto_uri)!
+        otherAccount.params["message"] = "all"
+
+        //missing amount
+        await failOrThrow("invalid-input", () =>
+          api.createTransaction({ username, token }, {
+            payto_uri: stringifyPaytoUri(otherAccount),
+            // amount: userInfo.balance.amount
+          })
+        )
+        //missing subject
+        await failOrThrow("invalid-input", () =>
+          api.createTransaction({ username, token }, {
+            payto_uri: otherInfo.payto_uri,
+            amount: userInfo.balance.amount
+          })
+        )
+
+      },
+      success: async (api) => {
+        const { username, token } = await createRandomTestUser(api, adminToken)
+        const { username: otherUser, token: otherToken } = await 
createRandomTestUser(api, adminToken)
+
+        const userInfo = await succeedOrThrow(() =>
+          api.getAccount({ username, token })
+        )
+        const otherInfo = await succeedOrThrow(() =>
+          api.getAccount({ username: otherUser, token: otherToken })
+        )
+        const otherAccount = parsePaytoUri(otherInfo.payto_uri)!
+        otherAccount.params["message"] = "all"
+
+        await succeedOrThrow(() =>
+          api.createTransaction({ username, token }, {
+            payto_uri: stringifyPaytoUri(otherAccount),
+            amount: userInfo.balance.amount
+          })
+        )
+
+      },
+      unauthorized: async (api) => {
+        const { username, token } = await createRandomTestUser(api, adminToken)
+        const { username: otherUser, token: otherToken } = await 
createRandomTestUser(api, adminToken)
+
+        const userInfo = await succeedOrThrow(() =>
+          api.getAccount({ username, token })
+        )
+        const otherInfo = await succeedOrThrow(() =>
+          api.getAccount({ username: otherUser, token: otherToken })
+        )
+        const otherAccount = parsePaytoUri(otherInfo.payto_uri)!
+        otherAccount.params["message"] = "all"
+
+        await failOrThrow("unauthorized", () =>
+          api.createTransaction({ username, token: "wrongtoken" as AccessToken 
}, {
+            payto_uri: stringifyPaytoUri(otherAccount),
+            amount: userInfo.balance.amount
+          })
+        )
+      },
+    },
+    test_createWithdrawal: {
+      "account-not-found": async (api) => {
+        const userInfo = await succeedOrThrow(() =>
+          api.getAccount({ username: "admin", token: adminToken })
+        )
+        await succeedOrThrow(() =>
+          api.createWithdrawal({ username: "notfound", token: adminToken }, {
+            amount: userInfo.balance.amount
+          })
+        )
+
+      },
+      "insufficient-funds": async (api) => {
+        const { username, token } = await createRandomTestUser(api, adminToken)
+        const userInfo = await succeedOrThrow(() =>
+          api.getAccount({ username, token })
+        )
+
+        const balance = Amounts.parseOrThrow(userInfo.balance.amount)
+        const moreThanBalance = Amounts.stringify(Amounts.mult(balance, 
5).amount)
+        await failOrThrow("insufficient-funds", () =>
+          api.createWithdrawal({ username, token }, {
+            amount: moreThanBalance
+          })
+        )
+      },
+      success: async (api) => {
+        const { username, token } = await createRandomTestUser(api, adminToken)
+        const userInfo = await succeedOrThrow(() =>
+          api.getAccount({ username, token })
+        )
+        await succeedOrThrow(() =>
+          api.createWithdrawal({ username, token }, {
+            amount: userInfo.balance.amount
+          })
+        )
+
+      },
+      unauthorized: async (api) => {
+        const { username, token } = await createRandomTestUser(api, adminToken)
+        const userInfo = await succeedOrThrow(() =>
+          api.getAccount({ username, token })
+        )
+        await failOrThrow("unauthorized", () =>
+          api.createWithdrawal({ username, token: "wrongtoken" as AccessToken 
}, {
+            amount: userInfo.balance.amount
+          })
+        )
+
+      },
+    },
+    test_deleteAccount: {
+      "balance-not-zero": async (api) => {
+        const { username, token } = await createRandomTestUser(api, adminToken)
+
+        await failOrThrow("balance-not-zero", () =>
+          api.deleteAccount({ username, token: adminToken })
+        )
+
+      },
+      "not-found": async (api) => {
+        await failOrThrow("not-found", () =>
+          api.deleteAccount({ username: "not-found", token: adminToken })
+        )
+      },
+      "username-reserved": async (api) => {
+        await failOrThrow("username-reserved", () =>
+          api.deleteAccount({ username: "admin", token: adminToken })
+        )
+        await failOrThrow("username-reserved", () =>
+          api.deleteAccount({ username: "bank", token: adminToken })
+        )
+      },
+      success: async (api) => {
+        const { username, token } = await createRandomTestUser(api, adminToken)
+
+        const userInfo = await succeedOrThrow(() =>
+          api.getAccount({ username, token })
+        )
+
+        const adminInfo = await succeedOrThrow(() =>
+          api.getAccount({ username: "admin", token: adminToken })
+        )
+
+        const adminAccount = parsePaytoUri(adminInfo.payto_uri)!
+        adminAccount.params["message"] = "all my money"
+        const withSubject = stringifyPaytoUri(adminAccount)
+
+        await succeedOrThrow(() =>
+          api.createTransaction({ username, token }, {
+            payto_uri: withSubject,
+            amount: userInfo.balance.amount
+          })
+        )
+
+      },
+      unauthorized: async (api) => {
+        const username = "harness-" + 
encodeCrock(getRandomBytes(10)).toLowerCase();
+
+        await succeedOrThrow(() =>
+          api.createAccount(adminToken, {
+            name: username,
+            username, password: "123"
+          })
+        )
+
+        const { token } = await createRandomTestUser(api, adminToken)
+        await failOrThrow("unauthorized", () =>
+          api.deleteAccount({ username: username, token })
+        )
+
+      },
+    },
+    test_getAccount: {
+      "not-found": async (api) => {
+        await failOrThrow("not-found", () =>
+          api.getAccount({ username: "not-found", token: adminToken })
+        )
+
+      },
+      success: async (api) => {
+        const { username, token } = await createRandomTestUser(api, adminToken)
+        await succeedOrThrow(() =>
+          api.getAccount({ username, token })
+        )
+      },
+      unauthorized: async (api) => {
+        const { username, token } = await createRandomTestUser(api, adminToken)
+        await failOrThrow("unauthorized", () =>
+          api.getAccount({ username, token: "wrongtoken" as AccessToken })
+        )
+      },
+    },
+    test_getAccounts: {
+      success: async (api) => {
+        await succeedOrThrow(() =>
+          api.getAccounts(adminToken)
+        )
+        await succeedOrThrow(() =>
+          api.getAccounts(adminToken, {
+            account: "admin"
+          })
+        )
+        await succeedOrThrow(() =>
+          api.getAccounts(adminToken, undefined, {
+            order: "dec",
+            limit: 10,
+            offset: "1"
+          })
+        )
+      },
+      unauthorized: async (api) => {
+        await failOrThrow("unauthorized", () =>
+          api.getAccounts("ASDASD" as AccessToken)
+        )
+      },
+    },
+    test_getConfig: {
+      success: async (api) => {
+        const config = await succeedOrThrow(() => api.getConfig())
+
+        if (!api.isCompatible(config.version)) {
+          throw Error(`not compatible with server ${config.version}`)
+        }
+
+      },
+    },
+    test_getMonitor: {
+      "unauthorized": async (api) => {
+        await failOrThrow("unauthorized", () => (
+          api.getMonitor("wrongtoken" as AccessToken)
+        ))
+
+      },
+      "invalid-input": async (api) => {
+
+        await failOrThrow("invalid-input", () => (
+          api.getMonitor(adminToken, {
+            timeframe: TalerCorebankApi.MonitorTimeframeParam.day,
+            which: 100
+          })
+        ))
+
+      },
+      "monitor-not-supported": undefined,
+      success: async (api) => {
+
+        await succeedOrThrow(() => (
+          api.getMonitor(adminToken)
+        ))
+
+        await succeedOrThrow(() => (
+          api.getMonitor(adminToken, {
+            timeframe: TalerCorebankApi.MonitorTimeframeParam.day,
+            which: (new Date()).getDate() - 1
+          })
+        ))
+
+      },
+    },
+    test_getPublicAccounts: {
+      success: async (api) => {
+        await succeedOrThrow(() => (
+          api.getPublicAccounts()
+        ))
+
+        await succeedOrThrow(() => (
+          api.getPublicAccounts({
+            order: "asc"
+          })
+        ))
+        await succeedOrThrow(() => (
+          api.getPublicAccounts({
+            order: "dec"
+          })
+        ))
+        await succeedOrThrow(() => (
+          api.getPublicAccounts({
+            order: "dec", limit: 10, offset: String(1)
+          })
+        ))
+      },
+    },
+    test_getTransactionById: {
+      "not-found": async (api) => {
+        const { username, token } = await createRandomTestUser(api, adminToken)
+        await failOrThrow("not-found", () =>
+          api.getTransactionById({ username, token }, 123123123)
+        )
+      },
+      success: async (api) => {
+        const { username, token } = await createRandomTestUser(api, adminToken)
+        const { username: otherUser, token: otherToken } = await 
createRandomTestUser(api, adminToken)
+
+        const userInfo = await succeedOrThrow(() =>
+          api.getAccount({ username, token })
+        )
+        const otherInfo = await succeedOrThrow(() =>
+          api.getAccount({ username: otherUser, token: otherToken })
+        )
+        const otherAccount = parsePaytoUri(otherInfo.payto_uri)!
+        otherAccount.params["message"] = "all"
+
+        await succeedOrThrow(() =>
+          api.createTransaction({ username, token }, {
+            payto_uri: stringifyPaytoUri(otherAccount),
+            amount: userInfo.balance.amount
+          })
+        )
+
+        const txs = await succeedOrThrow(() => api.getTransactions({ username, 
token }, {
+          limit: 5,
+          order: "asc"
+        }))
+        const rowId = txs.transactions[0].row_id
+
+        await succeedOrThrow(() =>
+          api.getTransactionById({ username, token }, rowId)
+        )
+
+      },
+      unauthorized: async (api) => {
+        const { username, token } = await createRandomTestUser(api, adminToken)
+        await failOrThrow("unauthorized", () =>
+          api.getTransactionById({ username, token: "wrongtoken" as 
AccessToken }, 123123123)
+        )
+      },
+    },
+    test_getTransactions: {
+      "not-found": async (api) => {
+        await failOrThrow("not-found", () => api.getTransactions({
+          username: "not-found",
+          token: adminToken,
+        }))
+      },
+      success: async (api) => {
+        const { username, token } = await createRandomTestUser(api, adminToken)
+        // await succeedOrThrow(() => api.getTransactions(creds))
+        const txs = await succeedOrThrow(() => api.getTransactions({ username, 
token }, {
+          limit: 5,
+          order: "asc"
+        }))
+      },
+      unauthorized: async (api) => {
+        const { username, token } = await createRandomTestUser(api, adminToken)
+
+        await failOrThrow("unauthorized", () => api.getTransactions({
+          username: username,
+          token: "wrongtoken" as AccessToken,
+        }))
+
+      },
+    },
+    test_getWithdrawalById: {
+      "invalid-id": async (api) => {
+
+        await failOrThrow("invalid-id", () =>
+          api.getWithdrawalById("invalid")
+        )
+
+      },
+      "not-found": async (api) => {
+        await failOrThrow("not-found", () =>
+          api.getWithdrawalById("11111111-1111-1111-1111-111111111111")
+        )
+
+      },
+      success: async (api) => {
+        const { username, token } = await createRandomTestUser(api, adminToken)
+        const userInfo = await succeedOrThrow(() =>
+          api.getAccount({ username, token })
+        )
+        const { withdrawal_id } = await succeedOrThrow(() =>
+          api.createWithdrawal({ username, token }, {
+            amount: userInfo.balance.amount
+          })
+        )
+        await succeedOrThrow(() =>
+          api.getWithdrawalById(withdrawal_id)
+        )
+      },
+    },
+    test_updateAccount: {
+      "cant-change-legal-name-or-admin": async (api) => {
+        const { username, token } = await createRandomTestUser(api, adminToken)
+
+        await failOrThrow("cant-change-legal-name-or-admin", () =>
+          api.updateAccount({ username, token }, {
+            name: "something else",
+          })
+        )
+
+      },
+      "not-found": async (api) => {
+        const { username, token } = await createRandomTestUser(api, adminToken)
+        await failOrThrow("not-found", () =>
+          api.updateAccount({ username: "notfound", token }, {
+            challenge_contact_data: {
+              email: "asd@Aasd.com"
+            }
+          })
+        )
+      },
+      success: async (api) => {
+        const { username, token } = await createRandomTestUser(api, adminToken)
+
+        await succeedOrThrow(() =>
+          api.updateAccount({ username, token }, {
+            challenge_contact_data: {
+              email: "asd@Aasd.com"
+            }
+          })
+        )
+
+      },
+      unauthorized: async (api) => {
+
+        await failOrThrow("unauthorized", () =>
+          api.updateAccount({ username: "notfound", token: "wrongtoken" as 
AccessToken }, {
+            challenge_contact_data: {
+              email: "asd@Aasd.com"
+            }
+          })
+        )
+      },
+    },
+    test_updatePassword: {
+      "not-found": async (api) => {
+
+        await failOrThrow("not-found", () =>
+          api.updatePassword({ username: "notfound", token: adminToken }, {
+            old_password: "123",
+            new_password: "234"
+          })
+        )
+
+
+      },
+      "old-password-invalid-or-not-allowed": async (api) => {
+        const { username, token } = await createRandomTestUser(api, adminToken)
+
+        await failOrThrow("old-password-invalid-or-not-allowed", () =>
+          api.updatePassword({ username, token }, {
+            old_password: "1233",
+            new_password: "234"
+          })
+        )
+
+      },
+      success: async (api) => {
+        const { username, token } = await createRandomTestUser(api, adminToken)
+
+        await succeedOrThrow(() =>
+          api.updatePassword({ username, token }, {
+            old_password: "123",
+            new_password: "234"
+          })
+        )
+
+
+      },
+      unauthorized: async (api) => {
+        const { username, token } = await createRandomTestUser(api, adminToken)
+        await failOrThrow("unauthorized", () =>
+          api.updatePassword({ username: "admin", token }, {
+            old_password: "123",
+            new_password: "234"
+          })
+        )
+
+
+      },
+    },
   }
-
-  async testTransactions(adminPassword: string) {
-    const { access_token: adminToken } = await succeedOrThrow(() =>
-      this.api.getAuthenticationAPI("admin").createAccessToken(adminPassword, {
-        scope: "readwrite"
-      })
-    )
-    // get transactions
-    {
-      const { username, token } = await createRandomTestUser(this.api, 
adminToken)
-      // await succeedOrThrow(() => this.api.getTransactions(creds))
-      const txs = await succeedOrThrow(() => this.api.getTransactions({ 
username, token }, {
-        limit: 5,
-        order: "asc"
-      }))
-      // await failOrThrow("not-found",() => this.api.getTransactions({
-      //   username:"not-found",
-      //   token: creds.token,
-      // }))
-      await failOrThrow("unauthorized", () => this.api.getTransactions({
-        username: username,
-        token: "wrongtoken" as AccessToken,
-      }))
-    }
-
-    /**
-     * getTxby id
-     */
-    {
-      const { username, token } = await createRandomTestUser(this.api, 
adminToken)
-      const { username: otherUser, token: otherToken } = await 
createRandomTestUser(this.api, adminToken)
-
-      const userInfo = await succeedOrThrow(() =>
-        this.api.getAccount({ username, token })
-      )
-      const otherInfo = await succeedOrThrow(() =>
-        this.api.getAccount({ username: otherUser, token: otherToken })
-      )
-      const otherAccount = parsePaytoUri(otherInfo.payto_uri)!
-      otherAccount.params["message"] = "all"
-
-      await succeedOrThrow(() =>
-        this.api.createTransaction({ username, token }, {
-          payto_uri: stringifyPaytoUri(otherAccount),
-          amount: userInfo.balance.amount
-        })
-      )
-
-      const txs = await succeedOrThrow(() => this.api.getTransactions({ 
username, token }, {
-        limit: 5,
-        order: "asc"
-      }))
-      const rowId = txs.transactions[0].row_id
-
-      await succeedOrThrow(() =>
-        this.api.getTransactionById({ username, token }, rowId)
-      )
-
-      await failOrThrow("not-found", () =>
-        this.api.getTransactionById({ username, token }, 123123123)
-      )
-
-      await failOrThrow("unauthorized", () =>
-        this.api.getTransactionById({ username, token: "wrongtoken" as 
AccessToken }, 123123123)
-      )
-    }
-
-    /**
-     * create transactions
-     */
-    {
-      const { username, token } = await createRandomTestUser(this.api, 
adminToken)
-      const { username: otherUser, token: otherToken } = await 
createRandomTestUser(this.api, adminToken)
-
-      const userInfo = await succeedOrThrow(() =>
-        this.api.getAccount({ username, token })
-      )
-      const otherInfo = await succeedOrThrow(() =>
-        this.api.getAccount({ username: otherUser, token: otherToken })
-      )
-      const otherAccount = parsePaytoUri(otherInfo.payto_uri)!
-      otherAccount.params["message"] = "all"
-
-      await succeedOrThrow(() =>
-        this.api.createTransaction({ username, token }, {
-          payto_uri: stringifyPaytoUri(otherAccount),
-          amount: userInfo.balance.amount
-        })
-      )
-      //missing amount
-      await failOrThrow("invalid-input", () =>
-        this.api.createTransaction({ username, token }, {
-          payto_uri: stringifyPaytoUri(otherAccount),
-          // amount: userInfo.balance.amount
-        })
-      )
-      //missing subject
-      await failOrThrow("invalid-input", () =>
-        this.api.createTransaction({ username, token }, {
-          payto_uri: otherInfo.payto_uri,
-          amount: userInfo.balance.amount
-        })
-      )
-      await failOrThrow("unauthorized", () =>
-        this.api.createTransaction({ username, token: "wrongtoken" as 
AccessToken }, {
-          payto_uri: otherInfo.payto_uri,
-          amount: userInfo.balance.amount
-        })
-      )
-
-      const notFoundAccount = buildPayto("iban", "DE1231231231", undefined)
-      notFoundAccount.params["message"] = "not-found"
-      await failOrThrow("account-not-found", () =>
-        this.api.createTransaction({ username, token }, {
-          payto_uri: stringifyPaytoUri(notFoundAccount),
-          amount: userInfo.balance.amount
-        })
-      )
-    }
-  }
-
-
 }
 
 export async function createRandomTestUser(api: TalerCoreBankHttpClient, 
adminToken: AccessToken, options: 
Partial<TalerCorebankApi.RegisterAccountRequest> = {}) {
diff --git a/packages/taler-harness/src/index.ts 
b/packages/taler-harness/src/index.ts
index 717aee57d..af4e5c788 100644
--- a/packages/taler-harness/src/index.ts
+++ b/packages/taler-harness/src/index.ts
@@ -51,7 +51,7 @@ import {
 } from "@gnu-taler/taler-wallet-core";
 import { deepStrictEqual } from "assert";
 import fs from "fs";
-import { BankCoreSmokeTest } from "http-client/bank-core.js";
+import { createRandomTestUser, createTestForBankCore } from 
"http-client/bank-core.js";
 import os from "os";
 import path from "path";
 import { runBench1 } from "./bench1.js";
@@ -667,68 +667,69 @@ deploymentCli
     const httpLib = createPlatformHttpLib();
     const api = new 
TalerCoreBankHttpClient(args.testBankAPI.corebankApiBaseUrl, httpLib);
 
-    const tester = new BankCoreSmokeTest(api)
+    process.stdout.write("config: ");
+    const config = await api.getConfig()
+    if (!api.isCompatible(config.body.version)) {
+      console.log("fail")
+      return;
+    } else {
+      console.log("ok")
+    }
+
+    if (!args.testBankAPI.adminPwd) {
+      console.log("no admin password, exit")
+      return;
+    }
+
+    const resp = await 
api.getAuthenticationAPI("admin").createAccessToken(args.testBankAPI.adminPwd, {
+      scope: "readwrite"
+    })
+
+    if (resp.type === "fail") {
+      console.log("wrong admin password")
+      return;
+    }
+    const tester = createTestForBankCore(resp.body.access_token)
+
     if (args.testBankAPI.showCurl) {
       setPrintHttpRequestAsCurl(true)
     }
-    try {
-      process.stdout.write("config: ");
-      const config = await tester.testConfig()
-      console.log("ok")
-      const admin = args.testBankAPI.adminPwd
-      process.stdout.write("account management: ");
-      const withAdmin = !!admin && admin !== "-"
-      if (withAdmin) {
-        await tester.testAccountManagement(admin)
-        console.log("ok")
-      } else {
-        console.log("skipped")
-      }
-
-      process.stdout.write("transactions: ");
-      if (withAdmin) {
-        await tester.testTransactions(admin)
-        console.log("ok")
-      } else {
-        console.log("skipped")
-      }
 
-      process.stdout.write("withdrawals: ");
-      if (withAdmin) {
-        await tester.testWithdrawals(admin)
-        console.log("ok")
-      } else {
-        console.log("skipped")
+    const apiState = await 
Promise.all(Object.entries(tester).flatMap(([testName, casesMap]) => {
+      return Object.entries(casesMap).map(([caseName, caseFunc]) => {
+        if (!caseFunc) {
+          return { testName, caseName, result: "skiped" as const }
+        }
+        return caseFunc(api)
+          .then(r => ({ testName, caseName, result: "ok" as const }))
+          .catch(error => ({ testName, caseName, result: "fail" as const, 
error }))
+      })
+    }))
+
+    const total = apiState.reduce((prev, testResult) => {
+      if (testResult.result === "ok") {
+        prev.ok += 1
       }
-
-      process.stdout.write("monitor: ");
-      if (withAdmin && config.have_cashout) {
-        await tester.testMonitor(admin)
-        console.log("ok")
-      } else {
-        console.log("skipped")
+      if (testResult.result === "skiped") {
+        prev.skiped += 1
       }
-
-      process.stdout.write("cashout: ");
-      if (withAdmin && config.have_cashout) {
-        await tester.testCashouts(admin)
-        console.log("ok")
-      } else {
-        console.log("skipped")
+      if (testResult.result === "fail") {
+        prev.fail += 1
       }
-
-    } catch (e: any) {
-      console.log("")
-      if (e instanceof TalerError) {
-        console.error("FAILED", JSON.stringify(e.errorDetail, undefined, 2))
-        console.error(e.stack)
-      } else if (e instanceof Error) {
-        console.error(`FAILED: ${e.message}`)
-        console.error(e.stack)
-      } else {
-        console.error(`FAILED: ${e}`)
+      return prev
+    }, { "ok": 0, "skiped": 0, "fail": 0 })
+
+    console.log("successful tests:", total.ok)
+    console.log("uncompleted tests:", total.skiped)
+    apiState.forEach((testResult) => {
+      if (testResult.result === "skiped") {
+        console.log("    ", testResult.testName, testResult.caseName)
       }
-    }
+    })
+    console.log("failed tests:", total.fail)
+    apiState.filter(t => t.result === "fail").forEach((testResult, i) => {
+      console.log(i, ")", testResult)
+    })
   });
 
 
diff --git a/packages/taler-util/src/errors.ts 
b/packages/taler-util/src/errors.ts
index f17f7db6a..cb61a5994 100644
--- a/packages/taler-util/src/errors.ts
+++ b/packages/taler-util/src/errors.ts
@@ -47,7 +47,7 @@ export interface DetailsMap {
     walletProtocolVersion: string;
   };
   [TalerErrorCode.WALLET_WITHDRAWAL_OPERATION_ABORTED_BY_BANK]: empty;
-  [TalerErrorCode.WALLET_TIPPING_COIN_SIGNATURE_INVALID]: empty;
+  [TalerErrorCode.WALLET_REWARD_COIN_SIGNATURE_INVALID]: empty;
   [TalerErrorCode.WALLET_ORDER_ALREADY_CLAIMED]: {
     orderId: string;
     claimUrl: string;
diff --git a/packages/taler-util/src/http-client/bank-core.ts 
b/packages/taler-util/src/http-client/bank-core.ts
index 35f216220..1107a3c93 100644
--- a/packages/taler-util/src/http-client/bank-core.ts
+++ b/packages/taler-util/src/http-client/bank-core.ts
@@ -18,7 +18,9 @@ import {
   AmountJson,
   Amounts,
   HttpStatusCode,
-  LibtoolVersion
+  LibtoolVersion,
+  TalerErrorCode,
+  codecForTalerErrorDetail
 } from "@gnu-taler/taler-util";
 import {
   HttpRequestLibrary,
@@ -92,16 +94,19 @@ export class TalerCoreBankHttpClient {
     });
     switch (resp.status) {
       case HttpStatusCode.Created: return opEmptySuccess()
-      case HttpStatusCode.BadRequest: return opKnownFailure("invalid-input", 
resp);
-      //FIXME: check when the server add code spec
-      case HttpStatusCode.Forbidden: {
-        if (body.username === "bank" || body.username === "admin") {
-          return opKnownFailure("unable-to-create", resp);
-        } else {
-          return opKnownFailure("unauthorized", resp);
+      case HttpStatusCode.BadRequest: return 
opKnownFailure("invalid-phone-or-email", resp);
+      case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", 
resp);
+      case HttpStatusCode.Conflict: {
+        const body = await resp.json()
+        const details = codecForTalerErrorDetail().decode(body)
+        switch (details.code) {
+          case TalerErrorCode.BANK_REGISTER_USERNAME_REUSE: return 
opKnownFailure("username-already-exists", resp);
+          case TalerErrorCode.BANK_REGISTER_PAYTO_URI_REUSE: return 
opKnownFailure("payto-already-exists", resp);
+          case TalerErrorCode.BANK_UNALLOWED_DEBIT: return 
opKnownFailure("insufficient-funds", resp);
+          case TalerErrorCode.BANK_RESERVED_USERNAME_CONFLICT: return 
opKnownFailure("username-reserved", resp);
+          default: return opUnknownFailure(resp, body)
         }
       }
-      case HttpStatusCode.Conflict: return opKnownFailure("already-exist", 
resp);
       default: return opUnknownFailure(resp, await resp.text())
     }
   }
@@ -120,19 +125,16 @@ export class TalerCoreBankHttpClient {
     switch (resp.status) {
       case HttpStatusCode.NoContent: return opEmptySuccess()
       case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp);
-      //FIXME: check when the server add code spec
-      case HttpStatusCode.Forbidden: {
-        if (auth.username === "bank" || auth.username === "admin") {
-          return opKnownFailure("unable-to-delete", resp);
-        } else {
-          return opKnownFailure("unauthorized", resp);
+      case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", 
resp);
+      case HttpStatusCode.Conflict: {
+        const body = await resp.json()
+        const details = codecForTalerErrorDetail().decode(body)
+        switch (details.code) {
+          case TalerErrorCode.BANK_RESERVED_USERNAME_CONFLICT: return 
opKnownFailure("username-reserved", resp);
+          case TalerErrorCode.BANK_ACCOUNT_BALANCE_NOT_ZERO: return 
opKnownFailure("balance-not-zero", resp);
+          default: return opUnknownFailure(resp, body)
         }
       }
-      //FIXME: this should be forbidden
-      case HttpStatusCode.Unauthorized: {
-        return opKnownFailure("unauthorized", resp);
-      }
-      case HttpStatusCode.Conflict: return opKnownFailure("balance-not-zero", 
resp);
       default: return opUnknownFailure(resp, await resp.text())
     }
   }
@@ -223,10 +225,7 @@ export class TalerCoreBankHttpClient {
     switch (resp.status) {
       case HttpStatusCode.Ok: return opSuccess(resp, 
codecForListBankAccountsResponse())
       case HttpStatusCode.NoContent: return opFixedSuccess({ accounts: [] })
-      case HttpStatusCode.Forbidden: return opKnownFailure("no-rights", resp);
-      case HttpStatusCode.Unauthorized: {
-        return opKnownFailure("unauthorized", resp);
-      }
+      case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", 
resp);
       default: return opUnknownFailure(resp, await resp.text())
     }
   }
@@ -247,7 +246,6 @@ export class TalerCoreBankHttpClient {
       case HttpStatusCode.Ok: return opSuccess(resp, codecForAccountData())
       case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", 
resp);
       case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp);
-      case HttpStatusCode.Forbidden: return opKnownFailure("no-rights", resp);
       default: return opUnknownFailure(resp, await resp.text())
     }
   }
@@ -312,14 +310,21 @@ export class TalerCoreBankHttpClient {
       body,
     });
     switch (resp.status) {
-      //FIXME: return txid
-      //FIXME: remove this after server has been updated 
-      case HttpStatusCode.Ok: return opEmptySuccess()
+      //FIXME: expect txid as response
       case HttpStatusCode.NoContent: return opEmptySuccess()
-      case HttpStatusCode.NotFound: return opKnownFailure("account-not-found", 
resp);
-      //FIXME: check when the server add codes spec
       case HttpStatusCode.BadRequest: return opKnownFailure("invalid-input", 
resp);
       case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", 
resp);
+      case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp);
+      case HttpStatusCode.Conflict: {
+        const body = await resp.json()
+        const details = codecForTalerErrorDetail().decode(body)
+        switch (details.code) {
+          case TalerErrorCode.BANK_SAME_ACCOUNT: return 
opKnownFailure("creditor-same", resp);
+          case TalerErrorCode.BANK_UNKNOWN_CREDITOR: return 
opKnownFailure("creditor-not-found", resp);
+          case TalerErrorCode.BANK_UNALLOWED_DEBIT: return 
opKnownFailure("insufficient-funds", resp);
+          default: return opUnknownFailure(resp, body)
+        }
+      }
       default: return opUnknownFailure(resp, await resp.text())
     }
   }
@@ -343,69 +348,74 @@ export class TalerCoreBankHttpClient {
     });
     switch (resp.status) {
       case HttpStatusCode.Ok: return opSuccess(resp, 
codecForBankAccountCreateWithdrawalResponse())
+      case HttpStatusCode.NotFound: return opKnownFailure("account-not-found", 
resp);
       case HttpStatusCode.Conflict: return 
opKnownFailure("insufficient-funds", resp);
       case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", 
resp);
-      case HttpStatusCode.NotFound: return opKnownFailure("account-not-found", 
resp);
-      //FIXME: remove when server is updated
-      case HttpStatusCode.Forbidden: return 
opKnownFailure("insufficient-funds", resp);
       default: return opUnknownFailure(resp, await resp.text())
     }
   }
 
   /**
-   * 
https://docs.taler.net/core/api-corebank.html#post-$BANK_API_BASE_URL-accounts-$account_name-withdrawals
+   * 
https://docs.taler.net/core/api-corebank.html#post-$BANK_API_BASE_URL-withdrawals-$withdrawal_id-abort
    * 
    */
-  async getWithdrawalById(wid: string) {
-    const url = new URL(`withdrawals/${wid}`, this.baseUrl);
+  async abortWithdrawalById(wid: string) {
+    const url = new URL(`withdrawals/${wid}/abort`, this.baseUrl);
     const resp = await this.httpLib.fetch(url.href, {
-      method: "GET",
+      method: "POST",
     });
     switch (resp.status) {
-      case HttpStatusCode.Ok: return opSuccess(resp, 
codecForBankAccountGetWithdrawalResponse())
+      case HttpStatusCode.NoContent: return opEmptySuccess()
+      //FIXME: missing in docs
       case HttpStatusCode.BadRequest: return opKnownFailure("invalid-id", resp)
       case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp)
+      case HttpStatusCode.Conflict: return 
opKnownFailure("previously-confirmed", resp);
       default: return opUnknownFailure(resp, await resp.text())
     }
   }
 
   /**
-   * 
https://docs.taler.net/core/api-corebank.html#post-$BANK_API_BASE_URL-withdrawals-$withdrawal_id-abort
+   * 
https://docs.taler.net/core/api-corebank.html#post-$BANK_API_BASE_URL-withdrawals-$withdrawal_id-confirm
    * 
    */
-  async abortWithdrawalById(wid: string) {
-    const url = new URL(`withdrawals/${wid}/abort`, this.baseUrl);
+  async confirmWithdrawalById(wid: string) {
+    const url = new URL(`withdrawals/${wid}/confirm`, this.baseUrl);
     const resp = await this.httpLib.fetch(url.href, {
       method: "POST",
     });
     switch (resp.status) {
-      //FIXME: remove when the server is fixed
-      case HttpStatusCode.Ok: return opEmptySuccess()
       case HttpStatusCode.NoContent: return opEmptySuccess()
+      //FIXME: missing in docs
       case HttpStatusCode.BadRequest: return opKnownFailure("invalid-id", resp)
       case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp)
-      case HttpStatusCode.Conflict: return 
opKnownFailure("previously-confirmed", resp);
+      case HttpStatusCode.Conflict: {
+        const body = await resp.json()
+        const details = codecForTalerErrorDetail().decode(body)
+        switch (details.code) {
+          case TalerErrorCode.BANK_UNALLOWED_DEBIT: return 
opKnownFailure("insufficient-funds", resp);
+          case TalerErrorCode.BANK_CONFIRM_INCOMPLETE: return 
opKnownFailure("no-exchange-or-reserve-selected", resp);
+          case TalerErrorCode.BANK_CONFIRM_ABORT_CONFLICT: return 
opKnownFailure("previously-aborted", resp);
+          default: return opUnknownFailure(resp, body)
+        }
+      }
       default: return opUnknownFailure(resp, await resp.text())
     }
   }
 
   /**
-   * 
https://docs.taler.net/core/api-corebank.html#post-$BANK_API_BASE_URL-withdrawals-$withdrawal_id-confirm
+   * 
https://docs.taler.net/core/api-corebank.html#post-$BANK_API_BASE_URL-accounts-$account_name-withdrawals
    * 
    */
-  async confirmWithdrawalById(wid: string) {
-    const url = new URL(`withdrawals/${wid}/confirm`, this.baseUrl);
+  async getWithdrawalById(wid: string) {
+    const url = new URL(`withdrawals/${wid}`, this.baseUrl);
     const resp = await this.httpLib.fetch(url.href, {
-      method: "POST",
+      method: "GET",
     });
     switch (resp.status) {
-      //FIXME: remove when the server is fixed
-      case HttpStatusCode.Ok: return opEmptySuccess()
-      case HttpStatusCode.NoContent: return opEmptySuccess()
+      case HttpStatusCode.Ok: return opSuccess(resp, 
codecForBankAccountGetWithdrawalResponse())
+      //FIXME: missing in docs
       case HttpStatusCode.BadRequest: return opKnownFailure("invalid-id", resp)
       case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp)
-      case HttpStatusCode.Conflict: return 
opKnownFailure("previously-aborted", resp);
-      case HttpStatusCode.UnprocessableEntity: return 
opKnownFailure("no-exchange-or-reserve-selected", resp);
       default: return opUnknownFailure(resp, await resp.text())
     }
   }
@@ -429,14 +439,18 @@ export class TalerCoreBankHttpClient {
     });
     switch (resp.status) {
       case HttpStatusCode.Accepted: return opSuccess(resp, 
codecForCashoutPending())
-      //FIXME: change when the server has been updated
-      case HttpStatusCode.Conflict: return opKnownFailure("no-contact-info", 
resp);
-      //FIXME: missing in the docs
-      case HttpStatusCode.PreconditionFailed: return 
opKnownFailure("no-enough-balance", resp);
-      //FIXME: missing in the docs
-      case HttpStatusCode.BadRequest: return 
opKnownFailure("incorrect-exchange-rate", resp);
-      //FIXME: check the code response to tell cashout or tan not supported
-      case HttpStatusCode.ServiceUnavailable: return 
opKnownFailure("cashout-or-tan-not-supported", resp);
+      case HttpStatusCode.NotFound: return opKnownFailure("account-not-found", 
resp)
+      case HttpStatusCode.Conflict: {
+        const body = await resp.json()
+        const details = codecForTalerErrorDetail().decode(body)
+        switch (details.code) {
+          case TalerErrorCode.BANK_BAD_CONVERSION: return 
opKnownFailure("incorrect-exchange-rate", resp);
+          case TalerErrorCode.BANK_MISSING_TAN_INFO: return 
opKnownFailure("no-contact-info", resp);
+          case TalerErrorCode.BANK_UNALLOWED_DEBIT: return 
opKnownFailure("no-enough-balance", resp);
+          default: return opUnknownFailure(resp, body)
+        }
+      }
+      case HttpStatusCode.ServiceUnavailable: return 
opKnownFailure("cashout-not-supported", resp);
       default: return opUnknownFailure(resp, await resp.text())
     }
   }
@@ -457,6 +471,7 @@ export class TalerCoreBankHttpClient {
       case HttpStatusCode.NoContent: return opEmptySuccess()
       case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp);
       case HttpStatusCode.Conflict: return opKnownFailure("already-confirmed", 
resp);
+      //FIXME: should be 404 ?
       case HttpStatusCode.ServiceUnavailable: return 
opKnownFailure("cashout-not-supported", resp);
       default: return opUnknownFailure(resp, await resp.text())
     }
@@ -480,42 +495,40 @@ export class TalerCoreBankHttpClient {
       case HttpStatusCode.Forbidden: return 
opKnownFailure("wrong-tan-or-credential", resp);
       case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp);
       case HttpStatusCode.Conflict: return 
opKnownFailure("cashout-address-changed", resp);
+      //FIXME: should be 404 ?
       case HttpStatusCode.ServiceUnavailable: return 
opKnownFailure("cashout-not-supported", resp);
       default: return opUnknownFailure(resp, await resp.text())
     }
   }
 
   /**
-   * 
https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-cashouts-$CASHOUT_ID-confirm
+   * 
https://docs.taler.net/core/api-corebank.html#get--accounts-$USERNAME-cashouts
    * 
    */
-  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_debit", 
Amounts.stringify(conversion.credit))
-    }
+  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: {
+        Authorization: makeBearerTokenAuthHeader(auth.token)
+      },
     });
     switch (resp.status) {
-      case HttpStatusCode.Ok: return opSuccess(resp, 
codecForCashoutConversionResponse())
-      case HttpStatusCode.BadRequest: return 
opKnownFailure("wrong-calculation", resp);
-      case HttpStatusCode.NotFound: return opKnownFailure("not-supported", 
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.ServiceUnavailable: 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
+   * 
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: {
@@ -523,8 +536,8 @@ export class TalerCoreBankHttpClient {
       },
     });
     switch (resp.status) {
-      case HttpStatusCode.Ok: return opSuccess(resp, codecForCashouts())
-      case HttpStatusCode.NoContent: return opFixedSuccess({ cashouts: [] });
+      case HttpStatusCode.Ok: return opSuccess(resp, 
codecForCashoutStatusResponse())
+      case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp);
       case HttpStatusCode.ServiceUnavailable: return 
opKnownFailure("cashout-not-supported", resp);
       default: return opUnknownFailure(resp, await resp.text())
     }
@@ -552,20 +565,28 @@ export class TalerCoreBankHttpClient {
   }
 
   /**
-   * 
https://docs.taler.net/core/api-corebank.html#get--accounts-$USERNAME-cashouts-$CASHOUT_ID
+   * 
https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-cashouts-$CASHOUT_ID-confirm
    * 
    */
-  async getCashoutById(auth: UserAndToken, cid: string) {
-    const url = new URL(`accounts/${auth.username}/cashouts/${cid}`, 
this.baseUrl);
+  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_debit", 
Amounts.stringify(conversion.credit))
+    }
     const resp = await this.httpLib.fetch(url.href, {
       method: "GET",
-      headers: {
-        Authorization: makeBearerTokenAuthHeader(auth.token)
-      },
     });
     switch (resp.status) {
-      case HttpStatusCode.Ok: return opSuccess(resp, 
codecForCashoutStatusResponse())
-      case HttpStatusCode.NotFound: return opKnownFailure("already-aborted", 
resp);
+      case HttpStatusCode.Ok: return opSuccess(resp, 
codecForCashoutConversionResponse())
+      // FIXME: error code for
+      // * the requested currency was not supported
+      // * the calculation is not correct
+      case HttpStatusCode.BadRequest: return 
opKnownFailure("wrong-calculation", resp);
+      case HttpStatusCode.NotFound: return opKnownFailure("not-supported", 
resp);
+      //FIXME: should be 404 ?
       case HttpStatusCode.ServiceUnavailable: return 
opKnownFailure("cashout-not-supported", resp);
       default: return opUnknownFailure(resp, await resp.text())
     }
@@ -579,22 +600,26 @@ export class TalerCoreBankHttpClient {
    * https://docs.taler.net/core/api-corebank.html#get--monitor
    * 
    */
-  async getMonitor(params: { timeframe?: 
TalerCorebankApi.MonitorTimeframeParam, which?: number } = {}) {
+  async getMonitor(auth: AccessToken, params: { timeframe?: 
TalerCorebankApi.MonitorTimeframeParam, which?: number } = {}) {
     const url = new URL(`monitor`, this.baseUrl);
     if (params.timeframe) {
-      url.searchParams.set("timeframe", params.timeframe.toString())
+      url.searchParams.set("timeframe", 
TalerCorebankApi.MonitorTimeframeParam[params.timeframe])
     }
     if (params.which) {
       url.searchParams.set("which", String(params.which))
     }
     const resp = await this.httpLib.fetch(url.href, {
       method: "GET",
+      headers: {
+        Authorization: makeBearerTokenAuthHeader(auth)
+      },
     });
     switch (resp.status) {
       case HttpStatusCode.Ok: return opSuccess(resp, codecForMonitorResponse())
       case HttpStatusCode.BadRequest: return opKnownFailure("invalid-input", 
resp);
+      case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", 
resp);
       //FIXME remove when server is updated
-      case HttpStatusCode.NotFound: return 
opKnownFailure("monitor-not-supported", resp);
+      //FIXME: should be 404 ?
       case HttpStatusCode.ServiceUnavailable: return 
opKnownFailure("monitor-not-supported", resp);
       default: return opUnknownFailure(resp, await resp.text())
     }
@@ -641,5 +666,3 @@ export class TalerCoreBankHttpClient {
   }
 
 }
-
-
diff --git a/packages/taler-util/src/http-client/types.ts 
b/packages/taler-util/src/http-client/types.ts
index 1bb8f99c1..95d0f8dd6 100644
--- a/packages/taler-util/src/http-client/types.ts
+++ b/packages/taler-util/src/http-client/types.ts
@@ -192,9 +192,6 @@ export interface CurrencySpecification {
   // Name of the currency.
   name: string;
 
-  // Decimal separator for fractional digits.
-  decimal_separator: string;
-
   // how many digits the user may enter after the decimal_separator
   num_fractional_input_digits: Integer;
 
@@ -205,10 +202,6 @@ export interface CurrencySpecification {
   // padding with zeros.
   num_fractional_trailing_zero_digits: Integer;
 
-  // Whether the currency name should be rendered before (true) or
-  // after (false) the numeric value
-  is_currency_name_leading: boolean;
-
   // map of powers of 10 to alternative currency names / symbols, must
   // always have an entry under "0" that defines the base name,
   // e.g.  "0 => €" or "3 => k€". For BTC, would be "0 => BTC, -3 => mBTC".
@@ -228,11 +221,9 @@ export const codecForCurrencySpecificiation =
   (): Codec<CurrencySpecification> =>
     buildCodecForObject<CurrencySpecification>()
       .property("name", codecForString())
-      .property("decimal_separator", codecForString())
       .property("num_fractional_input_digits", codecForNumber())
       .property("num_fractional_normal_digits", codecForNumber())
       .property("num_fractional_trailing_zero_digits", codecForNumber())
-      .property("is_currency_name_leading", codecForBoolean())
       .property("alt_unit_names", codecForMap(codecForString()))
       .build("CurrencySpecification")
 
@@ -241,6 +232,8 @@ export const codecForCoreBankConfig =
     buildCodecForObject<TalerCorebankApi.Config>()
       .property("name", codecForString())
       .property("version", codecForString())
+      .property("allow_deletions", codecForBoolean())
+      .property("allow_registrations", codecForBoolean())
       .property("have_cashout", codecOptional(codecForBoolean()))
       .property("currency", codecForCurrencySpecificiation())
       .property("fiat_currency", 
codecOptional(codecForCurrencySpecificiation()))
@@ -398,16 +391,32 @@ export const codecForConversionRatesResponse =
       .property("sell_out_fee", codecForDecimalNumber())
       .build("TalerCorebankApi.ConversionRatesResponse");
 
-export const codecForMonitorResponse =
-  (): Codec<TalerCorebankApi.MonitorResponse> =>
-    buildCodecForObject<TalerCorebankApi.MonitorResponse>()
-      .property("cashinCount", codecOptional(codecForNumber()))
-      .property("cashinExternalVolume", codecOptional(codecForAmountString()))
-      .property("cashoutCount", codecOptional(codecForNumber()))
-      .property("cashoutExternalVolume", codecOptional(codecForAmountString()))
+
+export const codecForMonitorResponse = (): 
Codec<TalerCorebankApi.MonitorResponse> => 
buildCodecForUnion<TalerCorebankApi.MonitorResponse>()
+  .discriminateOn("type")
+  .alternative("just-payouts", codecForMonitorResponseJustPayout())
+  .alternative("with-cashout", codecForMonitorResponseWithCashout())
+  .build("TalerWireGatewayApi.IncomingBankTransaction");
+
+export const codecForMonitorResponseJustPayout =
+  (): Codec<TalerCorebankApi.MonitorJustPayouts> =>
+    buildCodecForObject<TalerCorebankApi.MonitorJustPayouts>()
+      .property("type", codecForConstString("just-payouts"))
       .property("talerPayoutCount", codecForNumber())
       .property("talerPayoutInternalVolume", codecForAmountString())
-      .build("TalerCorebankApi.MonitorResponse");
+      .build("TalerCorebankApi.MonitorJustPayouts");
+
+export const codecForMonitorResponseWithCashout =
+  (): Codec<TalerCorebankApi.MonitorWithCashout> =>
+    buildCodecForObject<TalerCorebankApi.MonitorWithCashout>()
+      .property("type", codecForConstString("with-cashout"))
+      .property("cashinCount", (codecForNumber()))
+      .property("cashinExternalVolume", (codecForAmountString()))
+      .property("cashoutCount", (codecForNumber()))
+      .property("cashoutExternalVolume", (codecForAmountString()))
+      .property("talerPayoutCount", codecForNumber())
+      .property("talerPayoutInternalVolume", codecForAmountString())
+      .build("TalerCorebankApi.MonitorWithCashout");
 
 export const codecForBankVersion =
   (): Codec<TalerBankIntegrationApi.BankVersion> =>
@@ -839,6 +848,14 @@ export namespace TalerCorebankApi {
     // API version in the form $n:$n:$n
     version: string;
 
+    // If 'true' anyone can register
+    // If 'false' only the admin can
+    allow_registrations: boolean;
+
+    // If 'true' account can delete themselves
+    // 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
@@ -997,6 +1014,11 @@ export namespace TalerCorebankApi {
     // If present, change the is_exchange configuration.
     // See RegisterAccountRequest
     is_exchange?: boolean;
+
+    // If present, change the max debit allowed for this user
+    // Only admin can change this property.
+    debit_threshold?: AmountString
+
   }
 
 
@@ -1181,30 +1203,50 @@ export namespace TalerCorebankApi {
     hour, day, month, year, decade,
   }
 
-  export interface MonitorResponse {
+  export type MonitorResponse =
+    | MonitorJustPayouts
+    | MonitorWithCashout;
+
+  // Monitoring stats when conversion is not supported
+  export interface MonitorJustPayouts {
+    type: "just-payouts";
+
+    // This number identifies how many payments were made by a
+    // Taler exchange to a merchant bank account in the internal
+    // currency, in the timeframe specified in the request.
+    talerPayoutCount: number;
+
+    // This amount accounts the overall *internal* currency that
+    // has been paid by a Taler exchange to a merchant internal
+    // bank account, in the timeframe specified in the request.
+    talerPayoutInternalVolume: AmountString;
+  }
+  // Monitoring stats when conversion is supported
+  export interface MonitorWithCashout {
+    type: "with-cashout";
 
     // This number identifies how many cashin operations
     // took place in the timeframe specified in the request.
     // This number corresponds to how many withdrawals have
     // been initiated by a wallet owner.  Note: wallet owners
     // are NOT required to be customers of the libeufin-bank.
-    cashinCount?: number;
+    cashinCount: number;
 
     // This amount accounts how much external currency has been
     // spent to withdraw Taler coins in the internal currency.
     // The exact amount of internal currency being created can be
     // calculated using the advertised conversion rates.
-    cashinExternalVolume?: AmountString;
+    cashinExternalVolume: AmountString;
 
     // This number identifies how many cashout operations were
     // confirmed in the timeframe speficied in the request.
-    cashoutCount?: number;
+    cashoutCount: number;
 
     // This amount corresponds to how much *external* currency was
     // paid by the libeufin-bank administrator to fulfill all the
     // confirmed cashouts related to the timeframe specified in the
     // request.
-    cashoutExternalVolume?: AmountString;
+    cashoutExternalVolume: AmountString;
 
     // This number identifies how many payments were made by a
     // Taler exchange to a merchant bank account in the internal
diff --git a/packages/taler-util/src/operation.ts 
b/packages/taler-util/src/operation.ts
index 8adbbb0e2..9d7594d14 100644
--- a/packages/taler-util/src/operation.ts
+++ b/packages/taler-util/src/operation.ts
@@ -67,8 +67,29 @@ export async function failOrThrow<E>(s: E, cb: () => 
Promise<OperationResult<unk
 
 export type ResultByMethod<TT extends object, p extends keyof TT> = TT[p] 
extends (...args: any[]) => infer Ret ?
   Ret extends Promise<infer Result> ?
-  Result :
+  Result extends OperationResult<any, any> ? Result : never :
   never : //api always use Promises
   never; //error cases just for functions
 
 export type FailCasesByMethod<TT extends object, p extends keyof TT> = 
Exclude<ResultByMethod<TT, p>, OperationOk<any>>
+
+type MethodsOfOperations<T extends object> = keyof {
+  [P in keyof T as
+  //when the property is a function
+  T[P] extends (...args: any[]) => infer Ret ?
+  // that returns a promise
+  Ret extends Promise<infer Result> ?
+  // of an operation
+  Result extends OperationResult<any, any> ?
+  P : never
+  : never
+  : never]: any
+}
+
+type AllKnownCases<t extends object, d extends keyof t> = "success" | 
FailCasesByMethod<t, d>["case"]
+
+export type TestForApi<ApiType extends object> = {
+  [OpType in MethodsOfOperations<ApiType> as `test_${OpType & string}`]: {
+    [c in AllKnownCases<ApiType, OpType>]: undefined | ((api: ApiType) => 
Promise<void>);
+  };
+};
diff --git a/packages/taler-util/src/taler-error-codes.ts 
b/packages/taler-util/src/taler-error-codes.ts
index fa1f4a86a..979fb332e 100644
--- a/packages/taler-util/src/taler-error-codes.ts
+++ b/packages/taler-util/src/taler-error-codes.ts
@@ -96,6 +96,14 @@ export enum TalerErrorCode {
   GENERIC_UNEXPECTED_REQUEST_ERROR = 15,
 
 
+  /**
+   * The token used by the client to authorize the request does not grant the 
required permissions for the request.
+   * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  GENERIC_TOKEN_PERMISSION_INSUFFICIENT = 16,
+
+
   /**
    * The HTTP method used is invalid for this endpoint.
    * Returned with an HTTP status code of #MHD_HTTP_METHOD_NOT_ALLOWED (405).
@@ -161,7 +169,15 @@ export enum TalerErrorCode {
 
 
   /**
-   * The currencies involved in the operation do not match.
+   * The body in the request could not be decompressed by the server.
+   * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  GENERIC_COMPRESSION_INVALID = 28,
+
+
+  /**
+   * The currency involved in the operation is not acceptable for this backend.
    * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
    * (A value of 0 indicates that the error is generated client-side).
    */
@@ -184,6 +200,46 @@ export enum TalerErrorCode {
   GENERIC_UPLOAD_EXCEEDS_LIMIT = 32,
 
 
+  /**
+   * The service refused the request due to lack of proper authorization.
+   * Returned with an HTTP status code of #MHD_HTTP_UNAUTHORIZED (401).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  GENERIC_UNAUTHORIZED = 40,
+
+
+  /**
+   * The service refused the request as the given authorization token is 
unknown.
+   * Returned with an HTTP status code of #MHD_HTTP_UNAUTHORIZED (401).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  GENERIC_TOKEN_UNKNOWN = 41,
+
+
+  /**
+   * The service refused the request as the given authorization token expired.
+   * Returned with an HTTP status code of #MHD_HTTP_UNAUTHORIZED (401).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  GENERIC_TOKEN_EXPIRED = 42,
+
+
+  /**
+   * The service refused the request as the given authorization token is 
malformed.
+   * Returned with an HTTP status code of #MHD_HTTP_UNAUTHORIZED (401).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  GENERIC_TOKEN_MALFORMED = 43,
+
+
+  /**
+   * The service refused the request due to lack of proper rights on the 
resource.
+   * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  GENERIC_FORBIDDEN = 44,
+
+
   /**
    * The service failed initialize its connection to the database.
    * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR 
(500).
@@ -464,6 +520,14 @@ export enum TalerErrorCode {
   EXCHANGE_GENERIC_NEW_DENOMS_ARRAY_SIZE_EXCESSIVE = 1018,
 
 
+  /**
+   * The coin is not known to the exchange (yet).
+   * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  EXCHANGE_GENERIC_COIN_UNKNOWN = 1019,
+
+
   /**
    * The time at the server is too far off from the time specified in the 
request. Most likely the client system time is wrong.
    * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
@@ -778,7 +842,7 @@ export enum TalerErrorCode {
 
   /**
    * The maximum age in the commitment is too large for the reserve
-   * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
+   * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
    * (A value of 0 indicates that the error is generated client-side).
    */
   EXCHANGE_AGE_WITHDRAW_MAXIMUM_AGE_TOO_LARGE = 1165,
@@ -873,19 +937,11 @@ export enum TalerErrorCode {
 
 
   /**
-   * The reserve balance, status or history was requested for a reserve which 
is not known to the exchange.
-   * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
-   * (A value of 0 indicates that the error is generated client-side).
-   */
-  EXCHANGE_RESERVES_STATUS_UNKNOWN = 1250,
-
-
-  /**
-   * The reserve status was requested with a bad signature.
+   * The coin history was requested with a bad signature.
    * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
    * (A value of 0 indicates that the error is generated client-side).
    */
-  EXCHANGE_RESERVES_STATUS_BAD_SIGNATURE = 1251,
+  EXCHANGE_COIN_HISTORY_BAD_SIGNATURE = 1251,
 
 
   /**
@@ -893,7 +949,7 @@ export enum TalerErrorCode {
    * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
    * (A value of 0 indicates that the error is generated client-side).
    */
-  EXCHANGE_RESERVES_HISTORY_BAD_SIGNATURE = 1252,
+  EXCHANGE_RESERVE_HISTORY_BAD_SIGNATURE = 1252,
 
 
   /**
@@ -2057,11 +2113,11 @@ export enum TalerErrorCode {
 
 
   /**
-   * The tip ID is unknown.  This could happen if the tip has expired.
+   * The reward ID is unknown.  This could happen if the reward has expired.
    * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
    * (A value of 0 indicates that the error is generated client-side).
    */
-  MERCHANT_GENERIC_TIP_ID_UNKNOWN = 2007,
+  MERCHANT_GENERIC_REWARD_ID_UNKNOWN = 2007,
 
 
   /**
@@ -2168,6 +2224,38 @@ export enum TalerErrorCode {
   MERCHANT_GENERIC_PENDING_WEBHOOK_UNKNOWN = 2020,
 
 
+  /**
+   * The backend could not find the OTP device(id) because it is not exist.
+   * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  MERCHANT_GENERIC_OTP_DEVICE_UNKNOWN = 2021,
+
+
+  /**
+   * The account is not known to the backend.
+   * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  MERCHANT_GENERIC_ACCOUNT_UNKNOWN = 2022,
+
+
+  /**
+   * The wire hash was malformed.
+   * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  MERCHANT_GENERIC_H_WIRE_MALFORMED = 2023,
+
+
+  /**
+   * The currency specified in the operation does not work with the current 
state of the given resource.
+   * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  MERCHANT_GENERIC_CURRENCY_MISMATCH = 2024,
+
+
   /**
    * The exchange failed to provide a valid answer to the tracking request, 
thus those details are not in the response.
    * Returned with an HTTP status code of #MHD_HTTP_OK (200).
@@ -2509,7 +2597,7 @@ export enum TalerErrorCode {
    * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
    * (A value of 0 indicates that the error is generated client-side).
    */
-  MERCHANT_TIP_PICKUP_UNBLIND_FAILURE = 2400,
+  MERCHANT_REWARD_PICKUP_UNBLIND_FAILURE = 2400,
 
 
   /**
@@ -2517,7 +2605,7 @@ export enum TalerErrorCode {
    * Returned with an HTTP status code of #MHD_HTTP_BAD_GATEWAY (502).
    * (A value of 0 indicates that the error is generated client-side).
    */
-  MERCHANT_TIP_PICKUP_EXCHANGE_ERROR = 2403,
+  MERCHANT_REWARD_PICKUP_EXCHANGE_ERROR = 2403,
 
 
   /**
@@ -2525,15 +2613,15 @@ export enum TalerErrorCode {
    * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR 
(500).
    * (A value of 0 indicates that the error is generated client-side).
    */
-  MERCHANT_TIP_PICKUP_SUMMATION_FAILED = 2404,
+  MERCHANT_REWARD_PICKUP_SUMMATION_FAILED = 2404,
 
 
   /**
-   * The tip expired.
+   * The reward expired.
    * Returned with an HTTP status code of #MHD_HTTP_GONE (410).
    * (A value of 0 indicates that the error is generated client-side).
    */
-  MERCHANT_TIP_PICKUP_HAS_EXPIRED = 2405,
+  MERCHANT_REWARD_PICKUP_HAS_EXPIRED = 2405,
 
 
   /**
@@ -2541,7 +2629,7 @@ export enum TalerErrorCode {
    * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
    * (A value of 0 indicates that the error is generated client-side).
    */
-  MERCHANT_TIP_PICKUP_AMOUNT_EXCEEDS_TIP_REMAINING = 2406,
+  MERCHANT_REWARD_PICKUP_AMOUNT_EXCEEDS_REWARD_REMAINING = 2406,
 
 
   /**
@@ -2549,7 +2637,7 @@ export enum TalerErrorCode {
    * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
    * (A value of 0 indicates that the error is generated client-side).
    */
-  MERCHANT_TIP_PICKUP_DENOMINATION_UNKNOWN = 2407,
+  MERCHANT_REWARD_PICKUP_DENOMINATION_UNKNOWN = 2407,
 
 
   /**
@@ -2937,11 +3025,11 @@ export enum TalerErrorCode {
 
 
   /**
-   * The requested exchange does not allow tipping.
+   * The requested exchange does not allow rewards.
    * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
    * (A value of 0 indicates that the error is generated client-side).
    */
-  MERCHANT_PRIVATE_POST_RESERVES_TIPPING_NOT_ALLOWED = 2701,
+  MERCHANT_PRIVATE_POST_RESERVES_REWARDS_NOT_ALLOWED = 2701,
 
 
   /**
@@ -2953,35 +3041,35 @@ export enum TalerErrorCode {
 
 
   /**
-   * The reserve that was used to fund the tips has expired.
+   * The reserve that was used to fund the rewards has expired.
    * Returned with an HTTP status code of #MHD_HTTP_GONE (410).
    * (A value of 0 indicates that the error is generated client-side).
    */
-  MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_RESERVE_EXPIRED = 2750,
+  MERCHANT_PRIVATE_POST_REWARD_AUTHORIZE_RESERVE_EXPIRED = 2750,
 
 
   /**
-   * The reserve that was used to fund the tips was not found in the DB.
+   * The reserve that was used to fund the rewards was not found in the DB.
    * Returned with an HTTP status code of #MHD_HTTP_SERVICE_UNAVAILABLE (503).
    * (A value of 0 indicates that the error is generated client-side).
    */
-  MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_RESERVE_UNKNOWN = 2751,
+  MERCHANT_PRIVATE_POST_REWARD_AUTHORIZE_RESERVE_UNKNOWN = 2751,
 
 
   /**
-   * The backend knows the instance that was supposed to support the tip, and 
it was configured for tipping. However, the funds remaining are insufficient to 
cover the tip, and the merchant should top up the reserve.
+   * The backend knows the instance that was supposed to support the reward, 
and it was configured for rewardping. However, the funds remaining are 
insufficient to cover the reward, and the merchant should top up the reserve.
    * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
    * (A value of 0 indicates that the error is generated client-side).
    */
-  MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_INSUFFICIENT_FUNDS = 2752,
+  MERCHANT_PRIVATE_POST_REWARD_AUTHORIZE_INSUFFICIENT_FUNDS = 2752,
 
 
   /**
-   * The backend failed to find a reserve needed to authorize the tip.
+   * The backend failed to find a reserve needed to authorize the reward.
    * Returned with an HTTP status code of #MHD_HTTP_SERVICE_UNAVAILABLE (503).
    * (A value of 0 indicates that the error is generated client-side).
    */
-  MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_RESERVE_NOT_FOUND = 2753,
+  MERCHANT_PRIVATE_POST_REWARD_AUTHORIZE_RESERVE_NOT_FOUND = 2753,
 
 
   /**
@@ -3000,6 +3088,14 @@ export enum TalerErrorCode {
   MERCHANT_PRIVATE_POST_TEMPLATES_CONFLICT_TEMPLATE_EXISTS = 2850,
 
 
+  /**
+   * The OTP device ID already exists.
+   * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  MERCHANT_PRIVATE_POST_OTP_DEVICES_CONFLICT_OTP_DEVICE_EXISTS = 2851,
+
+
   /**
    * Amount given in the using template and in the template contract. There is 
a conflict.
    * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
@@ -3096,14 +3192,6 @@ export enum TalerErrorCode {
   BANK_NUMBER_TOO_BIG = 5104,
 
 
-  /**
-   * Could not login for the requested operation.
-   * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
-   * (A value of 0 indicates that the error is generated client-side).
-   */
-  BANK_LOGIN_FAILED = 5105,
-
-
   /**
    * The bank account referenced in the requested operation was not found.
    * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
@@ -3216,6 +3304,94 @@ export enum TalerErrorCode {
   BANK_POST_WITHDRAWAL_OPERATION_REQUIRED = 5119,
 
 
+  /**
+   * The client tried to register a new account under a reserved username 
(like 'admin' for example).
+   * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  BANK_RESERVED_USERNAME_CONFLICT = 5120,
+
+
+  /**
+   * The client tried to register a new account with an username already in 
use.
+   * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  BANK_REGISTER_USERNAME_REUSE = 5121,
+
+
+  /**
+   * The client tried to register a new account with a payto:// URI already in 
use.
+   * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  BANK_REGISTER_PAYTO_URI_REUSE = 5122,
+
+
+  /**
+   * The client tried to delete an account with a non null balance.
+   * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  BANK_ACCOUNT_BALANCE_NOT_ZERO = 5123,
+
+
+  /**
+   * The client tried to create a transaction or an operation that credit an 
unknown account.
+   * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  BANK_UNKNOWN_CREDITOR = 5124,
+
+
+  /**
+   * The client tried to create a transaction or an operation that debit an 
unknown account.
+   * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  BANK_UNKNOWN_DEBTOR = 5125,
+
+
+  /**
+   * The client tried to perform an action prohibited for exchange accounts.
+   * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  BANK_ACCOUNT_IS_EXCHANGE = 5126,
+
+
+  /**
+   * The client tried to perform an action reserved for exchange accounts.
+   * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  BANK_ACCOUNT_IS_NOT_EXCHANGE = 5127,
+
+
+  /**
+   * Received currency conversion is wrong.
+   * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  BANK_BAD_CONVERSION = 5128,
+
+
+  /**
+   * The account referenced in this operation is missing tan info for the 
chosen channel.
+   * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  BANK_MISSING_TAN_INFO = 5129,
+
+
+  /**
+   * The client attempted to confirm a transaction with incomplete info.
+   * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  BANK_CONFIRM_INCOMPLETE = 5130,
+
+
   /**
    * The sync service failed find the account in its database.
    * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
@@ -3465,11 +3641,11 @@ export enum TalerErrorCode {
 
 
   /**
-   * The signature on a coin by the exchange's denomination key (obtained 
through the merchant via tipping) is invalid after unblinding it.
+   * The signature on a coin by the exchange's denomination key (obtained 
through the merchant via a reward) is invalid after unblinding it.
    * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
    * (A value of 0 indicates that the error is generated client-side).
    */
-  WALLET_TIPPING_COIN_SIGNATURE_INVALID = 7016,
+  WALLET_REWARD_COIN_SIGNATURE_INVALID = 7016,
 
 
   /**
@@ -4240,6 +4416,14 @@ export enum TalerErrorCode {
   CHALLENGER_INVALID_PIN = 9758,
 
 
+  /**
+   * The token cannot be valid as no address was ever provided by the client.
+   * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  CHALLENGER_MISSING_ADDRESS = 9759,
+
+
   /**
    * End of error code range.
    * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
diff --git a/packages/taler-util/src/wallet-types.ts 
b/packages/taler-util/src/wallet-types.ts
index 7503a4665..aaec09b66 100644
--- a/packages/taler-util/src/wallet-types.ts
+++ b/packages/taler-util/src/wallet-types.ts
@@ -592,7 +592,7 @@ export interface ConfirmPayResultPending {
 export const codecForTalerErrorDetail = (): Codec<TalerErrorDetail> =>
   buildCodecForObject<TalerErrorDetail>()
     .property("code", codecForNumber())
-    .property("when", codecForAbsoluteTime)
+    .property("when", codecOptional(codecForAbsoluteTime))
     .property("hint", codecOptional(codecForString()))
     .build("TalerErrorDetail");
 
diff --git a/packages/taler-wallet-core/src/operations/reward.ts 
b/packages/taler-wallet-core/src/operations/reward.ts
index ed9927bab..5d609f41d 100644
--- a/packages/taler-wallet-core/src/operations/reward.ts
+++ b/packages/taler-wallet-core/src/operations/reward.ts
@@ -379,9 +379,9 @@ export async function processTip(
       return {
         type: TaskRunResultType.Error,
         errorDetail: makeErrorDetail(
-          TalerErrorCode.WALLET_TIPPING_COIN_SIGNATURE_INVALID,
+          TalerErrorCode.WALLET_REWARD_COIN_SIGNATURE_INVALID,
           {},
-          "invalid signature from the exchange (via merchant tip) after 
unblinding",
+          "invalid signature from the exchange (via merchant reward) after 
unblinding",
         ),
       };
     }
diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts 
b/packages/taler-wallet-core/src/operations/withdraw.ts
index fc4214e56..b0d7706a3 100644
--- a/packages/taler-wallet-core/src/operations/withdraw.ts
+++ b/packages/taler-wallet-core/src/operations/withdraw.ts
@@ -1236,11 +1236,7 @@ async function queryReserve(
     logger.trace(
       `got reserve status error, EC=${result.talerErrorResponse.code}`,
     );
-    if (
-      resp.status === 404 &&
-      result.talerErrorResponse.code ===
-      TalerErrorCode.EXCHANGE_RESERVES_STATUS_UNKNOWN
-    ) {
+    if (resp.status === HttpStatusCode.NotFound) {
       return { ready: false };
     } else {
       throwUnexpectedRequestError(resp, result.talerErrorResponse);
diff --git a/packages/taler-wallet-core/src/wallet.ts 
b/packages/taler-wallet-core/src/wallet.ts
index 2dc984979..e917e8059 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -932,9 +932,9 @@ async function dumpCoins(ws: InternalWalletState): 
Promise<CoinDumpJson> {
           ageCommitmentProof: c.ageCommitmentProof,
           spend_allocation: c.spendAllocation
             ? {
-                amount: c.spendAllocation.amount,
-                id: c.spendAllocation.id,
-              }
+              amount: c.spendAllocation.amount,
+              id: c.spendAllocation.id,
+            }
             : undefined,
         });
       }
@@ -1461,12 +1461,10 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
       if (req.scope.currency === "KUDOS") {
         const kudosResp: GetCurrencySpecificationResponse = {
           currencySpecification: {
-            decimal_separator: ",",
             name: "Kudos (Taler Demonstrator)",
             num_fractional_input_digits: 2,
             num_fractional_normal_digits: 2,
             num_fractional_trailing_zero_digits: 2,
-            is_currency_name_leading: true,
             alt_unit_names: {
               "0": "ク",
             },
@@ -1476,14 +1474,12 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
       } else if (req.scope.currency === "TESTKUDOS") {
         const testkudosResp: GetCurrencySpecificationResponse = {
           currencySpecification: {
-            decimal_separator: ",",
             name: "Test (Taler Unstable Demonstrator)",
             num_fractional_input_digits: 0,
             num_fractional_normal_digits: 0,
             num_fractional_trailing_zero_digits: 0,
-            is_currency_name_leading: false,
             alt_unit_names: {
-                "0": "テ",
+              "0": "テ",
             },
           },
         };
@@ -1491,14 +1487,12 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
       }
       const defaultResp: GetCurrencySpecificationResponse = {
         currencySpecification: {
-          decimal_separator: ",",
           name: "Unknown",
           num_fractional_input_digits: 2,
           num_fractional_normal_digits: 2,
           num_fractional_trailing_zero_digits: 2,
-          is_currency_name_leading: true,
           alt_unit_names: {
-              "0": "??",
+            "0": "??",
           },
         },
       };
diff --git a/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx 
b/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx
index 6d1175d0f..05f979a37 100644
--- a/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx
@@ -384,14 +384,12 @@ export function View({
       {downloadedDatabase && (
         <div>
           <i18n.Translate>
-            Database exported at
-            <Time
+            Database exported at <Time
               timestamp={AbsoluteTime.fromMilliseconds(
                 downloadedDatabase.time.getTime(),
               )}
               format="yyyy/MM/dd HH:mm:ss"
-            />
-            <a
+            /> <a
               href={`data:text/plain;charset=utf-8;base64,${toBase64(
                 downloadedDatabase.content,
               )}`}
@@ -401,8 +399,7 @@ export function View({
               )}.json`}
             >
               <i18n.Translate>click here</i18n.Translate>
-            </a>
-            to download
+            </a> to download
           </i18n.Translate>
         </div>
       )}
diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx 
b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
index fa4b75639..076110522 100644
--- a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
@@ -463,20 +463,22 @@ export function TransactionView({
           {transaction.exchangeBaseUrl}
         </Header>
 
-        {/**FIXME: DD37 check if this holds */}
         {transaction.txState.major !==
-          TransactionMajorState.Pending ? undefined : transaction
-            .withdrawalDetails.type === WithdrawalType.ManualTransfer ? (
-          //manual withdrawal
-          <BankDetailsByPaytoType
-            amount={raw}
-            exchangeBaseUrl={transaction.exchangeBaseUrl}
-            subject={transaction.withdrawalDetails.reservePub}
-          />
-        ) : (
-          //integrated bank withdrawal
-          <ShowWithdrawalDetailForBankIntegrated transaction={transaction} />
-        )}
+          TransactionMajorState.Pending ? undefined :
+          transaction.txState.minor === TransactionMinorState.KycRequired ||
+            transaction.txState.minor === TransactionMinorState.AmlRequired ? 
undefined :
+            transaction
+              .withdrawalDetails.type === WithdrawalType.ManualTransfer ? (
+              //manual withdrawal
+              <BankDetailsByPaytoType
+                amount={raw}
+                exchangeBaseUrl={transaction.exchangeBaseUrl}
+                subject={transaction.withdrawalDetails.reservePub}
+              />
+            ) : (
+              //integrated bank withdrawal
+              <ShowWithdrawalDetailForBankIntegrated transaction={transaction} 
/>
+            )}
         <Part
           title={i18n.str`Details`}
           text={

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