gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated (31cf3187e -> 61563d1b4)


From: gnunet
Subject: [taler-wallet-core] branch master updated (31cf3187e -> 61563d1b4)
Date: Sun, 05 Nov 2023 22:20:32 +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 31cf3187e exchange and merchant api
     new b58d53dd9 sharing components in web-util
     new 61563d1b4 aml exchange API

The 2 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:
 packages/aml-backoffice-ui/src/App.tsx             |  11 +-
 packages/aml-backoffice-ui/src/Dashboard.tsx       |  26 +--
 packages/aml-backoffice-ui/src/account.ts          | 128 -------------
 .../src/context/config.ts                          |  18 +-
 .../aml-backoffice-ui/src/handlers/Caption.tsx     |   7 +-
 packages/aml-backoffice-ui/src/hooks/useBackend.ts |  27 ++-
 .../aml-backoffice-ui/src/hooks/useCaseDetails.ts  | 103 +++-------
 packages/aml-backoffice-ui/src/hooks/useCases.ts   | 126 ++++++-------
 packages/aml-backoffice-ui/src/hooks/useOfficer.ts |  20 +-
 .../aml-backoffice-ui/src/pages/CaseDetails.tsx    |  83 ++++----
 packages/aml-backoffice-ui/src/pages/Cases.tsx     |  46 ++---
 .../aml-backoffice-ui/src/pages/NewFormEntry.tsx   |  19 +-
 packages/aml-backoffice-ui/src/pages/Officer.tsx   |   4 +-
 .../aml-backoffice-ui/src/pages/UnlockAccount.tsx  |   3 +-
 packages/aml-backoffice-ui/src/utils/errors.tsx    |  77 --------
 .../demobank-ui/src/components/Cashouts/index.ts   |   2 +-
 .../demobank-ui/src/components/Cashouts/views.tsx  |   5 +-
 .../src/components/EmptyComponentExample/index.ts  |   5 +-
 packages/demobank-ui/src/components/Routing.tsx    |   4 -
 .../src/components/Transactions/index.ts           |   5 +-
 packages/demobank-ui/src/components/app.tsx        |   1 +
 packages/demobank-ui/src/context/config.ts         |   2 +-
 .../demobank-ui/src/pages/AccountPage/index.ts     |   4 +-
 .../demobank-ui/src/pages/AccountPage/views.tsx    |   2 +-
 packages/demobank-ui/src/pages/BankFrame.tsx       |  10 +-
 packages/demobank-ui/src/pages/LoginForm.tsx       |   6 +-
 .../demobank-ui/src/pages/OperationState/index.ts  |   4 +-
 .../demobank-ui/src/pages/OperationState/views.tsx |   8 +-
 .../src/pages/PaytoWireTransferForm.tsx            |   4 +-
 .../demobank-ui/src/pages/PublicHistoriesPage.tsx  |   2 +-
 packages/demobank-ui/src/pages/QrCodeSection.tsx   |   2 +-
 .../demobank-ui/src/pages/RegistrationPage.tsx     |   4 +-
 .../demobank-ui/src/pages/ShowAccountDetails.tsx   |   6 +-
 .../src/pages/UpdateAccountPassword.tsx            |   8 +-
 .../demobank-ui/src/pages/WalletWithdrawForm.tsx   |   4 +-
 .../src/pages/WithdrawalConfirmationQuestion.tsx   |   4 +-
 .../src/pages/WithdrawalOperationPage.tsx          |   2 +-
 .../demobank-ui/src/pages/WithdrawalQRCode.tsx     |   6 +-
 packages/demobank-ui/src/pages/admin/Account.tsx   |   4 +-
 .../demobank-ui/src/pages/admin/AccountForm.tsx    |   4 +-
 .../demobank-ui/src/pages/admin/AccountList.tsx    |   4 +-
 packages/demobank-ui/src/pages/admin/AdminHome.tsx |   2 +-
 .../src/pages/admin/CreateNewAccount.tsx           |   4 +-
 .../demobank-ui/src/pages/admin/RemoveAccount.tsx  |  10 +-
 .../src/pages/business/CreateCashout.tsx           |  10 +-
 .../src/pages/business/ShowCashoutDetails.tsx      |  10 +-
 packages/demobank-ui/tailwind.config.js            |   8 +-
 packages/taler-util/src/errors.ts                  |   4 +
 packages/taler-util/src/http-client/exchange.ts    | 125 ++++++++++++-
 packages/taler-util/src/http-client/merchant.ts    |   6 +
 .../taler-util/src/http-client/officer-account.ts  |  81 ++++++++
 packages/taler-util/src/http-client/types.ts       | 208 ++++++++++++++++++++-
 packages/taler-util/src/index.ts                   |   3 +
 packages/web-util/build.mjs                        |   1 +
 .../{demobank-ui => web-util}/src/assets/lang.svg  |   0
 .../src/components/Attention.tsx                   |  13 +-
 .../src/components/CopyButton.tsx                  |   2 -
 .../src/components/ErrorLoading.tsx                |   5 +-
 .../src/components/LangSelector.tsx                |  12 +-
 .../src/components/Loading.tsx                     |   0
 .../src/components/ShowInputErrorLabel.tsx         |   0
 .../src/components/ShowLocalNotification.tsx       |  24 +--
 packages/web-util/src/components/index.ts          |   7 +
 .../{demobank-ui => web-util}/src/declaration.d.ts |   0
 64 files changed, 741 insertions(+), 604 deletions(-)
 delete mode 100644 packages/aml-backoffice-ui/src/account.ts
 copy packages/{demobank-ui => aml-backoffice-ui}/src/context/config.ts (82%)
 delete mode 100644 packages/aml-backoffice-ui/src/utils/errors.tsx
 create mode 100644 packages/taler-util/src/http-client/officer-account.ts
 rename packages/{demobank-ui => web-util}/src/assets/lang.svg (100%)
 rename packages/{demobank-ui => web-util}/src/components/Attention.tsx (93%)
 rename packages/{demobank-ui => web-util}/src/components/CopyButton.tsx (99%)
 rename packages/{demobank-ui => web-util}/src/components/ErrorLoading.tsx (96%)
 rename packages/{demobank-ui => web-util}/src/components/LangSelector.tsx (92%)
 rename packages/{demobank-ui => web-util}/src/components/Loading.tsx (100%)
 rename packages/{demobank-ui => 
web-util}/src/components/ShowInputErrorLabel.tsx (100%)
 rename packages/{demobank-ui => 
web-util}/src/components/ShowLocalNotification.tsx (70%)
 copy packages/{demobank-ui => web-util}/src/declaration.d.ts (100%)

diff --git a/packages/aml-backoffice-ui/src/App.tsx 
b/packages/aml-backoffice-ui/src/App.tsx
index 600131219..0e29279ff 100644
--- a/packages/aml-backoffice-ui/src/App.tsx
+++ b/packages/aml-backoffice-ui/src/App.tsx
@@ -1,12 +1,19 @@
 import { TranslationProvider } from "@gnu-taler/web-util/browser";
 import { h, VNode } from "preact";
-import { Dashboard } from "./Dashboard.js";
+import { ExchangeAmlFrame, Main } from "./Dashboard.js";
 import "./scss/main.css";
+import { ExchangeApiProvider } from "./context/config.js";
+import { getInitialBackendBaseURL } from "./hooks/useBackend.js";
 
 export function App(): VNode {
+  const baseUrl = getInitialBackendBaseURL();
   return (
     <TranslationProvider source={{}}>
-      <Dashboard />
+      <ExchangeApiProvider baseUrl={baseUrl} frameOnError={ExchangeAmlFrame}>
+        <ExchangeAmlFrame>
+          <Main />
+        </ExchangeAmlFrame>
+      </ExchangeApiProvider>
     </TranslationProvider>
   );
 }
diff --git a/packages/aml-backoffice-ui/src/Dashboard.tsx 
b/packages/aml-backoffice-ui/src/Dashboard.tsx
index 6794ca1f8..bd8a48c45 100644
--- a/packages/aml-backoffice-ui/src/Dashboard.tsx
+++ b/packages/aml-backoffice-ui/src/Dashboard.tsx
@@ -182,7 +182,7 @@ function LeftMenu() {
   );
 }
 
-export function Dashboard({
+export function ExchangeAmlFrame({
   children,
 }: {
   children?: ComponentChildren;
@@ -211,21 +211,25 @@ export function Dashboard({
           }}
         />
         <Notifications />
-        <main class="py-10 px-4 sm:px-6 lg:px-8">
-          <div class="mx-auto max-w-3xl">
-            <Router
-              pageList={pageList}
-              onNotFound={() => {
-                return <div>not found</div>;
-              }}
-            />
-          </div>
-        </main>
+        {children}
       </div>
     </Fragment>
   );
 }
 
+export function Main(): VNode {
+  return <main class="py-10 px-4 sm:px-6 lg:px-8">
+    <div class="mx-auto max-w-3xl">
+      <Router
+        pageList={pageList}
+        onNotFound={() => {
+          return <div>not found</div>;
+        }}
+      />
+    </div>
+  </main>
+}
+
 const pageList = Object.values(Pages);
 
 function NavigationBar({
diff --git a/packages/aml-backoffice-ui/src/account.ts 
b/packages/aml-backoffice-ui/src/account.ts
deleted file mode 100644
index 615d843c4..000000000
--- a/packages/aml-backoffice-ui/src/account.ts
+++ /dev/null
@@ -1,128 +0,0 @@
-import {
-  Amounts,
-  TalerSignaturePurpose,
-  amountToBuffer,
-  bufferForUint32,
-  buildSigPS,
-  createEddsaKeyPair,
-  decodeCrock,
-  decryptWithDerivedKey,
-  eddsaGetPublic,
-  eddsaSign,
-  encodeCrock,
-  encryptWithDerivedKey,
-  getRandomBytesF,
-  hash,
-  hashTruncate32,
-  stringToBytes,
-  timestampRoundedToBuffer
-} from "@gnu-taler/taler-util";
-import { AmlExchangeBackend } from "./types.js";
-
-export interface Account {
-  accountId: AccountId;
-  signingKey: SigningKey;
-}
-
-/**
- * Restore previous session and unlock account with password
- *
- * @param salt string from which crypto params will be derived
- * @param key secured private key
- * @param password password for the private key
- * @returns
- */
-export async function unlockAccount(
-  account: LockedAccount,
-  password: string,
-): Promise<Account> {
-  const rawKey = decodeCrock(account);
-  const rawPassword = stringToBytes(password);
-
-  const signingKey = (await decryptWithDerivedKey(
-    rawKey,
-    rawPassword,
-    password,
-  ).catch((e: Error) => {
-    throw new UnwrapKeyError(e.message);
-  })) as SigningKey;
-
-  const publicKey = eddsaGetPublic(signingKey);
-
-  const accountId = encodeCrock(publicKey) as AccountId;
-
-  return { accountId, signingKey };
-}
-
-export function buildQuerySignature(key: SigningKey): string {
-  const sigBlob = buildSigPS(
-    TalerSignaturePurpose.TALER_SIGNATURE_AML_QUERY,
-  ).build();
-
-  return encodeCrock(eddsaSign(sigBlob, key));
-}
-
-export function buildDecisionSignature(
-  key: SigningKey,
-  decision: AmlExchangeBackend.AmlDecision,
-): string {
-  const zero = new Uint8Array(new ArrayBuffer(64))
-
-  const sigBlob = 
buildSigPS(TalerSignaturePurpose.TALER_SIGNATURE_AML_DECISION)
-    //TODO: new need the null terminator, also in the exchange
-    .put(hash(stringToBytes(decision.justification)))//check null
-    .put(timestampRoundedToBuffer(decision.decision_time))
-    .put(amountToBuffer(decision.new_threshold))
-    .put(decodeCrock(decision.h_payto))
-    .put(zero) //kyc_requirement
-    .put(bufferForUint32(decision.new_state))
-    .build();
-
-  return encodeCrock(eddsaSign(sigBlob, key));
-}
-
-declare const opaque_Account: unique symbol;
-export type LockedAccount = string & { [opaque_Account]: true };
-
-declare const opaque_AccountId: unique symbol;
-export type AccountId = string & { [opaque_AccountId]: true };
-
-declare const opaque_SigningKey: unique symbol;
-export type SigningKey = Uint8Array & { [opaque_SigningKey]: true };
-
-/**
- * Create new account (secured private key)
- * secured with the given password
- *
- * @param sessionId
- * @param password
- * @returns
- */
-export async function createNewAccount(
-  password: string,
-): Promise<Account & { safe: LockedAccount }> {
-  const { eddsaPriv, eddsaPub } = createEddsaKeyPair();
-
-  const key = stringToBytes(password);
-
-  const protectedPrivKey = await encryptWithDerivedKey(
-    getRandomBytesF(24),
-    key,
-    eddsaPriv,
-    password,
-  );
-
-  const signingKey = eddsaPriv as SigningKey;
-  const accountId = encodeCrock(eddsaPub) as AccountId;
-  const safe = encodeCrock(protectedPrivKey) as LockedAccount;
-
-  return { accountId, signingKey, safe };
-}
-
-export class UnwrapKeyError extends Error {
-  public cause: string;
-  constructor(cause: string) {
-    super(`Recovering private key failed on: ${cause}`);
-    this.cause = cause;
-  }
-}
diff --git a/packages/demobank-ui/src/context/config.ts 
b/packages/aml-backoffice-ui/src/context/config.ts
similarity index 82%
copy from packages/demobank-ui/src/context/config.ts
copy to packages/aml-backoffice-ui/src/context/config.ts
index a31d914b8..2866717de 100644
--- a/packages/demobank-ui/src/context/config.ts
+++ b/packages/aml-backoffice-ui/src/context/config.ts
@@ -14,11 +14,11 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { TalerCorebankApi, TalerCoreBankHttpClient, TalerError } from 
"@gnu-taler/taler-util";
+import { TalerExchangeApi, TalerExchangeHttpClient, TalerError } from 
"@gnu-taler/taler-util";
 import { BrowserHttpLib, useTranslationContext } from 
"@gnu-taler/web-util/browser";
 import { ComponentChildren, createContext, FunctionComponent, h, VNode } from 
"preact";
 import { useContext, useEffect, useState } from "preact/hooks";
-import { ErrorLoading } from "../components/ErrorLoading.js";
+import { ErrorLoading } from "@gnu-taler/web-util/browser";
 
 /**
  *
@@ -27,20 +27,20 @@ import { ErrorLoading } from 
"../components/ErrorLoading.js";
 
 export type Type = {
   url: URL,
-  config: TalerCorebankApi.Config,
-  api: TalerCoreBankHttpClient,
+  config: TalerExchangeApi.ExchangeVersionResponse,
+  api: TalerExchangeHttpClient,
 };
 
 const Context = createContext<Type>(undefined as any);
 
-export const useBankCoreApiContext = (): Type => useContext(Context);
+export const useExchangeApiContext = (): Type => useContext(Context);
 
 export type ConfigResult = undefined
-  | { type: "ok", config: TalerCorebankApi.Config }
-  | { type: "incompatible", result: TalerCorebankApi.Config, supported: string 
}
+  | { type: "ok", config: TalerExchangeApi.ExchangeVersionResponse }
+  | { type: "incompatible", result: TalerExchangeApi.ExchangeVersionResponse, 
supported: string }
   | { type: "error", error: TalerError }
 
-export const BankCoreApiProvider = ({
+export const ExchangeApiProvider = ({
   baseUrl,
   children,
   frameOnError,
@@ -52,7 +52,7 @@ export const BankCoreApiProvider = ({
   const [checked, setChecked] = useState<ConfigResult>()
   const { i18n } = useTranslationContext();
   const url = new URL(baseUrl)
-  const api = new TalerCoreBankHttpClient(url.href, new BrowserHttpLib())
+  const api = new TalerExchangeHttpClient(url.href, new BrowserHttpLib())
   useEffect(() => {
     api.getConfig()
       .then((resp) => {
diff --git a/packages/aml-backoffice-ui/src/handlers/Caption.tsx 
b/packages/aml-backoffice-ui/src/handlers/Caption.tsx
index fbf154d89..8facddec3 100644
--- a/packages/aml-backoffice-ui/src/handlers/Caption.tsx
+++ b/packages/aml-backoffice-ui/src/handlers/Caption.tsx
@@ -1,11 +1,8 @@
+import { TranslatedString } from "@gnu-taler/taler-util";
 import { VNode, h } from "preact";
 import {
-  IconAddon,
-  InputLine,
-  LabelWithTooltipMaybeRequired,
-  UIFormProps,
+  LabelWithTooltipMaybeRequired
 } from "./InputLine.js";
-import { TranslatedString } from "@gnu-taler/taler-util";
 
 interface Props {
   label: TranslatedString;
diff --git a/packages/aml-backoffice-ui/src/hooks/useBackend.ts 
b/packages/aml-backoffice-ui/src/hooks/useBackend.ts
index b9d66fca6..95277a915 100644
--- a/packages/aml-backoffice-ui/src/hooks/useBackend.ts
+++ b/packages/aml-backoffice-ui/src/hooks/useBackend.ts
@@ -1,3 +1,4 @@
+import { canonicalizeBaseUrl } from "@gnu-taler/taler-util";
 import {
   HttpResponseOk,
   RequestOptions,
@@ -5,9 +6,6 @@ import {
 } from "@gnu-taler/web-util/browser";
 import { useCallback } from "preact/hooks";
 import { uiSettings } from "../settings.js";
-import { canonicalizeBaseUrl } from "@gnu-taler/taler-util";
-import { useOfficer } from "./useOfficer.js";
-import { buildQuerySignature } from "../account.js";
 
 interface useBackendType {
   request: <T>(
@@ -35,7 +33,7 @@ export function usePublicBackend(): useBackendType {
   );
 
   const fetcher = useCallback(
-    function fetcherImpl<T>([endpoint, talerAmlOfficerSignature]: 
[string,string]): Promise<HttpResponseOk<T>> {
+    function fetcherImpl<T>([endpoint, talerAmlOfficerSignature]: [string, 
string]): Promise<HttpResponseOk<T>> {
       return requestHandler<T>(baseUrl, endpoint, {
         talerAmlOfficerSignature
       });
@@ -66,18 +64,29 @@ export function usePublicBackend(): useBackendType {
 export function getInitialBackendBaseURL(): string {
   const overrideUrl =
     typeof localStorage !== "undefined"
-      ? localStorage.getItem("exchange-aml-base-url")
+      ? localStorage.getItem("exchange-base-url")
       : undefined;
+
+  let result: string;
+
   if (!overrideUrl) {
     //normal path
     if (!uiSettings.backendBaseURL) {
       console.error(
         "ERROR: backendBaseURL was overridden by a setting file and missing. 
Setting value to 'window.origin'",
       );
-      return canonicalizeBaseUrl(window.origin);
+      result = window.origin
+    } else {
+      result = uiSettings.backendBaseURL;
     }
-    return canonicalizeBaseUrl(uiSettings.backendBaseURL);
+  } else {
+    // testing/development path
+    result = overrideUrl
+  }
+  try {
+    return canonicalizeBaseUrl(result)
+  } catch (e) {
+    //fall back
+    return canonicalizeBaseUrl(window.origin)
   }
-  // testing/development path
-  return canonicalizeBaseUrl(overrideUrl);
 }
diff --git a/packages/aml-backoffice-ui/src/hooks/useCaseDetails.ts 
b/packages/aml-backoffice-ui/src/hooks/useCaseDetails.ts
index 980a35f21..9db1e2aec 100644
--- a/packages/aml-backoffice-ui/src/hooks/useCaseDetails.ts
+++ b/packages/aml-backoffice-ui/src/hooks/useCaseDetails.ts
@@ -1,34 +1,29 @@
 
 import {
   HttpResponse,
-  HttpResponseOk,
-  RequestError
+  HttpResponseOk
 } from "@gnu-taler/web-util/browser";
 import { AmlExchangeBackend } from "../types.js";
 // FIX default import https://github.com/microsoft/TypeScript/issues/49189
+import { AmountString, OfficerAccount, PaytoString, TalerExchangeApi, 
TalerExchangeResultByMethod, TalerHttpError } from "@gnu-taler/taler-util";
 import _useSWR, { SWRHook, useSWRConfig } from "swr";
-import { AccountId } from "../account.js";
+import { useExchangeApiContext } from "../context/config.js";
 import { usePublicBackend } from "./useBackend.js";
+import { useOfficer } from "./useOfficer.js";
 const useSWR = _useSWR as unknown as SWRHook;
 
-export function useCaseDetails(
-  account: AccountId,
-  paytoHash: string,
-  signature: string | undefined,
-): HttpResponse<
-  AmlExchangeBackend.AmlDecisionDetails,
-  AmlExchangeBackend.AmlError
-> {
-  const { fetcher } = usePublicBackend();
+export function useCaseDetails(paytoHash: string) {
+  const officer = useOfficer();
+  const session = officer.state === "ready" ? officer.account : undefined;
 
-  const { data, error } = useSWR<
-  HttpResponseOk<AmlExchangeBackend.AmlDecisionDetails>,
-  RequestError<AmlExchangeBackend.AmlError>
->(    [
-  `aml/${account}/decision/${(paytoHash)}`,
-  signature,
-],
-fetcher, {
+  const { api } = useExchangeApiContext();
+
+  async function fetcher([officer, account]: [OfficerAccount, PaytoString]) {
+    return await api.getDecisionDetails(officer, account)
+  }
+
+  const { data, error } = 
useSWR<TalerExchangeResultByMethod<"getDecisionDetails">, TalerHttpError>(
+    !session ? undefined : [session, paytoHash], fetcher, {
     refreshInterval: 0,
     refreshWhenHidden: false,
     revalidateOnFocus: false,
@@ -41,11 +36,11 @@ fetcher, {
   });
 
   if (data) return data;
-  if (error) return error.cause;
-  return { loading: true };
+  if (error) return error;
+  return undefined;
 }
 
-const example1: AmlExchangeBackend.AmlDecisionDetails = {
+const example1: TalerExchangeApi.AmlDecisionDetails = {
   aml_history: [
     {
       justification: "Lack of documentation",
@@ -54,7 +49,7 @@ const example1: AmlExchangeBackend.AmlDecisionDetails = {
         t_s: Date.now() / 1000,
       },
       new_state: 2,
-      new_threshold: "USD:0",
+      new_threshold: "USD:0" as AmountString,
     },
     {
       justification: "Doing a transfer of high amount",
@@ -63,7 +58,7 @@ const example1: AmlExchangeBackend.AmlDecisionDetails = {
         t_s: Date.now() / 1000 - 60 * 60 * 24 * 30 * 6,
       },
       new_state: 1,
-      new_threshold: "USD:2000",
+      new_threshold: "USD:2000" as AmountString,
     },
     {
       justification: "Account is known to the system",
@@ -72,7 +67,7 @@ const example1: AmlExchangeBackend.AmlDecisionDetails = {
         t_s: Date.now() / 1000 - 60 * 60 * 24 * 30 * 9,
       },
       new_state: 0,
-      new_threshold: "USD:100",
+      new_threshold: "USD:100" as AmountString,
     },
   ],
   kyc_attributes: [
@@ -103,60 +98,4 @@ const example1: AmlExchangeBackend.AmlDecisionDetails = {
   ],
 };
 
-export const exampleResponse: 
HttpResponse<AmlExchangeBackend.AmlDecisionDetails,AmlExchangeBackend.AmlError> 
= {
-  ok: true,
-  data: example1,
-}
-
-
-export function useAmlCasesAPI(): AmlCaseAPI {
-  const { request } = usePublicBackend();
-  const mutateAll = useMatchMutate();
-
-  const updateDecision = async (
-    officer: AccountId,
-    data: AmlExchangeBackend.AmlDecision,
-  ): Promise<HttpResponseOk<void>> => {
-    const res = await request<void>(`aml/${officer}/decision`, {
-      method: "POST",
-      data,
-      contentType: "json",
-    });
-    await mutateAll(/.*aml.*/);
-    return res;
-  };
-
-  return {
-    updateDecision,
-  };
-}
-
-export interface AmlCaseAPI {
-  updateDecision: (
-    officer: AccountId,
-    data: AmlExchangeBackend.AmlDecision,
-  ) => Promise<HttpResponseOk<void>>;
-}
-
 
-function useMatchMutate(): (
-  re: RegExp,
-  value?: unknown,
-) => Promise<any> {
-  const { cache, mutate } = useSWRConfig();
-
-  if (!(cache instanceof Map)) {
-    throw new Error(
-      "matchMutate requires the cache provider to be a Map instance",
-    );
-  }
-
-  return function matchRegexMutate(re: RegExp, value?: unknown) {
-    const allKeys = Array.from(cache.keys());
-    const keys = allKeys.filter((key) => re.test(key));
-    const mutations = keys.map((key) => {
-      return mutate(key, value, true);
-    });
-    return Promise.all(mutations);
-  };
-}
diff --git a/packages/aml-backoffice-ui/src/hooks/useCases.ts 
b/packages/aml-backoffice-ui/src/hooks/useCases.ts
index c07bd5f18..2a133f46d 100644
--- a/packages/aml-backoffice-ui/src/hooks/useCases.ts
+++ b/packages/aml-backoffice-ui/src/hooks/useCases.ts
@@ -1,16 +1,13 @@
-import { useEffect, useState } from "preact/hooks";
+import { useState } from "preact/hooks";
 
-import { AmlExchangeBackend } from "../types.js";
 import {
-  HttpResponse,
-  HttpResponseOk,
-  HttpResponsePaginated,
-  RequestError,
+  HttpResponsePaginated
 } from "@gnu-taler/web-util/browser";
+import { AmlExchangeBackend } from "../types.js";
 // FIX default import https://github.com/microsoft/TypeScript/issues/49189
+import { AmountString, OfficerAccount, TalerExchangeApi, 
TalerExchangeResultByMethod, TalerHttpError } from "@gnu-taler/taler-util";
 import _useSWR, { SWRHook } from "swr";
-import { usePublicBackend } from "./useBackend.js";
-import { AccountId, buildQuerySignature } from "../account.js";
+import { useExchangeApiContext } from "../context/config.js";
 import { useOfficer } from "./useOfficer.js";
 const useSWR = _useSWR as unknown as SWRHook;
 
@@ -22,59 +19,49 @@ const MAX_RESULT_SIZE = PAGE_SIZE * 2 - 1;
  * @param args
  * @returns
  */
-export function useCases(
-  account: AccountId,
-  state: AmlExchangeBackend.AmlState,
-  signature: string | undefined,
-): HttpResponsePaginated<
-  AmlExchangeBackend.AmlRecords,
-  AmlExchangeBackend.AmlError
-> {
-  const { paginatedFetcher } = usePublicBackend();
+export function useCases(state: AmlExchangeBackend.AmlState) {
+  const officer = useOfficer();
+  const session = officer.state === "ready" ? officer.account : undefined;
+  const { api } = useExchangeApiContext();
 
-  const [page, setPage] = useState(1);
+  const [offset, setOffet] = useState<string>();
+
+  async function fetcher([officer, state, offset]: [OfficerAccount, 
AmlExchangeBackend.AmlState, string | undefined]) {
+    return await api.getDecisionsByState(officer, state, {
+      order: "asc", offset, limit: MAX_RESULT_SIZE
+    })
+  }
 
-  const {
-    data: afterData,
-    error: afterError,
-    isValidating: loadingAfter,
-  } = useSWR<
-    HttpResponseOk<AmlExchangeBackend.AmlRecords>,
-    RequestError<AmlExchangeBackend.AmlError>
-  >(
-    [
-      `aml/${account}/decisions/${AmlExchangeBackend.AmlState[state]}`,
-      page,
-      PAGE_SIZE,
-      signature,
-    ],
-    paginatedFetcher,
+  const { data, error } = 
useSWR<TalerExchangeResultByMethod<"getDecisionsByState">, TalerHttpError>(
+    !session ? undefined : [session, state, offset],
+    fetcher,
   );
 
-  const [lastAfter, setLastAfter] = useState<
-    HttpResponse<AmlExchangeBackend.AmlRecords, AmlExchangeBackend.AmlError>
-  >({ loading: true });
+  // const [lastAfter, setLastAfter] = useState<
+  //   HttpResponse<AmlExchangeBackend.AmlRecords, AmlExchangeBackend.AmlError>
+  // >({ loading: true });
 
-  useEffect(() => {
-    if (afterData) setLastAfter(afterData);
-  }, [afterData]);
+  // useEffect(() => {
+  //   if (afterData) setLastAfter(afterData);
+  // }, [afterData]);
 
-  if (afterError) {
-    return afterError.cause;
-  }
+  // if (afterError) {
+  //   return afterError.cause;
+  // }
 
   // if the query returns less that we ask, then we have reach the end or 
beginning
-  const isReachingEnd =
-    afterData && afterData.data && afterData.data.records.length < PAGE_SIZE;
-  const isReachingStart = false;
+  const isLastPage =
+    data && data.type === "ok" && data.body.records.length < PAGE_SIZE;
+  const isFirstPage = !offset;
 
   const pagination = {
-    isReachingEnd,
-    isReachingStart,
+    isLastPage,
+    isFirstPage,
     loadMore: () => {
-      if (!afterData || isReachingEnd) return;
-      if (afterData.data && afterData.data.records.length < MAX_RESULT_SIZE) {
-        setPage(page + 1);
+      if (isLastPage || data?.type !== "ok") return;
+      const list = data.body.records
+      if (list.length < MAX_RESULT_SIZE) {
+        // setOffset(list[list.length-1].account_name);
       }
     },
     loadMorePrev: () => {
@@ -82,65 +69,62 @@ export function useCases(
     },
   };
 
-  const records = !afterData
-    ? []
-    : ((afterData ?? lastAfter).data ?? { records: [] }).records;
-  if (loadingAfter) return { loading: true, data: { records } };
-  if (afterData) {
-    return { ok: true, data: { records }, ...pagination };
+  // const public_accountslist = data?.type !== "ok" ? [] : 
data.body.public_accounts;
+  if (data) {
+    if (data.type === "fail") {
+      return { data }
+    }
+    return { data, pagination }
+  }
+  if (error) {
+    return error;
   }
-  return { loading: true };
+  return undefined;
 }
 
-const example1: AmlExchangeBackend.AmlRecords = {
+const example1: TalerExchangeApi.AmlRecords = {
   records: [
     {
       current_state: 0,
       h_payto: "QWEQWEQWEQWEWQE",
       rowid: 1,
-      threshold: "USD 100",
+      threshold: "USD 100" as AmountString,
     },
     {
       current_state: 1,
       h_payto: "ASDASDASD",
       rowid: 1,
-      threshold: "USD 100",
+      threshold: "USD 100" as AmountString,
     },
     {
       current_state: 2,
       h_payto: "ZXCZXCZXCXZC",
       rowid: 1,
-      threshold: "USD 1000",
+      threshold: "USD 1000" as AmountString,
     },
     {
       current_state: 0,
       h_payto: "QWEQWEQWEQWEWQE",
       rowid: 1,
-      threshold: "USD 100",
+      threshold: "USD 100" as AmountString,
     },
     {
       current_state: 1,
       h_payto: "ASDASDASD",
       rowid: 1,
-      threshold: "USD 100",
+      threshold: "USD 100" as AmountString,
     },
     {
       current_state: 2,
       h_payto: "ZXCZXCZXCXZC",
       rowid: 1,
-      threshold: "USD 1000",
+      threshold: "USD 1000" as AmountString,
     },
   ].map((e, idx) => {
     e.rowid = idx;
-    e.threshold = `${e.threshold}${idx}`;
+    e.threshold = `${e.threshold}${idx}` as AmountString;
     return e;
   }),
 };
 
-export const exampleResponse: 
HttpResponsePaginated<AmlExchangeBackend.AmlRecords,AmlExchangeBackend.AmlError>
 = {
-  ok: true,
-  data: example1,
-  loadMore: () => {},
-  loadMorePrev: () => {},  
-}
 
diff --git a/packages/aml-backoffice-ui/src/hooks/useOfficer.ts 
b/packages/aml-backoffice-ui/src/hooks/useOfficer.ts
index 4ec43569b..0747170e8 100644
--- a/packages/aml-backoffice-ui/src/hooks/useOfficer.ts
+++ b/packages/aml-backoffice-ui/src/hooks/useOfficer.ts
@@ -1,16 +1,14 @@
 import {
   AbsoluteTime,
   Codec,
+  LockedAccount,
+  OfficerAccount,
   buildCodecForObject,
   codecForAbsoluteTime,
   codecForString,
+  createNewOfficerAccount,
+  unlockOfficerAccount,
 } from "@gnu-taler/taler-util";
-import {
-  Account,
-  LockedAccount,
-  createNewAccount,
-  unlockAccount,
-} from "../account.js";
 import {
   buildStorageKey,
   useLocalStorage,
@@ -43,7 +41,7 @@ interface OfficerLocked {
 }
 interface OfficerReady {
   state: "ready";
-  account: Account;
+  account: OfficerAccount;
   forget: () => void;
   lock: () => void;
 }
@@ -52,7 +50,7 @@ const OFFICER_KEY = buildStorageKey("officer", 
codecForOfficer());
 const ACCOUNT_KEY = "account";
 
 export function useOfficer(): OfficerState {
-  const accountStorage = useMemoryStorage<Account>(ACCOUNT_KEY);
+  const accountStorage = useMemoryStorage<OfficerAccount>(ACCOUNT_KEY);
   const officerStorage = useLocalStorage(OFFICER_KEY);
 
   const officer = officerStorage.value;
@@ -62,13 +60,13 @@ export function useOfficer(): OfficerState {
     return {
       state: "not-found",
       create: async (pwd: string) => {
-        const { accountId, safe, signingKey } = await createNewAccount(pwd);
+        const { id, safe, signingKey } = await createNewOfficerAccount(pwd);
         officerStorage.update({
           account: safe,
           when: AbsoluteTime.now(),
         });
 
-        accountStorage.update({ accountId, signingKey });
+        accountStorage.update({ id, signingKey });
       },
     };
   }
@@ -80,7 +78,7 @@ export function useOfficer(): OfficerState {
         officerStorage.reset();
       },
       tryUnlock: async (pwd: string) => {
-        const ac = await unlockAccount(officer.account, pwd);
+        const ac = await unlockOfficerAccount(officer.account, pwd);
         accountStorage.update(ac);
       },
     };
diff --git a/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx 
b/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx
index ce820d612..f618a3592 100644
--- a/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx
+++ b/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx
@@ -1,24 +1,23 @@
-import { Fragment, VNode, h } from "preact";
 import {
   AbsoluteTime,
   AmountJson,
   Amounts,
+  PaytoString,
+  TalerError,
   TranslatedString,
+  assertUnreachable,
 } from "@gnu-taler/taler-util";
-import { format } from "date-fns";
+import { ErrorLoading, Loading, useTranslationContext } from 
"@gnu-taler/web-util/browser";
 import { ArrowDownCircleIcon, ClockIcon } from "@heroicons/react/20/solid";
+import { format } from "date-fns";
+import { Fragment, VNode, h } from "preact";
 import { useState } from "preact/hooks";
 import { NiceForm } from "../NiceForm.js";
 import { FlexibleForm } from "../forms/index.js";
 import { UIFormField } from "../handlers/forms.js";
+import { useCaseDetails } from "../hooks/useCaseDetails.js";
 import { Pages } from "../pages.js";
 import { AmlExchangeBackend } from "../types.js";
-import { HandleAccountNotReady } from "./HandleAccountNotReady.js";
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
-import { useOfficer } from "../hooks/useOfficer.js";
-import { buildQuerySignature } from "../account.js";
-import { useCaseDetails } from "../hooks/useCaseDetails.js";
-import { handleNotOkResult } from "../utils/errors.js";
 
 type AmlEvent = AmlFormEvent | KycCollectionEvent | KycExpirationEvent;
 type AmlFormEvent = {
@@ -85,30 +84,33 @@ function getEventsFromAmlHistory(
   return ae.concat(ke).sort(selectSooner);
 }
 
-export function CaseDetails({ account: paytoHash }: { account: string }) {
+export function CaseDetails({ account }: { account: string }) {
   const [selected, setSelected] = useState<AmlEvent | undefined>(undefined);
 
-  const officer = useOfficer();
   const { i18n } = useTranslationContext();
-  if (officer.state !== "ready") {
-    return <HandleAccountNotReady officer={officer} />;
+  const details = useCaseDetails(account)
+  if (!details) {
+    return <Loading />
   }
-  const signature =
-    officer.state === "ready"
-      ? buildQuerySignature(officer.account.signingKey)
-      : undefined;
-  const details = useCaseDetails(officer.account.accountId, paytoHash, 
signature)
-  if (!details.ok && !details.loading) {
-    return handleNotOkResult(i18n)(details);
+  if (details instanceof TalerError) {
+    return <ErrorLoading error={details} />
   }
-  const aml_history = details.loading ? [] : details.data.aml_history
-  const kyc_attributes = details.loading ? [] : details.data.kyc_attributes
-  const events = getEventsFromAmlHistory(aml_history,kyc_attributes);
-  
+  if (details.type === "fail") {
+    switch (details.case) {
+      case "unauthorized":
+      case "officer-not-found":
+      case "officer-disabled": return <div />
+      default: assertUnreachable(details)
+    }
+  }
+  const { aml_history, kyc_attributes } = details.body
+
+  const events = getEventsFromAmlHistory(aml_history, kyc_attributes);
+
   return (
     <div>
       <a
-        href={Pages.newFormEntry.url({ account: paytoHash })}
+        href={Pages.newFormEntry.url({ account })}
         class="m-4 block rounded-md w-fit border-0 px-3 py-2 text-center 
text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700"
       >
         New AML form
@@ -287,23 +289,22 @@ function ShowConsolidated({
       },
       Object.entries(cons.kyc).length > 0
         ? {
-            title: "KYC" as TranslatedString,
-            fields: Object.entries(cons.kyc).map(([key, field]) => {
-              const result: UIFormField = {
-                type: "text",
-                props: {
-                  label: key as TranslatedString,
-                  name: `kyc.${key}.value`,
-                  help: `${field.provider} since ${
-                    field.since.t_ms === "never"
-                      ? "never"
-                      : format(field.since.t_ms, "dd/MM/yyyy")
+          title: "KYC" as TranslatedString,
+          fields: Object.entries(cons.kyc).map(([key, field]) => {
+            const result: UIFormField = {
+              type: "text",
+              props: {
+                label: key as TranslatedString,
+                name: `kyc.${key}.value`,
+                help: `${field.provider} since ${field.since.t_ms === "never"
+                  ? "never"
+                  : format(field.since.t_ms, "dd/MM/yyyy")
                   }` as TranslatedString,
-                },
-              };
-              return result;
-            }),
-          }
+              },
+            };
+            return result;
+          }),
+        }
         : undefined,
     ],
   };
@@ -319,7 +320,7 @@ function ShowConsolidated({
         key={`${String(Date.now())}`}
         form={form}
         initial={cons}
-        onUpdate={() => {}}
+        onUpdate={() => { }}
       />
     </Fragment>
   );
diff --git a/packages/aml-backoffice-ui/src/pages/Cases.tsx 
b/packages/aml-backoffice-ui/src/pages/Cases.tsx
index 990c0d2d4..5f79db71e 100644
--- a/packages/aml-backoffice-ui/src/pages/Cases.tsx
+++ b/packages/aml-backoffice-ui/src/pages/Cases.tsx
@@ -1,4 +1,5 @@
-import { TranslatedString } from "@gnu-taler/taler-util";
+import { TalerError, TranslatedString, assertUnreachable } from 
"@gnu-taler/taler-util";
+import { ErrorLoading, Loading, useTranslationContext } from 
"@gnu-taler/web-util/browser";
 import { VNode, h } from "preact";
 import { useState } from "preact/hooks";
 import { createNewForm } from "../handlers/forms.js";
@@ -7,34 +8,37 @@ import { useOfficer } from "../hooks/useOfficer.js";
 import { Pages } from "../pages.js";
 import { AmlExchangeBackend } from "../types.js";
 import { amlStateConverter } from "./CaseDetails.js";
-import { HandleAccountNotReady } from "./HandleAccountNotReady.js";
-import { buildQuerySignature } from "../account.js";
-import { handleNotOkResult } from "../utils/errors.js";
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
 
 export function Cases() {
-  const officer = useOfficer();
   const { i18n } = useTranslationContext();
-  if (officer.state !== "ready") {
-    return <HandleAccountNotReady officer={officer} />;
-  }
-  const form = createNewForm<{
-    state: AmlExchangeBackend.AmlState;
-  }>();
 
-  const signature =
-    officer.state === "ready"
-      ? buildQuerySignature(officer.account.signingKey)
-      : undefined;
+  const form = createNewForm<{ state: AmlExchangeBackend.AmlState }>();
+
 
   const initial = AmlExchangeBackend.AmlState.pending;
   const [stateFilter, setStateFilter] = useState(initial);
-  const list = useCases(officer.account.accountId, stateFilter, signature);
 
-  if (!list.ok && !list.loading) {
-    return handleNotOkResult(i18n)(list);
+  const list = useCases(stateFilter);
+
+  if (!list) {
+    return <Loading />
+  }
+
+  if (list instanceof TalerError) {
+    return <ErrorLoading error={list} />
   }
-  const records = list.loading ? [] : list.data.records
+
+  if (list.data.type === "fail") {
+    switch (list.data.case) {
+      case "unauthorized":
+      case "officer-not-found":
+      case "officer-disabled": return <div />
+      default: assertUnreachable(list.data)
+    }
+  }
+
+  const { records } = list.data.body
+
   return (
     <div>
       <div class="px-4 sm:px-6 lg:px-8">
@@ -52,7 +56,7 @@ export function Cases() {
             onUpdate={(v) => {
               setStateFilter(v.state ?? initial);
             }}
-            onSubmit={(v) => {}}
+            onSubmit={(v) => { }}
           >
             <form.InputChoiceHorizontal
               name="state"
diff --git a/packages/aml-backoffice-ui/src/pages/NewFormEntry.tsx 
b/packages/aml-backoffice-ui/src/pages/NewFormEntry.tsx
index 429cfb9ca..fa79bb476 100644
--- a/packages/aml-backoffice-ui/src/pages/NewFormEntry.tsx
+++ b/packages/aml-backoffice-ui/src/pages/NewFormEntry.tsx
@@ -2,12 +2,11 @@ import { VNode, h } from "preact";
 import { allForms } from "./AntiMoneyLaunderingForm.js";
 import { Pages } from "../pages.js";
 import { NiceForm } from "../NiceForm.js";
-import { AbsoluteTime, Amounts, TalerProtocolTimestamp } from 
"@gnu-taler/taler-util";
+import { AbsoluteTime, Amounts, TalerExchangeApi, TalerProtocolTimestamp } 
from "@gnu-taler/taler-util";
 import { AmlExchangeBackend } from "../types.js";
-import { useAmlCasesAPI } from "../hooks/useCaseDetails.js";
 import { useOfficer } from "../hooks/useOfficer.js";
 import { HandleAccountNotReady } from "./HandleAccountNotReady.js";
-import { buildDecisionSignature, buildQuerySignature } from "../account.js";
+import { useExchangeApiContext } from "../context/config.js";
 
 export function NewFormEntry({
   account,
@@ -40,21 +39,21 @@ export function NewFormEntry({
     state: AmlExchangeBackend.AmlState.pending,
     threshold: Amounts.parseOrThrow("KUDOS:1000"),
   };
-  const api = useAmlCasesAPI()
-  
+  const { api } = useExchangeApiContext()
+
   return (
     <NiceForm
       initial={initial}
       form={showingFrom(initial)}
       onSubmit={(formValue) => {
         if (formValue.state === undefined || formValue.threshold === 
undefined) return;
-        
+
         const justification = {
           index: selectedForm,
           name: formName,
           value: formValue
         }
-        const decision: AmlExchangeBackend.AmlDecision = {
+        const decision: TalerExchangeApi.AmlDecision = {
           justification: JSON.stringify(justification),
           decision_time: TalerProtocolTimestamp.now(),
           h_payto: account,
@@ -63,9 +62,9 @@ export function NewFormEntry({
           officer_sig: "",
           kyc_requirements: undefined
         }
-        const signature = buildDecisionSignature(officer.account.signingKey, 
decision);
-        decision.officer_sig = signature
-        api.updateDecision(officer.account.accountId, decision);
+        // const signature = 
buildDecisionSignature(officer.account.signingKey, decision);
+        // decision.officer_sig = signature
+        api.addDecisionDetails(officer.account, decision);
 
         // alert(JSON.stringify(formValue));
       }}
diff --git a/packages/aml-backoffice-ui/src/pages/Officer.tsx 
b/packages/aml-backoffice-ui/src/pages/Officer.tsx
index 5320369e4..4af34805a 100644
--- a/packages/aml-backoffice-ui/src/pages/Officer.tsx
+++ b/packages/aml-backoffice-ui/src/pages/Officer.tsx
@@ -14,12 +14,12 @@ export function Officer() {
         Public key
       </h1>
       <div class="max-w-xl text-base leading-7 text-gray-700 lg:max-w-lg">
-        <p class="mt-6 font-mono break-all">{officer.account.accountId}</p>
+        <p class="mt-6 font-mono break-all">{officer.account.id}</p>
       </div>
       <p>
         <a
           href={`mailto:aml@exchange.taler.net?body=${encodeURIComponent(
-            `I want my AML account\n\n\nPubKey: ${officer.account.accountId}`,
+            `I want my AML account\n\n\nPubKey: ${officer.account.id}`,
           )}`}
           target="_blank"
           rel="noreferrer"
diff --git a/packages/aml-backoffice-ui/src/pages/UnlockAccount.tsx 
b/packages/aml-backoffice-ui/src/pages/UnlockAccount.tsx
index 39f8addd3..83d8767fb 100644
--- a/packages/aml-backoffice-ui/src/pages/UnlockAccount.tsx
+++ b/packages/aml-backoffice-ui/src/pages/UnlockAccount.tsx
@@ -1,7 +1,6 @@
-import { TranslatedString } from "@gnu-taler/taler-util";
+import { TranslatedString, UnwrapKeyError } from "@gnu-taler/taler-util";
 import { notifyError, notifyInfo } from "@gnu-taler/web-util/browser";
 import { VNode, h } from "preact";
-import { UnwrapKeyError } from "../account.js";
 import { createNewForm } from "../handlers/forms.js";
 
 export function UnlockAccount({
diff --git a/packages/aml-backoffice-ui/src/utils/errors.tsx 
b/packages/aml-backoffice-ui/src/utils/errors.tsx
deleted file mode 100644
index b67d61a5f..000000000
--- a/packages/aml-backoffice-ui/src/utils/errors.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-import {
-  ErrorType,
-  HttpResponse,
-  HttpResponsePaginated,
-  notifyError,
-  useTranslationContext,
-} from "@gnu-taler/web-util/browser";
-import { VNode, h } from "preact";
-import { Loading } from "./Loading.js";
-import { HttpStatusCode, TranslatedString } from "@gnu-taler/taler-util";
-import { AmlExchangeBackend } from "../types.js";
-
-export function handleNotOkResult<Error extends AmlExchangeBackend.AmlError>(
-  i18n: ReturnType<typeof useTranslationContext>["i18n"],
-): <T>(
-  result: HttpResponsePaginated<T, Error> | HttpResponse<T, Error>,
-) => VNode {
-  return function handleNotOkResult2<T>(
-    result: HttpResponsePaginated<T, Error> | HttpResponse<T, Error>,
-  ): VNode {
-    if (result.loading) return <Loading />;
-    if (!result.ok) {
-      switch (result.type) {
-        case ErrorType.TIMEOUT: {
-          notifyError(i18n.str`Request timeout, try again later.`, undefined);
-          break;
-        }
-        case ErrorType.CLIENT: {
-          if (result.status === HttpStatusCode.Unauthorized) {
-            notifyError(i18n.str`Wrong credentials`, undefined);
-            return <div> not authorized</div>;
-          }
-          const errorData = result.payload;
-          notifyError(
-            i18n.str`Could not load due to a client error`,
-            errorData.hint as TranslatedString,
-            JSON.stringify(result),
-          );
-          break;
-        }
-        case ErrorType.SERVER: {
-          notifyError(
-            i18n.str`Server returned with error`,
-            result.payload.hint as TranslatedString,
-            JSON.stringify(result.payload),
-          );
-          break;
-        }
-        case ErrorType.UNREADABLE: {
-          notifyError(
-            i18n.str`Unexpected error.`,
-            `Response from ${result.info?.url} is unreadable, http status: 
${result.status}` as TranslatedString,
-            JSON.stringify(result),
-          );
-          break;
-        }
-        case ErrorType.UNEXPECTED: {
-          notifyError(
-            i18n.str`Unexpected error.`,
-            `Diagnostic from ${result.info?.url} is "${result.message}"` as 
TranslatedString,
-            JSON.stringify(result),
-          );
-          break;
-        }
-        default: {
-          assertUnreachable(result);
-        }
-      }
-
-      return <div>error</div>;
-    }
-    return <div />;
-  };
-}
-export function assertUnreachable(x: never): never {
-  throw new Error("Didn't expect to get here");
-}
diff --git a/packages/demobank-ui/src/components/Cashouts/index.ts 
b/packages/demobank-ui/src/components/Cashouts/index.ts
index 09839e753..6cbb1247d 100644
--- a/packages/demobank-ui/src/components/Cashouts/index.ts
+++ b/packages/demobank-ui/src/components/Cashouts/index.ts
@@ -15,7 +15,7 @@
  */
 
 import { HttpError, utils } from "@gnu-taler/web-util/browser";
-import { Loading } from "../Loading.js";
+import { Loading } from "@gnu-taler/web-util/browser";
 // import { compose, StateViewMap } from "../../utils/index.js";
 // import { wxApi } from "../../wxApi.js";
 import { AbsoluteTime, AmountJson, TalerCoreBankErrorsByMethod, 
TalerCorebankApi, TalerError } from "@gnu-taler/taler-util";
diff --git a/packages/demobank-ui/src/components/Cashouts/views.tsx 
b/packages/demobank-ui/src/components/Cashouts/views.tsx
index 89f173b0d..76a3a90df 100644
--- a/packages/demobank-ui/src/components/Cashouts/views.tsx
+++ b/packages/demobank-ui/src/components/Cashouts/views.tsx
@@ -18,10 +18,9 @@ import { Fragment, h, VNode } from "preact";
 import { useTranslationContext } from "@gnu-taler/web-util/browser";
 import { State } from "./index.js";
 import { format } from "date-fns";
-import { Amounts } from "@gnu-taler/taler-util";
+import { Amounts, assertUnreachable } from "@gnu-taler/taler-util";
 import { RenderAmount } from "../../pages/PaytoWireTransferForm.js";
-import { assertUnreachable } from "../Routing.js";
-import { Attention } from "../Attention.js";
+import { Attention } from "@gnu-taler/web-util/browser";
 
 export function LoadingUriView({ error }: State.LoadingUriError): VNode {
   const { i18n } = useTranslationContext();
diff --git a/packages/demobank-ui/src/components/EmptyComponentExample/index.ts 
b/packages/demobank-ui/src/components/EmptyComponentExample/index.ts
index 013904ff3..d80e6bdf9 100644
--- a/packages/demobank-ui/src/components/EmptyComponentExample/index.ts
+++ b/packages/demobank-ui/src/components/EmptyComponentExample/index.ts
@@ -14,10 +14,7 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { Loading } from "../../components/Loading.js";
-import { HookError, utils } from "@gnu-taler/web-util/browser";
-//import { compose, StateViewMap } from "../../utils/index.js";
-//import { wxApi } from "../../wxApi.js";
+import { HookError, Loading, utils } from "@gnu-taler/web-util/browser";
 import { useComponentState } from "./state.js";
 import { LoadingUriView, ReadyView } from "./views.js";
 
diff --git a/packages/demobank-ui/src/components/Routing.tsx 
b/packages/demobank-ui/src/components/Routing.tsx
index c94e74201..65a7b6e86 100644
--- a/packages/demobank-ui/src/components/Routing.tsx
+++ b/packages/demobank-ui/src/components/Routing.tsx
@@ -323,7 +323,3 @@ function Redirect({ to }: { to: string }): VNode {
   }, []);
   return <div>being redirected to {to}</div>;
 }
-
-export function assertUnreachable(x: never): never {
-  throw new Error("Didn't expect to get here");
-}
diff --git a/packages/demobank-ui/src/components/Transactions/index.ts 
b/packages/demobank-ui/src/components/Transactions/index.ts
index 3c4fb5ce9..b95aa4cb1 100644
--- a/packages/demobank-ui/src/components/Transactions/index.ts
+++ b/packages/demobank-ui/src/components/Transactions/index.ts
@@ -14,10 +14,7 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-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 { Loading, utils } from "@gnu-taler/web-util/browser";
 import { AbsoluteTime, AmountJson, TalerError } from "@gnu-taler/taler-util";
 import { useComponentState } from "./state.js";
 import { LoadingUriView, ReadyView } from "./views.js";
diff --git a/packages/demobank-ui/src/components/app.tsx 
b/packages/demobank-ui/src/components/app.tsx
index 55e1178fe..f79bd96b0 100644
--- a/packages/demobank-ui/src/components/app.tsx
+++ b/packages/demobank-ui/src/components/app.tsx
@@ -72,6 +72,7 @@ function getInitialBackendBaseURL(): string {
       ? localStorage.getItem("bank-base-url")
       : undefined;
   let result: string;
+
   if (!overrideUrl) {
     //normal path
     if (!bankUiSettings.backendBaseURL) {
diff --git a/packages/demobank-ui/src/context/config.ts 
b/packages/demobank-ui/src/context/config.ts
index a31d914b8..a55af719d 100644
--- a/packages/demobank-ui/src/context/config.ts
+++ b/packages/demobank-ui/src/context/config.ts
@@ -18,7 +18,7 @@ import { TalerCorebankApi, TalerCoreBankHttpClient, 
TalerError } from "@gnu-tale
 import { BrowserHttpLib, useTranslationContext } from 
"@gnu-taler/web-util/browser";
 import { ComponentChildren, createContext, FunctionComponent, h, VNode } from 
"preact";
 import { useContext, useEffect, useState } from "preact/hooks";
-import { ErrorLoading } from "../components/ErrorLoading.js";
+import { ErrorLoading } from "@gnu-taler/web-util/browser";
 
 /**
  *
diff --git a/packages/demobank-ui/src/pages/AccountPage/index.ts 
b/packages/demobank-ui/src/pages/AccountPage/index.ts
index 87ed878b0..3a3001ad4 100644
--- a/packages/demobank-ui/src/pages/AccountPage/index.ts
+++ b/packages/demobank-ui/src/pages/AccountPage/index.ts
@@ -16,8 +16,8 @@
 
 import { AbsoluteTime, AmountJson, TalerCorebankApi, TalerError } from 
"@gnu-taler/taler-util";
 import { utils } from "@gnu-taler/web-util/browser";
-import { ErrorLoading } from "../../components/ErrorLoading.js";
-import { Loading } from "../../components/Loading.js";
+import { ErrorLoading } from "@gnu-taler/web-util/browser";
+import { Loading } from "@gnu-taler/web-util/browser";
 import { LoginForm } from "../LoginForm.js";
 import { useComponentState } from "./state.js";
 import { InvalidIbanView, ReadyView } from "./views.js";
diff --git a/packages/demobank-ui/src/pages/AccountPage/views.tsx 
b/packages/demobank-ui/src/pages/AccountPage/views.tsx
index 8fff37624..0f5236192 100644
--- a/packages/demobank-ui/src/pages/AccountPage/views.tsx
+++ b/packages/demobank-ui/src/pages/AccountPage/views.tsx
@@ -16,7 +16,7 @@
 
 import { useTranslationContext } from "@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
-import { Attention } from "../../components/Attention.js";
+import { Attention } from "@gnu-taler/web-util/browser";
 import { Transactions } from "../../components/Transactions/index.js";
 import { useSettings } from "../../hooks/settings.js";
 import { PaymentOptions } from "../PaymentOptions.js";
diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx 
b/packages/demobank-ui/src/pages/BankFrame.tsx
index c0babd0c9..5561d7b42 100644
--- a/packages/demobank-ui/src/pages/BankFrame.tsx
+++ b/packages/demobank-ui/src/pages/BankFrame.tsx
@@ -14,15 +14,11 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { Amounts, TalerError, TranslatedString, parsePaytoUri, 
stringifyPaytoUri } from "@gnu-taler/taler-util";
-import { notifyError, notifyException, useNotifications, useTranslationContext 
} from "@gnu-taler/web-util/browser";
+import { Amounts, TalerError, TranslatedString } from "@gnu-taler/taler-util";
+import { Attention, LangSelector, Loading, notifyError, notifyException, 
useNotifications, useTranslationContext } from "@gnu-taler/web-util/browser";
 import { ComponentChildren, Fragment, VNode, h } from "preact";
 import { useEffect, useErrorBoundary, useState } from "preact/hooks";
 import logo from "../assets/logo-2021.svg";
-import { Attention } from "../components/Attention.js";
-import { CopyButton } from "../components/CopyButton.js";
-import { LangSelector } from "../components/LangSelector.js";
-import { Loading } from "../components/Loading.js";
 import { useAccountDetails } from "../hooks/access.js";
 import { useBackendState } from "../hooks/backend.js";
 import { getAllBooleanSettings, getLabelForSetting, useSettings } from 
"../hooks/settings.js";
@@ -179,7 +175,7 @@ export function BankFrame({
                               </li>
                               : undefined}
                             <li>
-                              <LangSelector />
+                              <LangSelector supportedLangs={["en", "es", 
"de"]} />
                             </li>
                             <li>
                               <div class="text-xs font-semibold leading-6 
text-gray-400">
diff --git a/packages/demobank-ui/src/pages/LoginForm.tsx 
b/packages/demobank-ui/src/pages/LoginForm.tsx
index f21e98343..707c1e688 100644
--- a/packages/demobank-ui/src/pages/LoginForm.tsx
+++ b/packages/demobank-ui/src/pages/LoginForm.tsx
@@ -18,15 +18,15 @@ import { TranslatedString } from "@gnu-taler/taler-util";
 import { Notification, useLocalNotification, useTranslationContext } from 
"@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
 import { useEffect, useRef, useState } from "preact/hooks";
-import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
+import { ShowInputErrorLabel } from "@gnu-taler/web-util/browser";
 import { useBankCoreApiContext } from "../context/config.js";
 import { useBackendState } from "../hooks/backend.js";
 import { bankUiSettings } from "../settings.js";
 import { undefinedIfEmpty, withRuntimeErrorHandling } from "../utils.js";
 import { assertUnreachable } from "./WithdrawalOperationPage.js";
 import { doAutoFocus } from "./PaytoWireTransferForm.js";
-import { Attention } from "../components/Attention.js";
-import { ShowLocalNotification } from "../components/ShowLocalNotification.js";
+import { Attention } from "@gnu-taler/web-util/browser";
+import { ShowLocalNotification } from "@gnu-taler/web-util/browser";
 
 
 /**
diff --git a/packages/demobank-ui/src/pages/OperationState/index.ts 
b/packages/demobank-ui/src/pages/OperationState/index.ts
index b17b0d787..120fd7b45 100644
--- a/packages/demobank-ui/src/pages/OperationState/index.ts
+++ b/packages/demobank-ui/src/pages/OperationState/index.ts
@@ -16,8 +16,8 @@
 
 import { AbsoluteTime, AmountJson, TalerCoreBankErrorsByMethod, TalerError, 
TalerErrorDetail, TranslatedString, WithdrawUriResult } from 
"@gnu-taler/taler-util";
 import { utils } from "@gnu-taler/web-util/browser";
-import { ErrorLoading } from "../../components/ErrorLoading.js";
-import { Loading } from "../../components/Loading.js";
+import { ErrorLoading } from "@gnu-taler/web-util/browser";
+import { Loading } from "@gnu-taler/web-util/browser";
 import { useComponentState } from "./state.js";
 import { AbortedView, ConfirmedView, FailedView, InvalidPaytoView, 
InvalidReserveView, InvalidWithdrawalView, NeedConfirmationView, ReadyView } 
from "./views.js";
 
diff --git a/packages/demobank-ui/src/pages/OperationState/views.tsx 
b/packages/demobank-ui/src/pages/OperationState/views.tsx
index e623b0dc2..2c4019de2 100644
--- a/packages/demobank-ui/src/pages/OperationState/views.tsx
+++ b/packages/demobank-ui/src/pages/OperationState/views.tsx
@@ -19,13 +19,13 @@ import { notifyInfo, useLocalNotification, 
useTranslationContext } from "@gnu-ta
 import { Fragment, VNode, h } from "preact";
 import { useEffect, useMemo, useState } from "preact/hooks";
 import { QR } from "../../components/QR.js";
-import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js";
+import { ShowInputErrorLabel } from "@gnu-taler/web-util/browser";
 import { useSettings } from "../../hooks/settings.js";
 import { undefinedIfEmpty } from "../../utils.js";
 import { State } from "./index.js";
-import { ShowLocalNotification } from 
"../../components/ShowLocalNotification.js";
-import { ErrorLoading } from "../../components/ErrorLoading.js";
-import { Attention } from "../../components/Attention.js";
+import { ShowLocalNotification } from "@gnu-taler/web-util/browser";
+import { ErrorLoading } from "@gnu-taler/web-util/browser";
+import { Attention } from "@gnu-taler/web-util/browser";
 import { assertUnreachable } from "../WithdrawalOperationPage.js";
 
 export function InvalidPaytoView({ payto, onClose }: State.InvalidPayto) {
diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx 
b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
index 31592039f..55eba423c 100644
--- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
+++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
@@ -32,7 +32,7 @@ import {
 import { Fragment, Ref, VNode, h } from "preact";
 import { useState } from "preact/hooks";
 import { mutate } from "swr";
-import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
+import { ShowInputErrorLabel } from "@gnu-taler/web-util/browser";
 import { useBankCoreApiContext } from "../context/config.js";
 import { useBackendState } from "../hooks/backend.js";
 import {
@@ -41,7 +41,7 @@ import {
   withRuntimeErrorHandling
 } from "../utils.js";
 import { assertUnreachable } from "./WithdrawalOperationPage.js";
-import { ShowLocalNotification } from "../components/ShowLocalNotification.js";
+import { ShowLocalNotification } from "@gnu-taler/web-util/browser";
 
 const logger = new Logger("PaytoWireTransferForm");
 
diff --git a/packages/demobank-ui/src/pages/PublicHistoriesPage.tsx 
b/packages/demobank-ui/src/pages/PublicHistoriesPage.tsx
index d33353180..d441d002e 100644
--- a/packages/demobank-ui/src/pages/PublicHistoriesPage.tsx
+++ b/packages/demobank-ui/src/pages/PublicHistoriesPage.tsx
@@ -18,7 +18,7 @@ import { Logger, TalerError } from "@gnu-taler/taler-util";
 import { useTranslationContext } from "@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
 import { useState } from "preact/hooks";
-import { Loading } from "../components/Loading.js";
+import { Loading } from "@gnu-taler/web-util/browser";
 import { Transactions } from "../components/Transactions/index.js";
 import { usePublicAccounts } from "../hooks/access.js";
 
diff --git a/packages/demobank-ui/src/pages/QrCodeSection.tsx 
b/packages/demobank-ui/src/pages/QrCodeSection.tsx
index 22bf604f2..e8c1a0e6e 100644
--- a/packages/demobank-ui/src/pages/QrCodeSection.tsx
+++ b/packages/demobank-ui/src/pages/QrCodeSection.tsx
@@ -29,7 +29,7 @@ import { QR } from "../components/QR.js";
 import { useBankCoreApiContext } from "../context/config.js";
 import { withRuntimeErrorHandling } from "../utils.js";
 import { assertUnreachable } from "./WithdrawalOperationPage.js";
-import { ShowLocalNotification } from "../components/ShowLocalNotification.js";
+import { ShowLocalNotification } from "@gnu-taler/web-util/browser";
 
 export function QrCodeSection({
   withdrawUri,
diff --git a/packages/demobank-ui/src/pages/RegistrationPage.tsx 
b/packages/demobank-ui/src/pages/RegistrationPage.tsx
index c2eca25e8..e8969afb9 100644
--- a/packages/demobank-ui/src/pages/RegistrationPage.tsx
+++ b/packages/demobank-ui/src/pages/RegistrationPage.tsx
@@ -20,13 +20,13 @@ import {
 } from "@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
 import { useState } from "preact/hooks";
-import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
+import { ShowInputErrorLabel } from "@gnu-taler/web-util/browser";
 import { useBankCoreApiContext } from "../context/config.js";
 import { useBackendState } from "../hooks/backend.js";
 import { bankUiSettings } from "../settings.js";
 import { undefinedIfEmpty, withRuntimeErrorHandling } from "../utils.js";
 import { getRandomPassword, getRandomUsername } from "./rnd.js";
-import { ShowLocalNotification } from "../components/ShowLocalNotification.js";
+import { ShowLocalNotification } from "@gnu-taler/web-util/browser";
 
 const logger = new Logger("RegistrationPage");
 
diff --git a/packages/demobank-ui/src/pages/ShowAccountDetails.tsx 
b/packages/demobank-ui/src/pages/ShowAccountDetails.tsx
index c07802273..43fd39205 100644
--- a/packages/demobank-ui/src/pages/ShowAccountDetails.tsx
+++ b/packages/demobank-ui/src/pages/ShowAccountDetails.tsx
@@ -2,8 +2,8 @@ import { TalerCorebankApi, TalerError, TranslatedString } from 
"@gnu-taler/taler
 import { notifyInfo, useLocalNotification, useTranslationContext } from 
"@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
 import { useState } from "preact/hooks";
-import { ErrorLoading } from "../components/ErrorLoading.js";
-import { Loading } from "../components/Loading.js";
+import { ErrorLoading } from "@gnu-taler/web-util/browser";
+import { Loading } from "@gnu-taler/web-util/browser";
 import { useBankCoreApiContext } from "../context/config.js";
 import { useAccountDetails } from "../hooks/access.js";
 import { useBackendState } from "../hooks/backend.js";
@@ -12,7 +12,7 @@ import { LoginForm } from "./LoginForm.js";
 import { ProfileNavigation } from "./ProfileNavigation.js";
 import { assertUnreachable } from "./WithdrawalOperationPage.js";
 import { AccountForm } from "./admin/AccountForm.js";
-import { ShowLocalNotification } from "../components/ShowLocalNotification.js";
+import { ShowLocalNotification } from "@gnu-taler/web-util/browser";
 
 export function ShowAccountDetails({
   account,
diff --git a/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx 
b/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx
index d30216f3f..759182997 100644
--- a/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx
+++ b/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx
@@ -1,14 +1,14 @@
 import { notifyInfo, useLocalNotification, useTranslationContext } from 
"@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
 import { useState } from "preact/hooks";
-import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
+import { ShowInputErrorLabel } from "@gnu-taler/web-util/browser";
 import { useBankCoreApiContext } from "../context/config.js";
 import { useBackendState } from "../hooks/backend.js";
 import { undefinedIfEmpty, withRuntimeErrorHandling } from "../utils.js";
 import { doAutoFocus } from "./PaytoWireTransferForm.js";
 import { ProfileNavigation } from "./ProfileNavigation.js";
 import { assertUnreachable } from "./WithdrawalOperationPage.js";
-import { ShowLocalNotification } from "../components/ShowLocalNotification.js";
+import { ShowLocalNotification } from "@gnu-taler/web-util/browser";
 
 export function UpdateAccountPassword({
   account: accountName,
@@ -63,7 +63,7 @@ export function UpdateAccountPassword({
           })
           case "old-password-invalid-or-not-allowed": return notify({
             type: "error",
-            title: current ? 
+            title: current ?
               i18n.str`This user have no right on to change the password.` :
               i18n.str`This user have no right on to change the password or 
the old password doesn't match.`
           })
@@ -79,7 +79,7 @@ export function UpdateAccountPassword({
 
   return (
     <Fragment>
-          <ShowLocalNotification notification={notification} />
+      <ShowLocalNotification notification={notification} />
       {accountIsTheCurrentUser ?
         <ProfileNavigation current="credentials" /> :
         <h1 class="text-base font-semibold leading-6 text-gray-900">
diff --git a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx 
b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
index e3a713fdd..9a45e6285 100644
--- a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
+++ b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
@@ -29,7 +29,7 @@ import {
 import { Fragment, VNode, h } from "preact";
 import { forwardRef } from "preact/compat";
 import { useState } from "preact/hooks";
-import { Attention } from "../components/Attention.js";
+import { Attention } from "@gnu-taler/web-util/browser";
 import { useBankCoreApiContext } from "../context/config.js";
 import { useBackendState } from "../hooks/backend.js";
 import { useSettings } from "../hooks/settings.js";
@@ -37,7 +37,7 @@ import { undefinedIfEmpty, withRuntimeErrorHandling } from 
"../utils.js";
 import { OperationState } from "./OperationState/index.js";
 import { InputAmount, doAutoFocus } from "./PaytoWireTransferForm.js";
 import { assertUnreachable } from "./WithdrawalOperationPage.js";
-import { ShowLocalNotification } from "../components/ShowLocalNotification.js";
+import { ShowLocalNotification } from "@gnu-taler/web-util/browser";
 
 const logger = new Logger("WalletWithdrawForm");
 const RefAmount = forwardRef(InputAmount);
diff --git a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx 
b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
index b548c0d16..f34e8a919 100644
--- a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
@@ -31,13 +31,13 @@ import {
 import { Fragment, VNode, h } from "preact";
 import { useMemo, useState } from "preact/hooks";
 import { mutate } from "swr";
-import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
+import { ShowInputErrorLabel } from "@gnu-taler/web-util/browser";
 import { useBankCoreApiContext } from "../context/config.js";
 import { useSettings } from "../hooks/settings.js";
 import { undefinedIfEmpty, withRuntimeErrorHandling } from "../utils.js";
 import { RenderAmount } from "./PaytoWireTransferForm.js";
 import { assertUnreachable } from "./WithdrawalOperationPage.js";
-import { ShowLocalNotification } from "../components/ShowLocalNotification.js";
+import { ShowLocalNotification } from "@gnu-taler/web-util/browser";
 
 const logger = new Logger("WithdrawalConfirmationQuestion");
 
diff --git a/packages/demobank-ui/src/pages/WithdrawalOperationPage.tsx 
b/packages/demobank-ui/src/pages/WithdrawalOperationPage.tsx
index 4620e5456..5ed57a0f7 100644
--- a/packages/demobank-ui/src/pages/WithdrawalOperationPage.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalOperationPage.tsx
@@ -23,7 +23,7 @@ import {
   useTranslationContext
 } from "@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
-import { Attention } from "../components/Attention.js";
+import { Attention } from "@gnu-taler/web-util/browser";
 import { useBankCoreApiContext } from "../context/config.js";
 import { useSettings } from "../hooks/settings.js";
 import { WithdrawalQRCode } from "./WithdrawalQRCode.js";
diff --git a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx 
b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
index bdd8ea585..52e3c63ee 100644
--- a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
@@ -23,9 +23,9 @@ import {
 } from "@gnu-taler/taler-util";
 import { notifyInfo, useTranslationContext } from 
"@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
-import { Attention } from "../components/Attention.js";
-import { ErrorLoading } from "../components/ErrorLoading.js";
-import { Loading } from "../components/Loading.js";
+import { Attention } from "@gnu-taler/web-util/browser";
+import { ErrorLoading } from "@gnu-taler/web-util/browser";
+import { Loading } from "@gnu-taler/web-util/browser";
 import { useWithdrawalDetails } from "../hooks/access.js";
 import { QrCodeSection } from "./QrCodeSection.js";
 import { WithdrawalConfirmationQuestion } from 
"./WithdrawalConfirmationQuestion.js";
diff --git a/packages/demobank-ui/src/pages/admin/Account.tsx 
b/packages/demobank-ui/src/pages/admin/Account.tsx
index 19189bec4..588d945ba 100644
--- a/packages/demobank-ui/src/pages/admin/Account.tsx
+++ b/packages/demobank-ui/src/pages/admin/Account.tsx
@@ -1,8 +1,8 @@
 import { Amounts, TalerError } from "@gnu-taler/taler-util";
 import { notifyInfo, useTranslationContext } from 
"@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
-import { ErrorLoading } from "../../components/ErrorLoading.js";
-import { Loading } from "../../components/Loading.js";
+import { ErrorLoading } from "@gnu-taler/web-util/browser";
+import { Loading } from "@gnu-taler/web-util/browser";
 import { useAccountDetails } from "../../hooks/access.js";
 import { assertUnreachable } from "../WithdrawalOperationPage.js";
 import { LoginForm } from "../LoginForm.js";
diff --git a/packages/demobank-ui/src/pages/admin/AccountForm.tsx 
b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
index fa3a28057..7311d826e 100644
--- a/packages/demobank-ui/src/pages/admin/AccountForm.tsx
+++ b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
@@ -1,11 +1,11 @@
 import { ComponentChildren, Fragment, VNode, h } from "preact";
-import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js";
+import { ShowInputErrorLabel } from "@gnu-taler/web-util/browser";
 import { PartialButDefined, RecursivePartial, WithIntermediate, 
undefinedIfEmpty, validateIBAN } from "../../utils.js";
 import { useEffect, useRef, useState } from "preact/hooks";
 import { useTranslationContext } from "@gnu-taler/web-util/browser";
 import { PaytoString, TalerCorebankApi, buildPayto, parsePaytoUri } from 
"@gnu-taler/taler-util";
 import { doAutoFocus } from "../PaytoWireTransferForm.js";
-import { CopyButton } from "../../components/CopyButton.js";
+import { CopyButton } from "@gnu-taler/web-util/browser";
 import { assertUnreachable } from "../WithdrawalOperationPage.js";
 
 const IBAN_REGEX = /^[A-Z][A-Z0-9]*$/;
diff --git a/packages/demobank-ui/src/pages/admin/AccountList.tsx 
b/packages/demobank-ui/src/pages/admin/AccountList.tsx
index be5194e6d..2aefde715 100644
--- a/packages/demobank-ui/src/pages/admin/AccountList.tsx
+++ b/packages/demobank-ui/src/pages/admin/AccountList.tsx
@@ -1,8 +1,8 @@
 import { Amounts, TalerError } from "@gnu-taler/taler-util";
 import { useTranslationContext } from "@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
-import { ErrorLoading } from "../../components/ErrorLoading.js";
-import { Loading } from "../../components/Loading.js";
+import { ErrorLoading } from "@gnu-taler/web-util/browser";
+import { Loading } from "@gnu-taler/web-util/browser";
 import { useBankCoreApiContext } from "../../context/config.js";
 import { useBusinessAccounts } from "../../hooks/circuit.js";
 import { RenderAmount } from "../PaytoWireTransferForm.js";
diff --git a/packages/demobank-ui/src/pages/admin/AdminHome.tsx 
b/packages/demobank-ui/src/pages/admin/AdminHome.tsx
index a30cae547..9bc2ee571 100644
--- a/packages/demobank-ui/src/pages/admin/AdminHome.tsx
+++ b/packages/demobank-ui/src/pages/admin/AdminHome.tsx
@@ -2,7 +2,7 @@ import { AmountString, Amounts, TalerCorebankApi, TalerError } 
from "@gnu-taler/
 import { useTranslationContext } from "@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
 import { useState } from "preact/hooks";
-import { ErrorLoading } from "../../components/ErrorLoading.js";
+import { ErrorLoading } from "@gnu-taler/web-util/browser";
 import { Transactions } from "../../components/Transactions/index.js";
 import { useLastMonitorInfo } from "../../hooks/circuit.js";
 import { RenderAmount } from "../PaytoWireTransferForm.js";
diff --git a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx 
b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
index 3f4364c16..0369a6283 100644
--- a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
+++ b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
@@ -3,14 +3,14 @@ import { notifyInfo, useLocalNotification, 
useTranslationContext } from "@gnu-ta
 import { Fragment, VNode, h } from "preact";
 import { useState } from "preact/hooks";
 import { mutate } from "swr";
-import { Attention } from "../../components/Attention.js";
+import { Attention } from "@gnu-taler/web-util/browser";
 import { useBankCoreApiContext } from "../../context/config.js";
 import { useBackendState } from "../../hooks/backend.js";
 import { withRuntimeErrorHandling } from "../../utils.js";
 import { assertUnreachable } from "../WithdrawalOperationPage.js";
 import { getRandomPassword } from "../rnd.js";
 import { AccountForm, AccountFormData } from "./AccountForm.js";
-import { ShowLocalNotification } from 
"../../components/ShowLocalNotification.js";
+import { ShowLocalNotification } from "@gnu-taler/web-util/browser";
 
 export function CreateNewAccount({
   onCancel,
diff --git a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx 
b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
index fa9693941..01136fdaf 100644
--- a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
+++ b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
@@ -2,10 +2,10 @@ import { Amounts, TalerError, TranslatedString } from 
"@gnu-taler/taler-util";
 import { notifyInfo, useLocalNotification, useTranslationContext } from 
"@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
 import { useState } from "preact/hooks";
-import { Attention } from "../../components/Attention.js";
-import { ErrorLoading } from "../../components/ErrorLoading.js";
-import { Loading } from "../../components/Loading.js";
-import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js";
+import { Attention } from "@gnu-taler/web-util/browser";
+import { ErrorLoading } from "@gnu-taler/web-util/browser";
+import { Loading } from "@gnu-taler/web-util/browser";
+import { ShowInputErrorLabel } from "@gnu-taler/web-util/browser";
 import { useBankCoreApiContext } from "../../context/config.js";
 import { useAccountDetails } from "../../hooks/access.js";
 import { useBackendState } from "../../hooks/backend.js";
@@ -13,7 +13,7 @@ import { undefinedIfEmpty } from "../../utils.js";
 import { LoginForm } from "../LoginForm.js";
 import { doAutoFocus } from "../PaytoWireTransferForm.js";
 import { assertUnreachable } from "../WithdrawalOperationPage.js";
-import { ShowLocalNotification } from 
"../../components/ShowLocalNotification.js";
+import { ShowLocalNotification } from "@gnu-taler/web-util/browser";
 
 export function RemoveAccount({
   account,
diff --git a/packages/demobank-ui/src/pages/business/CreateCashout.tsx 
b/packages/demobank-ui/src/pages/business/CreateCashout.tsx
index 5c284be24..735d84847 100644
--- a/packages/demobank-ui/src/pages/business/CreateCashout.tsx
+++ b/packages/demobank-ui/src/pages/business/CreateCashout.tsx
@@ -25,10 +25,10 @@ import {
 import { Fragment, VNode, h } from "preact";
 import { useEffect, useState } from "preact/hooks";
 import { mutate } from "swr";
-import { Attention } from "../../components/Attention.js";
-import { ErrorLoading } from "../../components/ErrorLoading.js";
-import { Loading } from "../../components/Loading.js";
-import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js";
+import { Attention } from "@gnu-taler/web-util/browser";
+import { ErrorLoading } from "@gnu-taler/web-util/browser";
+import { Loading } from "@gnu-taler/web-util/browser";
+import { ShowInputErrorLabel } from "@gnu-taler/web-util/browser";
 import { useBankCoreApiContext } from "../../context/config.js";
 import { useAccountDetails } from "../../hooks/access.js";
 import { useBackendState } from "../../hooks/backend.js";
@@ -43,7 +43,7 @@ import {
 import { LoginForm } from "../LoginForm.js";
 import { InputAmount } from "../PaytoWireTransferForm.js";
 import { assertUnreachable } from "../WithdrawalOperationPage.js";
-import { ShowLocalNotification } from 
"../../components/ShowLocalNotification.js";
+import { ShowLocalNotification } from "@gnu-taler/web-util/browser";
 
 interface Props {
   account: string;
diff --git a/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx 
b/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
index a8b57b90c..80e585cf5 100644
--- a/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
+++ b/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
@@ -25,10 +25,10 @@ import { format } from "date-fns";
 import { Fragment, VNode, h } from "preact";
 import { useState } from "preact/hooks";
 import { mutate } from "swr";
-import { Attention } from "../../components/Attention.js";
-import { ErrorLoading } from "../../components/ErrorLoading.js";
-import { Loading } from "../../components/Loading.js";
-import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js";
+import { Attention } from "@gnu-taler/web-util/browser";
+import { ErrorLoading } from "@gnu-taler/web-util/browser";
+import { Loading } from "@gnu-taler/web-util/browser";
+import { ShowInputErrorLabel } from "@gnu-taler/web-util/browser";
 import { useBankCoreApiContext } from "../../context/config.js";
 import { useBackendState } from "../../hooks/backend.js";
 import {
@@ -39,7 +39,7 @@ import {
   withRuntimeErrorHandling
 } from "../../utils.js";
 import { assertUnreachable } from "../WithdrawalOperationPage.js";
-import { ShowLocalNotification } from 
"../../components/ShowLocalNotification.js";
+import { ShowLocalNotification } from "@gnu-taler/web-util/browser";
 
 interface Props {
   id: string;
diff --git a/packages/demobank-ui/tailwind.config.js 
b/packages/demobank-ui/tailwind.config.js
index 01f058b2e..ec51dfbb8 100644
--- a/packages/demobank-ui/tailwind.config.js
+++ b/packages/demobank-ui/tailwind.config.js
@@ -1,6 +1,12 @@
 /** @type {import('tailwindcss').Config} */
 export default {
-  content: ["./src/**/*.{html,tsx}"],
+  content: {
+    relative: true,
+    files: [
+      "./src/**/*.{html,tsx}",
+      "./node_modules/@gnu-taler/web-util/src/**/*.{html,tsx}"
+    ],
+  },
   theme: {
     extend: {},
   },
diff --git a/packages/taler-util/src/errors.ts 
b/packages/taler-util/src/errors.ts
index cb61a5994..cbf4263fc 100644
--- a/packages/taler-util/src/errors.ts
+++ b/packages/taler-util/src/errors.ts
@@ -280,3 +280,7 @@ export function getErrorDetailFromException(e: any): 
TalerErrorDetail {
   );
   return err;
 }
+
+export function assertUnreachable(x: never): never {
+  throw new Error("Didn't expect to get here");
+}
diff --git a/packages/taler-util/src/http-client/exchange.ts 
b/packages/taler-util/src/http-client/exchange.ts
index 52f5dc5a6..2d3e40863 100644
--- a/packages/taler-util/src/http-client/exchange.ts
+++ b/packages/taler-util/src/http-client/exchange.ts
@@ -1,8 +1,12 @@
 import { HttpRequestLibrary } from "../http-common.js";
 import { HttpStatusCode } from "../http-status-codes.js";
 import { createPlatformHttpLib } from "../http.js";
-import { FailCasesByMethod, ResultByMethod, opSuccess, opUnknownFailure } from 
"../operation.js";
-import { codecForExchangeConfig } from "./types.js";
+import { LibtoolVersion } from "../libtool-version.js";
+import { hash } from "../nacl-fast.js";
+import { FailCasesByMethod, ResultByMethod, opEmptySuccess, opFixedSuccess, 
opKnownFailure, opSuccess, opUnknownFailure } from "../operation.js";
+import { TalerSignaturePurpose, amountToBuffer, bufferForUint32, buildSigPS, 
decodeCrock, eddsaSign, encodeCrock, stringToBytes, timestampRoundedToBuffer } 
from "../taler-crypto.js";
+import { OfficerAccount, PaginationParams, SigningKey, TalerExchangeApi, 
codecForAmlDecisionDetails, codecForAmlRecords, codecForExchangeConfig } from 
"./types.js";
+import { addPaginationParams } from "./utils.js";
 
 export type TalerExchangeResultByMethod<prop extends keyof 
TalerExchangeHttpClient> = ResultByMethod<TalerExchangeHttpClient, prop>
 export type TalerExchangeErrorsByMethod<prop extends keyof 
TalerExchangeHttpClient> = FailCasesByMethod<TalerExchangeHttpClient, prop>
@@ -11,6 +15,7 @@ export type TalerExchangeErrorsByMethod<prop extends keyof 
TalerExchangeHttpClie
  */
 export class TalerExchangeHttpClient {
   httpLib: HttpRequestLibrary;
+  public readonly PROTOCOL_VERSION = "17:0:0";
 
   constructor(
     readonly baseUrl: string,
@@ -19,6 +24,10 @@ export class TalerExchangeHttpClient {
     this.httpLib = httpClient ?? createPlatformHttpLib();
   }
 
+  isCompatible(version: string): boolean {
+    const compare = LibtoolVersion.compare(this.PROTOCOL_VERSION, version)
+    return compare?.compatible ?? false
+  }
   /**
    * https://docs.taler.net/core/api-merchant.html#get--config
    * 
@@ -34,4 +43,116 @@ export class TalerExchangeHttpClient {
     }
   }
 
+  //
+  // AML operations
+  //
+
+  /**
+   * 
https://docs.taler.net/core/api-exchange.html#get--aml-$OFFICER_PUB-decisions-$STATE
+   * 
+   */
+  async getDecisionsByState(auth: OfficerAccount, state: 
TalerExchangeApi.AmlState, pagination?: PaginationParams) {
+    const url = new 
URL(`aml/${auth.id}/decisions/${TalerExchangeApi.AmlState[state]}`, 
this.baseUrl);
+    addPaginationParams(url, pagination)
+
+    const resp = await this.httpLib.fetch(url.href, {
+      method: "GET",
+      headers: {
+        "Taler-AML-Officer-Signature": buildQuerySignature(auth.signingKey)
+      }
+    });
+
+    switch (resp.status) {
+      case HttpStatusCode.Ok: return opSuccess(resp, codecForAmlRecords())
+      case HttpStatusCode.NoContent: return opFixedSuccess({ records: [] })
+      //this should be unauthorized
+      case HttpStatusCode.Forbidden: return opKnownFailure("unauthorized", 
resp);
+      case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", 
resp);
+      case HttpStatusCode.NotFound: return opKnownFailure("officer-not-found", 
resp);
+      case HttpStatusCode.Conflict: return opKnownFailure("officer-disabled", 
resp);
+      default: return opUnknownFailure(resp, await resp.text())
+    }
+  }
+
+  /**
+   * 
https://docs.taler.net/core/api-exchange.html#get--aml-$OFFICER_PUB-decision-$H_PAYTO
+   * 
+   */
+  async getDecisionDetails(auth: OfficerAccount, account: string) {
+    const url = new URL(`aml/${auth.id}/decision/${account}`, this.baseUrl);
+
+    const resp = await this.httpLib.fetch(url.href, {
+      method: "GET",
+      headers: {
+        "Taler-AML-Officer-Signature": buildQuerySignature(auth.signingKey)
+      }
+    });
+
+    switch (resp.status) {
+      case HttpStatusCode.Ok: return opSuccess(resp, 
codecForAmlDecisionDetails())
+      case HttpStatusCode.NoContent: return opFixedSuccess({ aml_history: [], 
kyc_attributes: [] })
+      //this should be unauthorized
+      case HttpStatusCode.Forbidden: return opKnownFailure("unauthorized", 
resp);
+      case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", 
resp);
+      case HttpStatusCode.NotFound: return opKnownFailure("officer-not-found", 
resp);
+      case HttpStatusCode.Conflict: return opKnownFailure("officer-disabled", 
resp);
+      default: return opUnknownFailure(resp, await resp.text())
+    }
+  }
+
+  /**
+   * 
https://docs.taler.net/core/api-exchange.html#post--aml-$OFFICER_PUB-decision
+   * 
+   */
+  async addDecisionDetails(auth: OfficerAccount, body: 
TalerExchangeApi.AmlDecision) {
+    const url = new URL(`aml/${auth.id}/decision`, this.baseUrl);
+
+    const resp = await this.httpLib.fetch(url.href, {
+      method: "POST",
+      body,
+      headers: {
+        "Taler-AML-Officer-Signature": buildDecisionSignature(auth.signingKey, 
body)
+      },
+    });
+
+    switch (resp.status) {
+      case HttpStatusCode.NoContent: return opEmptySuccess()
+      //FIXME: this should be unauthorized
+      case HttpStatusCode.Forbidden: return opKnownFailure("unauthorized", 
resp);
+      case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", 
resp);
+      //FIXME: this two need to be splitted by error code
+      case HttpStatusCode.NotFound: return 
opKnownFailure("officer-or-account-not-found", resp);
+      case HttpStatusCode.Conflict: return 
opKnownFailure("officer-disabled-or-recent-decision", resp);
+      default: return opUnknownFailure(resp, await resp.text())
+    }
+  }
+
+
+}
+
+function buildQuerySignature(key: SigningKey): string {
+  const sigBlob = buildSigPS(
+    TalerSignaturePurpose.TALER_SIGNATURE_AML_QUERY,
+  ).build();
+
+  return encodeCrock(eddsaSign(sigBlob, key));
+}
+
+function buildDecisionSignature(
+  key: SigningKey,
+  decision: TalerExchangeApi.AmlDecision,
+): string {
+  const zero = new Uint8Array(new ArrayBuffer(64))
+
+  const sigBlob = 
buildSigPS(TalerSignaturePurpose.TALER_SIGNATURE_AML_DECISION)
+    //TODO: new need the null terminator, also in the exchange
+    .put(hash(stringToBytes(decision.justification)))//check null
+    .put(timestampRoundedToBuffer(decision.decision_time))
+    .put(amountToBuffer(decision.new_threshold))
+    .put(decodeCrock(decision.h_payto))
+    .put(zero) //kyc_requirement
+    .put(bufferForUint32(decision.new_state))
+    .build();
+
+  return encodeCrock(eddsaSign(sigBlob, key));
 }
\ No newline at end of file
diff --git a/packages/taler-util/src/http-client/merchant.ts 
b/packages/taler-util/src/http-client/merchant.ts
index 5aace2d78..a6dc4661f 100644
--- a/packages/taler-util/src/http-client/merchant.ts
+++ b/packages/taler-util/src/http-client/merchant.ts
@@ -1,6 +1,7 @@
 import { HttpRequestLibrary } from "../http-common.js";
 import { HttpStatusCode } from "../http-status-codes.js";
 import { createPlatformHttpLib } from "../http.js";
+import { LibtoolVersion } from "../libtool-version.js";
 import { FailCasesByMethod, ResultByMethod, opSuccess, opUnknownFailure } from 
"../operation.js";
 import { codecForMerchantConfig } from "./types.js";
 
@@ -11,6 +12,7 @@ export type TalerMerchantErrorsByMethod<prop extends keyof 
TalerMerchantHttpClie
  */
 export class TalerMerchantHttpClient {
   httpLib: HttpRequestLibrary;
+  public readonly PROTOCOL_VERSION = "5:0:1";
 
   constructor(
     readonly baseUrl: string,
@@ -19,6 +21,10 @@ export class TalerMerchantHttpClient {
     this.httpLib = httpClient ?? createPlatformHttpLib();
   }
 
+  isCompatible(version: string): boolean {
+    const compare = LibtoolVersion.compare(this.PROTOCOL_VERSION, version)
+    return compare?.compatible ?? false
+  }
   /**
    * https://docs.taler.net/core/api-merchant.html#get--config
    * 
diff --git a/packages/taler-util/src/http-client/officer-account.ts 
b/packages/taler-util/src/http-client/officer-account.ts
new file mode 100644
index 000000000..4b2529e20
--- /dev/null
+++ b/packages/taler-util/src/http-client/officer-account.ts
@@ -0,0 +1,81 @@
+import {
+  LockedAccount,
+  OfficerAccount,
+  OfficerId,
+  SigningKey,
+  createEddsaKeyPair,
+  decodeCrock,
+  decryptWithDerivedKey,
+  eddsaGetPublic,
+  encodeCrock,
+  encryptWithDerivedKey,
+  getRandomBytesF,
+  stringToBytes
+} from "@gnu-taler/taler-util";
+
+/**
+ * Restore previous session and unlock account with password
+ *
+ * @param salt string from which crypto params will be derived
+ * @param key secured private key
+ * @param password password for the private key
+ * @returns
+ */
+export async function unlockOfficerAccount(
+  account: LockedAccount,
+  password: string,
+): Promise<OfficerAccount> {
+  const rawKey = decodeCrock(account);
+  const rawPassword = stringToBytes(password);
+
+  const signingKey = (await decryptWithDerivedKey(
+    rawKey,
+    rawPassword,
+    password,
+  ).catch((e: Error) => {
+    throw new UnwrapKeyError(e.message);
+  })) as SigningKey;
+
+  const publicKey = eddsaGetPublic(signingKey);
+
+  const accountId = encodeCrock(publicKey) as OfficerId;
+
+  return { id: accountId, signingKey };
+}
+
+/**
+ * Create new account (secured private key)
+ * secured with the given password
+ *
+ * @param sessionId
+ * @param password
+ * @returns
+ */
+export async function createNewOfficerAccount(
+  password: string,
+): Promise<OfficerAccount & { safe: LockedAccount }> {
+  const { eddsaPriv, eddsaPub } = createEddsaKeyPair();
+
+  const key = stringToBytes(password);
+
+  const protectedPrivKey = await encryptWithDerivedKey(
+    getRandomBytesF(24),
+    key,
+    eddsaPriv,
+    password,
+  );
+
+  const signingKey = eddsaPriv as SigningKey;
+  const accountId = encodeCrock(eddsaPub) as OfficerId;
+  const safe = encodeCrock(protectedPrivKey) as LockedAccount;
+
+  return { id: accountId, signingKey, safe };
+}
+
+export class UnwrapKeyError extends Error {
+  public cause: string;
+  constructor(cause: string) {
+    super(`Recovering private key failed on: ${cause}`);
+    this.cause = cause;
+  }
+}
diff --git a/packages/taler-util/src/http-client/types.ts 
b/packages/taler-util/src/http-client/types.ts
index fe69925f6..77004cf5b 100644
--- a/packages/taler-util/src/http-client/types.ts
+++ b/packages/taler-util/src/http-client/types.ts
@@ -1,10 +1,9 @@
 import { codecForAmountString } from "../amounts.js";
-import { Codec, buildCodecForObject, buildCodecForUnion, codecForBoolean, 
codecForConstString, codecForEither, codecForList, codecForMap, codecForNumber, 
codecForString, codecOptional } from "../codec.js";
-import { PaytoString, PaytoUri, codecForPaytoString } from "../payto.js";
+import { Codec, buildCodecForObject, buildCodecForUnion, codecForAny, 
codecForBoolean, codecForConstString, codecForEither, codecForList, 
codecForMap, codecForNumber, codecForString, codecOptional } from "../codec.js";
+import { PaytoString, codecForPaytoString } from "../payto.js";
 import { AmountString } from "../taler-types.js";
-import { TalerActionString, WithdrawUriResult, codecForTalerActionString } 
from "../taleruri.js";
+import { TalerActionString, codecForTalerActionString } from "../taleruri.js";
 import { codecForTimestamp } from "../time.js";
-import { TalerErrorDetail } from "../wallet-types.js";
 
 
 export type UserAndPassword = {
@@ -17,6 +16,22 @@ export type UserAndToken = {
   token: AccessToken,
 }
 
+declare const opaque_OfficerAccount: unique symbol;
+export type LockedAccount = string & { [opaque_OfficerAccount]: true };
+
+declare const opaque_OfficerId: unique symbol;
+export type OfficerId = string & { [opaque_OfficerId]: true };
+
+declare const opaque_OfficerSigningKey: unique symbol;
+export type SigningKey = Uint8Array & { [opaque_OfficerSigningKey]: true };
+
+
+export interface OfficerAccount {
+  id: OfficerId;
+  signingKey: SigningKey;
+}
+
+
 export type PaginationParams = {
   /**
    * row identifier as the starting point of the query
@@ -44,6 +59,10 @@ export type PaginationParams = {
 // 64-byte hash code.
 type HashCode = string;
 
+type PaytoHash = string;
+
+type AmlOfficerPublicKeyP = string;
+
 // 32-byte hash code.
 type ShortHashCode = string;
 
@@ -150,13 +169,18 @@ export interface LoginToken {
   token: AccessToken,
   expiration: Timestamp,
 }
-// token used to get loginToken
-// must forget after used
+
 declare const __ac_token: unique symbol;
 export type AccessToken = string & {
   [__ac_token]: true;
 };
 
+
+declare const __officer_signature: unique symbol;
+export type OfficerSignature = string & {
+  [__officer_signature]: true;
+};
+
 export namespace TalerAuthentication {
 
   export interface TokenRequest {
@@ -564,6 +588,66 @@ export const codecForAddIncomingResponse =
       .property("timestamp", codecForTimestamp)
       .build("TalerWireGatewayApi.AddIncomingResponse");
 
+export const codecForAmlRecords =
+  (): Codec<TalerExchangeApi.AmlRecords> =>
+    buildCodecForObject<TalerExchangeApi.AmlRecords>()
+      .property("records", codecForList(codecForAmlRecord()))
+      .build("TalerExchangeApi.PublicAccountsResponse");
+
+export const codecForAmlRecord =
+  (): Codec<TalerExchangeApi.AmlRecord> =>
+    buildCodecForObject<TalerExchangeApi.AmlRecord>()
+      .property("current_state", codecForNumber())
+      .property("h_payto", codecForString())
+      .property("rowid", codecForNumber())
+      .property("threshold", codecForAmountString())
+      .build("TalerExchangeApi.AmlRecord");
+
+export const codecForAmlDecisionDetails =
+  (): Codec<TalerExchangeApi.AmlDecisionDetails> =>
+    buildCodecForObject<TalerExchangeApi.AmlDecisionDetails>()
+      .property("aml_history", codecForList(codecForAmlDecisionDetail()))
+      .property("kyc_attributes", codecForList(codecForKycDetail()))
+      .build("TalerExchangeApi.AmlDecisionDetails");
+
+export const codecForAmlDecisionDetail =
+  (): Codec<TalerExchangeApi.AmlDecisionDetail> =>
+    buildCodecForObject<TalerExchangeApi.AmlDecisionDetail>()
+      .property("justification", codecForString())
+      .property("new_state", codecForNumber())
+      .property("decision_time", codecForTimestamp)
+      .property("new_threshold", codecForAmountString())
+      .property("decider_pub", codecForString())
+      .build("TalerExchangeApi.AmlDecisionDetail");
+
+interface KycDetail {
+  provider_section: string;
+  attributes?: Object;
+  collection_time: Timestamp;
+  expiration_time: Timestamp;
+}
+export const codecForKycDetail =
+  (): Codec<TalerExchangeApi.KycDetail> =>
+    buildCodecForObject<TalerExchangeApi.KycDetail>()
+      .property("provider_section", codecForString())
+      .property("attributes", codecOptional(codecForAny()))
+      .property("collection_time", codecForTimestamp)
+      .property("expiration_time", codecForTimestamp)
+      .build("TalerExchangeApi.KycDetail");
+
+export const codecForAmlDecision =
+  (): Codec<TalerExchangeApi.AmlDecision> =>
+    buildCodecForObject<TalerExchangeApi.AmlDecision>()
+      .property("justification", codecForString())
+      .property("new_threshold", codecForAmountString())
+      .property("h_payto", codecForString())
+      .property("new_state", codecForNumber())
+      .property("officer_sig", codecForString())
+      .property("decision_time", codecForTimestamp)
+      .property("kyc_requirements", 
codecOptional(codecForList(codecForString())))
+      .build("TalerExchangeApi.AmlDecision");
+
+
 // export const codecFor =
 //   (): Codec<TalerWireGatewayApi.PublicAccountsResponse> =>
 //     buildCodecForObject<TalerWireGatewayApi.PublicAccountsResponse>()
@@ -1341,6 +1425,112 @@ export namespace TalerCorebankApi {
 
 export namespace TalerExchangeApi {
 
+  export enum AmlState {
+    normal = 0,
+    pending = 1,
+    frozen = 2,
+  }
+
+  export interface AmlRecords {
+
+    // Array of AML records matching the query.
+    records: AmlRecord[];
+  }
+  export interface AmlRecord {
+
+    // Which payto-address is this record about.
+    // Identifies a GNU Taler wallet or an affected bank account.
+    h_payto: PaytoHash;
+
+    // What is the current AML state.
+    current_state: AmlState;
+
+    // Monthly transaction threshold before a review will be triggered
+    threshold: AmountString;
+
+    // RowID of the record.
+    rowid: Integer;
+
+  }
+
+  export interface AmlDecisionDetails {
+
+    // Array of AML decisions made for this account. Possibly
+    // contains only the most recent decision if "history" was
+    // not set to 'true'.
+    aml_history: AmlDecisionDetail[];
+
+    // Array of KYC attributes obtained for this account.
+    kyc_attributes: KycDetail[];
+  }
+  export interface AmlDecisionDetail {
+
+    // What was the justification given?
+    justification: string;
+
+    // What is the new AML state.
+    new_state: Integer;
+
+    // When was this decision made?
+    decision_time: Timestamp;
+
+    // What is the new AML decision threshold (in monthly transaction volume)?
+    new_threshold: AmountString;
+
+    // Who made the decision?
+    decider_pub: AmlOfficerPublicKeyP;
+
+  }
+  export interface KycDetail {
+
+    // Name of the configuration section that specifies the provider
+    // which was used to collect the KYC details
+    provider_section: string;
+
+    // The collected KYC data.  NULL if the attribute data could not
+    // be decrypted (internal error of the exchange, likely the
+    // attribute key was changed).
+    attributes?: Object;
+
+    // Time when the KYC data was collected
+    collection_time: Timestamp;
+
+    // Time when the validity of the KYC data will expire
+    expiration_time: Timestamp;
+
+  }
+
+
+  export interface AmlDecision {
+
+    // Human-readable justification for the decision.
+    justification: string;
+
+    // At what monthly transaction volume should the
+    // decision be automatically reviewed?
+    new_threshold: AmountString;
+
+    // Which payto-address is the decision about?
+    // Identifies a GNU Taler wallet or an affected bank account.
+    h_payto: PaytoHash;
+
+    // What is the new AML state (e.g. frozen, unfrozen, etc.)
+    // Numerical values are defined in AmlDecisionState.
+    new_state: Integer;
+
+    // Signature by the AML officer over a
+    // TALER_MasterAmlOfficerStatusPS.
+    // Must have purpose TALER_SIGNATURE_MASTER_AML_KEY.
+    officer_sig: EddsaSignature;
+
+    // When was the decision made?
+    decision_time: Timestamp;
+
+    // Optional argument to impose new KYC requirements
+    // that the customer has to satisfy to unblock transactions.
+    kyc_requirements?: string[];
+  }
+
 
   export interface ExchangeVersionResponse {
     // libtool-style representation of the Exchange protocol version, see
@@ -1362,19 +1552,19 @@ export namespace TalerExchangeApi {
 
   }
 
-  type AccountRestriction =
+  export type AccountRestriction =
     | RegexAccountRestriction
     | DenyAllAccountRestriction
   // Account restriction that disables this type of
   // account for the indicated operation categorically.
-  interface DenyAllAccountRestriction {
+  export interface DenyAllAccountRestriction {
 
     type: "deny";
   }
   // Accounts interacting with this type of account
   // restriction must have a payto://-URI matching
   // the given regex.
-  interface RegexAccountRestriction {
+  export interface RegexAccountRestriction {
 
     type: "regex";
 
diff --git a/packages/taler-util/src/index.ts b/packages/taler-util/src/index.ts
index ea5a805a0..053a25ab7 100644
--- a/packages/taler-util/src/index.ts
+++ b/packages/taler-util/src/index.ts
@@ -43,6 +43,9 @@ export * from "./libeufin-api-types.js";
 export * from "./MerchantApiClient.js";
 export * from "./bank-api-client.js";
 export * from "./http-client/bank-core.js";
+export * from "./http-client/exchange.js";
+export * from "./http-client/merchant.js";
+export * from "./http-client/officer-account.js";
 export * from "./http-client/bank-integration.js";
 export * from "./http-client/bank-revenue.js";
 export * from "./http-client/bank-wire.js";
diff --git a/packages/web-util/build.mjs b/packages/web-util/build.mjs
index 0b015f22c..c15a2715b 100755
--- a/packages/web-util/build.mjs
+++ b/packages/web-util/build.mjs
@@ -59,6 +59,7 @@ const buildConfigBase = {
     ".key": "text",
     ".crt": "text",
     ".html": "text",
+    ".svg": "dataurl",
   },
   sourcemap: true,
   define: {
diff --git a/packages/demobank-ui/src/assets/lang.svg 
b/packages/web-util/src/assets/lang.svg
similarity index 100%
rename from packages/demobank-ui/src/assets/lang.svg
rename to packages/web-util/src/assets/lang.svg
diff --git a/packages/demobank-ui/src/components/Attention.tsx 
b/packages/web-util/src/components/Attention.tsx
similarity index 93%
rename from packages/demobank-ui/src/components/Attention.tsx
rename to packages/web-util/src/components/Attention.tsx
index 57d0a4199..b85230a1b 100644
--- a/packages/demobank-ui/src/components/Attention.tsx
+++ b/packages/web-util/src/components/Attention.tsx
@@ -1,12 +1,11 @@
-import { TranslatedString } from "@gnu-taler/taler-util";
+import { TranslatedString, assertUnreachable } from "@gnu-taler/taler-util";
 import { ComponentChildren, Fragment, VNode, h } from "preact";
-import { assertUnreachable } from "./Routing.js";
 
-interface Props { 
-  type?: "info" | "success" | "warning" | "danger", 
-  onClose?: () => void, 
-  title: TranslatedString, 
-  children?: ComponentChildren ,
+interface Props {
+  type?: "info" | "success" | "warning" | "danger",
+  onClose?: () => void,
+  title: TranslatedString,
+  children?: ComponentChildren,
 }
 export function Attention({ type = "info", title, children, onClose }: Props): 
VNode {
   return <div class={`group attention-${type} mt-2 shadow-lg`}>
diff --git a/packages/demobank-ui/src/components/CopyButton.tsx 
b/packages/web-util/src/components/CopyButton.tsx
similarity index 99%
rename from packages/demobank-ui/src/components/CopyButton.tsx
rename to packages/web-util/src/components/CopyButton.tsx
index ca1ceaa8a..0096da365 100644
--- a/packages/demobank-ui/src/components/CopyButton.tsx
+++ b/packages/web-util/src/components/CopyButton.tsx
@@ -1,8 +1,6 @@
 import { h, VNode } from "preact";
 import { useEffect, useState } from "preact/hooks";
 
-
-
 export function CopyIcon(): VNode {
   return (
     <svg xmlns="http://www.w3.org/2000/svg"; fill="none" viewBox="0 0 24 24" 
stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
diff --git a/packages/demobank-ui/src/components/ErrorLoading.tsx 
b/packages/web-util/src/components/ErrorLoading.tsx
similarity index 96%
rename from packages/demobank-ui/src/components/ErrorLoading.tsx
rename to packages/web-util/src/components/ErrorLoading.tsx
index 84e72c5a1..02f2a3282 100644
--- a/packages/demobank-ui/src/components/ErrorLoading.tsx
+++ b/packages/web-util/src/components/ErrorLoading.tsx
@@ -15,11 +15,10 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { TalerError, TalerErrorCode } from "@gnu-taler/taler-util";
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
+import { TalerError, TalerErrorCode, assertUnreachable } from 
"@gnu-taler/taler-util";
 import { Fragment, VNode, h } from "preact";
 import { Attention } from "./Attention.js";
-import { assertUnreachable } from "./Routing.js";
+import { useTranslationContext } from "../index.browser.js";
 
 export function ErrorLoading({ error, showDetail }: { error: TalerError, 
showDetail?: boolean }): VNode {
   const { i18n } = useTranslationContext()
diff --git a/packages/demobank-ui/src/components/LangSelector.tsx 
b/packages/web-util/src/components/LangSelector.tsx
similarity index 92%
rename from packages/demobank-ui/src/components/LangSelector.tsx
rename to packages/web-util/src/components/LangSelector.tsx
index 7cf0300df..a8d910129 100644
--- a/packages/demobank-ui/src/components/LangSelector.tsx
+++ b/packages/web-util/src/components/LangSelector.tsx
@@ -21,12 +21,12 @@
 
 import { Fragment, h, VNode } from "preact";
 import { useEffect, useState } from "preact/hooks";
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
-import { strings as messages } from "../i18n/strings.js";
+// import { strings as messages } from "../i18n/strings.js";
 import langIcon from "../assets/lang.svg";
+import { useTranslationContext } from "../index.browser.js";
 
 type LangsNames = {
-  [P in keyof typeof messages]: string;
+  [P: string]: string;
 };
 
 const names: LangsNames = {
@@ -43,7 +43,7 @@ function getLangName(s: keyof LangsNames | string): string {
   return String(s);
 }
 
-export function LangSelector(): VNode {
+export function LangSelector({ supportedLangs }: { supportedLangs: string[] 
}): VNode {
   const [updatingLang, setUpdatingLang] = useState(false);
   const { lang, changeLanguage } = useTranslationContext();
   const [hidden, setHidden] = useState(true);
@@ -70,7 +70,7 @@ export function LangSelector(): VNode {
             setHidden((h) => !h);
           }}>
           <span class="flex items-center">
-            <img src={langIcon} alt="" class="h-5 w-5 flex-shrink-0 
rounded-full" />
+            <img alt="language" class="h-5 w-5 flex-shrink-0 rounded-full" 
src={langIcon} />
             <span class="ml-3 block truncate">{getLangName(lang)}</span>
           </span>
           <span class="pointer-events-none absolute inset-y-0 right-0 flex 
items-center pr-2">
@@ -82,7 +82,7 @@ export function LangSelector(): VNode {
 
         {!hidden &&
           <ul class="absolute z-10 mt-1 max-h-60 w-full overflow-auto 
rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 
focus:outline-none sm:text-sm" tabIndex={-1} role="listbox" 
aria-labelledby="listbox-label" aria-activedescendant="listbox-option-3">
-            {Object.keys(messages)
+            {supportedLangs
               .filter((l) => l !== lang)
               .map((lang) => (
                 <li class="text-gray-900 hover:bg-indigo-600 hover:text-white 
cursor-pointer relative select-none py-2 pl-3 pr-9" role="option"
diff --git a/packages/demobank-ui/src/components/Loading.tsx 
b/packages/web-util/src/components/Loading.tsx
similarity index 100%
rename from packages/demobank-ui/src/components/Loading.tsx
rename to packages/web-util/src/components/Loading.tsx
diff --git a/packages/demobank-ui/src/components/ShowInputErrorLabel.tsx 
b/packages/web-util/src/components/ShowInputErrorLabel.tsx
similarity index 100%
rename from packages/demobank-ui/src/components/ShowInputErrorLabel.tsx
rename to packages/web-util/src/components/ShowInputErrorLabel.tsx
diff --git a/packages/demobank-ui/src/components/ShowLocalNotification.tsx 
b/packages/web-util/src/components/ShowLocalNotification.tsx
similarity index 70%
rename from packages/demobank-ui/src/components/ShowLocalNotification.tsx
rename to packages/web-util/src/components/ShowLocalNotification.tsx
index bb62a48f0..cb947e536 100644
--- a/packages/demobank-ui/src/components/ShowLocalNotification.tsx
+++ b/packages/web-util/src/components/ShowLocalNotification.tsx
@@ -1,7 +1,7 @@
-import { Notification } from "@gnu-taler/web-util/browser";
 import { h, Fragment, VNode } from "preact";
 import { Attention } from "./Attention.js";
-import { useSettings } from "../hooks/settings.js";
+import { Notification } from "../index.browser.js";
+// import { useSettings } from "../hooks/settings.js";
 
 export function ShowLocalNotification({ notification }: { notification?: 
Notification }): VNode {
   if (!notification) return <Fragment />
@@ -17,7 +17,7 @@ export function ShowLocalNotification({ notification }: { 
notification?: Notific
                 {notification.message.description}
               </div>
             }
-            <MaybeShowDebugInfo info={notification.message.debug} />
+            {/* <MaybeShowDebugInfo info={notification.message.debug} /> */}
           </Attention>
         </div>
       </div>
@@ -31,13 +31,13 @@ export function ShowLocalNotification({ notification }: { 
notification?: Notific
 }
 
 
-function MaybeShowDebugInfo({ info }: { info: any }): VNode {
-  const [settings] = useSettings()
-  if (settings.showDebugInfo) {
-    return <pre class="whitespace-break-spaces ">
-      {info}
-    </pre>
-  }
-  return <Fragment />
-}
+// function MaybeShowDebugInfo({ info }: { info: any }): VNode {
+//   const [settings] = useSettings()
+//   if (settings.showDebugInfo) {
+//     return <pre class="whitespace-break-spaces ">
+//       {info}
+//     </pre>
+//   }
+//   return <Fragment />
+// }
 
diff --git a/packages/web-util/src/components/index.ts 
b/packages/web-util/src/components/index.ts
index 9441e971d..8344d4a7a 100644
--- a/packages/web-util/src/components/index.ts
+++ b/packages/web-util/src/components/index.ts
@@ -1 +1,8 @@
 export * as utils from "./utils.js";
+export * from "./Attention.js";
+export * from "./CopyButton.js";
+export * from "./ErrorLoading.js";
+export * from "./LangSelector.js";
+export * from "./Loading.js";
+export * from "./ShowInputErrorLabel.js";
+export * from "./ShowLocalNotification.js";
diff --git a/packages/demobank-ui/src/declaration.d.ts 
b/packages/web-util/src/declaration.d.ts
similarity index 100%
copy from packages/demobank-ui/src/declaration.d.ts
copy to packages/web-util/src/declaration.d.ts

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