commit fa1cb3b8f8aab5fe8de07fe21b31e67f29f8f986
parent e1e8d150e3eb6d832f65810cc3fb189f943b8466
Author: Florian Dold <dold@taler.net>
Date: Tue, 3 Nov 2026 09:35:49 +0100
wallet-core: expose coins after hard-limit recovery
Diffstat:
3 files changed, 65 insertions(+), 0 deletions(-)
diff --git a/packages/taler-harness/src/integrationtests/test-kyc-withdrawal-recovery.ts b/packages/taler-harness/src/integrationtests/test-kyc-withdrawal-recovery.ts
@@ -19,6 +19,11 @@ import {
WithdrawalKycStatus,
TalerErrorCode,
AmountString,
+ Amounts,
+ encodeCrock,
+ Paytos,
+ LimitOperationType,
+ TalerProtocolTimestamp,
TransactionState,
TransactionMajorState as Major,
TransactionMinorState as Minor,
@@ -29,9 +34,11 @@ import {
KycTestEnv,
configureSimpleKycInfoRule,
createKycTestkudosEnvironmentFull,
+ postAmlDecision,
postAmlDecisionNoRules,
applyTimeTravelV2,
} from "../harness/environments.js";
+import { coin_u1 } from "../harness/denomStructures.js";
async function withdraw(
env: KycTestEnv,
user: { username: string },
@@ -185,5 +192,59 @@ export async function runKycWithdrawalExpiryTest(t: GlobalTestState) {
);
}
+export async function runKycWithdrawalPartialHardTest(t: GlobalTestState) {
+ const env = await createKycTestkudosEnvironmentFull(t, {
+ walletPersistent: true,
+ coinConfig: [{ ...coin_u1("TESTKUDOS"), feeWithdraw: "TESTKUDOS:0" }],
+ adjustExchangeConfig(config) {
+ configureSimpleKycInfoRule(config, "withdraw");
+ },
+ });
+ const user = await env.bankClient.createRandomBankUser();
+ env.bankClient.setAuth({ username: user.username, password: user.password });
+ await withdraw(env, user, "TESTKUDOS:1", { major: Major.Done });
+ await postAmlDecision(t, {
+ exchangeBaseUrl: env.exchange.baseUrl,
+ paytoHash: encodeCrock(Paytos.hashNormalized(user.accountPaytoUri)),
+ amlPub: env.amlKeypair.pub,
+ amlPriv: env.amlKeypair.priv,
+ newRules: {
+ custom_measures: {},
+ expiration_time: TalerProtocolTimestamp.never(),
+ rules: [
+ {
+ operation_type: LimitOperationType.withdraw,
+ display_priority: 1,
+ measures: ["verboten"],
+ threshold: "TESTKUDOS:100",
+ timeframe: { d_us: "forever" },
+ exposed: true,
+ is_and_combinator: false,
+ },
+ ],
+ },
+ });
+ const id = await withdraw(env, user, "TESTKUDOS:200", {
+ major: Major.Failed,
+ minor: Minor.KycHardLimit,
+ });
+ const tx = await env.walletClient.call(Op.GetTransactionById, {
+ transactionId: id,
+ });
+ const balances = await env.walletClient.call(Op.GetBalances, {});
+ const coins = await env.walletClient.call(Op.DumpCoins, {});
+ assert.equal(tx.txState.major, Major.Failed);
+ assert.ok(coins.coins.length > 1);
+ t.assertAmountEquals(
+ balances.balances[0].available,
+ `TESTKUDOS:${coins.coins.length}`,
+ );
+ assert.ok(
+ Amounts.cmp(balances.balances[0].available, "TESTKUDOS:1") > 0,
+ "Coins already withdrawn before hard-limit recovery must appear in the balance",
+ );
+}
+
runKycWithdrawalCumulativeHardTest.suites = ["wallet"];
runKycWithdrawalExpiryTest.suites = ["wallet"];
+runKycWithdrawalPartialHardTest.suites = ["wallet"];
diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts
@@ -118,6 +118,7 @@ import { runKycWithdrawalCumulativeTest } from "./test-kyc-withdrawal-cumulative
import {
runKycWithdrawalCumulativeHardTest,
runKycWithdrawalExpiryTest,
+ runKycWithdrawalPartialHardTest,
} from "./test-kyc-withdrawal-recovery.js";
import { runKycWithdrawalVerbotenTest } from "./test-kyc-withdrawal-verboten.js";
import { runLibeufinBankTest } from "./test-libeufin-bank.js";
@@ -480,6 +481,8 @@ const allTests: TestMainFunction[] = [
withWalletDbBackend(runKycWithdrawalCumulativeHardTest, "indexeddb"),
withWalletDbBackend(runKycWithdrawalExpiryTest, "default"),
withWalletDbBackend(runKycWithdrawalExpiryTest, "indexeddb"),
+ withWalletDbBackend(runKycWithdrawalPartialHardTest, "default"),
+ withWalletDbBackend(runKycWithdrawalPartialHardTest, "indexeddb"),
withWalletDbBackend(runKycWithdrawalCumulativeTest, "default"),
withWalletDbBackend(runKycWithdrawalCumulativeTest, "indexeddb"),
runKycNewMeasureTest,
diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts
@@ -2896,6 +2896,7 @@ async function processWithdrawalKycHardLimitRecovery(
if (rec?.status !== WithdrawalGroupStatus.FinalizingKycHardLimit) {
return;
}
+ await makeCoinsVisible(wex, tx, ctx.transactionId);
rec.status = finalStatus;
rec.timestampFinish = timestampPreciseToDb(TalerPreciseTimestamp.now());
if (recoveryFailure) {