gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated: sync aml/kyc api, wip


From: gnunet
Subject: [taler-wallet-core] branch master updated: sync aml/kyc api, wip
Date: Tue, 13 Aug 2024 20:28:17 +0200

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

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

The following commit(s) were added to refs/heads/master by this push:
     new aa4fc5647 sync aml/kyc api, wip
aa4fc5647 is described below

commit aa4fc564777aab82e16dd4c02012682ff67a3e8a
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Tue Aug 13 15:27:41 2024 -0300

    sync aml/kyc api, wip
---
 .../src/hooks/{useCaseDetails.ts => account.ts}    |   6 +-
 .../src/hooks/{useCases.ts => decisions.ts}        |  62 +-
 .../aml-backoffice-ui/src/pages/CaseDetails.tsx    |  95 +--
 .../aml-backoffice-ui/src/pages/CaseUpdate.tsx     |  24 +-
 .../aml-backoffice-ui/src/pages/Cases.stories.tsx  |  22 +-
 packages/aml-backoffice-ui/src/pages/Cases.tsx     | 153 ++---
 .../src/pages/ShowConsolidated.stories.tsx         |  68 +-
 .../src/pages/ShowConsolidated.tsx                 |   2 +-
 packages/taler-util/src/http-client/exchange.ts    | 447 +++++++++++--
 .../taler-util/src/http-client/officer-account.ts  |  68 ++
 packages/taler-util/src/types-taler-common.ts      |  28 +
 packages/taler-util/src/types-taler-exchange.ts    | 743 ++++++++++++++++++---
 12 files changed, 1371 insertions(+), 347 deletions(-)

diff --git a/packages/aml-backoffice-ui/src/hooks/useCaseDetails.ts 
b/packages/aml-backoffice-ui/src/hooks/account.ts
similarity index 91%
rename from packages/aml-backoffice-ui/src/hooks/useCaseDetails.ts
rename to packages/aml-backoffice-ui/src/hooks/account.ts
index 78574ada4..1c5f013a7 100644
--- a/packages/aml-backoffice-ui/src/hooks/useCaseDetails.ts
+++ b/packages/aml-backoffice-ui/src/hooks/account.ts
@@ -20,17 +20,17 @@ import { useOfficer } from "./officer.js";
 import { useExchangeApiContext } from "@gnu-taler/web-util/browser";
 const useSWR = _useSWR as unknown as SWRHook;
 
-export function useCaseDetails(paytoHash: string) {
+export function useAccountInformation(paytoHash: string) {
   const officer = useOfficer();
   const session = officer.state === "ready" ? officer.account : undefined;
 
   const { lib: {exchange: api} } = useExchangeApiContext();
 
   async function fetcher([officer, account]: [OfficerAccount, PaytoString]) {
-    return await api.getDecisionDetails(officer, account)
+    return await api.getAmlAttributesForAccount(officer, account)
   }
 
-  const { data, error } = 
useSWR<TalerExchangeResultByMethod<"getDecisionDetails">, TalerHttpError>(
+  const { data, error } = 
useSWR<TalerExchangeResultByMethod<"getAmlAttributesForAccount">, 
TalerHttpError>(
     !session ? undefined : [session, paytoHash], fetcher, {
     refreshInterval: 0,
     refreshWhenHidden: false,
diff --git a/packages/aml-backoffice-ui/src/hooks/useCases.ts 
b/packages/aml-backoffice-ui/src/hooks/decisions.ts
similarity index 68%
rename from packages/aml-backoffice-ui/src/hooks/useCases.ts
rename to packages/aml-backoffice-ui/src/hooks/decisions.ts
index d3a1c1018..e652f233e 100644
--- a/packages/aml-backoffice-ui/src/hooks/useCases.ts
+++ b/packages/aml-backoffice-ui/src/hooks/decisions.ts
@@ -19,13 +19,12 @@ import { useState } from "preact/hooks";
 import {
   OfficerAccount,
   OperationOk,
-  TalerExchangeApi,
   TalerExchangeResultByMethod,
-  TalerHttpError,
+  TalerHttpError
 } from "@gnu-taler/taler-util";
+import { useExchangeApiContext } from "@gnu-taler/web-util/browser";
 import _useSWR, { SWRHook } from "swr";
 import { useOfficer } from "./officer.js";
-import { useExchangeApiContext } from "@gnu-taler/web-util/browser";
 const useSWR = _useSWR as unknown as SWRHook;
 
 export const PAGINATED_LIST_SIZE = 10;
@@ -34,12 +33,54 @@ export const PAGINATED_LIST_SIZE = 10;
 export const PAGINATED_LIST_REQUEST = PAGINATED_LIST_SIZE + 1;
 
 /**
- * FIXME: mutate result when balance change (transaction )
  * @param account
  * @param args
  * @returns
  */
-export function useCases(state: TalerExchangeApi.AmlState) {
+export function useCurrentDecisions() {
+  const officer = useOfficer();
+  const session = officer.state === "ready" ? officer.account : undefined;
+  const {
+    lib: { exchange: api },
+  } = useExchangeApiContext();
+
+  const [offset, setOffset] = useState<string>();
+
+  async function fetcher([officer, offset]: [
+    OfficerAccount,
+    string | undefined,
+  ]) {
+    return await api.getAmlDecisions(officer, {
+      order: "asc",
+      offset,
+      active: true,
+      limit: PAGINATED_LIST_REQUEST,
+    });
+  }
+
+  const { data, error } = useSWR<
+    TalerExchangeResultByMethod<"getAmlDecisions">,
+    TalerHttpError
+  >(
+    !session ? undefined : [session, offset, "getAmlDecisions"],
+    fetcher,
+  );
+
+  if (error) return error;
+  if (data === undefined) return undefined;
+  if (data.type !== "ok") return data;
+
+  return buildPaginatedResult(data.body.records, offset, setOffset, (d) =>
+    String(d.rowid),
+  );
+}
+
+/**
+ * @param account
+ * @param args
+ * @returns
+ */
+export function useAccountDecisions(accountStr: string) {
   const officer = useOfficer();
   const session = officer.state === "ready" ? officer.account : undefined;
   const {
@@ -48,23 +89,24 @@ export function useCases(state: TalerExchangeApi.AmlState) {
 
   const [offset, setOffset] = useState<string>();
 
-  async function fetcher([officer, state, offset]: [
+  async function fetcher([officer, account, offset]: [
     OfficerAccount,
-    TalerExchangeApi.AmlState,
+    string,
     string | undefined,
   ]) {
-    return await api.getDecisionsByState(officer, state, {
+    return await api.getAmlDecisions(officer, {
       order: "asc",
       offset,
+      account,
       limit: PAGINATED_LIST_REQUEST,
     });
   }
 
   const { data, error } = useSWR<
-    TalerExchangeResultByMethod<"getDecisionsByState">,
+    TalerExchangeResultByMethod<"getAmlDecisions">,
     TalerHttpError
   >(
-    !session ? undefined : [session, state, offset, "getDecisionsByState"],
+    !session ? undefined : [session, accountStr, offset, "getAmlDecisions"],
     fetcher,
   );
 
diff --git a/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx 
b/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx
index bb936cebf..2fd95d2c6 100644
--- a/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx
+++ b/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx
@@ -45,7 +45,7 @@ import { useState } from "preact/hooks";
 import { privatePages } from "../Routing.js";
 import { useUiFormsContext } from "../context/ui-forms.js";
 import { preloadedForms } from "../forms/index.js";
-import { useCaseDetails } from "../hooks/useCaseDetails.js";
+import { useAccountInformation } from "../hooks/account.js";
 import { ShowConsolidated } from "./ShowConsolidated.js";
 
 export type AmlEvent =
@@ -77,7 +77,7 @@ type KycCollectionEvent = {
   when: AbsoluteTime;
   title: TranslatedString;
   values: object;
-  provider: string;
+  provider?: string;
 };
 type KycExpirationEvent = {
   type: "kyc-expiration";
@@ -115,45 +115,54 @@ function titleForJustification(
 }
 
 export function getEventsFromAmlHistory(
-  aml: TalerExchangeApi.AmlDecisionDetail[],
-  kyc: TalerExchangeApi.KycDetail[],
+  events: TalerExchangeApi.KycAttributeCollectionEvent[],
   i18n: InternationalizationAPI,
   forms: FormMetadata[],
 ): AmlEvent[] {
-  const ae: AmlEvent[] = aml.map((a) => {
-    const just = parseJustification(a.justification, forms);
+  // const ae: AmlEvent[] = aml.map((a) => {
+  //   const just = parseJustification(a.justification, forms);
+  //   return {
+  //     type: just.type === "ok" ? "aml-form" : "aml-form-error",
+  //     state: a.new_state,
+  //     threshold: Amounts.parseOrThrow(a.new_threshold),
+  //     title: titleForJustification(just, i18n),
+  //     metadata: just.type === "ok" ? just.body.metadata : undefined,
+  //     justification: just.type === "ok" ? just.body.justification : 
undefined,
+  //     when: {
+  //       t_ms:
+  //         a.decision_time.t_s === "never"
+  //           ? "never"
+  //           : a.decision_time.t_s * 1000,
+  //     },
+  //   } as AmlEvent;
+  // });
+  // const ke = kyc.reduce((prev, k) => {
+  //   prev.push({
+  //     type: "kyc-collection",
+  //     title: i18n.str`collection`,
+  //     when: AbsoluteTime.fromProtocolTimestamp(k.collection_time),
+  //     values: !k.attributes ? {} : k.attributes,
+  //     provider: k.provider_section,
+  //   });
+  //   prev.push({
+  //     type: "kyc-expiration",
+  //     title: i18n.str`expiration`,
+  //     when: AbsoluteTime.fromProtocolTimestamp(k.expiration_time),
+  //     fields: !k.attributes ? [] : Object.keys(k.attributes),
+  //   });
+  //   return prev;
+  // }, [] as AmlEvent[]);
+
+  const ke = events.map((event) => {
     return {
-      type: just.type === "ok" ? "aml-form" : "aml-form-error",
-      state: a.new_state,
-      threshold: Amounts.parseOrThrow(a.new_threshold),
-      title: titleForJustification(just, i18n),
-      metadata: just.type === "ok" ? just.body.metadata : undefined,
-      justification: just.type === "ok" ? just.body.justification : undefined,
-      when: {
-        t_ms:
-          a.decision_time.t_s === "never"
-            ? "never"
-            : a.decision_time.t_s * 1000,
-      },
-    } as AmlEvent;
-  });
-  const ke = kyc.reduce((prev, k) => {
-    prev.push({
       type: "kyc-collection",
       title: i18n.str`collection`,
-      when: AbsoluteTime.fromProtocolTimestamp(k.collection_time),
-      values: !k.attributes ? {} : k.attributes,
-      provider: k.provider_section,
-    });
-    prev.push({
-      type: "kyc-expiration",
-      title: i18n.str`expiration`,
-      when: AbsoluteTime.fromProtocolTimestamp(k.expiration_time),
-      fields: !k.attributes ? [] : Object.keys(k.attributes),
-    });
-    return prev;
-  }, [] as AmlEvent[]);
-  return ae.concat(ke).sort(selectSooner);
+      when: AbsoluteTime.fromProtocolTimestamp(event.collection_time),
+      values: !event.attributes ? {} : event.attributes,
+      provider: event.provider_name,
+    } as AmlEvent
+  });
+  return ke.sort(selectSooner);
 }
 
 export function CaseDetails({ account }: { account: string }) {
@@ -164,7 +173,7 @@ export function CaseDetails({ account }: { account: string 
}) {
   }>();
 
   const { i18n } = useTranslationContext();
-  const details = useCaseDetails(account);
+  const details = useAccountInformation(account);
   const { forms } = useUiFormsContext();
 
   const allForms = [...forms, ...preloadedForms(i18n)];
@@ -176,7 +185,7 @@ export function CaseDetails({ account }: { account: string 
}) {
   }
   if (details.type === "fail") {
     switch (details.case) {
-      case HttpStatusCode.Unauthorized:
+      // case HttpStatusCode.Unauthorized:
       case HttpStatusCode.Forbidden:
       case HttpStatusCode.NotFound:
       case HttpStatusCode.Conflict:
@@ -185,11 +194,11 @@ export function CaseDetails({ account }: { account: 
string }) {
         assertUnreachable(details);
     }
   }
-  const { aml_history, kyc_attributes } = details.body;
+  const { details: accountDetails } = details.body;
+
 
   const events = getEventsFromAmlHistory(
-    aml_history,
-    kyc_attributes,
+    accountDetails,
     i18n,
     allForms,
   );
@@ -424,9 +433,9 @@ function parseJustification(
   listOfAllKnownForms: FormMetadata[],
 ):
   | OperationOk<{
-      justification: Justification;
-      metadata: FormMetadata;
-    }>
+    justification: Justification;
+    metadata: FormMetadata;
+  }>
   | OperationFail<ParseJustificationFail> {
   try {
     const justification = JSON.parse(s);
diff --git a/packages/aml-backoffice-ui/src/pages/CaseUpdate.tsx 
b/packages/aml-backoffice-ui/src/pages/CaseUpdate.tsx
index 7801625d0..d1257c8fa 100644
--- a/packages/aml-backoffice-ui/src/pages/CaseUpdate.tsx
+++ b/packages/aml-backoffice-ui/src/pages/CaseUpdate.tsx
@@ -157,20 +157,25 @@ export function CaseUpdate({
               value: validatedForm,
             };
 
-            const decision: Omit<TalerExchangeApi.AmlDecision, "officer_sig"> =
+            const decision: Omit<TalerExchangeApi.AmlDecisionRequest, 
"officer_sig"> =
               {
                 justification: JSON.stringify(justification),
                 decision_time: TalerProtocolTimestamp.now(),
                 h_payto: account,
-                new_state: justification.value
-                  .state as TalerExchangeApi.AmlState,
-                new_threshold: Amounts.stringify(
-                  justification.value.threshold as AmountJson,
-                ),
-                kyc_requirements: undefined,
+                keep_investigating: false,
+                new_rules: {
+                  custom_measures: {},
+                  expiration_time: {
+                    t_s: "never"
+                  },
+                  rules: [],
+                  successor_measure: undefined
+                },
+                properties: {},
+                new_measure: undefined,
               };
 
-            return api.addDecisionDetails(officer.account, decision);
+            return api.makeAmlDesicion(officer.account, decision);
           },
           () => {
             window.location.href = privatePages.cases.url({});
@@ -178,10 +183,9 @@ export function CaseUpdate({
           (fail) => {
             switch (fail.case) {
               case HttpStatusCode.Forbidden:
-              case HttpStatusCode.Unauthorized:
                 return i18n.str`Wrong credentials for "${officer.account}"`;
               case HttpStatusCode.NotFound:
-                return i18n.str`Officer or account not found`;
+                return i18n.str`The account was not found`;
               case HttpStatusCode.Conflict:
                 return i18n.str`Officer disabled or more recent decision was 
already submitted.`;
               default:
diff --git a/packages/aml-backoffice-ui/src/pages/Cases.stories.tsx 
b/packages/aml-backoffice-ui/src/pages/Cases.stories.tsx
index 22a6d1867..372fb912f 100644
--- a/packages/aml-backoffice-ui/src/pages/Cases.stories.tsx
+++ b/packages/aml-backoffice-ui/src/pages/Cases.stories.tsx
@@ -21,21 +21,33 @@
 
 import * as tests from "@gnu-taler/web-util/testing";
 import { CasesUI as TestedComponent } from "./Cases.js";
-import { AmountString, TalerExchangeApi } from "@gnu-taler/taler-util";
 
 export default {
   title: "cases",
 };
 
 export const OneRow = tests.createExample(TestedComponent, {
-  filter: TalerExchangeApi.AmlState.normal,
-  onChangeFilter: () => null,
   records: [
     {
-      current_state: TalerExchangeApi.AmlState.normal,
+      // current_state: TalerExchangeApi.AmlState.normal,
       h_payto: "QWEQWEQWEQWE",
       rowid: 1,
-      threshold: "USD:1" as AmountString,
+      decision_time: {
+        t_s: "never"
+      },
+      is_active: false,
+      limits: {
+        custom_measures: {},
+        expiration_time: {
+          t_s: "never"
+        },
+        rules: [],
+        successor_measure: undefined,
+      },
+      to_investigate: false,
+      justification: undefined,
+      properties: undefined,
+      // threshold: "USD:1" as AmountString,
     },
   ],
 });
diff --git a/packages/aml-backoffice-ui/src/pages/Cases.tsx 
b/packages/aml-backoffice-ui/src/pages/Cases.tsx
index f66eca33f..613e57493 100644
--- a/packages/aml-backoffice-ui/src/pages/Cases.tsx
+++ b/packages/aml-backoffice-ui/src/pages/Cases.tsx
@@ -29,8 +29,7 @@ import {
   useTranslationContext,
 } from "@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
-import { useEffect, useState } from "preact/hooks";
-import { useCases } from "../hooks/useCases.js";
+import { useCurrentDecisions } from "../hooks/decisions.js";
 
 import { privatePages } from "../Routing.js";
 import { FormErrors, RecursivePartial, useFormState } from "../hooks/form.js";
@@ -38,58 +37,58 @@ import { undefinedIfEmpty } from "./CreateAccount.js";
 import { Officer } from "./Officer.js";
 
 type FormType = {
-  state: TalerExchangeApi.AmlState;
+  // state: TalerExchangeApi.AmlState;
 };
 
 export function CasesUI({
   records,
-  filter,
-  onChangeFilter,
+  // filter,
+  // onChangeFilter,
   onFirstPage,
   onNext,
 }: {
   onFirstPage?: () => void;
   onNext?: () => void;
-  filter: TalerExchangeApi.AmlState;
-  onChangeFilter: (f: TalerExchangeApi.AmlState) => void;
-  records: TalerExchangeApi.AmlRecord[];
+  // filter: TalerExchangeApi.AmlState;
+  // onChangeFilter: (f: TalerExchangeApi.AmlState) => void;
+  records: TalerExchangeApi.AmlDecision[];
 }): VNode {
   const { i18n } = useTranslationContext();
 
-  const [form, status] = useFormState<FormType>(
-    [".state"] as Array<UIHandlerId>,
-    {
-      state: filter,
-    },
-    (state) => {
-      const errors = undefinedIfEmpty<FormErrors<FormType>>({
-        state: state.state === undefined ? i18n.str`required` : undefined,
-      });
-      if (errors === undefined) {
-        const result: FormType = {
-          state: state.state!,
-        };
-        return {
-          status: "ok",
-          result,
-          errors,
-        };
-      }
-      const result: RecursivePartial<FormType> = {
-        state: state.state,
-      };
-      return {
-        status: "fail",
-        result,
-        errors,
-      };
-    },
-  );
-  useEffect(() => {
-    if (status.status === "ok" && filter !== status.result.state) {
-      onChangeFilter(status.result.state);
-    }
-  }, [form?.state?.value]);
+  // const [form, status] = useFormState<FormType>(
+  //   [".state"] as Array<UIHandlerId>,
+  //   {
+  //     // state: filter,
+  //   },
+  //   (state) => {
+  //     const errors = undefinedIfEmpty<FormErrors<FormType>>({
+  //       state: state.state === undefined ? i18n.str`required` : undefined,
+  //     });
+  //     if (errors === undefined) {
+  //       const result: FormType = {
+  //         state: state.state!,
+  //       };
+  //       return {
+  //         status: "ok",
+  //         result,
+  //         errors,
+  //       };
+  //     }
+  //     const result: RecursivePartial<FormType> = {
+  //       state: state.state,
+  //     };
+  //     return {
+  //       status: "fail",
+  //       result,
+  //       errors,
+  //     };
+  //   },
+  // );
+  // useEffect(() => {
+  //   if (status.status === "ok" && filter !== status.result.state) {
+  //     onChangeFilter(status.result.state);
+  //   }
+  // }, [form?.state?.value]);
 
   return (
     <div>
@@ -105,7 +104,7 @@ export function CasesUI({
           </p>
         </div>
         <div class="px-2">
-          <InputChoiceHorizontal<FormType, "state">
+          {/* <InputChoiceHorizontal<FormType, "state">
             name="state"
             label={i18n.str`Filter`}
             handler={form.state}
@@ -124,7 +123,7 @@ export function CasesUI({
                 value: "normal",
               },
             ]}
-          />
+          /> */}
         </div>
       </div>
       <div class="mt-8 flow-root">
@@ -173,34 +172,10 @@ export function CasesUI({
                           </div>
                         </td>
                         <td class="whitespace-nowrap px-3 py-5 text-sm 
text-gray-500">
-                          {((state: TalerExchangeApi.AmlState): VNode => {
-                            switch (state) {
-                              case TalerExchangeApi.AmlState.normal: {
-                                return (
-                                  <span class="inline-flex items-center 
rounded-md bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 
ring-inset ring-green-600/20">
-                                    Normal
-                                  </span>
-                                );
-                              }
-                              case TalerExchangeApi.AmlState.pending: {
-                                return (
-                                  <span class="inline-flex items-center 
rounded-md bg-yellow-50 px-2 py-1 text-xs font-medium text-yellow-700 ring-1 
ring-inset ring-green-600/20">
-                                    Pending
-                                  </span>
-                                );
-                              }
-                              case TalerExchangeApi.AmlState.frozen: {
-                                return (
-                                  <span class="inline-flex items-center 
rounded-md bg-red-50 px-2 py-1 text-xs font-medium text-red-700 ring-1 
ring-inset ring-green-600/20">
-                                    Frozen
-                                  </span>
-                                );
-                              }
-                            }
-                          })(r.current_state)}
+                          {r.rowid}
                         </td>
                         <td class="whitespace-nowrap px-3 py-5 text-sm 
text-gray-900">
-                          {r.threshold}
+                          ???
                         </td>
                       </tr>
                     );
@@ -217,11 +192,11 @@ export function CasesUI({
 }
 
 export function Cases() {
-  const [stateFilter, setStateFilter] = useState(
-    TalerExchangeApi.AmlState.pending,
-  );
+  // const [stateFilter, setStateFilter] = useState(
+  //   TalerExchangeApi.AmlState.pending,
+  // );
 
-  const list = useCases(stateFilter);
+  const list = useCurrentDecisions();
   const { i18n } = useTranslationContext();
 
   if (!list) {
@@ -238,28 +213,38 @@ export function Cases() {
           <Fragment>
             <Attention type="danger" title={i18n.str`Operation denied`}>
               <i18n.Translate>
-                This account doesn't have access. Request account activation
-                sending your public key.
+                This account signature is wrong, contact administrator or 
create a new one.
+              </i18n.Translate>
+            </Attention>
+            <Officer />
+          </Fragment>
+        );
+      }
+      case HttpStatusCode.NotFound: {
+        return (
+          <Fragment>
+            <Attention type="danger" title={i18n.str`Operation denied`}>
+              <i18n.Translate>
+                This account is not known.
               </i18n.Translate>
             </Attention>
             <Officer />
           </Fragment>
         );
       }
-      case HttpStatusCode.Unauthorized: {
+      case HttpStatusCode.Conflict: {
         return (
           <Fragment>
             <Attention type="danger" title={i18n.str`Operation denied`}>
               <i18n.Translate>
-                This account is not allowed to perform list the cases.
+                This account doesn't have access. Request account activation
+                sending your public key.
               </i18n.Translate>
             </Attention>
             <Officer />
           </Fragment>
         );
       }
-      case HttpStatusCode.NotFound:
-      case HttpStatusCode.Conflict:
         return <Officer />;
       default:
         assertUnreachable(list);
@@ -271,10 +256,10 @@ export function Cases() {
       records={list.body}
       onFirstPage={list.isFirstPage ? undefined : list.loadFirst}
       onNext={list.isLastPage ? undefined : list.loadNext}
-      filter={stateFilter}
-      onChangeFilter={(d) => {
-        setStateFilter(d);
-      }}
+      // filter={stateFilter}
+      // onChangeFilter={(d) => {
+      //   setStateFilter(d);
+      // }}
     />
   );
 }
diff --git a/packages/aml-backoffice-ui/src/pages/ShowConsolidated.stories.tsx 
b/packages/aml-backoffice-ui/src/pages/ShowConsolidated.stories.tsx
index 714bf6580..2fc661bd4 100644
--- a/packages/aml-backoffice-ui/src/pages/ShowConsolidated.stories.tsx
+++ b/packages/aml-backoffice-ui/src/pages/ShowConsolidated.stories.tsx
@@ -42,74 +42,12 @@ const nullTranslator: InternationalizationAPI = {
 };
 
 export const WithEmptyHistory = tests.createExample(TestedComponent, {
-  history: getEventsFromAmlHistory([], [], nullTranslator, []),
+  history: getEventsFromAmlHistory([], nullTranslator, []),
   until: AbsoluteTime.now(),
 });
 
 export const WithSomeEvents = tests.createExample(TestedComponent, {
   history: getEventsFromAmlHistory(
-    [
-      {
-        decider_pub: "JD70N2XZ8FZKB7C146ZWR6XBDCS4Z84PJKJMPB73PMJ2B1X35ZFG",
-        justification:
-          '{"index":0,"name":"Simple 
comment","value":{"fullName":"loggedIn_user_fullname","when":{"t_ms":1700207199558},"state":1,"threshold":{"currency":"STATER","fraction":0,"value":0},"comment":"test"}}',
-        new_threshold: "STATER:0" as AmountString,
-        new_state: 1,
-        decision_time: {
-          t_s: 1700208199,
-        },
-      },
-      {
-        decider_pub: "JD70N2XZ8FZKB7C146ZWR6XBDCS4Z84PJKJMPB73PMJ2B1X35ZFG",
-        justification:
-          '{"index":0,"name":"Simple 
comment","value":{"fullName":"loggedIn_user_fullname","when":{"t_ms":1700207199558},"state":1,"threshold":{"currency":"STATER","fraction":0,"value":0},"comment":"test"}}',
-        new_threshold: "STATER:0" as AmountString,
-        new_state: 1,
-        decision_time: {
-          t_s: 1700208211,
-        },
-      },
-      {
-        decider_pub: "JD70N2XZ8FZKB7C146ZWR6XBDCS4Z84PJKJMPB73PMJ2B1X35ZFG",
-        justification:
-          '{"index":0,"name":"Simple 
comment","value":{"fullName":"loggedIn_user_fullname","when":{"t_ms":1700207199558},"state":1,"threshold":{"currency":"STATER","fraction":0,"value":0},"comment":"test"}}',
-        new_threshold: "STATER:0" as AmountString,
-        new_state: 1,
-        decision_time: {
-          t_s: 1700208220,
-        },
-      },
-      {
-        decider_pub: "JD70N2XZ8FZKB7C146ZWR6XBDCS4Z84PJKJMPB73PMJ2B1X35ZFG",
-        justification:
-          '{"index":4,"name":"Declaration for trusts 
(902.13e)","value":{"fullName":"loggedIn_user_fullname","when":{"t_ms":1700208362854},"state":1,"threshold":{"currency":"STATER","fraction":0,"value":0},"contractingPartner":"f","knownAs":"a","trust":{"name":"b","type":"discretionary","revocability":"irrevocable"}}}',
-        new_threshold: "STATER:0" as AmountString,
-        new_state: 1,
-        decision_time: {
-          t_s: 1700208385,
-        },
-      },
-      {
-        decider_pub: "6CD3J8XSKWQPFFDJY4SP4RK2D7T7WW7JRJDTXHNZY7YKGXDCE2QG",
-        justification:
-          '{"id":"simple_comment","label":"Simple 
comment","version":1,"value":{"when":{"t_ms":1700488420810},"state":1,"threshold":{"currency":"STATER","fraction":0,"value":0},"comment":"qwe"}}',
-        new_threshold: "STATER:0" as AmountString,
-        new_state: 1,
-        decision_time: {
-          t_s: 1700488423,
-        },
-      },
-      {
-        decider_pub: "6CD3J8XSKWQPFFDJY4SP4RK2D7T7WW7JRJDTXHNZY7YKGXDCE2QG",
-        justification:
-          '{"id":"simple_comment","label":"Simple 
comment","version":1,"value":{"when":{"t_ms":1700488671251},"state":1,"threshold":{"currency":"STATER","fraction":0,"value":0},"comment":"asd
 asd asd "}}',
-        new_threshold: "STATER:0" as AmountString,
-        new_state: 1,
-        decision_time: {
-          t_s: 1700488677,
-        },
-      },
-    ],
     [
       {
         collection_time: AbsoluteTime.toProtocolTimestamp(
@@ -118,11 +56,11 @@ export const WithSomeEvents = 
tests.createExample(TestedComponent, {
             Duration.fromPrettyString("1d"),
           ),
         ),
-        expiration_time: { t_s: "never" },
-        provider_section: "asd",
+        provider_name: "asd",
         attributes: {
           email: "sebasjm@qwdde.com",
         },
+        rowid: 1,
       },
     ],
     nullTranslator,
diff --git a/packages/aml-backoffice-ui/src/pages/ShowConsolidated.tsx 
b/packages/aml-backoffice-ui/src/pages/ShowConsolidated.tsx
index cdc5d0bc1..2fbbefe0c 100644
--- a/packages/aml-backoffice-ui/src/pages/ShowConsolidated.tsx
+++ b/packages/aml-backoffice-ui/src/pages/ShowConsolidated.tsx
@@ -125,7 +125,7 @@ interface Consolidated {
   kyc: {
     [field: string]: {
       value: unknown;
-      provider: string;
+      provider?: string;
       since: AbsoluteTime;
     };
   };
diff --git a/packages/taler-util/src/http-client/exchange.ts 
b/packages/taler-util/src/http-client/exchange.ts
index 2b81855d6..4a27c824f 100644
--- a/packages/taler-util/src/http-client/exchange.ts
+++ b/packages/taler-util/src/http-client/exchange.ts
@@ -14,9 +14,10 @@ import {
   ResultByMethod,
   opEmptySuccess,
   opFixedSuccess,
+  opKnownAlternativeFailure,
   opKnownHttpFailure,
   opSuccessFromHttp,
-  opUnknownFailure,
+  opUnknownFailure
 } from "../operation.js";
 import {
   TalerSignaturePurpose,
@@ -30,22 +31,35 @@ import {
   timestampRoundedToBuffer,
 } from "../taler-crypto.js";
 import {
+  AccessToken,
+  AmountString,
   OfficerAccount,
   PaginationParams,
+  ReserveAccount,
   SigningKey,
-  codecForTalerCommonConfigResponse,
+  codecForTalerCommonConfigResponse
 } from "../types-taler-common.js";
 import {
-  codecForAmlDecisionDetails,
-  codecForAmlRecords,
+  AmlDecisionRequest,
+  ExchangeVersionResponse,
+  KycRequirementInformationId,
+  WalletKycRequest,
+  codecForAccountKycStatus,
+  codecForAmlDecisionsResponse,
+  codecForAmlKycAttributes,
+  codecForAmlWalletKycCheckResponse,
+  codecForAvailableMeasureSummary,
+  codecForEventCounter,
   codecForExchangeConfig,
   codecForExchangeKeys,
+  codecForKycProcessClientInformation,
+  codecForLegitimizationNeededResponse
 } from "../types-taler-exchange.js";
-import { CacheEvictor, addPaginationParams, nullEvictor } from "./utils.js";
+import { CacheEvictor, addMerchantPaginationParams, nullEvictor } from 
"./utils.js";
 
 import { TalerError } from "../errors.js";
 import { TalerErrorCode } from "../taler-error-codes.js";
-import * as TalerExchangeApi from "../types-taler-exchange.js";
+import { AmountJson, Duration } from "../index.node.js";
 
 export type TalerExchangeResultByMethod<
   prop extends keyof TalerExchangeHttpClient,
@@ -62,7 +76,7 @@ export enum TalerExchangeCacheEviction {
  */
 export class TalerExchangeHttpClient {
   httpLib: HttpRequestLibrary;
-  public readonly PROTOCOL_VERSION = "18:0:1";
+  public readonly PROTOCOL_VERSION = "20:0:0";
   cacheEvictor: CacheEvictor<TalerExchangeCacheEviction>;
 
   constructor(
@@ -105,7 +119,7 @@ export class TalerExchangeHttpClient {
    */
   async getConfig(): Promise<
     | OperationFail<HttpStatusCode.NotFound>
-    | OperationOk<TalerExchangeApi.ExchangeVersionResponse>
+    | OperationOk<ExchangeVersionResponse>
   > {
     const url = new URL(`config`, this.baseUrl);
     const resp = await this.httpLib.fetch(url.href, {
@@ -170,6 +184,166 @@ export class TalerExchangeHttpClient {
 
   // TERMS
 
+  //
+  // KYC operations
+  //
+
+  /**
+   * https://docs.taler.net/core/api-exchange.html#post--kyc-wallet
+   *
+   */
+  async notifyKycBalanceLimit(account: ReserveAccount, balance: AmountString) {
+    const url = new URL(`kyc-wallet`, this.baseUrl);
+
+    const body: WalletKycRequest = {
+      balance,
+      reserve_pub: account.id,
+      reserve_sig: encodeCrock(account.signingKey),
+    }
+
+    const resp = await this.httpLib.fetch(url.href, {
+      method: "POST",
+      body,
+    });
+
+    switch (resp.status) {
+      case HttpStatusCode.Ok:
+        return opSuccessFromHttp(resp, codecForAmlWalletKycCheckResponse());
+      case HttpStatusCode.NoContent:
+        return opEmptySuccess(resp);
+      case HttpStatusCode.Forbidden:
+        return opKnownHttpFailure(resp.status, resp);
+      case HttpStatusCode.UnavailableForLegalReasons:
+        return opKnownAlternativeFailure(resp, resp.status, 
codecForLegitimizationNeededResponse());
+      default:
+        return opUnknownFailure(resp, await readTalerErrorResponse(resp));
+    }
+  }
+
+  /**
+   * https://docs.taler.net/core/api-exchange.html#post--kyc-wallet
+   *
+   */
+  async checkKycStatus(account: ReserveAccount, requirementId: number, params: 
{
+    timeout?: number,
+  } = {}) {
+    const url = new URL(`kyc-check/${String(requirementId)}`, this.baseUrl);
+
+    if (params.timeout !== undefined) {
+      url.searchParams.set("timeout_ms", String(params.timeout));
+    }
+
+    const resp = await this.httpLib.fetch(url.href, {
+      method: "GET",
+      headers: {
+        "Account-Owner-Signature": buildKYCQuerySignature(account.signingKey),
+      },
+    });
+
+    switch (resp.status) {
+      case HttpStatusCode.Ok:
+        return opSuccessFromHttp(resp, codecForAccountKycStatus());
+      case HttpStatusCode.Accepted:
+        return opKnownAlternativeFailure(resp, resp.status, 
codecForAccountKycStatus());
+      case HttpStatusCode.NoContent:
+        return opEmptySuccess(resp);
+      case HttpStatusCode.Forbidden:
+        return opKnownHttpFailure(resp.status, resp);
+      case HttpStatusCode.NotFound:
+        return opKnownHttpFailure(resp.status, resp);
+      case HttpStatusCode.Conflict:
+        return opKnownHttpFailure(resp.status, resp);
+      default:
+        return opUnknownFailure(resp, await readTalerErrorResponse(resp));
+    }
+  }
+
+  /**
+     * 
https://docs.taler.net/core/api-exchange.html#get--kyc-info-$ACCESS_TOKEN
+     *
+     */
+  async checkKycInfo(token: AccessToken, known: KycRequirementInformationId[], 
params: {
+    timeout?: number,
+  } = {}) {
+    const url = new URL(`kyc-info/${token}`, this.baseUrl);
+
+    if (params.timeout !== undefined) {
+      url.searchParams.set("timeout_ms", String(params.timeout));
+    }
+
+    const resp = await this.httpLib.fetch(url.href, {
+      method: "GET",
+      headers: {
+        "If-None-Match": known.length ? known.join(",") : undefined
+      }
+    });
+
+    switch (resp.status) {
+      case HttpStatusCode.Ok:
+        return opSuccessFromHttp(resp, codecForKycProcessClientInformation());
+      case HttpStatusCode.NoContent:
+        return opEmptySuccess(resp);
+      case HttpStatusCode.NotModified:
+        return opKnownHttpFailure(resp.status, resp);
+      default:
+        return opUnknownFailure(resp, await readTalerErrorResponse(resp));
+    }
+  }
+
+
+  /**
+   * https://docs.taler.net/core/api-exchange.html#post--kyc-upload-$ID
+   *
+   */
+  async uploadKycForm(requirement: KycRequirementInformationId, body: object) {
+    const url = new URL(`kyc-upload/${requirement}`, this.baseUrl);
+
+
+    const resp = await this.httpLib.fetch(url.href, {
+      method: "POST",
+      body,
+    });
+
+    switch (resp.status) {
+      case HttpStatusCode.NoContent:
+        return opEmptySuccess(resp);
+      case HttpStatusCode.NotFound:
+        return opKnownHttpFailure(resp.status, resp);
+      case HttpStatusCode.Conflict:
+        return opKnownHttpFailure(resp.status, resp);
+      case HttpStatusCode.PayloadTooLarge:
+        return opKnownHttpFailure(resp.status, resp);
+      default:
+        return opUnknownFailure(resp, await readTalerErrorResponse(resp));
+    }
+  }
+
+  /**
+   * https://docs.taler.net/core/api-exchange.html#post--kyc-start-$ID
+   *
+   */
+  async startKycProcess(requirement: KycRequirementInformationId) {
+    const url = new URL(`kyc-start/${requirement}`, this.baseUrl);
+
+
+    const resp = await this.httpLib.fetch(url.href, {
+      method: "POST",
+    });
+
+    switch (resp.status) {
+      case HttpStatusCode.NoContent:
+        return opEmptySuccess(resp);
+      case HttpStatusCode.NotFound:
+        return opKnownHttpFailure(resp.status, resp);
+      case HttpStatusCode.Conflict:
+        return opKnownHttpFailure(resp.status, resp);
+      case HttpStatusCode.PayloadTooLarge:
+        return opKnownHttpFailure(resp.status, resp);
+      default:
+        return opUnknownFailure(resp, await readTalerErrorResponse(resp));
+    }
+  }
+
   //
   // AML operations
   //
@@ -178,34 +352,206 @@ export class TalerExchangeHttpClient {
    * 
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);
+  // 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 opSuccessFromHttp(resp, codecForAmlRecords());
+  //     case HttpStatusCode.NoContent:
+  //       return opFixedSuccess({ records: [] });
+  //     //this should be unauthorized
+  //     case HttpStatusCode.Forbidden:
+  //       return opKnownHttpFailure(resp.status, resp);
+  //     case HttpStatusCode.Unauthorized:
+  //       return opKnownHttpFailure(resp.status, resp);
+  //     case HttpStatusCode.NotFound:
+  //       return opKnownHttpFailure(resp.status, resp);
+  //     case HttpStatusCode.Conflict:
+  //       return opKnownHttpFailure(resp.status, resp);
+  //     default:
+  //       return opUnknownFailure(resp, await readTalerErrorResponse(resp));
+  //   }
+  // }
+
+  // /**
+  //  * 
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 opSuccessFromHttp(resp, codecForAmlDecisionDetails());
+  //     case HttpStatusCode.NoContent:
+  //       return opFixedSuccess({ aml_history: [], kyc_attributes: [] });
+  //     //this should be unauthorized
+  //     case HttpStatusCode.Forbidden:
+  //       return opKnownHttpFailure(resp.status, resp);
+  //     case HttpStatusCode.Unauthorized:
+  //       return opKnownHttpFailure(resp.status, resp);
+  //     case HttpStatusCode.NotFound:
+  //       return opKnownHttpFailure(resp.status, resp);
+  //     case HttpStatusCode.Conflict:
+  //       return opKnownHttpFailure(resp.status, resp);
+  //     default:
+  //       return opUnknownFailure(resp, await readTalerErrorResponse(resp));
+  //   }
+  // }
+
+  // /**
+  //  * 
https://docs.taler.net/core/api-exchange.html#post--aml-$OFFICER_PUB-decision
+  //  *
+  //  */
+  // async addDecisionDetails(
+  //   auth: OfficerAccount,
+  //   decision: Omit<TalerExchangeApi.AmlDecision, "officer_sig">,
+  // ) {
+  //   const url = new URL(`aml/${auth.id}/decision`, this.baseUrl);
+
+  //   const body = buildDecisionSignature(auth.signingKey, decision);
+  //   const resp = await this.httpLib.fetch(url.href, {
+  //     method: "POST",
+  //     body,
+  //   });
+
+  //   switch (resp.status) {
+  //     case HttpStatusCode.NoContent:
+  //       return opEmptySuccess(resp);
+  //     //FIXME: this should be unauthorized
+  //     case HttpStatusCode.Forbidden:
+  //       return opKnownHttpFailure(resp.status, resp);
+  //     case HttpStatusCode.Unauthorized:
+  //       return opKnownHttpFailure(resp.status, resp);
+  //     //FIXME: this two need to be split by error code
+  //     case HttpStatusCode.NotFound:
+  //       return opKnownHttpFailure(resp.status, resp);
+  //     case HttpStatusCode.Conflict:
+  //       return opKnownHttpFailure(resp.status, resp);
+  //     default:
+  //       return opUnknownFailure(resp, await readTalerErrorResponse(resp));
+  //   }
+  // }
+
+  /**
+   * 
https://docs.taler.net/core/api-exchange.html#get--aml-$OFFICER_PUB-measures
+   *
+   */
+  async getAmlMesasures(auth: OfficerAccount) {
+    const url = new URL(`aml/${auth.id}/measures`, this.baseUrl);
 
     const resp = await this.httpLib.fetch(url.href, {
       method: "GET",
       headers: {
-        "Taler-AML-Officer-Signature": buildQuerySignature(auth.signingKey),
+        "Taler-AML-Officer-Signature": buildAMLQuerySignature(auth.signingKey),
       },
     });
 
     switch (resp.status) {
       case HttpStatusCode.Ok:
-        return opSuccessFromHttp(resp, codecForAmlRecords());
+        return opSuccessFromHttp(resp, codecForAvailableMeasureSummary());
+      default:
+        return opUnknownFailure(resp, await readTalerErrorResponse(resp));
+    }
+  }
+
+  /**
+   * 
https://docs.taler.net/core/api-exchange.html#get--aml-$OFFICER_PUB-measures
+   *
+   */
+  async getAmlKycStatistics(auth: OfficerAccount, name: string, filter: {
+    since?: Date
+    until?: Date
+  } = {}) {
+    const url = new URL(`aml/${auth.id}/kyc-statistics/${name}`, this.baseUrl);
+
+    if (filter.since !== undefined) {
+      url.searchParams.set(
+        "start_date",
+        String(filter.since.getTime())
+      );
+    }
+    if (filter.until !== undefined) {
+      url.searchParams.set(
+        "end_date",
+        String(filter.until.getTime())
+      );
+    }
+
+
+    const resp = await this.httpLib.fetch(url.href, {
+      method: "GET",
+      headers: {
+        "Taler-AML-Officer-Signature": buildAMLQuerySignature(auth.signingKey),
+      },
+    });
+    switch (resp.status) {
+      case HttpStatusCode.Ok:
+        return opSuccessFromHttp(resp, codecForEventCounter());
+      default:
+        return opUnknownFailure(resp, await readTalerErrorResponse(resp));
+    }
+  }
+
+  /**
+   * 
https://docs.taler.net/core/api-exchange.html#get--aml-$OFFICER_PUB-decisions
+   *
+   */
+  async getAmlDecisions(auth: OfficerAccount, params: PaginationParams & {
+    account?: string,
+    active?: boolean,
+    investigation?: boolean,
+  } = {}) {
+    const url = new URL(`aml/${auth.id}/decisions`, this.baseUrl);
+
+    addMerchantPaginationParams(url, params);
+    if (params.account !== undefined) {
+      url.searchParams.set("h_payto", params.account);
+    }
+    if (params.active !== undefined) {
+      url.searchParams.set("active", params.active ? "YES" : "NO");
+    }
+    if (params.investigation !== undefined) {
+      url.searchParams.set("investigation", params.investigation ? "YES" : 
"NO");
+    }
+
+    const resp = await this.httpLib.fetch(url.href, {
+      method: "GET",
+      headers: {
+        "Taler-AML-Officer-Signature": buildAMLQuerySignature(auth.signingKey),
+      },
+    });
+
+    switch (resp.status) {
+      case HttpStatusCode.Ok:
+        return opSuccessFromHttp(resp, codecForAmlDecisionsResponse());
       case HttpStatusCode.NoContent:
         return opFixedSuccess({ records: [] });
-      //this should be unauthorized
       case HttpStatusCode.Forbidden:
         return opKnownHttpFailure(resp.status, resp);
-      case HttpStatusCode.Unauthorized:
-        return opKnownHttpFailure(resp.status, resp);
       case HttpStatusCode.NotFound:
         return opKnownHttpFailure(resp.status, resp);
       case HttpStatusCode.Conflict:
@@ -216,29 +562,27 @@ export class TalerExchangeHttpClient {
   }
 
   /**
-   * 
https://docs.taler.net/core/api-exchange.html#get--aml-$OFFICER_PUB-decision-$H_PAYTO
+   * 
https://docs.taler.net/core/api-exchange.html#get--aml-$OFFICER_PUB-attributes-$H_PAYTO
    *
    */
-  async getDecisionDetails(auth: OfficerAccount, account: string) {
-    const url = new URL(`aml/${auth.id}/decision/${account}`, this.baseUrl);
+  async getAmlAttributesForAccount(auth: OfficerAccount, account: string, 
params: PaginationParams = {}) {
+    const url = new URL(`aml/${auth.id}/attributes/${account}`, this.baseUrl);
 
+    addMerchantPaginationParams(url, params);
     const resp = await this.httpLib.fetch(url.href, {
       method: "GET",
       headers: {
-        "Taler-AML-Officer-Signature": buildQuerySignature(auth.signingKey),
+        "Taler-AML-Officer-Signature": buildAMLQuerySignature(auth.signingKey),
       },
     });
 
     switch (resp.status) {
       case HttpStatusCode.Ok:
-        return opSuccessFromHttp(resp, codecForAmlDecisionDetails());
+        return opSuccessFromHttp(resp, codecForAmlKycAttributes());
       case HttpStatusCode.NoContent:
-        return opFixedSuccess({ aml_history: [], kyc_attributes: [] });
-      //this should be unauthorized
+        return opFixedSuccess({ details: [] });
       case HttpStatusCode.Forbidden:
         return opKnownHttpFailure(resp.status, resp);
-      case HttpStatusCode.Unauthorized:
-        return opKnownHttpFailure(resp.status, resp);
       case HttpStatusCode.NotFound:
         return opKnownHttpFailure(resp.status, resp);
       case HttpStatusCode.Conflict:
@@ -248,31 +592,28 @@ export class TalerExchangeHttpClient {
     }
   }
 
+
   /**
-   * 
https://docs.taler.net/core/api-exchange.html#post--aml-$OFFICER_PUB-decision
+   * 
https://docs.taler.net/core/api-exchange.html#get--aml-$OFFICER_PUB-attributes-$H_PAYTO
    *
    */
-  async addDecisionDetails(
-    auth: OfficerAccount,
-    decision: Omit<TalerExchangeApi.AmlDecision, "officer_sig">,
-  ) {
+  async makeAmlDesicion(auth: OfficerAccount, decision: 
Omit<AmlDecisionRequest, "officer_sig">) {
     const url = new URL(`aml/${auth.id}/decision`, this.baseUrl);
 
-    const body = buildDecisionSignature(auth.signingKey, decision);
+    const body = buildAMLDecisionSignature(auth.signingKey, decision);
     const resp = await this.httpLib.fetch(url.href, {
       method: "POST",
+      headers: {
+        "Taler-AML-Officer-Signature": buildAMLQuerySignature(auth.signingKey),
+      },
       body,
     });
 
     switch (resp.status) {
       case HttpStatusCode.NoContent:
         return opEmptySuccess(resp);
-      //FIXME: this should be unauthorized
       case HttpStatusCode.Forbidden:
         return opKnownHttpFailure(resp.status, resp);
-      case HttpStatusCode.Unauthorized:
-        return opKnownHttpFailure(resp.status, resp);
-      //FIXME: this two need to be split by error code
       case HttpStatusCode.NotFound:
         return opKnownHttpFailure(resp.status, resp);
       case HttpStatusCode.Conflict:
@@ -281,9 +622,19 @@ export class TalerExchangeHttpClient {
         return opUnknownFailure(resp, await readTalerErrorResponse(resp));
     }
   }
+
+}
+
+function buildKYCQuerySignature(key: SigningKey): string {
+  const sigBlob = buildSigPS(
+    TalerSignaturePurpose.TALER_SIGNATURE_AML_QUERY,
+    // TalerSignaturePurpose.TALER_SIGNATURE_WALLET_ACCOUNT_SETUP,
+  ).build();
+
+  return encodeCrock(eddsaSign(sigBlob, key));
 }
 
-function buildQuerySignature(key: SigningKey): string {
+function buildAMLQuerySignature(key: SigningKey): string {
   const sigBlob = buildSigPS(
     TalerSignaturePurpose.TALER_SIGNATURE_AML_QUERY,
   ).build();
@@ -291,20 +642,20 @@ function buildQuerySignature(key: SigningKey): string {
   return encodeCrock(eddsaSign(sigBlob, key));
 }
 
-function buildDecisionSignature(
+function buildAMLDecisionSignature(
   key: SigningKey,
-  decision: Omit<TalerExchangeApi.AmlDecision, "officer_sig">,
-): TalerExchangeApi.AmlDecision {
+  decision: Omit<AmlDecisionRequest, "officer_sig">,
+): AmlDecisionRequest {
   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(amountToBuffer(decision.new_threshold))
     .put(decodeCrock(decision.h_payto))
     .put(zero) //kyc_requirement
-    .put(bufferForUint32(decision.new_state))
+    // .put(bufferForUint32(decision.new_state))
     .build();
 
   const officer_sig = encodeCrock(eddsaSign(sigBlob, key));
diff --git a/packages/taler-util/src/http-client/officer-account.ts 
b/packages/taler-util/src/http-client/officer-account.ts
index 01b3681c0..612fd815e 100644
--- a/packages/taler-util/src/http-client/officer-account.ts
+++ b/packages/taler-util/src/http-client/officer-account.ts
@@ -17,8 +17,11 @@
 import {
   EncryptionNonce,
   LockedAccount,
+  LockedReserve,
   OfficerAccount,
   OfficerId,
+  ReserveAccount,
+  ReserveId,
   SigningKey,
   createEddsaKeyPair,
   decodeCrock,
@@ -96,6 +99,71 @@ export async function createNewOfficerAccount(
   return { id: accountId, signingKey, safe };
 }
 
+/**
+ * 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 unlockWalletKycAccount(
+  account: LockedReserve,
+  password: string,
+): Promise<ReserveAccount> {
+  const rawKey = decodeCrock(account);
+  const rawPassword = stringToBytes(password);
+
+  const signingKey = (await decryptWithDerivedKey(
+    rawKey,
+    rawPassword,
+    password,
+  ).catch((e) => {
+    throw new UnwrapKeyError(e instanceof Error ? e.message : String(e));
+  })) as SigningKey;
+
+  const publicKey = eddsaGetPublic(signingKey);
+
+  const accountId = encodeCrock(publicKey) as ReserveId;
+
+  return { id: accountId, signingKey };
+}
+
+/**
+ * Create new account (secured private key)
+ * secured with the given password
+ *
+ * @param extraNonce
+ * @param password
+ * @returns
+ */
+export async function createNewWalletKycAccount(
+  extraNonce: EncryptionNonce,
+  password: string,
+): Promise<OfficerAccount & { safe: LockedAccount }> {
+  const { eddsaPriv, eddsaPub } = createEddsaKeyPair();
+
+  const key = stringToBytes(password);
+
+  const localRnd = getRandomBytesF(24);
+  const mergedRnd: EncryptionNonce = extraNonce
+    ? kdf(24, stringToBytes("aml-officer"), extraNonce, localRnd)
+    : localRnd;
+
+  const protectedPrivKey = await encryptWithDerivedKey(
+    mergedRnd,
+    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) {
diff --git a/packages/taler-util/src/types-taler-common.ts 
b/packages/taler-util/src/types-taler-common.ts
index 2a5d017a7..6fc314f25 100644
--- a/packages/taler-util/src/types-taler-common.ts
+++ b/packages/taler-util/src/types-taler-common.ts
@@ -518,12 +518,21 @@ export type UserAndToken = {
 };
 
 declare const opaque_OfficerAccount: unique symbol;
+/**
+ * Sealed private key for AML officer
+ */
 export type LockedAccount = string & { [opaque_OfficerAccount]: true };
 
 declare const opaque_OfficerId: unique symbol;
+/**
+ * Public key for AML officer
+ */
 export type OfficerId = string & { [opaque_OfficerId]: true };
 
 declare const opaque_OfficerSigningKey: unique symbol;
+/**
+ * Private key for AML officer
+ */
 export type SigningKey = Uint8Array & { [opaque_OfficerSigningKey]: true };
 
 export interface OfficerAccount {
@@ -531,6 +540,25 @@ export interface OfficerAccount {
   signingKey: SigningKey;
 }
 
+
+declare const opaque_ReserveAccount: unique symbol;
+/**
+ * Sealed private key for AML officer
+ */
+export type LockedReserve = string & { [opaque_ReserveAccount]: true };
+
+declare const opaque_ReserveId: unique symbol;
+/**
+ * Public key for AML officer
+ */
+export type ReserveId = string & { [opaque_ReserveId]: true };
+
+export interface ReserveAccount {
+  id: ReserveId;
+  signingKey: SigningKey;
+}
+
+
 export type PaginationParams = {
   /**
    * row identifier as the starting point of the query
diff --git a/packages/taler-util/src/types-taler-exchange.ts 
b/packages/taler-util/src/types-taler-exchange.ts
index 421b62058..b71f302f5 100644
--- a/packages/taler-util/src/types-taler-exchange.ts
+++ b/packages/taler-util/src/types-taler-exchange.ts
@@ -33,6 +33,7 @@ import {
   codecForBoolean,
   codecForConstString,
   codecForCurrencySpecificiation,
+  codecForEither,
   codecForMap,
   codecForURN,
   strcmp,
@@ -41,13 +42,18 @@ import { Edx25519PublicKeyEnc } from "./taler-crypto.js";
 import {
   TalerProtocolDuration,
   TalerProtocolTimestamp,
+  codecForAbsoluteTime,
   codecForDuration,
   codecForTimestamp,
 } from "./time.js";
 import {
+  AccessToken,
   AmlOfficerPublicKeyP,
   AmountString,
   Base32String,
+  codecForAccessToken,
+  codecForInternationalizedString,
+  codecForURLString,
   CoinPublicKeyString,
   Cs25519Point,
   CurrencySpecification,
@@ -1380,31 +1386,80 @@ export interface BatchDepositRequestCoin {
   h_age_commitment?: string;
 }
 
-export enum AmlState {
-  normal = 0,
-  pending = 1,
-  frozen = 2,
+export interface AvailableMeasureSummary {
+
+  // Available original measures that can be
+  // triggered directly by default rules.
+  roots: { [measure_name: string]: MeasureInformation; };
+
+  // Available AML programs.
+  programs: { [prog_name: string]: AmlProgramRequirement; };
+
+  // Available KYC checks.
+  checks: { [check_name: string]: KycCheckInformation; };
+
 }
 
-export interface AmlRecords {
-  // Array of AML records matching the query.
-  records: AmlRecord[];
+export interface MeasureInformation {
+
+  // Name of a KYC check.
+  check_name: string;
+
+  // Name of an AML program.
+  prog_name: string;
+
+  // Context for the check. Optional.
+  context?: Object;
+
 }
-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;
+export interface AmlProgramRequirement {
 
-  // Monthly transaction threshold before a review will be triggered
-  threshold: AmountString;
+  // Description of what the AML program does.
+  description: string;
 
-  // RowID of the record.
-  rowid: Integer;
+  // List of required field names in the context to run this
+  // AML program. SPA must check that the AML staff is providing
+  // adequate CONTEXT when defining a measure using this program.
+  context: string[];
+
+  // List of required attribute names in the
+  // input of this AML program.  These attributes
+  // are the minimum that the check must produce
+  // (it may produce more).
+  inputs: string[];
+
+}
+
+export interface KycCheckInformation {
+
+  // Description of the KYC check.  Should be shown
+  // to the AML staff but will also be shown to the
+  // client when they initiate the check in the KYC SPA.
+  description: string;
+
+  // Map from IETF BCP 47 language tags to localized
+  // description texts.
+  description_i18n?: { [lang_tag: string]: string };
+
+  // Names of the fields that the CONTEXT must provide
+  // as inputs to this check.
+  // SPA must check that the AML staff is providing
+  // adequate CONTEXT when defining a measure using
+  // this check.
+  requires: string[];
+
+  // Names of the attributes the check will output.
+  // SPA must check that the outputs match the
+  // required inputs when combining a KYC check
+  // with an AML program into a measure.
+  outputs: string[];
+
+  // Name of a root measure taken when this check fails.
+  fallback: string;
 }
 
+
 export interface AmlDecisionDetails {
   // Array of AML decisions made for this account. Possibly
   // contains only the most recent decision if "history" was
@@ -1447,34 +1502,6 @@ export interface KycDetail {
   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: EddsaSignatureString;
-
-  // 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
@@ -1525,6 +1552,421 @@ export interface WireAccount {
   master_sig: EddsaSignatureString;
 }
 
+export interface WalletKycRequest {
+
+  // Balance threshold (not necessarily exact balance)
+  // to be crossed by the wallet that (may) trigger
+  // additional KYC requirements.
+  balance: AmountString;
+
+  // EdDSA signature of the wallet affirming the
+  // request, must be of purpose
+  // TALER_SIGNATURE_WALLET_ACCOUNT_SETUP
+  reserve_sig: EddsaSignatureString;
+
+  // long-term wallet reserve-account
+  // public key used to create the signature.
+  reserve_pub: EddsaPublicKeyString;
+}
+
+export interface WalletKycCheckResponse {
+
+  // Next balance limit above which a KYC check
+  // may be required. Optional, not given if no
+  // threshold exists (assume infinity).
+  next_threshold?: AmountString;
+
+  // When does the current set of AML/KYC rules
+  // expire and the wallet needs to check again
+  // for updated thresholds.
+  expiration_time: Timestamp;
+
+}
+
+
+// Implemented in this style since exchange
+// protocol **v20**.
+export interface LegitimizationNeededResponse {
+
+  // Numeric error code unique to the condition.
+  // Should always be TALER_EC_EXCHANGE_GENERIC_KYC_REQUIRED.
+  code: number;
+
+  // Human-readable description of the error, i.e. "missing parameter",
+  // "commitment violation", ...  Should give a human-readable hint
+  // about the error's nature. Optional, may change without notice!
+  hint?: string;
+
+  // Hash of the payto:// account URI for which KYC
+  // is required.
+  h_payto: PaytoHash;
+
+  // Public key associated with the account. The client must sign
+  // the initial request for the KYC status using the corresponding
+  // private key.  Will be either a reserve public key or a merchant
+  // (instance) public key.
+  //
+  // Absent if no public key is currently associated
+  // with the account and the client MUST thus first
+  // credit the exchange via an inbound wire transfer
+  // to associate a public key with the debited account.
+  account_pub?: EddsaPublicKeyString;
+
+  // Identifies a set of measures that were triggered and that are
+  // now preventing this operation from proceeding.  Gives the
+  // account holder a starting point for understanding why the
+  // transaction was blocked and how to lift it. The account holder
+  // should use the number to check for the account's AML/KYC status
+  // using the /kyc-check/$REQUIREMENT_ROW endpoint.
+  requirement_row: Integer;
+
+}
+
+export interface AccountKycStatus {
+
+  // Current AML state for the target account.  True if
+  // operations are not happening due to staff processing
+  // paperwork *or* due to legal requirements (so the
+  // client cannot do anything but wait).
+  //
+  // Note that not every AML staff action may be legally
+  // exposed to the client, so this is merely a hint that
+  // a client should be told that AML staff is currently
+  // reviewing the account.  AML staff *may* review
+  // accounts without this flag being set!
+  aml_review: boolean;
+
+  // Access token needed to construct the /kyc-spa/
+  // URL that the user should open in a browser to
+  // proceed with the KYC process (optional if the status
+  // type is 200 Ok, mandatory if the HTTP status
+  // is 202 Accepted).
+  access_token: AccessToken;
+
+  // Array with limitations that currently apply to this
+  // account and that may be increased or lifted if the
+  // KYC check is passed.
+  // Note that additional limits *may* exist and not be
+  // communicated to the client. If such limits are
+  // reached, this *may* be indicated by the account
+  // going into aml_review state. However, it is
+  // also possible that the exchange may legally have
+  // to deny operations without being allowed to provide
+  // any justification.
+  // The limits should be used by the client to
+  // possibly structure their operations (e.g. withdraw
+  // what is possible below the limit, ask the user to
+  // pass KYC checks or withdraw the rest after the time
+  // limit is passed, warn the user to not withdraw too
+  // much or even prevent the user from generating a
+  // request that would cause it to exceed hard limits).
+  limits?: AccountLimit[];
+
+}
+export interface AccountLimit {
+
+  // Operation that is limited.
+  // Must be one of "WITHDRAW", "DEPOSIT", "P2P-RECEIVE"
+  // or "WALLET-BALANCE".
+  operation_type: "WITHDRAW" | "DEPOSIT" | "P2P-RECEIVE" | "WALLET-BALANCE";
+
+  // Timeframe during which the limit applies.
+  timeframe: RelativeTime;
+
+  // Maximum amount allowed during the given timeframe.
+  // Zero if the operation is simply forbidden.
+  threshold: AmountString;
+
+  // True if this is a soft limit that could be raised
+  // by passing KYC checks.  Clients *may* deliberately
+  // try to cross limits and trigger measures resulting
+  // in 451 responses to begin KYC processes.
+  // Clients that are aware of hard limits *should*
+  // inform users about the hard limit and prevent flows
+  // in the UI that would cause violations of hard limits.
+  soft_limit: boolean;
+}
+
+export interface KycProcessClientInformation {
+
+  // Array of requirements.
+  requirements: KycRequirementInformation[];
+
+  // True if the client is expected to eventually satisfy all requirements.
+  // Default (if missing) is false.
+  is_and_combinator?: boolean
+
+  // List of available voluntary checks the client could pay for.
+  // Since **vATTEST**.
+  voluntary_checks?: { [name: string]: KycCheckPublicInformation };
+}
+
+declare const opaque_kycReq: unique symbol;
+export type KycRequirementInformationId = string & { [opaque_kycReq]: true }
+
+export interface KycRequirementInformation {
+
+  // Which form should be used? Common values include "INFO"
+  // (to just show the descriptions but allow no action),
+  // "LINK" (to enable the user to obtain a link via
+  // /kyc-start/) or any build-in form name supported
+  // by the SPA.
+  form: string;
+
+  // English description of the requirement.
+  description: string;
+
+  // Map from IETF BCP 47 language tags to localized
+  // description texts.
+  description_i18n?: { [lang_tag: string]: string };
+
+  // ID of the requirement, useful to construct the
+  // /kyc-upload/$ID or /kyc-start/$ID endpoint URLs.
+  // Present if and only if "form" is not "INFO".  The
+  // $ID value may itself contain / or ? and
+  // basically encode any URL path (and optional arguments).
+  id?: KycRequirementInformationId;
+}
+
+// Since **vATTEST**.
+export interface KycCheckPublicInformation {
+
+  // English description of the check.
+  description: string;
+
+  // Map from IETF BCP 47 language tags to localized
+  // description texts.
+  description_i18n?: { [lang_tag: string]: string };
+
+  // FIXME: is the above in any way sufficient
+  // to begin the check? Do we not need at least
+  // something more??!?
+}
+
+
+export interface EventCounter {
+  // Number of events of the specified type in
+  // the given range.
+  counter: Integer;
+}
+
+export interface AmlDecisionsResponse {
+
+  // Array of AML decisions matching the query.
+  records: AmlDecision[];
+}
+
+export interface AmlDecision {
+
+  // Which payto-address is this record about.
+  // Identifies a GNU Taler wallet or an affected bank account.
+  h_payto: PaytoHash;
+
+  // Row ID of the record.  Used to filter by offset.
+  rowid: Integer;
+
+  // Justification for the decision. NULL if none
+  // is available.
+  justification?: string;
+
+  // When was the decision made?
+  decision_time: Timestamp;
+
+  // Free-form properties about the account.
+  // Can be used to store properties such as PEP,
+  // risk category, type of business, hits on
+  // sanctions lists, etc.
+  properties?: AccountProperties;
+
+  // What are the new rules?
+  limits: LegitimizationRuleSet;
+
+  // True if the account is under investigation by AML staff
+  // after this decision.
+  to_investigate: boolean;
+
+  // True if this is the active decision for the
+  // account.
+  is_active: boolean;
+
+}
+
+// All fields in this object are optional. The actual
+// properties collected depend fully on the discretion
+// of the exchange operator;
+// however, some common fields are standardized
+// and thus described here.
+export interface AccountProperties {
+
+  // True if this is a politically exposed account.
+  // Rules for classifying accounts as politically
+  // exposed are country-dependent.
+  pep?: boolean;
+
+  // True if this is a sanctioned account.
+  // Rules for classifying accounts as sanctioned
+  // are country-dependent.
+  sanctioned?: boolean;
+
+  // True if this is a high-risk account.
+  // Rules for classifying accounts as at-risk
+  // are exchange operator-dependent.
+  high_risk?: boolean;
+
+  // Business domain of the account owner.
+  // The list of possible business domains is
+  // operator- or country-dependent.
+  business_domain?: string;
+
+  // Is the client's account currently frozen?
+  is_frozen?: boolean;
+
+  // Was the client's account reported to the authorities?
+  was_reported?: boolean;
+
+}
+
+export interface LegitimizationRuleSet {
+
+  // When does this set of rules expire and
+  // we automatically transition to the successor
+  // measure?
+  expiration_time: Timestamp;
+
+  // Name of the measure to apply when the expiration time is
+  // reached.  If not set, we refer to the default
+  // set of rules (and the default account state).
+  successor_measure?: string;
+
+  // Legitimization rules that are to be applied
+  // to this account.
+  rules: KycRule[];
+
+  // Custom measures that KYC rules and the
+  // successor_measure may refer to.
+  custom_measures: { [measure_name: string]: MeasureInformation; };
+
+}
+
+export interface AmlDecisionRequest {
+
+  // Human-readable justification for the decision.
+  justification: string;
+
+  // Which payto-address is the decision about?
+  // Identifies a GNU Taler wallet or an affected bank account.
+  h_payto: PaytoHash;
+
+  // What are the new rules?
+  // New since protocol **v20**.
+  new_rules: LegitimizationRuleSet;
+
+  // What are the new account properties?
+  // New since protocol **v20**.
+  properties: AccountProperties;
+
+  // New measure to apply immediately to the account.
+  // Should typically be used to give the user some
+  // information or request additional information.
+  // Use "verboten" to communicate to the customer
+  // that there is no KYC check that could be passed
+  // to modify the new_rules.
+  // New since protocol **v20**.
+  new_measure?: string;
+
+  // True if the account should remain under investigation by AML staff.
+  // New since protocol **v20**.
+  keep_investigating: boolean;
+
+  // Signature by the AML officer over a TALER_AmlDecisionPS.
+  // Must have purpose TALER_SIGNATURE_MASTER_AML_KEY.
+  officer_sig: EddsaSignatureString;
+
+  // When was the decision made?
+  decision_time: Timestamp;
+
+}
+
+
+export interface KycRule {
+
+  // Type of operation to which the rule applies.
+  operation_type: string;
+
+  // The measures will be taken if the given
+  // threshold is crossed over the given timeframe.
+  threshold: AmountString;
+
+  // Over which duration should the threshold be
+  // computed.  All amounts of the respective
+  // operation_type will be added up for this
+  // duration and the sum compared to the threshold.
+  timeframe: RelativeTime;
+
+  // Array of names of measures to apply.
+  // Names listed can be original measures or
+  // custom measures from the AmlOutcome.
+  // A special measure "verboten" is used if the
+  // threshold may never be crossed.
+  measures: string[];
+
+  // If multiple rules apply to the same account
+  // at the same time, the number with the highest
+  // rule determines which set of measures will
+  // be activated and thus become visible for the
+  // user.
+  display_priority: Integer;
+
+  // True if the rule (specifically, operation_type,
+  // threshold, timeframe) and the general nature of
+  // the measures (verboten or approval required)
+  // should be exposed to the client.
+  // Defaults to "false" if not set.
+  exposed?: boolean;
+
+  // True if all the measures will eventually need to
+  // be satisfied, false if any of the measures should
+  // do.  Primarily used by the SPA to indicate how
+  // the measures apply when showing them to the user;
+  // in the end, AML programs will decide after each
+  // measure what to do next.
+  // Default (if missing) is false.
+  is_and_combinator?: boolean;
+
+}
+
+
+export interface KycAttributes {
+
+  // Matching KYC attribute history of the account.
+  details: KycAttributeCollectionEvent[];
+
+}
+export interface KycAttributeCollectionEvent {
+
+  // Row ID of the record.  Used to filter by offset.
+  rowid: Integer;
+
+  // Name of the provider
+  // which was used to collect the attributes. NULL if they were
+  // just uploaded via a form by the account owner.
+  provider_name?: 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;
+
+}
+
+export enum AmlState {
+  normal = 0,
+  pending = 1,
+  frozen = 2,
+}
 export interface ExchangeKeysResponse {
   // libtool-style representation of the Exchange protocol version, see
   // 
https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning
@@ -1841,40 +2283,68 @@ export const codecForExchangeConfig = (): 
Codec<ExchangeVersionResponse> =>
     .property("currency_specification", codecForCurrencySpecificiation())
     .property("supported_kyc_requirements", codecForList(codecForString()))
     .build("TalerExchangeApi.ExchangeVersionResponse");
-
 export const codecForExchangeKeys = (): Codec<ExchangeKeysResponse> =>
   buildCodecForObject<ExchangeKeysResponse>()
     .property("version", codecForString())
     .property("base_url", codecForString())
     .property("currency", codecForString())
     .build("TalerExchangeApi.ExchangeKeysResponse");
-export const codecForAmlRecords = (): Codec<AmlRecords> =>
-  buildCodecForObject<AmlRecords>()
-    .property("records", codecForList(codecForAmlRecord()))
-    .build("TalerExchangeApi.AmlRecords");
-
-export const codecForAmlRecord = (): Codec<AmlRecord> =>
-  buildCodecForObject<AmlRecord>()
-    .property("current_state", codecForNumber())
-    .property("h_payto", codecForString())
-    .property("rowid", codecForNumber())
-    .property("threshold", codecForAmountString())
-    .build("TalerExchangeApi.AmlRecord");
-
-export const codecForAmlDecisionDetails = (): Codec<AmlDecisionDetails> =>
-  buildCodecForObject<AmlDecisionDetails>()
-    .property("aml_history", codecForList(codecForAmlDecisionDetail()))
-    .property("kyc_attributes", codecForList(codecForKycDetail()))
-    .build("TalerExchangeApi.AmlDecisionDetails");
-
-export const codecForAmlDecisionDetail = (): Codec<AmlDecisionDetail> =>
-  buildCodecForObject<AmlDecisionDetail>()
-    .property("justification", codecForString())
-    .property("new_state", codecForNumber())
-    .property("decision_time", codecForTimestamp)
-    .property("new_threshold", codecForAmountString())
-    .property("decider_pub", codecForString())
-    .build("TalerExchangeApi.AmlDecisionDetail");
+
+export const codecForEventCounter = (): Codec<EventCounter> =>
+  buildCodecForObject<EventCounter>()
+    .property("counter", codecForNumber())
+    .build("TalerExchangeApi.EventCounter");
+
+
+export const codecForAmlDecisionsResponse = (): Codec<AmlDecisionsResponse> =>
+  buildCodecForObject<AmlDecisionsResponse>()
+    .property("records", codecForList(codecForAmlDecision()))
+    .build("TalerExchangeApi.AmlDecisionsResponse");
+
+export const codecForAvailableMeasureSummary = (): 
Codec<AvailableMeasureSummary> =>
+  buildCodecForObject<AvailableMeasureSummary>()
+    .property("checks", codecForMap(codecForKycCheckInformation()))
+    .property("programs", codecForMap(codecForAmlProgramRequirement()))
+    .property("roots", codecForMap(codecForMeasureInformation()))
+    .build("TalerExchangeApi.AvailableMeasureSummary");
+
+export const codecForAmlProgramRequirement = (): Codec<AmlProgramRequirement> 
=>
+  buildCodecForObject<AmlProgramRequirement>()
+    .property("description", codecForString())
+    .property("context", codecForList(codecForString()))
+    .property("inputs", codecForList(codecForString()))
+    .build("TalerExchangeApi.AmlProgramRequirement");
+
+export const codecForKycCheckInformation = (): Codec<KycCheckInformation> =>
+  buildCodecForObject<KycCheckInformation>()
+    .property("description", codecForString())
+    .property("description_i18n", codecForInternationalizedString())
+    .property("fallback", codecForString())
+    .property("outputs", codecForList(codecForString()))
+    .property("requires", codecForList(codecForString()))
+    .build("TalerExchangeApi.KycCheckInformation");
+
+export const codecForMeasureInformation = (): Codec<MeasureInformation> =>
+  buildCodecForObject<MeasureInformation>()
+    .property("prog_name", codecForString())
+    .property("check_name", codecForString())
+    .property("context", codecForAny())
+    .build("TalerExchangeApi.MeasureInformation");
+
+// export const codecForAmlDecisionDetails = (): Codec<AmlDecisionDetails> =>
+//   buildCodecForObject<AmlDecisionDetails>()
+//     .property("aml_history", codecForList(codecForAmlDecisionDetail()))
+//     .property("kyc_attributes", codecForList(codecForKycDetail()))
+//     .build("TalerExchangeApi.AmlDecisionDetails");
+
+// export const codecForAmlDecisionDetail = (): Codec<AmlDecisionDetail> =>
+//   buildCodecForObject<AmlDecisionDetail>()
+//     .property("justification", codecForString())
+//     .property("new_state", codecForNumber())
+//     .property("decision_time", codecForTimestamp)
+//     .property("new_threshold", codecForAmountString())
+//     .property("decider_pub", codecForString())
+//     .build("TalerExchangeApi.AmlDecisionDetail");
 
 export const codecForKycDetail = (): Codec<KycDetail> =>
   buildCodecForObject<KycDetail>()
@@ -1886,11 +2356,128 @@ export const codecForKycDetail = (): Codec<KycDetail> 
=>
 
 export const codecForAmlDecision = (): Codec<AmlDecision> =>
   buildCodecForObject<AmlDecision>()
-    .property("justification", codecForString())
-    .property("new_threshold", codecForAmountString())
     .property("h_payto", codecForString())
-    .property("new_state", codecForNumber())
-    .property("officer_sig", codecForString())
+    .property("rowid", codecForNumber())
+    .property("justification", codecOptional(codecForString()))
     .property("decision_time", codecForTimestamp)
-    .property("kyc_requirements", 
codecOptional(codecForList(codecForString())))
+    .property("properties", codecForAccountProperties())
+    .property("limits", codecForLegitimizationRuleSet())
+    .property("to_investigate", codecForBoolean())
+    .property("is_active", codecForBoolean())
     .build("TalerExchangeApi.AmlDecision");
+
+export const codecForAccountProperties = (): Codec<AccountProperties> =>
+  buildCodecForObject<AccountProperties>()
+    .property("pep", codecOptional(codecForBoolean()))
+    .property("sanctioned", codecOptional(codecForBoolean()))
+    .property("high_risk", codecOptional(codecForBoolean()))
+    .property("business_domain", codecOptional(codecForString()))
+    .property("is_frozen", codecOptional(codecForBoolean()))
+    .property("was_reported", codecOptional(codecForBoolean()))
+    .build("TalerExchangeApi.AccountProperties");
+
+
+export const codecForLegitimizationRuleSet = (): Codec<LegitimizationRuleSet> 
=>
+  buildCodecForObject<LegitimizationRuleSet>()
+    .property("expiration_time", (codecForTimestamp))
+    .property("successor_measure", codecOptional(codecForString()))
+    .property("rules", codecForList(codecForKycRules()))
+    .property("custom_measures", codecForMap(codecForMeasureInformation()))
+    .build("TalerExchangeApi.LegitimizationRuleSet");
+
+export const codecForKycRules = (): Codec<KycRule> =>
+  buildCodecForObject<KycRule>()
+    .property("operation_type", codecForString())
+    .property("threshold", codecForAmountString())
+    .property("timeframe", codecForDuration)
+    .property("measures", codecForList(codecForString()))
+    .property("display_priority", codecForNumber())
+    .property("exposed", codecOptional(codecForBoolean()))
+    .property("is_and_combinator", codecOptional(codecForBoolean()))
+    .build("TalerExchangeApi.KycRule");
+
+
+
+export const codecForAmlKycAttributes = (): Codec<KycAttributes> =>
+  buildCodecForObject<KycAttributes>()
+    .property("details", codecForList(codecForKycAttributeCollectionEvent()))
+    .build("TalerExchangeApi.KycAttributes");
+
+export const codecForKycAttributeCollectionEvent = (): 
Codec<KycAttributeCollectionEvent> =>
+  buildCodecForObject<KycAttributeCollectionEvent>()
+    .property("rowid", codecForNumber())
+    .property("provider_name", codecOptional(codecForString()))
+    .property("collection_time", codecForTimestamp)
+    .property("attributes", codecOptional(codecForAny()))
+    .build("TalerExchangeApi.KycAttributeCollectionEvent");
+
+export const codecForAmlWalletKycCheckResponse = (): 
Codec<WalletKycCheckResponse> =>
+  buildCodecForObject<WalletKycCheckResponse>()
+    .property("next_threshold", codecOptional(codecForAmountString()))
+    .property("expiration_time", codecForTimestamp)
+    .build("TalerExchangeApi.WalletKycCheckResponse");
+
+export const codecForLegitimizationNeededResponse = (): 
Codec<LegitimizationNeededResponse> =>
+  buildCodecForObject<LegitimizationNeededResponse>()
+    .property("code", (codecForNumber()))
+    .property("hint", codecOptional(codecForString()))
+    .property("h_payto", (codecForString()))
+    .property("account_pub", codecOptional(codecForString()))
+    .property("requirement_row", (codecForNumber()))
+    .build("TalerExchangeApi.LegitimizationNeededResponse");
+
+export const codecForAccountKycStatus = (): Codec<AccountKycStatus> =>
+  buildCodecForObject<AccountKycStatus>()
+    .property("aml_review", codecForBoolean())
+    .property("access_token", codecForAccessToken())
+    .property("limits", codecOptional(codecForList(codecForAccountLimit())))
+    .build("TalerExchangeApi.AccountKycStatus");
+
+export const codecForAccountLimit = (): Codec<AccountLimit> =>
+  buildCodecForObject<AccountLimit>()
+    .property("operation_type", codecForEither(
+      codecForConstString("WITHDRAW"),
+      codecForConstString("DEPOSIT"),
+      codecForConstString("P2P-RECEIVE"),
+      codecForConstString("WALLET-BALANCE"))
+    )
+    .property("timeframe", codecForDuration)
+    .property("threshold", codecForAmountString())
+    .property("soft_limit", codecForBoolean())
+    .build("TalerExchangeApi.AccountLimit");
+
+
+export const codecForKycCheckPublicInformation = (): 
Codec<KycCheckPublicInformation> =>
+  buildCodecForObject<KycCheckPublicInformation>()
+    .property("description", codecForString())
+    .property("description_i18n", codecForInternationalizedString())
+    .build("TalerExchangeApi.KycCheckPublicInformation");
+
+export const codecForKycRequirementInformationId =
+  (): Codec<KycRequirementInformationId> => codecForString() as 
Codec<KycRequirementInformationId>;
+
+export const codecForKycRequirementInformation = (): 
Codec<KycRequirementInformation> =>
+  buildCodecForObject<KycRequirementInformation>()
+    .property("form", codecForString())
+    .property("description", codecForString())
+    .property("description_i18n", codecForInternationalizedString())
+    .property("id", codecOptional(codecForKycRequirementInformationId()))
+    .build("TalerExchangeApi.KycRequirementInformation");
+
+export const codecForKycProcessClientInformation = (): 
Codec<KycProcessClientInformation> =>
+  buildCodecForObject<KycProcessClientInformation>()
+    .property("requirements", 
codecForList(codecForKycRequirementInformation()))
+    .property("is_and_combinator", codecOptional(codecForBoolean()))
+    .property("voluntary_checks", 
codecForMap(codecForKycCheckPublicInformation()))
+    .build("TalerExchangeApi.KycProcessClientInformation");
+
+interface KycProcessStartInformation {
+
+  // URL to open.
+  redirect_url: string;
+}
+
+export const codecForKycProcessStartInformation = (): 
Codec<KycProcessStartInformation> =>
+  buildCodecForObject<KycProcessStartInformation>()
+    .property("redirect_url", codecForURLString())
+    .build("TalerExchangeApi.KycProcessStartInformation");

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