commit 97f0842cdd5072cf5eaa5284d333541dbf2e034a
parent f77c4e71874a2f4493079f796976354759b3f95a
Author: Florian Dold <florian@dold.me>
Date: Sat, 3 May 2025 22:16:00 +0200
wallet-core: request KYC for current balance threshold, not next
Diffstat:
3 files changed, 51 insertions(+), 161 deletions(-)
diff --git a/packages/taler-harness/src/integrationtests/test-kyc-balance-withdrawal.ts b/packages/taler-harness/src/integrationtests/test-kyc-balance-withdrawal.ts
@@ -18,180 +18,59 @@
* Imports.
*/
import {
+ Configuration,
encodeCrock,
ExchangeWalletKycStatus,
hashFullPaytoUri,
j2s,
- TalerCorebankApiClient,
TransactionIdStr,
TransactionMajorState,
TransactionMinorState,
} from "@gnu-taler/taler-util";
-import {
- createSyncCryptoApi,
- EddsaKeyPairStrings,
- WalletApiOperation,
-} from "@gnu-taler/taler-wallet-core";
-import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
-import {
- BankService,
- DbInfo,
- ExchangeService,
- getTestHarnessPaytoForLabel,
- GlobalTestState,
- HarnessExchangeBankAccount,
- setupDb,
- WalletClient,
- WalletService,
-} from "../harness/harness.js";
+import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import {
configureCommonKyc,
- EnvOptions,
+ createKycTestkudosEnvironment,
postAmlDecisionNoRules,
withdrawViaBankV3,
} from "../harness/environments.js";
-
-interface KycTestEnv {
- commonDb: DbInfo;
- bankClient: TalerCorebankApiClient;
- exchange: ExchangeService;
- exchangeBankAccount: HarnessExchangeBankAccount;
- walletClient: WalletClient;
- walletService: WalletService;
- amlKeypair: EddsaKeyPairStrings;
-}
-
-async function createKycTestkudosEnvironment(
- t: GlobalTestState,
- coinConfig: CoinConfig[] = defaultCoinConfig.map((x) => x("TESTKUDOS")),
- opts: EnvOptions = {},
-): Promise<KycTestEnv> {
- const db = await setupDb(t);
-
- const bank = await BankService.create(t, {
- allowRegistrations: true,
- currency: "TESTKUDOS",
- database: db.connStr,
- httpPort: 8082,
- });
-
- const exchange = ExchangeService.create(t, {
- name: "testexchange-1",
- currency: "TESTKUDOS",
- httpPort: 8081,
- database: db.connStr,
- });
-
- let receiverName = "Exchange";
- let exchangeBankUsername = "exchange";
- let exchangeBankPassword = "mypw-password";
- let exchangePaytoUri = getTestHarnessPaytoForLabel(exchangeBankUsername);
-
- const exchangeBankAccount: HarnessExchangeBankAccount = {
- wireGatewayAuth: {
- username: exchangeBankUsername,
- password: exchangeBankPassword,
- },
- wireGatewayApiBaseUrl: new URL(
- "accounts/exchange/taler-wire-gateway/",
- bank.baseUrl,
- ).href,
- accountPaytoUri: exchangePaytoUri,
- }
-
- await exchange.addBankAccount("1", exchangeBankAccount);
-
- bank.setSuggestedExchange(exchange, exchangePaytoUri);
-
- await bank.start();
-
- await bank.pingUntilAvailable();
-
- const bankClient = new TalerCorebankApiClient(bank.corebankApiBaseUrl, {
- auth: {
- username: "admin",
- password: "admin-password",
- },
- });
-
- await bankClient.registerAccountExtended({
- name: receiverName,
- password: exchangeBankPassword,
- username: exchangeBankUsername,
- is_taler_exchange: true,
- payto_uri: exchangePaytoUri,
- });
-
- exchange.addCoinConfigList(coinConfig);
-
- await exchange.modifyConfig(async (config) => {
- configureCommonKyc(config);
-
- config.setString("KYC-RULE-R1", "operation_type", "balance");
- config.setString("KYC-RULE-R1", "enabled", "yes");
- config.setString("KYC-RULE-R1", "exposed", "yes");
- config.setString("KYC-RULE-R1", "is_and_combinator", "yes");
- config.setString("KYC-RULE-R1", "threshold", "TESTKUDOS:10");
- config.setString("KYC-RULE-R1", "timeframe", "forever");
- config.setString("KYC-RULE-R1", "next_measures", "M1");
-
- config.setString("KYC-MEASURE-M1", "check_name", "C1");
- config.setString("KYC-MEASURE-M1", "context", "{}");
- config.setString("KYC-MEASURE-M1", "program", "NONE");
-
- config.setString("KYC-CHECK-C1", "type", "INFO");
- config.setString("KYC-CHECK-C1", "description", "my check!");
- config.setString("KYC-CHECK-C1", "fallback", "FREEZE");
- });
-
- await exchange.start();
-
- const cryptoApi = createSyncCryptoApi();
- const amlKeypair = await cryptoApi.createEddsaKeypair({});
-
- await exchange.enableAmlAccount(amlKeypair.pub, "Alice");
-
- const walletService = new WalletService(t, {
- name: "wallet",
- useInMemoryDb: true,
- });
- await walletService.start();
- await walletService.pingUntilAvailable();
-
- const walletClient = new WalletClient({
- name: "wallet",
- unixPath: walletService.socketPath,
- onNotification(n) {
- console.log("got notification", n);
- },
- });
- await walletClient.connect();
- await walletClient.client.call(WalletApiOperation.InitWallet, {
- config: {
- testing: {
- skipDefaults: true,
- },
- },
- });
-
- console.log("setup done!");
-
- return {
- commonDb: db,
- exchange,
- amlKeypair,
- walletClient,
- walletService,
- bankClient,
- exchangeBankAccount,
- };
+import { GlobalTestState } from "../harness/harness.js";
+
+function adjustExchangeConfig(config: Configuration): void {
+ configureCommonKyc(config);
+
+ config.setString("KYC-RULE-R1", "operation_type", "balance");
+ config.setString("KYC-RULE-R1", "enabled", "yes");
+ config.setString("KYC-RULE-R1", "exposed", "yes");
+ config.setString("KYC-RULE-R1", "is_and_combinator", "yes");
+ config.setString("KYC-RULE-R1", "threshold", "TESTKUDOS:10");
+ config.setString("KYC-RULE-R1", "timeframe", "forever");
+ config.setString("KYC-RULE-R1", "next_measures", "M1");
+
+ config.setString("KYC-RULE-R2", "operation_type", "balance");
+ config.setString("KYC-RULE-R2", "enabled", "yes");
+ config.setString("KYC-RULE-R2", "exposed", "yes");
+ config.setString("KYC-RULE-R2", "is_and_combinator", "yes");
+ config.setString("KYC-RULE-R2", "threshold", "TESTKUDOS:30");
+ config.setString("KYC-RULE-R2", "timeframe", "forever");
+ config.setString("KYC-RULE-R2", "next_measures", "M1");
+
+ config.setString("KYC-MEASURE-M1", "check_name", "C1");
+ config.setString("KYC-MEASURE-M1", "context", "{}");
+ config.setString("KYC-MEASURE-M1", "program", "NONE");
+
+ config.setString("KYC-CHECK-C1", "type", "INFO");
+ config.setString("KYC-CHECK-C1", "description", "my check!");
+ config.setString("KYC-CHECK-C1", "fallback", "FREEZE");
}
export async function runKycBalanceWithdrawalTest(t: GlobalTestState) {
// Set up test environment
const { walletClient, bankClient, exchange, amlKeypair } =
- await createKycTestkudosEnvironment(t);
+ await createKycTestkudosEnvironment(t, {
+ adjustExchangeConfig,
+ });
// Withdraw digital cash into the wallet.
@@ -238,6 +117,11 @@ export async function runKycBalanceWithdrawalTest(t: GlobalTestState) {
ExchangeWalletKycStatus.Legi,
);
+ t.assertDeepEqual(
+ exchangeEntry.walletKycRequestedThreshold,
+ "TESTKUDOS:10",
+ );
+
const kycReservePub = exchangeEntry.walletKycReservePub;
t.assertTrue(!!kycReservePub);
diff --git a/packages/taler-util/src/types-taler-wallet.ts b/packages/taler-util/src/types-taler-wallet.ts
@@ -1595,6 +1595,8 @@ export interface ExchangeListItem {
walletKycReservePub?: string;
walletKycAccessToken?: string;
walletKycUrl?: string;
+ /** Threshold that we've requested to satisfy. */
+ walletKycRequestedThreshold?: string;
/**
* P2P payments are disabled with this exchange
diff --git a/packages/taler-wallet-core/src/exchanges.ts b/packages/taler-wallet-core/src/exchanges.ts
@@ -504,6 +504,7 @@ async function makeExchangeListItem(
? new URL(`kyc-spa/${reserveRec.kycAccessToken}`, r.baseUrl).href
: undefined,
walletKycAccessToken: reserveRec?.kycAccessToken,
+ walletKycRequestedThreshold: reserveRec?.thresholdRequested,
tosStatus: getExchangeTosStatusFromRecord(r),
ageRestrictionOptions: exchangeDetails?.ageMask
? AgeRestriction.getAgeGroupsFromMask(exchangeDetails.ageMask)
@@ -3234,6 +3235,7 @@ export type BalanceThresholdCheckResult =
}
| {
result: "violation";
+ /** Threshold that needs to be requested to proceed. */
nextThreshold: AmountString;
walletKycStatus: ExchangeWalletKycStatus | undefined;
walletKycAccessToken: string | undefined;
@@ -3312,11 +3314,10 @@ export async function checkIncomingAmountLegalUnderKycBalanceThreshold(
limits.sort((a, b) => Amounts.cmp(a, b));
logger.trace(`applicable limits: ${j2s(limits)}`);
let limViolated: AmountString | undefined = undefined;
- let limNext: AmountString | undefined = undefined;
for (let i = 0; i < limits.length; i++) {
if (Amounts.cmp(limits[i], balExpected) <= 0) {
limViolated = limits[i];
- limNext = limits[i + 1];
+ const limNext = limits[i + 1];
if (limNext == null || Amounts.cmp(limNext, balExpected) > 0) {
break;
}
@@ -3328,12 +3329,10 @@ export async function checkIncomingAmountLegalUnderKycBalanceThreshold(
result: "ok",
};
} else {
- logger.info(
- `balance limit ${limViolated} would be violated, next is ${limNext}`,
- );
+ logger.info(`balance limit ${limViolated} would be violated`);
return {
result: "violation",
- nextThreshold: limNext ?? limViolated,
+ nextThreshold: limViolated,
walletKycStatus: reserveRec?.status
? getKycStatusFromReserveStatus(reserveRec.status)
: undefined,
@@ -3442,6 +3441,11 @@ export async function handleTestingWaitExchangeWalletKyc(
return {};
}
+/**
+ * Start a wallet KYC process.
+ *
+ * Does nothing if the KYC process has already been started.
+ */
export async function handleStartExchangeWalletKyc(
wex: WalletExecutionContext,
req: StartExchangeWalletKycRequest,