aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-util
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2024-08-13 15:27:41 -0300
committerSebastian <sebasjm@gmail.com>2024-08-13 15:27:59 -0300
commitaa4fc564777aab82e16dd4c02012682ff67a3e8a (patch)
tree79470e282d3b953982be30db4b2ae3d5a056d4c1 /packages/taler-util
parentd5f3a51c5684d6d29a3b9c1b956b2cfc68434f23 (diff)
downloadwallet-core-aa4fc564777aab82e16dd4c02012682ff67a3e8a.tar.xz
sync aml/kyc api, wip
Diffstat (limited to 'packages/taler-util')
-rw-r--r--packages/taler-util/src/http-client/exchange.ts447
-rw-r--r--packages/taler-util/src/http-client/officer-account.ts68
-rw-r--r--packages/taler-util/src/types-taler-common.ts28
-rw-r--r--packages/taler-util/src/types-taler-exchange.ts743
4 files changed, 1160 insertions, 126 deletions
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, {
@@ -171,6 +185,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");