aboutsummaryrefslogtreecommitdiff
path: root/packages/aml-backoffice-ui/src
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2023-07-21 15:50:53 -0300
committerSebastian <sebasjm@gmail.com>2023-07-21 15:50:53 -0300
commit7e37b347447b6fc418f11160d439a7596b039680 (patch)
tree31b57eedb262316264974991a937ca22bfa626b6 /packages/aml-backoffice-ui/src
parente90f1b4206e8843b85655ebe47485c70dbdab3f6 (diff)
downloadwallet-core-7e37b347447b6fc418f11160d439a7596b039680.tar.xz
case details and missing decision encryption
Diffstat (limited to 'packages/aml-backoffice-ui/src')
-rw-r--r--packages/aml-backoffice-ui/src/account.ts19
-rw-r--r--packages/aml-backoffice-ui/src/handlers/FormProvider.tsx2
-rw-r--r--packages/aml-backoffice-ui/src/hooks/useBackend.ts8
-rw-r--r--packages/aml-backoffice-ui/src/hooks/useCaseDetails.ts162
-rw-r--r--packages/aml-backoffice-ui/src/hooks/useCases.ts53
-rw-r--r--packages/aml-backoffice-ui/src/pages.ts2
-rw-r--r--packages/aml-backoffice-ui/src/pages/CaseDetails.tsx131
-rw-r--r--packages/aml-backoffice-ui/src/pages/Cases.tsx53
-rw-r--r--packages/aml-backoffice-ui/src/pages/NewFormEntry.tsx40
-rw-r--r--packages/aml-backoffice-ui/src/types.ts36
10 files changed, 362 insertions, 144 deletions
diff --git a/packages/aml-backoffice-ui/src/account.ts b/packages/aml-backoffice-ui/src/account.ts
index 1c8cd7f53..2225bf2ff 100644
--- a/packages/aml-backoffice-ui/src/account.ts
+++ b/packages/aml-backoffice-ui/src/account.ts
@@ -1,6 +1,7 @@
import {
- PaytoUri,
+ Amounts,
TalerSignaturePurpose,
+ amountToBuffer,
bufferForUint32,
buildSigPS,
createEddsaKeyPair,
@@ -11,8 +12,10 @@ import {
encodeCrock,
encryptWithDerivedKey,
getRandomBytesF,
+ hash,
+ hashTruncate32,
stringToBytes,
- stringifyPaytoUri,
+ timestampRoundedToBuffer
} from "@gnu-taler/taler-util";
import { AmlExchangeBackend } from "./types.js";
@@ -60,12 +63,16 @@ export function buildQuerySignature(key: SigningKey): string {
}
export function buildDecisionSignature(
key: SigningKey,
- payto: PaytoUri,
- state: AmlExchangeBackend.AmlState,
+ decision: AmlExchangeBackend.AmlDecision,
): string {
+
const sigBlob = buildSigPS(TalerSignaturePurpose.TALER_SIGNATURE_AML_DECISION)
- .put(decodeCrock(stringifyPaytoUri(payto)))
- .put(bufferForUint32(state))
+ .put(hash(stringToBytes(decision.justification)))
+ // .put(timestampRoundedToBuffer(decision.decision_time))
+ .put(amountToBuffer(decision.new_threshold))
+ .put(decodeCrock(decision.h_payto))
+ // .put(hash(stringToBytes(decision.kyc_requirements)))
+ .put(bufferForUint32(decision.new_state))
.build();
return encodeCrock(eddsaSign(sigBlob, key));
diff --git a/packages/aml-backoffice-ui/src/handlers/FormProvider.tsx b/packages/aml-backoffice-ui/src/handlers/FormProvider.tsx
index a195c2051..3da2a4f07 100644
--- a/packages/aml-backoffice-ui/src/handlers/FormProvider.tsx
+++ b/packages/aml-backoffice-ui/src/handlers/FormProvider.tsx
@@ -29,7 +29,7 @@ export type FormState<T> = {
? Partial<InputFieldState>
: T[field] extends Array<infer P>
? Partial<InputArrayFieldState<P>>
- : T[field] extends object
+ : T[field] extends (object | undefined)
? FormState<T[field]>
: Partial<InputFieldState>;
};
diff --git a/packages/aml-backoffice-ui/src/hooks/useBackend.ts b/packages/aml-backoffice-ui/src/hooks/useBackend.ts
index e68efb2e3..b9d66fca6 100644
--- a/packages/aml-backoffice-ui/src/hooks/useBackend.ts
+++ b/packages/aml-backoffice-ui/src/hooks/useBackend.ts
@@ -14,7 +14,7 @@ interface useBackendType {
path: string,
options?: RequestOptions,
) => Promise<HttpResponseOk<T>>;
- fetcher: <T>(endpoint: string) => Promise<HttpResponseOk<T>>;
+ fetcher: <T>(args: [string, string]) => Promise<HttpResponseOk<T>>;
paginatedFetcher: <T>(
args: [string, number, number, string],
) => Promise<HttpResponseOk<T>>;
@@ -35,8 +35,10 @@ export function usePublicBackend(): useBackendType {
);
const fetcher = useCallback(
- function fetcherImpl<T>(endpoint: string): Promise<HttpResponseOk<T>> {
- return requestHandler<T>(baseUrl, endpoint);
+ function fetcherImpl<T>([endpoint, talerAmlOfficerSignature]: [string,string]): Promise<HttpResponseOk<T>> {
+ return requestHandler<T>(baseUrl, endpoint, {
+ talerAmlOfficerSignature
+ });
},
[baseUrl],
);
diff --git a/packages/aml-backoffice-ui/src/hooks/useCaseDetails.ts b/packages/aml-backoffice-ui/src/hooks/useCaseDetails.ts
new file mode 100644
index 000000000..980a35f21
--- /dev/null
+++ b/packages/aml-backoffice-ui/src/hooks/useCaseDetails.ts
@@ -0,0 +1,162 @@
+
+import {
+ HttpResponse,
+ HttpResponseOk,
+ RequestError
+} from "@gnu-taler/web-util/browser";
+import { AmlExchangeBackend } from "../types.js";
+// FIX default import https://github.com/microsoft/TypeScript/issues/49189
+import _useSWR, { SWRHook, useSWRConfig } from "swr";
+import { AccountId } from "../account.js";
+import { usePublicBackend } from "./useBackend.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();
+
+ const { data, error } = useSWR<
+ HttpResponseOk<AmlExchangeBackend.AmlDecisionDetails>,
+ RequestError<AmlExchangeBackend.AmlError>
+>( [
+ `aml/${account}/decision/${(paytoHash)}`,
+ signature,
+],
+fetcher, {
+ refreshInterval: 0,
+ refreshWhenHidden: false,
+ revalidateOnFocus: false,
+ revalidateOnReconnect: false,
+ refreshWhenOffline: false,
+ errorRetryCount: 0,
+ errorRetryInterval: 1,
+ shouldRetryOnError: false,
+ keepPreviousData: true,
+ });
+
+ if (data) return data;
+ if (error) return error.cause;
+ return { loading: true };
+}
+
+const example1: AmlExchangeBackend.AmlDecisionDetails = {
+ aml_history: [
+ {
+ justification: "Lack of documentation",
+ decider_pub: "ASDASDASD",
+ decision_time: {
+ t_s: Date.now() / 1000,
+ },
+ new_state: 2,
+ new_threshold: "USD:0",
+ },
+ {
+ justification: "Doing a transfer of high amount",
+ decider_pub: "ASDASDASD",
+ decision_time: {
+ t_s: Date.now() / 1000 - 60 * 60 * 24 * 30 * 6,
+ },
+ new_state: 1,
+ new_threshold: "USD:2000",
+ },
+ {
+ justification: "Account is known to the system",
+ decider_pub: "ASDASDASD",
+ decision_time: {
+ t_s: Date.now() / 1000 - 60 * 60 * 24 * 30 * 9,
+ },
+ new_state: 0,
+ new_threshold: "USD:100",
+ },
+ ],
+ kyc_attributes: [
+ {
+ collection_time: {
+ t_s: Date.now() / 1000 - 60 * 60 * 24 * 30 * 8,
+ },
+ expiration_time: {
+ t_s: Date.now() / 1000 - 60 * 60 * 24 * 30 * 4,
+ },
+ provider_section: "asdasd",
+ attributes: {
+ name: "Sebastian",
+ },
+ },
+ {
+ collection_time: {
+ t_s: Date.now() / 1000 - 60 * 60 * 24 * 30 * 5,
+ },
+ expiration_time: {
+ t_s: Date.now() / 1000 - 60 * 60 * 24 * 30 * 2,
+ },
+ provider_section: "asdasd",
+ attributes: {
+ creditCard: "12312312312",
+ },
+ },
+ ],
+};
+
+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 04b7c383b..c5a0fc489 100644
--- a/packages/aml-backoffice-ui/src/hooks/useCases.ts
+++ b/packages/aml-backoffice-ui/src/hooks/useCases.ts
@@ -92,3 +92,56 @@ export function useCases(
}
return { loading: true };
}
+
+const example1: AmlExchangeBackend.AmlRecords = {
+ records: [
+ {
+ current_state: 0,
+ h_payto: "QWEQWEQWEQWEWQE",
+ rowid: 1,
+ threshold: "USD 100",
+ },
+ {
+ current_state: 1,
+ h_payto: "ASDASDASD",
+ rowid: 1,
+ threshold: "USD 100",
+ },
+ {
+ current_state: 2,
+ h_payto: "ZXCZXCZXCXZC",
+ rowid: 1,
+ threshold: "USD 1000",
+ },
+ {
+ current_state: 0,
+ h_payto: "QWEQWEQWEQWEWQE",
+ rowid: 1,
+ threshold: "USD 100",
+ },
+ {
+ current_state: 1,
+ h_payto: "ASDASDASD",
+ rowid: 1,
+ threshold: "USD 100",
+ },
+ {
+ current_state: 2,
+ h_payto: "ZXCZXCZXCXZC",
+ rowid: 1,
+ threshold: "USD 1000",
+ },
+ ].map((e, idx) => {
+ e.rowid = idx;
+ e.threshold = `${e.threshold}${idx}`;
+ return e;
+ }),
+};
+
+export const exampleResponse: HttpResponsePaginated<AmlExchangeBackend.AmlRecords,AmlExchangeBackend.AmlError> = {
+ ok: true,
+ data: example1,
+ loadMore: () => {},
+ loadMorePrev: () => {},
+}
+
diff --git a/packages/aml-backoffice-ui/src/pages.ts b/packages/aml-backoffice-ui/src/pages.ts
index 18fb7a158..e4e16f05f 100644
--- a/packages/aml-backoffice-ui/src/pages.ts
+++ b/packages/aml-backoffice-ui/src/pages.ts
@@ -16,7 +16,7 @@ const cases: PageEntry = {
url: "#/cases",
view: Cases,
};
-const account: PageEntry<{ account?: string }> = {
+const account: PageEntry<{ account: string }> = {
url: pageDefinition("#/account/:account"),
view: CaseDetails,
};
diff --git a/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx b/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx
index d02d8b395..ce820d612 100644
--- a/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx
+++ b/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx
@@ -13,64 +13,13 @@ import { FlexibleForm } from "../forms/index.js";
import { UIFormField } from "../handlers/forms.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";
-const response: AmlExchangeBackend.AmlDecisionDetails = {
- aml_history: [
- {
- justification: "Lack of documentation",
- decider_pub: "ASDASDASD",
- decision_time: {
- t_s: Date.now() / 1000,
- },
- new_state: 2,
- new_threshold: "USD:0",
- },
- {
- justification: "Doing a transfer of high amount",
- decider_pub: "ASDASDASD",
- decision_time: {
- t_s: Date.now() / 1000 - 60 * 60 * 24 * 30 * 6,
- },
- new_state: 1,
- new_threshold: "USD:2000",
- },
- {
- justification: "Account is known to the system",
- decider_pub: "ASDASDASD",
- decision_time: {
- t_s: Date.now() / 1000 - 60 * 60 * 24 * 30 * 9,
- },
- new_state: 0,
- new_threshold: "USD:100",
- },
- ],
- kyc_attributes: [
- {
- collection_time: {
- t_s: Date.now() / 1000 - 60 * 60 * 24 * 30 * 8,
- },
- expiration_time: {
- t_s: Date.now() / 1000 - 60 * 60 * 24 * 30 * 4,
- },
- provider_section: "asdasd",
- attributes: {
- name: "Sebastian",
- },
- },
- {
- collection_time: {
- t_s: Date.now() / 1000 - 60 * 60 * 24 * 30 * 5,
- },
- expiration_time: {
- t_s: Date.now() / 1000 - 60 * 60 * 24 * 30 * 2,
- },
- provider_section: "asdasd",
- attributes: {
- creditCard: "12312312312",
- },
- },
- ],
-};
type AmlEvent = AmlFormEvent | KycCollectionEvent | KycExpirationEvent;
type AmlFormEvent = {
type: "aml-form";
@@ -127,7 +76,7 @@ function getEventsFromAmlHistory(
});
prev.push({
type: "kyc-expiration",
- title: "expired" as TranslatedString,
+ title: "expiration" as TranslatedString,
when: AbsoluteTime.fromProtocolTimestamp(k.expiration_time),
fields: !k.attributes ? [] : Object.keys(k.attributes),
});
@@ -136,19 +85,30 @@ function getEventsFromAmlHistory(
return ae.concat(ke).sort(selectSooner);
}
-export function CaseDetails({ account }: { account?: string }) {
- const events = getEventsFromAmlHistory(
- response.aml_history,
- response.kyc_attributes,
- );
- console.log("DETAILS", events, events[events.length - 1 - 2]);
- const [selected, setSelected] = useState<AmlEvent>(
- events[events.length - 1 - 2],
- );
+export function CaseDetails({ account: paytoHash }: { account: string }) {
+ const [selected, setSelected] = useState<AmlEvent | undefined>(undefined);
+
+ const officer = useOfficer();
+ const { i18n } = useTranslationContext();
+ if (officer.state !== "ready") {
+ return <HandleAccountNotReady officer={officer} />;
+ }
+ 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);
+ }
+ 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);
+
return (
<div>
<a
- href={Pages.newFormEntry.url({ account })}
+ href={Pages.newFormEntry.url({ account: paytoHash })}
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
@@ -271,13 +231,24 @@ function ShowConsolidated({
history: AmlEvent[];
until: AmlEvent;
}): VNode {
- console.log("UNTIL", until);
const cons = getConsolidated(history, until.when);
const form: FlexibleForm<Consolidated> = {
versionId: "1",
behavior: (form) => {
- return {};
+ return {
+ aml: {
+ threshold: {
+ hidden: !form.aml
+ },
+ since: {
+ hidden: !form.aml
+ },
+ state: {
+ hidden: !form.aml
+ }
+ }
+ };
},
design: [
{
@@ -356,8 +327,8 @@ function ShowConsolidated({
interface Consolidated {
aml: {
- state?: AmlExchangeBackend.AmlState;
- threshold?: AmountJson;
+ state: AmlExchangeBackend.AmlState;
+ threshold: AmountJson;
since: AbsoluteTime;
};
kyc: {
@@ -375,7 +346,13 @@ function getConsolidated(
): Consolidated {
const initial: Consolidated = {
aml: {
- since: AbsoluteTime.never(),
+ state: AmlExchangeBackend.AmlState.normal,
+ threshold: {
+ currency: "ARS",
+ value: 1000,
+ fraction: 0,
+ },
+ since: AbsoluteTime.never()
},
kyc: {},
};
@@ -391,9 +368,11 @@ function getConsolidated(
break;
}
case "aml-form": {
- prev.aml.threshold = cur.threshold;
- prev.aml.state = cur.state;
- prev.aml.since = cur.when;
+ prev.aml = {
+ since: cur.when,
+ state: cur.state,
+ threshold: cur.threshold
+ }
break;
}
case "kyc-collection": {
diff --git a/packages/aml-backoffice-ui/src/pages/Cases.tsx b/packages/aml-backoffice-ui/src/pages/Cases.tsx
index 8b115ed7e..990c0d2d4 100644
--- a/packages/aml-backoffice-ui/src/pages/Cases.tsx
+++ b/packages/aml-backoffice-ui/src/pages/Cases.tsx
@@ -12,59 +12,6 @@ import { buildQuerySignature } from "../account.js";
import { handleNotOkResult } from "../utils/errors.js";
import { useTranslationContext } from "@gnu-taler/web-util/browser";
-const response: AmlExchangeBackend.AmlRecords = {
- records: [
- {
- current_state: 0,
- h_payto: "QWEQWEQWEQWEWQE",
- rowid: 1,
- threshold: "USD 100",
- },
- {
- current_state: 1,
- h_payto: "ASDASDASD",
- rowid: 1,
- threshold: "USD 100",
- },
- {
- current_state: 2,
- h_payto: "ZXCZXCZXCXZC",
- rowid: 1,
- threshold: "USD 1000",
- },
- {
- current_state: 0,
- h_payto: "QWEQWEQWEQWEWQE",
- rowid: 1,
- threshold: "USD 100",
- },
- {
- current_state: 1,
- h_payto: "ASDASDASD",
- rowid: 1,
- threshold: "USD 100",
- },
- {
- current_state: 2,
- h_payto: "ZXCZXCZXCXZC",
- rowid: 1,
- threshold: "USD 1000",
- },
- ].map((e, idx) => {
- e.rowid = idx;
- e.threshold = `${e.threshold}${idx}`;
- return e;
- }),
-};
-
-function doFilter(
- list: typeof response.records,
- filter: AmlExchangeBackend.AmlState | undefined,
-): typeof response.records {
- if (filter === undefined) return list;
- return list.filter((r) => r.current_state === filter);
-}
-
export function Cases() {
const officer = useOfficer();
const { i18n } = useTranslationContext();
diff --git a/packages/aml-backoffice-ui/src/pages/NewFormEntry.tsx b/packages/aml-backoffice-ui/src/pages/NewFormEntry.tsx
index bbd04daee..13e78b169 100644
--- a/packages/aml-backoffice-ui/src/pages/NewFormEntry.tsx
+++ b/packages/aml-backoffice-ui/src/pages/NewFormEntry.tsx
@@ -2,8 +2,12 @@ import { VNode, h } from "preact";
import { allForms } from "./AntiMoneyLaunderingForm.js";
import { Pages } from "../pages.js";
import { NiceForm } from "../NiceForm.js";
-import { AbsoluteTime, Amounts } from "@gnu-taler/taler-util";
+import { AbsoluteTime, Amounts, 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";
export function NewFormEntry({
account,
@@ -12,30 +16,58 @@ export function NewFormEntry({
account?: string;
type?: string;
}): VNode {
+ const officer = useOfficer();
+
if (!account) {
return <div>no account</div>;
}
if (!type) {
return <SelectForm account={account} />;
}
+ if (officer.state !== "ready") {
+ return <HandleAccountNotReady officer={officer} />;
+ }
const selectedForm = Number.parseInt(type ?? "0", 10);
if (Number.isNaN(selectedForm)) {
return <div>WHAT! {type}</div>;
}
const showingFrom = allForms[selectedForm].impl;
+ const formName = allForms[selectedForm].name
const initial = {
fullName: "loggedIn_user_fullname",
when: AbsoluteTime.now(),
state: AmlExchangeBackend.AmlState.pending,
- threshold: Amounts.parseOrThrow("USD:10"),
+ threshold: Amounts.parseOrThrow("ARS:1000"),
};
+ const api = useAmlCasesAPI()
+
return (
<NiceForm
initial={initial}
form={showingFrom(initial)}
- onSubmit={(v) => {
- alert(JSON.stringify(v));
+ onSubmit={(formValue) => {
+ if (formValue.state === undefined || formValue.threshold === undefined) return;
+
+ const justification = {
+ index: selectedForm,
+ name: formName,
+ value: formValue
+ }
+ const decision: AmlExchangeBackend.AmlDecision = {
+ justification: JSON.stringify(justification),
+ decision_time: TalerProtocolTimestamp.now(),
+ h_payto: account,
+ new_state: formValue.state,
+ new_threshold: Amounts.stringify(formValue.threshold),
+ officer_sig: "",
+ kyc_requirements: undefined
+ }
+ const signature = buildDecisionSignature(officer.account.signingKey, decision);
+ decision.officer_sig = signature
+ api.updateDecision(officer.account.accountId, decision);
+
+ // alert(JSON.stringify(formValue));
}}
>
<div class="mt-6 flex items-center justify-end gap-x-6">
diff --git a/packages/aml-backoffice-ui/src/types.ts b/packages/aml-backoffice-ui/src/types.ts
index 104d938b3..429b538e7 100644
--- a/packages/aml-backoffice-ui/src/types.ts
+++ b/packages/aml-backoffice-ui/src/types.ts
@@ -59,6 +59,9 @@ export namespace AmlExchangeBackend {
type PaytoHash = string;
type Integer = number;
type Amount = string;
+ // EdDSA signatures are transmitted as 64-bytes base32
+ // binary-encoded objects with just the R and S values (base32_ binary-only).
+ type EddsaSignature = string;
export interface AmlRecords {
// Array of AML records matching the query.
@@ -85,4 +88,37 @@ export namespace AmlExchangeBackend {
pending = 1,
frozen = 2,
}
+
+
+ export interface AmlDecision {
+
+ // Human-readable justification for the decision.
+ justification: string;
+
+ // At what monthly transaction volume should the
+ // decision be automatically reviewed?
+ new_threshold: Amount;
+
+ // 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[];
+ }
+
+
}