commit 9191d998f8327bfa0a165e9a9a353ecd3c726df0
parent 0fdd0c48dc747d16a5a5fe4752873c91b353e614
Author: Florian Dold <florian@dold.me>
Date: Sun, 6 Oct 2024 16:31:52 +0200
wallet-core: kyc hard/soft limits
Diffstat:
7 files changed, 370 insertions(+), 202 deletions(-)
diff --git a/packages/taler-harness/src/integrationtests/test-kyc-threshold-withdrawal.ts b/packages/taler-harness/src/integrationtests/test-kyc-threshold-withdrawal.ts
@@ -19,205 +19,55 @@
*/
import {
NotificationType,
- TalerCorebankApiClient,
TransactionMajorState,
TransactionMinorState,
TransactionType,
} from "@gnu-taler/taler-util";
+import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
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 { EnvOptions, postAmlDecisionNoRules } 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";
- let exchangePaytoUri = getTestHarnessPaytoForLabel(exchangeBankUsername);
-
- await exchange.addBankAccount("1", {
- accountName: exchangeBankUsername,
- accountPassword: exchangeBankPassword,
- wireGatewayApiBaseUrl: new URL(
- "accounts/exchange/taler-wire-gateway/",
- bank.baseUrl,
- ).href,
- accountPaytoUri: exchangePaytoUri,
- });
-
- bank.setSuggestedExchange(exchange, exchangePaytoUri);
-
- await bank.start();
-
- await bank.pingUntilAvailable();
-
- const bankClient = new TalerCorebankApiClient(bank.corebankApiBaseUrl, {
- auth: {
- username: "admin",
- password: "adminpw",
- },
- });
-
- await bankClient.registerAccountExtended({
- name: receiverName,
- password: exchangeBankPassword,
- username: exchangeBankUsername,
- is_taler_exchange: true,
- payto_uri: exchangePaytoUri,
- });
-
- const ageMaskSpec = opts.ageMaskSpec;
-
- if (ageMaskSpec) {
- exchange.enableAgeRestrictions(ageMaskSpec);
- // Enable age restriction for all coins.
- exchange.addCoinConfigList(
- coinConfig.map((x) => ({
- ...x,
- name: `${x.name}-age`,
- ageRestricted: true,
- })),
- );
- // For mixed age restrictions, we also offer coins without age restrictions
- if (opts.mixedAgeRestriction) {
- exchange.addCoinConfigList(
- coinConfig.map((x) => ({ ...x, ageRestricted: false })),
- );
- }
- } else {
- exchange.addCoinConfigList(coinConfig);
- }
-
- await exchange.modifyConfig(async (config) => {
- config.setString("exchange", "enable_kyc", "yes");
-
- config.setString("KYC-RULE-R1", "operation_type", "withdraw");
- 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:5");
- config.setString("KYC-RULE-R1", "timeframe", "1d");
- config.setString("KYC-RULE-R1", "next_measures", "M1");
-
- config.setString("KYC-RULE-R1", "operation_type", "withdraw");
- 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:300");
- config.setString("KYC-RULE-R1", "timeframe", "1d");
- config.setString("KYC-RULE-R1", "next_measures", "verboten");
-
- config.setString("KYC-MEASURE-M1", "check_name", "C1");
- config.setString("KYC-MEASURE-M1", "context", "{}");
- config.setString("KYC-MEASURE-M1", "program", "P1");
-
- config.setString("AML-PROGRAM-P1", "command", "/bin/true");
- config.setString("AML-PROGRAM-P1", "enabled", "true");
- config.setString("AML-PROGRAM-P1", "description", "this does nothing");
- config.setString("AML-PROGRAM-P1", "fallback", "M1");
-
- config.setString("KYC-CHECK-C1", "type", "INFO");
- config.setString("KYC-CHECK-C1", "description", "my check!");
- config.setString("KYC-CHECK-C1", "fallback", "M1");
- });
-
- 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: {
- accountName: "",
- accountPassword: "",
- accountPaytoUri: "",
- wireGatewayApiBaseUrl: "",
- },
- };
-}
+ createKycTestkudosEnvironment,
+ postAmlDecisionNoRules,
+} from "../harness/environments.js";
+import { GlobalTestState } from "../harness/harness.js";
export async function runKycThresholdWithdrawalTest(t: GlobalTestState) {
// Set up test environment
const { walletClient, bankClient, exchange, amlKeypair } =
- await createKycTestkudosEnvironment(t);
+ await createKycTestkudosEnvironment(t, {
+ adjustExchangeConfig(config) {
+ config.setString("exchange", "enable_kyc", "yes");
+
+ config.setString("KYC-RULE-R1", "operation_type", "withdraw");
+ 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:5");
+ config.setString("KYC-RULE-R1", "timeframe", "1d");
+ config.setString("KYC-RULE-R1", "next_measures", "M1");
+
+ config.setString("KYC-RULE-R1", "operation_type", "withdraw");
+ 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:300");
+ config.setString("KYC-RULE-R1", "timeframe", "1d");
+ config.setString("KYC-RULE-R1", "next_measures", "verboten");
+
+ config.setString("KYC-MEASURE-M1", "check_name", "C1");
+ config.setString("KYC-MEASURE-M1", "context", "{}");
+ config.setString("KYC-MEASURE-M1", "program", "P1");
+
+ config.setString("AML-PROGRAM-P1", "command", "/bin/true");
+ config.setString("AML-PROGRAM-P1", "enabled", "true");
+ config.setString("AML-PROGRAM-P1", "description", "this does nothing");
+ config.setString("AML-PROGRAM-P1", "fallback", "M1");
+
+ config.setString("KYC-CHECK-C1", "type", "INFO");
+ config.setString("KYC-CHECK-C1", "description", "my check!");
+ config.setString("KYC-CHECK-C1", "fallback", "M1");
+ },
+ });
// Withdraw digital cash into the wallet.
@@ -247,8 +97,8 @@ export async function runKycThresholdWithdrawalTest(t: GlobalTestState) {
},
);
- // t.assertTrue(!!withdrawalAmountInfo.kycHardLimit);
- // t.assertAmountEquals(withdrawalAmountInfo.kycHardLimit, "TESTKUDOS:300");
+ t.assertTrue(!!withdrawalAmountInfo.kycHardLimit);
+ t.assertAmountEquals(withdrawalAmountInfo.kycHardLimit, "TESTKUDOS:300");
// Withdraw
@@ -268,7 +118,6 @@ export async function runKycThresholdWithdrawalTest(t: GlobalTestState) {
withdrawalOperationId: wop.withdrawal_id,
});
-
t.logStep("waiting for pending(kyc-required)");
const kycNotificationCond = walletClient.waitForNotificationCond((x) => {
diff --git a/packages/taler-wallet-core/src/deposits.ts b/packages/taler-wallet-core/src/deposits.ts
@@ -115,7 +115,7 @@ import {
getScopeForAllExchanges,
} from "./exchanges.js";
import { EddsaKeyPairStrings } from "./index.js";
-import { getDepositLimitInfo } from "./kyc.js";
+import { checkDepositHardLimitExceeded, getDepositLimitInfo } from "./kyc.js";
import {
extractContractData,
generateDepositPermissions,
@@ -2035,6 +2035,21 @@ export async function createDepositGroup(
assertUnreachable(payCoinSel);
}
+ const usedExchangesSet = new Set<string>();
+ for (const c of coins) {
+ usedExchangesSet.add(c.exchangeBaseUrl);
+ }
+
+ const exchanges: ReadyExchangeSummary[] = [];
+
+ for (const exchangeBaseUrl of usedExchangesSet) {
+ exchanges.push(await fetchFreshExchange(wex, exchangeBaseUrl));
+ }
+
+ if (checkDepositHardLimitExceeded(exchanges, req.amount)) {
+ throw Error("deposit would exceed hard KYC limit");
+ }
+
// Heuristic for the merchant key pair: If there's an exchange where we made
// a withdrawal from, use that key pair, so the user doesn't have to do
// a KYC transfer to establish a kyc account key pair.
diff --git a/packages/taler-wallet-core/src/exchanges.ts b/packages/taler-wallet-core/src/exchanges.ts
@@ -1761,6 +1761,8 @@ export async function updateExchangeFromUrlHandler(
wireInfo,
ageMask,
walletBalanceLimits: keysInfo.walletBalanceLimits,
+ hardLimits: keysInfo.hardLimits,
+ zeroLimits: keysInfo.zeroLimits,
};
r.noFees = noFees;
r.peerPaymentsDisabled = peerPaymentsDisabled;
diff --git a/packages/taler-wallet-core/src/kyc.ts b/packages/taler-wallet-core/src/kyc.ts
@@ -0,0 +1,252 @@
+/*
+ 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 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 {
+ AmountJson,
+ AmountLike,
+ Amounts,
+ AmountString,
+} from "@gnu-taler/taler-util";
+import { ReadyExchangeSummary } from "./exchanges.js";
+
+/**
+ * @fileoverview Helpers for KYC.
+ * @author Florian Dold <dold@taler.net>
+ */
+
+export interface SimpleLimitInfo {
+ kycHardLimit: AmountString | undefined;
+ kycSoftLimit: AmountString | undefined;
+}
+
+export interface MultiExchangeLimitInfo {
+ kycHardLimit: AmountString | undefined;
+ kycSoftLimit: AmountString | undefined;
+
+ /**
+ * Exchanges that would require soft KYC.
+ */
+ kycExchanges: string[];
+}
+
+/**
+ * Return the smallest given amount, where an undefined amount
+ * is interpreted the larger amount.
+ */
+function minDefAmount(
+ a: AmountLike | undefined,
+ b: AmountLike | undefined,
+): AmountJson {
+ if (a == null) {
+ if (b == null) {
+ throw Error();
+ }
+ return Amounts.jsonifyAmount(b);
+ }
+ if (b == null) {
+ if (a == null) {
+ throw Error();
+ }
+ return Amounts.jsonifyAmount(a);
+ }
+ return Amounts.min(a, b);
+}
+
+/**
+ * Add to an amount.
+ * Interprets the second argument as zero if not defined.
+ */
+function addDefAmount(a: AmountLike, b: AmountLike | undefined): AmountJson {
+ if (b == null) {
+ return Amounts.jsonifyAmount(a);
+ }
+ return Amounts.add(a, b).amount;
+}
+
+export function getDepositLimitInfo(
+ exchanges: ReadyExchangeSummary[],
+ instructedAmount: AmountLike,
+): MultiExchangeLimitInfo {
+ let kycHardLimit: AmountJson | undefined;
+ let kycSoftLimit: AmountJson | undefined;
+ let kycExchanges: string[] = [];
+
+ // FIXME: Summing up the limits doesn't really make a lot of sense,
+ // as the funds at each exchange are limited (by the coins in the wallet),
+ // and thus an exchange where we don't have coins but that has a high
+ // KYC limit can't meaningfully contribute to the whole limit.
+
+ for (const exchange of exchanges) {
+ const exchLim = getSingleExchangeDepositLimitInfo(
+ exchange,
+ instructedAmount,
+ );
+ if (exchLim.kycSoftLimit) {
+ kycExchanges.push(exchange.exchangeBaseUrl);
+ kycSoftLimit = addDefAmount(exchLim.kycSoftLimit, kycSoftLimit);
+ }
+ if (exchLim.kycHardLimit) {
+ kycHardLimit = addDefAmount(exchLim.kycHardLimit, kycHardLimit);
+ }
+ }
+
+ return {
+ kycHardLimit: kycHardLimit ? Amounts.stringify(kycHardLimit) : undefined,
+ kycSoftLimit: kycSoftLimit ? Amounts.stringify(kycSoftLimit) : undefined,
+ kycExchanges,
+ };
+}
+
+export function getSingleExchangeDepositLimitInfo(
+ exchange: ReadyExchangeSummary,
+ instructedAmount: AmountLike,
+): SimpleLimitInfo {
+ let kycHardLimit: AmountJson | undefined;
+ let kycSoftLimit: AmountJson | undefined;
+
+ for (let lim of exchange.hardLimits) {
+ switch (lim.operation_type) {
+ case "DEPOSIT":
+ case "AGGREGATE":
+ // FIXME: This should consider past deposits and KYC checks
+ kycHardLimit = minDefAmount(kycHardLimit, lim.threshold);
+ break;
+ }
+ }
+
+ for (let limAmount of exchange.walletBalanceLimitWithoutKyc ?? []) {
+ kycSoftLimit = minDefAmount(kycSoftLimit, limAmount);
+ }
+
+ for (let lim of exchange.zeroLimits) {
+ switch (lim.operation_type) {
+ case "DEPOSIT":
+ case "AGGREGATE":
+ kycSoftLimit = Amounts.zeroOfAmount(instructedAmount);
+ break;
+ }
+ }
+
+ return {
+ kycHardLimit: kycHardLimit ? Amounts.stringify(kycHardLimit) : undefined,
+ kycSoftLimit: kycSoftLimit ? Amounts.stringify(kycSoftLimit) : undefined,
+ };
+}
+
+export function getPeerCreditLimitInfo(
+ exchange: ReadyExchangeSummary,
+ instructedAmount: AmountLike,
+): SimpleLimitInfo {
+ let kycHardLimit: AmountJson | undefined;
+ let kycSoftLimit: AmountJson | undefined;
+
+ for (let lim of exchange.hardLimits) {
+ switch (lim.operation_type) {
+ case "BALANCE":
+ case "MERGE":
+ // FIXME: This should consider past merges and KYC checks
+ kycHardLimit = minDefAmount(kycHardLimit, lim.threshold);
+ break;
+ }
+ }
+
+ for (let limAmount of exchange.walletBalanceLimitWithoutKyc ?? []) {
+ kycSoftLimit = minDefAmount(kycSoftLimit, limAmount);
+ }
+
+ for (let lim of exchange.zeroLimits) {
+ switch (lim.operation_type) {
+ case "BALANCE":
+ case "MERGE":
+ kycSoftLimit = Amounts.zeroOfAmount(instructedAmount);
+ break;
+ }
+ }
+
+ return {
+ kycHardLimit: kycHardLimit ? Amounts.stringify(kycHardLimit) : undefined,
+ kycSoftLimit: kycSoftLimit ? Amounts.stringify(kycSoftLimit) : undefined,
+ };
+}
+
+export function checkWithdrawalHardLimitExceeded(
+ exchange: ReadyExchangeSummary,
+ instructedAmount: AmountLike,
+): boolean {
+ const limitInfo = getWithdrawalLimitInfo(exchange, instructedAmount);
+ return (
+ limitInfo.kycHardLimit != null &&
+ Amounts.cmp(limitInfo.kycHardLimit, instructedAmount) <= 0
+ );
+}
+
+export function checkPeerCreditHardLimitExceeded(
+ exchange: ReadyExchangeSummary,
+ instructedAmount: AmountLike,
+): boolean {
+ const limitInfo = getPeerCreditLimitInfo(exchange, instructedAmount);
+ return (
+ limitInfo.kycHardLimit != null &&
+ Amounts.cmp(limitInfo.kycHardLimit, instructedAmount) <= 0
+ );
+}
+
+export function checkDepositHardLimitExceeded(
+ exchanges: ReadyExchangeSummary[],
+ instructedAmount: AmountLike,
+): boolean {
+ const limitInfo = getDepositLimitInfo(exchanges, instructedAmount);
+ return (
+ limitInfo.kycHardLimit != null &&
+ Amounts.cmp(limitInfo.kycHardLimit, instructedAmount) <= 0
+ );
+}
+
+export function getWithdrawalLimitInfo(
+ exchange: ReadyExchangeSummary,
+ instructedAmount: AmountLike,
+): SimpleLimitInfo {
+ let kycHardLimit: AmountJson | undefined;
+ let kycSoftLimit: AmountJson | undefined;
+
+ for (let lim of exchange.hardLimits) {
+ switch (lim.operation_type) {
+ case "BALANCE":
+ case "WITHDRAW":
+ // FIXME: This should consider past withdrawals and KYC checks
+ kycHardLimit = minDefAmount(kycHardLimit, lim.threshold);
+ break;
+ }
+ }
+
+ for (let limAmount of exchange.walletBalanceLimitWithoutKyc ?? []) {
+ kycSoftLimit = minDefAmount(kycSoftLimit, limAmount);
+ }
+
+ for (let lim of exchange.zeroLimits) {
+ switch (lim.operation_type) {
+ case "BALANCE":
+ case "WITHDRAW":
+ kycSoftLimit = Amounts.zeroOfAmount(instructedAmount);
+ break;
+ }
+ }
+
+ return {
+ kycHardLimit: kycHardLimit ? Amounts.stringify(kycHardLimit) : undefined,
+ kycSoftLimit: kycSoftLimit ? Amounts.stringify(kycSoftLimit) : undefined,
+ };
+}
diff --git a/packages/taler-wallet-core/src/pay-peer-pull-credit.ts b/packages/taler-wallet-core/src/pay-peer-pull-credit.ts
@@ -95,6 +95,7 @@ import {
getScopeForAllExchanges,
handleStartExchangeWalletKyc,
} from "./exchanges.js";
+import { checkPeerCreditHardLimitExceeded } from "./kyc.js";
import {
codecForExchangePurseStatus,
getMergeReserveInfo,
@@ -1465,6 +1466,12 @@ export async function initiatePeerPullPayment(
const exchange = await fetchFreshExchange(wex, exchangeBaseUrl);
requireExchangeTosAcceptedOrThrow(exchange);
+ if (
+ checkPeerCreditHardLimitExceeded(exchange, req.partialContractTerms.amount)
+ ) {
+ throw Error("peer credit would exceed hard KYC limit");
+ }
+
const mergeReserveInfo = await getMergeReserveInfo(wex, {
exchangeBaseUrl: exchangeBaseUrl,
});
diff --git a/packages/taler-wallet-core/src/pay-peer-push-credit.ts b/packages/taler-wallet-core/src/pay-peer-push-credit.ts
@@ -91,7 +91,10 @@ import {
getScopeForAllExchanges,
handleStartExchangeWalletKyc,
} from "./exchanges.js";
-import { getPeerCreditLimitInfo } from "./kyc.js";
+import {
+ checkPeerCreditHardLimitExceeded,
+ getPeerCreditLimitInfo,
+} from "./kyc.js";
import {
codecForExchangePurseStatus,
getMergeReserveInfo,
@@ -1298,31 +1301,55 @@ export async function confirmPeerPushCredit(
logger.trace(`confirming peer-push-credit ${ctx.peerPushCreditId}`);
- const peerInc = await wex.db.runReadWriteTx(
+ const res = await wex.db.runReadWriteTx(
{ storeNames: ["contractTerms", "peerPushCredit", "transactionsMeta"] },
async (tx) => {
const rec = await tx.peerPushCredit.get(ctx.peerPushCreditId);
if (!rec) {
return;
}
- if (rec.status === PeerPushCreditStatus.DialogProposed) {
- rec.status = PeerPushCreditStatus.PendingMerge;
+ const ct = await tx.contractTerms.get(rec.contractTermsHash);
+ if (!ct) {
+ return undefined;
}
- await tx.peerPushCredit.put(rec);
- await ctx.updateTransactionMeta(tx);
- return rec;
+ return {
+ peerInc: rec,
+ contractTerms: ct.contractTermsRaw as PeerContractTerms,
+ };
},
);
- if (!peerInc) {
+ if (!res) {
throw Error(
`can't accept unknown incoming p2p push payment (${req.transactionId})`,
);
}
+ const peerInc = res.peerInc;
+
const exchange = await fetchFreshExchange(wex, peerInc.exchangeBaseUrl);
requireExchangeTosAcceptedOrThrow(exchange);
+ if (checkPeerCreditHardLimitExceeded(exchange, res.contractTerms.amount)) {
+ throw Error("peer credit would exceed hard KYC limit");
+ }
+
+ await wex.db.runReadWriteTx(
+ { storeNames: ["contractTerms", "peerPushCredit", "transactionsMeta"] },
+ async (tx) => {
+ const rec = await tx.peerPushCredit.get(ctx.peerPushCreditId);
+ if (!rec) {
+ return;
+ }
+ if (rec.status === PeerPushCreditStatus.DialogProposed) {
+ rec.status = PeerPushCreditStatus.PendingMerge;
+ }
+ await tx.peerPushCredit.put(rec);
+ await ctx.updateTransactionMeta(tx);
+ return rec;
+ },
+ );
+
wex.taskScheduler.startShepherdTask(ctx.taskId);
return {
@@ -1454,3 +1481,6 @@ export function computePeerPushCreditTransactionActions(
assertUnreachable(pushCreditRecord.status);
}
}
+function checkPeerHardLimitExceeded(exchanges: any, amount: any) {
+ throw new Error("Function not implemented.");
+}
diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts
@@ -173,7 +173,10 @@ import {
lookupExchangeByUri,
markExchangeUsed,
} from "./exchanges.js";
-import { getWithdrawalLimitInfo } from "./kyc.js";
+import {
+ checkWithdrawalHardLimitExceeded,
+ getWithdrawalLimitInfo,
+} from "./kyc.js";
import { DbAccess } from "./query.js";
import {
TransitionInfo,
@@ -2568,6 +2571,8 @@ export async function getExchangeWithdrawalInfo(
throw Error("exchange is in invalid state");
}
+ logger.info(`exchange ready summary: ${j2s(exchange)}`);
+
const ret: ExchangeWithdrawalDetails = {
exchangePaytoUris: paytoUris,
exchangeWireAccounts,
@@ -3407,6 +3412,10 @@ export async function confirmWithdrawal(
const exchange = await fetchFreshExchange(wex, selectedExchange);
requireExchangeTosAcceptedOrThrow(exchange);
+ if (checkWithdrawalHardLimitExceeded(exchange, req.amount)) {
+ throw Error("withdrawal would exceed hard KYC limit");
+ }
+
const talerWithdrawUri = withdrawalGroup.wgInfo.bankInfo.talerWithdrawUri;
/**
@@ -3877,6 +3886,10 @@ export async function createManualWithdrawal(
);
}
+ if (checkWithdrawalHardLimitExceeded(exchange, req.amount)) {
+ throw Error("withdrawal would exceed hard KYC limit");
+ }
+
let reserveKeyPair: EddsaKeyPairStrings;
if (req.forceReservePriv) {
const pubResp = await wex.cryptoApi.eddsaGetPublic({