commit 59c9ff665fe605baa8d7ca7ead09940e43c9bf2c
parent 7983507bfe19dc1a64050a6d24fe85c1f47fa5bc
Author: Florian Dold <florian@dold.me>
Date: Wed, 19 Feb 2025 21:30:16 +0100
wallet-core: store bank account based on reserve response
Diffstat:
3 files changed, 81 insertions(+), 27 deletions(-)
diff --git a/packages/taler-harness/src/integrationtests/test-withdrawal-manual.ts b/packages/taler-harness/src/integrationtests/test-withdrawal-manual.ts
@@ -40,6 +40,16 @@ export async function runWithdrawalManualTest(t: GlobalTestState) {
const { walletClient, bankClient, exchange, exchangeBankAccount } =
await createSimpleTestkudosEnvironmentV3(t);
+ {
+ const accounts = await walletClient.call(
+ WalletApiOperation.ListBankAccounts,
+ {},
+ );
+
+ // Wallet doesn't have known bank accounts initially
+ t.assertTrue(accounts.accounts.length == 0);
+ }
+
// Create a withdrawal operation
const user = await bankClient.createRandomBankUser();
@@ -116,7 +126,18 @@ export async function runWithdrawalManualTest(t: GlobalTestState) {
const balResp = await walletClient.call(WalletApiOperation.GetBalances, {});
t.assertAmountEquals("TESTKUDOS:9.72", balResp.balances[0].available);
- await t.shutdown();
+ {
+ const accounts = await walletClient.call(
+ WalletApiOperation.ListBankAccounts,
+ {},
+ );
+
+ console.log(j2s(accounts));
+
+ // Account from withdrawal is now known to the wallet,
+ // as the exchange returns it in the reserve status response
+ t.assertTrue(accounts.accounts.length == 1);
+ }
}
runWithdrawalManualTest.suites = ["wallet"];
diff --git a/packages/taler-util/src/ReserveStatus.ts b/packages/taler-util/src/ReserveStatus.ts
@@ -22,7 +22,12 @@
* Imports.
*/
import { codecForAmountString } from "./amounts.js";
-import { Codec, buildCodecForObject } from "./codec.js";
+import {
+ Codec,
+ buildCodecForObject,
+ codecForString,
+ codecOptional,
+} from "./codec.js";
import { AmountString } from "./types-taler-common.js";
/**
@@ -35,9 +40,19 @@ export interface ReserveStatus {
* Balance left in the reserve.
*/
balance: AmountString;
+
+ /**
+ * Full payto URI of the bank account that
+ * (most recently) funded this reserve.
+ * Useful as a hint for deposit operations for wallets.
+ * Missing if this reserve was only filled via P2P merges.
+ * @since protocol **v23**.
+ */
+ last_origin?: string;
}
export const codecForReserveStatus = (): Codec<ReserveStatus> =>
buildCodecForObject<ReserveStatus>()
.property("balance", codecForAmountString())
+ .property("last_origin", codecOptional(codecForString()))
.build("ReserveStatus");
diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts
@@ -1962,7 +1962,7 @@ async function processQueryReserve(
const transitionResult = await ctx.transition(
{
- extraStores: ["denominations"],
+ extraStores: ["denominations", "bankAccountsV2"],
},
async (wg, tx) => {
if (!wg) {
@@ -1972,6 +1972,10 @@ async function processQueryReserve(
if (wg.status !== WithdrawalGroupStatus.PendingQueryingStatus) {
return TransitionResult.stay();
}
+ const lastOrigin = result.response.last_origin;
+ if (lastOrigin != null) {
+ await storeKnownBankAccount(tx, currency, lastOrigin);
+ }
if (amountChanged) {
const candidates = await getCandidateWithdrawalDenomsTx(
wex,
@@ -3496,6 +3500,43 @@ export async function prepareBankIntegratedWithdrawal(
};
}
+/**
+ * Store account as known bank account.
+ *
+ * Return ID of the new bank account or undefined
+ * if the account already exists.
+ */
+async function storeKnownBankAccount(
+ tx: WalletDbReadWriteTransaction<["bankAccountsV2"]>,
+ instructedCurrency: string,
+ senderWire: string,
+): Promise<string | undefined> {
+ const existingAccount =
+ await tx.bankAccountsV2.indexes.byPaytoUri.get(senderWire);
+ if (existingAccount) {
+ // Add currency for existing known bank account if necessary
+ if (existingAccount.currencies?.includes(instructedCurrency)) {
+ existingAccount.currencies = [
+ instructedCurrency,
+ ...(existingAccount.currencies ?? []),
+ ];
+ existingAccount.currencies.sort();
+ await tx.bankAccountsV2.put(existingAccount);
+ }
+ return undefined;
+ }
+
+ const myId = `acct:${encodeCrock(getRandomBytes(32))}`;
+ await tx.bankAccountsV2.put({
+ currencies: [instructedCurrency],
+ kycCompleted: false,
+ paytoUri: senderWire,
+ bankAccountId: myId,
+ label: undefined,
+ });
+ return myId;
+}
+
export async function confirmWithdrawal(
wex: WalletExecutionContext,
req: ConfirmWithdrawalRequest,
@@ -3628,30 +3669,7 @@ export async function confirmWithdrawal(
storeNames: ["bankAccountsV2"],
},
async (tx) => {
- const existingAccount =
- await tx.bankAccountsV2.indexes.byPaytoUri.get(senderWire);
- if (existingAccount) {
- // Add currency for existing known bank account if necessary
- if (existingAccount.currencies?.includes(instructedCurrency)) {
- existingAccount.currencies = [
- instructedCurrency,
- ...(existingAccount.currencies ?? []),
- ];
- existingAccount.currencies.sort();
- await tx.bankAccountsV2.put(existingAccount);
- }
- return;
- }
-
- const myId = `acct:${encodeCrock(getRandomBytes(32))}`;
- await tx.bankAccountsV2.put({
- currencies: [instructedCurrency],
- kycCompleted: false,
- paytoUri: senderWire,
- bankAccountId: myId,
- label: undefined,
- });
- return myId;
+ return await storeKnownBankAccount(tx, instructedCurrency, senderWire);
},
);