taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit 7b93f4abc1015179071b37c1fb5251a1b9c96312
parent 9838505956eb5e4b5067258311b8d4482ef1a5ff
Author: Sebastian <sebasjm@gmail.com>
Date:   Fri,  1 Nov 2024 14:32:53 -0300

reproducing #9154

Diffstat:
Mpackages/taler-harness/src/harness/environments.ts | 6+++---
Mpackages/taler-harness/src/index.ts | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Mpackages/taler-harness/src/integrationtests/test-kyc-skip-expiration.ts | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mpackages/taler-harness/src/integrationtests/testrunner.ts | 8+++++++-
Mpackages/taler-util/src/http-client/exchange.ts | 16++++++++++++++++
Mpackages/taler-util/src/index.node.ts | 1+
Mpackages/taler-util/src/index.ts | 13+++++++++++--
Apackages/taler-util/src/kyc-aml-utils.ts | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dpackages/taler-util/src/types-taler-aml.ts | 162-------------------------------------------------------------------------------
Mpackages/taler-util/src/types-taler-exchange.ts | 5+++++
Apackages/taler-util/src/types-taler-kyc-aml.ts | 333+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
11 files changed, 561 insertions(+), 176 deletions(-)

diff --git a/packages/taler-harness/src/harness/environments.ts b/packages/taler-harness/src/harness/environments.ts @@ -24,6 +24,7 @@ * Imports */ import { + AccountProperties, AmlDecisionRequest, AmlDecisionRequestWithoutSignature, AmountString, @@ -1063,6 +1064,7 @@ export async function postAmlDecision( amlPub: string; newRules: LegitimizationRuleSet; newMeasure?: string | undefined; + properties?: AccountProperties; }, ) { const { exchangeBaseUrl, paytoHash, amlPriv, amlPub } = req; @@ -1074,9 +1076,7 @@ export async function postAmlDecision( keep_investigating: false, new_rules: req.newRules, new_measures: req.newMeasure, - properties: { - foo: "42", - }, + properties: req.properties ?? {}, }; const sig = signAmlDecision(decodeCrock(amlPriv), sigData); diff --git a/packages/taler-harness/src/index.ts b/packages/taler-harness/src/index.ts @@ -64,10 +64,7 @@ import { deepStrictEqual } from "assert"; import fs from "fs"; import os from "os"; import path from "path"; -import { - AmlOutcome, - codecForAmlProgramInput, -} from "../../taler-util/src/types-taler-aml.js"; +import { TalerKycAml } from "@gnu-taler/taler-util"; import { runBench1 } from "./bench1.js"; import { runBench2 } from "./bench2.js"; import { runBench3 } from "./bench3.js"; @@ -85,6 +82,7 @@ import { } from "./harness/harness.js"; import { getTestInfo, runTests } from "./integrationtests/testrunner.js"; import { lintExchangeDeployment } from "./lint.js"; +import { AML_PROGRAM_FROM_ATTRIBUTES_TO_CONTEXT } from "integrationtests/test-kyc-skip-expiration.js"; const logger = new Logger("taler-harness:index.ts"); @@ -1434,6 +1432,71 @@ export const amlProgramCli = testingCli.subcommand( }, ); +const allAmlPrograms: TalerKycAml.AmlProgramDefinition[] = [ + { + name: "no-rules", + logic: (_input, _config) => { + const outcome: TalerKycAml.AmlOutcome = { + new_rules: { + expiration_time: TalerProtocolTimestamp.never(), + rules: [], + custom_measures: {}, + }, + events: [], + }; + return outcome; + }, + requiredAttributes: [], + requiredContext: [], + }, + AML_PROGRAM_FROM_ATTRIBUTES_TO_CONTEXT, +]; + +amlProgramCli + .subcommand("run", "run-program") + .requiredOption("name", ["-n", "--name"], clk.STRING) + .flag("requires", ["-r"]) + .flag("attributes", ["-a"]) + .maybeOption("config", ["-c", "--config"], clk.STRING) + .action(async (args) => { + const found = allAmlPrograms.find((p) => p.name === args.run.name); + if (!found) { + logger.error(`Program not found: ${args.run.name}`); + logger.error( + `you can try "${allAmlPrograms.map((p) => p.name).join(",")}"`, + ); + return; + } + if (args.run.requires) { + logger.info("Reporting requirements"); + console.log(found.requiredContext.join("\n")); + return; + } + + if (args.run.attributes) { + logger.info("reporting attributes"); + console.log(found.requiredAttributes.join("\n")); + return; + } + + const buffers = []; + // node.js readable streams implement the async iterator protocol + for await (const data of process.stdin) { + buffers.push(data); + } + + const finalBuffer = Buffer.concat(buffers); + const inputStr = finalBuffer.toString("utf-8"); + const inputJson = JSON.parse(inputStr); + const progInput = TalerKycAml.codecForAmlProgramInput().decode(inputJson); + + logger.info(`got input: ${j2s(progInput)}`); + + const outcome = found.logic(progInput, args.run.config); + + console.log(j2s(outcome)); + }); + amlProgramCli .subcommand("noRules", "no-rules") .flag("requires", ["-r"]) @@ -1463,11 +1526,11 @@ amlProgramCli const finalBuffer = Buffer.concat(buffers); const inputStr = finalBuffer.toString("utf-8"); const inputJson = JSON.parse(inputStr); - const progInput = codecForAmlProgramInput().decode(inputJson); + const progInput = TalerKycAml.codecForAmlProgramInput().decode(inputJson); logger.info(`got input: ${j2s(progInput)}`); - const outcome: AmlOutcome = { + const outcome: TalerKycAml.AmlOutcome = { new_rules: { expiration_time: TalerProtocolTimestamp.never(), rules: [], diff --git a/packages/taler-harness/src/integrationtests/test-kyc-skip-expiration.ts b/packages/taler-harness/src/integrationtests/test-kyc-skip-expiration.ts @@ -18,6 +18,7 @@ * Imports. */ import { + AbsoluteTime, codecForAny, codecForKycProcessClientInformation, codecOptional, @@ -26,6 +27,7 @@ import { encodeCrock, j2s, signAmlQuery, + TalerKycAml, TalerProtocolTimestamp, TransactionIdStr, TransactionMajorState, @@ -40,6 +42,44 @@ import { } from "../harness/environments.js"; import { GlobalTestState, harnessHttpLib, waitMs } from "../harness/harness.js"; +export const AML_PROGRAM_FROM_ATTRIBUTES_TO_CONTEXT: TalerKycAml.AmlProgramDefinition = + { + name: "from-attr-to-context", + logic: (_input, config) => { + const now = AbsoluteTime.toProtocolTimestamp(AbsoluteTime.now()); + const outcome: TalerKycAml.AmlOutcome = { + to_investigate: false, + // pushing to info into properties for testing purposes + properties: { + "this comes": "from the program", + input: _input as any, + config, + }, + events: [], + new_rules: { + expiration_time: now, + new_check: "info-oauth-test-passed", + rules: [], + successor_measure: "ask_more_info", + custom_measures: { + ask_more_info: { + context: { + info: _input?.attributes, + // this is the context info that the KYC-SPA will see + WAT: "REALLY?", + }, + check_name: "C2", + prog_name: "P2", + }, + }, + }, + }; + return outcome; + }, + requiredAttributes: [], + requiredContext: [], + }; + function adjustExchangeConfig(config: Configuration) { config.setString("exchange", "enable_kyc", "yes"); @@ -66,7 +106,7 @@ function adjustExchangeConfig(config: Configuration) { config.setString( "AML-PROGRAM-P1", "command", - "taler-harness aml-program no-rules", + "taler-harness aml-program run-program --name from-attr-to-context", ); config.setString("AML-PROGRAM-P1", "enabled", "true"); config.setString("AML-PROGRAM-P1", "description", "remove all rules"); @@ -86,9 +126,11 @@ function adjustExchangeConfig(config: Configuration) { config.setString("KYC-CHECK-C1", "outputs", "full_name birthdate"); config.setString("KYC-CHECK-C1", "fallback", "M1"); - config.setString("KYC-CHECK-C2", "type", "INFO"); + config.setString("KYC-CHECK-C2", "type", "FORM"); + config.setString("KYC-CHECK-C2", "form_name", "dynamicform"); config.setString("KYC-CHECK-C2", "description", "my check info!"); config.setString("KYC-CHECK-C2", "description_i18n", "{}"); + config.setString("KYC-CHECK-C1", "outputs", "what_the_officer_asked"); config.setString("KYC-CHECK-C2", "fallback", "M2"); config.setString("KYC-CHECK-C3", "type", "INFO"); @@ -180,6 +222,9 @@ export async function runKycSkipExpirationTest(t: GlobalTestState) { exchangeBaseUrl: exchange.baseUrl, paytoHash: kycPaytoHash, newMeasure: "M3", + properties: { + form: { name: "string" }, + }, newRules: { expiration_time: TalerProtocolTimestamp.now(), custom_measures: {}, @@ -231,6 +276,14 @@ export async function runKycSkipExpirationTest(t: GlobalTestState) { console.log(j2s(clientInfo)); + // Finally here we must see the officer defined form + t.assertDeepEqual(clientInfo?.requirements[0].context, { + // info contains the properties in the aml decision + info: { form: { name: "string" } }, + // this is fixed by the aml program + WAT: "REALLY?", + }); + break; } } diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts @@ -14,7 +14,13 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { CancellationToken, Logger, minimatch } from "@gnu-taler/taler-util"; +import { + CancellationToken, + Logger, + minimatch, + TalerKycAml, + TalerProtocolTimestamp, +} from "@gnu-taler/taler-util"; import * as child_process from "child_process"; import { spawnSync } from "child_process"; import * as fs from "fs"; diff --git a/packages/taler-util/src/http-client/exchange.ts b/packages/taler-util/src/http-client/exchange.ts @@ -1,3 +1,19 @@ +/* + This file is part of GNU Taler + (C) 2022-2024 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + import { HttpRequestLibrary, readSuccessResponseJsonOrThrow, diff --git a/packages/taler-util/src/index.node.ts b/packages/taler-util/src/index.node.ts @@ -21,4 +21,5 @@ initNodePrng(); export * from "./index.js"; export * from "./talerconfig.js"; export * from "./globbing/minimatch.js"; +export * from "./kyc-aml-utils.js"; export { setPrintHttpRequestAsCurl } from "./http-impl.node.js"; diff --git a/packages/taler-util/src/index.ts b/packages/taler-util/src/index.ts @@ -22,7 +22,12 @@ export * from "./http-client/challenger.js"; export * from "./http-client/exchange.js"; export * from "./http-client/merchant.js"; export * from "./http-client/officer-account.js"; -export { CacheEvictor, BasicOrTokenAuth, BasicAuth, TokenAuth } from "./http-client/utils.js"; +export { + CacheEvictor, + BasicOrTokenAuth, + BasicAuth, + TokenAuth, +} from "./http-client/utils.js"; export * from "./http-status-codes.js"; export * from "./i18n.js"; export * from "./iban.js"; @@ -56,12 +61,15 @@ export * from "./timer.js"; export * from "./transaction-test-data.js"; export * from "./url.js"; +// FIXME: remove all this, needs refactor export * from "./types-taler-bank-conversion.js"; export * from "./types-taler-bank-integration.js"; -export * from "./types-taler-common.js"; export * from "./types-taler-corebank.js"; export * from "./types-taler-exchange.js"; export * from "./types-taler-merchant.js"; +// end + +export * from "./types-taler-common.js"; export * from "./types-taler-sync.js"; export * from "./types-taler-wallet-transactions.js"; export * from "./types-taler-wallet.js"; @@ -74,6 +82,7 @@ export * as TalerExchangeApi from "./types-taler-exchange.js"; export * as TalerMerchantApi from "./types-taler-merchant.js"; export * as TalerRevenueApi from "./types-taler-revenue.js"; export * as TalerWireGatewayApi from "./types-taler-wire-gateway.js"; +export * as TalerKycAml from "./types-taler-kyc-aml.js"; export * from "./taler-signatures.js"; diff --git a/packages/taler-util/src/kyc-aml-utils.ts b/packages/taler-util/src/kyc-aml-utils.ts @@ -0,0 +1,61 @@ +/* + This file is part of GNU Taler + (C) 2022-2024 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +import { AmlProgramParams, KycConverterParams } from "./types-taler-kyc-aml.js"; + +// https://docs.taler.net/taler-kyc-manual.html#implementing-your-own-aml-programs + +export function parseKycConverterParams(): KycConverterParams { + return process.argv.reduce((prev, arg, idx, list) => { + if (idx === 0) { + prev.name = arg; + } + if (arg === "-v") { + prev.showVersion = true; + } else if (arg === "-V") { + prev.debug = true; + } else if (arg === "--list-outputs") { + prev.showOutputs = true; + } else if (arg === "-h") { + prev.showHelp = true; + } else if (arg === "-c") { + prev.config = list[idx + 1]; + } + return prev; + }, {} as KycConverterParams); +} + +export function parseAmlProgramParams(): AmlProgramParams { + return process.argv.reduce((prev, arg, idx, list) => { + if (idx === 0) { + prev.name = arg; + } + if (arg === "-v") { + prev.showVersion = true; + } else if (arg === "-V") { + prev.debug = true; + } else if (arg === "-r") { + prev.showRequiredContext = true; + } else if (arg === "-a") { + prev.showRequiredAttributes = true; + } else if (arg === "-h") { + prev.showHelp = true; + } else if (arg === "-c") { + prev.config = list[idx + 1]; + } + return prev; + }, {} as AmlProgramParams); +} diff --git a/packages/taler-util/src/types-taler-aml.ts b/packages/taler-util/src/types-taler-aml.ts @@ -1,162 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2024 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU Affero General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU Affero General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - - SPDX-License-Identifier: AGPL-3.0-or-later - */ - -/** - * @fileoverview Type and schema definitions and helpers for the Taler AML helpers. - */ - -/** - * Imports. - */ -import { - buildCodecForObject, - Codec, - codecForAny, - codecForList, -} from "./index.js"; -import { TalerProtocolTimestamp } from "./time.js"; -import { - AccountProperties, - codecForAccountProperties, - LegitimizationRuleSet, -} from "./types-taler-exchange.js"; - -export interface AmlProgramInput { - // JSON object that was provided as - // part of the *measure*. This JSON object is - // provided under "context" in the main JSON object - // input to the AML program. This "context" should - // satify both the REQUIRES clause of the respective - // check and the output of "-r" from the - // AML program's command-line option. - context?: Record<string, unknown>; - - // JSON object that captures the - // output of a ``[kyc-provider-]`` or (HTML) FORM. - // In the case of KYC data provided by providers, - // the keys in the JSON object will be the attribute - // names and the values must be strings representing - // the data. In the case of file uploads, the data - // MUST be base64-encoded. - // In the case of KYC data provided by HTML FORMs, the - // keys will match the HTML FORM field names and - // the values will use the `KycStructuredFormData` - // encoding. - attributes: Record<string, unknown>; - - // JSON array with the results of historic - // AML desisions about the account. - aml_history: AmlHistoryEntry[]; - - // JSON array with the results of historic - // KYC data about the account. - kyc_history: KycHistoryEntry[]; -} - -export interface AmlHistoryEntry { - // When was the AML decision taken. - decision_time: TalerProtocolTimestamp; - - // What was the justification given for the decision. - justification: string; - - // Public key of the AML officer taking the decision. - decider_pub: string; - - // Properties associated with the account by the decision. - properties: Object; - - // New set of legitimization rules that was put in place. - new_rules: LegitimizationRuleSet; - - // True if the account was flagged for (further) - // investigation. - to_investigate: boolean; - - // True if this is the currently active decision. - is_active: boolean; -} - -export interface KycHistoryEntry { - // 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; - - // True if the KYC process completed. - finished: boolean; - - // Numeric `error code <error-codes>`, if the - // KYC process did not succeed; 0 on success. - code: number; - - // Human-readable description of ``code``. Optional. - hint?: string; - - // Optional detail given when the KYC process failed. - error_message?: string; - - // Identifier of the user at the KYC provider. Optional. - provider_user_id?: string; - - // Identifier of the KYC process at the KYC provider. Optional. - provider_legitimization_id?: string; - - // The collected KYC data. - // NULL if the attribute data could not - // be decrypted or was not yet collected. - attributes?: Record<string, unknown>; - - // Time when the KYC data was collected - collection_time: TalerProtocolTimestamp; - - // Time when the KYC data will expire. - expiration_time: TalerProtocolTimestamp; -} - -export interface AmlOutcome { - // Should the client's account be investigated - // by AML staff? - // Defaults to false. - to_investigate?: boolean; - - // 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; - - // Types of events to add to the KYC events table. - // (for statistics). - events?: string[]; - - // KYC rules to apply. Note that this - // overrides *all* of the default rules - // until the ``expiration_time`` and specifies - // the successor measure to apply after the - // expiration time. - new_rules: LegitimizationRuleSet; -} - -export const codecForAmlProgramInput = (): Codec<AmlProgramInput> => - buildCodecForObject<AmlProgramInput>() - .property("aml_history", codecForList(codecForAny())) - .property("kyc_history", codecForList(codecForAny())) - .property("attributes", codecForAccountProperties()) - .property("context", codecForAny()) - .build("AmlProgramInput"); diff --git a/packages/taler-util/src/types-taler-exchange.ts b/packages/taler-util/src/types-taler-exchange.ts @@ -1776,6 +1776,10 @@ export interface KycRequirementInformation { // English description of the requirement. description: string; + // Object with arbitrary additional context, completely depends on + // the specific form. + context?: Object; + // Map from IETF BCP 47 language tags to localized // description texts. description_i18n?: { [lang_tag: string]: string }; @@ -2581,6 +2585,7 @@ export const codecForKycRequirementInformation = ), ) .property("description", codecForString()) + .property("context", codecOptional(codecForAny())) .property( "description_i18n", codecOptional(codecForInternationalizedString()), diff --git a/packages/taler-util/src/types-taler-kyc-aml.ts b/packages/taler-util/src/types-taler-kyc-aml.ts @@ -0,0 +1,333 @@ +/* + This file is part of GNU Taler + (C) 2022-2024 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +import { + buildCodecForObject, + Codec, + codecForAccountProperties, + codecForAny, + codecForList, +} from "./index.js"; +import { + AmountString, + Integer, + RelativeTime, + Timestamp, +} from "./types-taler-common.js"; + +// https://docs.taler.net/taler-kyc-manual.html#implementing-your-own-aml-programs + +export type AmlProgram = ( + input: AmlProgramInput | undefined, + config: string | undefined, +) => AmlOutcome; + +export type KycConverter = ( + input: object | undefined, + config: string | undefined, +) => object; + +export type AmlProgramParams = { + name: string; + debug?: boolean; + showVersion?: boolean; + showRequiredContext?: boolean; + showRequiredAttributes?: boolean; + showHelp?: boolean; + config?: string; +}; + +export type KycConverterParams = { + name: string; + debug?: boolean; + showVersion?: boolean; + showOutputs?: boolean; + showHelp?: boolean; + config?: string; +}; + +export type KycConverterDefinition = { + name: string; + logic: KycConverter; + outputs: string[]; +}; + +export type AmlProgramDefinition = { + name: string; + requiredContext: string[]; + requiredAttributes: string[]; + logic: AmlProgram; +}; + +export interface AmlProgramInput { + // JSON object that was provided as + // part of the *measure*. This JSON object is + // provided under "context" in the main JSON object + // input to the AML program. This "context" should + // satify both the REQUIRES clause of the respective + // check and the output of "-r" from the + // AML program's command-line option. + context?: any; + + // JSON object that captures the + // output of a [kyc-provider-] or (HTML) FORM. + // In the case of KYC data provided by providers, + // the keys in the JSON object will be the attribute + // names and the values must be strings representing + // the data. In the case of file uploads, the data + // MUST be base64-encoded. + // In the case of KYC data provided by HTML FORMs, the + // keys will match the HTML FORM field names and + // the values will use the KycStructuredFormData + // encoding. + attributes: any; + + // JSON array with the results of historic + // AML desisions about the account. + aml_history: AmlHistoryEntry[]; + + // JSON array with the results of historic + // KYC data about the account. + kyc_history: KycHistoryEntry[]; +} + +export interface AmlHistoryEntry { + // When was the AML decision taken. + decision_time: Timestamp; + + // What was the justification given for the decision. + justification: string; + + // Public key of the AML officer taking the decision. + decider_pub: string; + + // Properties associated with the account by the decision. + properties: Object; + + // New set of legitimization rules that was put in place. + new_rules: LegitimizationRuleSet; + + // True if the account was flagged for (further) + // investigation. + to_investigate: boolean; + + // True if this is the currently active decision. + is_active: boolean; +} +export interface KycHistoryEntry { + // 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; + + // True if the KYC process completed. + finished: boolean; + + // Numeric error code, if the + // KYC process did not succeed; 0 on success. + code: number; + + // Human-readable description of code. Optional. + hint?: string; + + // Optional detail given when the KYC process failed. + error_message?: string; + + // Identifier of the user at the KYC provider. Optional. + provider_user_id?: string; + + // Identifier of the KYC process at the KYC provider. Optional. + provider_legitimization_id?: string; + + // The collected KYC data. + // NULL if the attribute data could not + // be decrypted or was not yet collected. + attributes?: Object; + + // Time when the KYC data was collected + collection_time: Timestamp; + + // Time when the KYC data will expire. + expiration_time: Timestamp; +} + +export interface AmlOutcome { + // Should the client's account be investigated + // by AML staff? + // Defaults to false. + to_investigate?: boolean; + + // 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; + + // Types of events to add to the KYC events table. + // (for statistics). + events?: string[]; + + // KYC rules to apply. Note that this + // overrides *all* of the default rules + // until the expiration_time and specifies + // the successor measure to apply after the + // expiration time. + new_rules: LegitimizationRuleSet; +} + +// 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; + + [key: string]: string | boolean | number | undefined; +} + +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 + new_check?: string; + // successor_measure may refer to. + custom_measures: { [measure: string]: MeasureInformation }; +} + +export interface KycRule { + // Type of operation to which the rule applies. + // + // Must be one of "WITHDRAW", "DEPOSIT", + // (p2p) "MERGE", (wallet) "BALANCE", + // (reserve) "CLOSE", "AGGREGATE", + // "TRANSACTION" or "REFUND". + 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 MeasureInformation { + // Name of a KYC check. + check_name: string; + + // Name of an AML program. + prog_name: string; + + // Context for the check. Optional. + context?: Object; + + // Operation that this measure relates to. + // NULL if unknown. Useful as a hint to the + // user if there are many (voluntary) measures + // and some related to unlocking certain operations. + // (and due to zero-amount thresholds, no measure + // was actually specifically triggered). + // + // Must be one of "WITHDRAW", "DEPOSIT", + // (p2p) "MERGE", (wallet) "BALANCE", + // (reserve) "CLOSE", "AGGREGATE", + // "TRANSACTION" or "REFUND". + // New in protocol **v21**. + operation_type?: string; + + // Can this measure be undertaken voluntarily? + // Optional, default is false. + // Since protocol **vATTEST**. + voluntary?: boolean; +} + +export const codecForAmlProgramInput = (): Codec<AmlProgramInput> => + buildCodecForObject<AmlProgramInput>() + .property("aml_history", codecForList(codecForAny())) + .property("kyc_history", codecForList(codecForAny())) + .property("attributes", codecForAccountProperties()) + .property("context", codecForAny()) + .build("AmlProgramInput");