summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2024-04-03 16:21:33 +0200
committerFlorian Dold <florian@dold.me>2024-04-03 16:21:33 +0200
commit65a656163797e9dd298b34ec916b982082db7f52 (patch)
tree1a226c657639c69194ddf7682a805bf2aa14191c
parent5417b8b7b866f1c4f4d99d6ec9ad001af67822b6 (diff)
downloadwallet-core-65a656163797e9dd298b34ec916b982082db7f52.tar.gz
wallet-core-65a656163797e9dd298b34ec916b982082db7f52.tar.bz2
wallet-core-65a656163797e9dd298b34ec916b982082db7f52.zip
wallet-core: allow deposits with balance locked behind refresh
-rw-r--r--packages/taler-harness/src/integrationtests/test-wallet-blocked-deposit.ts (renamed from packages/taler-harness/src/integrationtests/test-wallet-refresh-blocked.ts)44
-rw-r--r--packages/taler-harness/src/integrationtests/testrunner.ts4
-rw-r--r--packages/taler-wallet-core/src/db.ts6
-rw-r--r--packages/taler-wallet-core/src/deposits.ts230
-rw-r--r--packages/taler-wallet-core/src/dev-experiments.ts96
-rw-r--r--packages/taler-wallet-core/src/transactions.ts29
6 files changed, 282 insertions, 127 deletions
diff --git a/packages/taler-harness/src/integrationtests/test-wallet-refresh-blocked.ts b/packages/taler-harness/src/integrationtests/test-wallet-blocked-deposit.ts
index 4662c5110..cb9c54f1d 100644
--- a/packages/taler-harness/src/integrationtests/test-wallet-refresh-blocked.ts
+++ b/packages/taler-harness/src/integrationtests/test-wallet-blocked-deposit.ts
@@ -17,10 +17,16 @@
/**
* Imports.
*/
-import { AmountString, j2s } from "@gnu-taler/taler-util";
+import {
+ AmountString,
+ NotificationType,
+ TransactionMajorState,
+ TransactionMinorState,
+ j2s,
+} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { CoinConfig } from "../harness/denomStructures.js";
-import { GlobalTestState } from "../harness/harness.js";
+import { GlobalTestState, generateRandomPayto } from "../harness/harness.js";
import {
createSimpleTestkudosEnvironmentV2,
createWalletDaemonWithClient,
@@ -43,7 +49,7 @@ const coinCommon = {
/**
* Run test for refreshe after a payment.
*/
-export async function runWalletRefreshBlockedTest(t: GlobalTestState) {
+export async function runWalletBlockedDeposit(t: GlobalTestState) {
// Set up test environment
const coinConfigList: CoinConfig[] = [
@@ -66,6 +72,7 @@ export async function runWalletRefreshBlockedTest(t: GlobalTestState) {
const { walletClient: w1 } = await createWalletDaemonWithClient(t, {
name: "w1",
+ persistent: true,
config: {
testing: {
devModeActive: true,
@@ -97,6 +104,8 @@ export async function runWalletRefreshBlockedTest(t: GlobalTestState) {
},
});
+ const userPayto = generateRandomPayto("foo");
+
const bal = await w1.call(WalletApiOperation.GetBalances, {});
console.log(`balance: ${j2s(bal)}`);
@@ -109,12 +118,35 @@ export async function runWalletRefreshBlockedTest(t: GlobalTestState) {
const depositCheckResp = await w1.call(WalletApiOperation.PrepareDeposit, {
amount: "TESTKUDOS:18" as AmountString,
- depositPaytoUri: "payto://x-taler-bank/localhost/myuser",
+ depositPaytoUri: userPayto,
});
console.log(`check resp: ${j2s(depositCheckResp)}`);
- // t.assertTrue(false);
+ const depositCreateResp = await w1.call(
+ WalletApiOperation.CreateDepositGroup,
+ {
+ amount: "TESTKUDOS:18" as AmountString,
+ depositPaytoUri: userPayto,
+ },
+ );
+
+ console.log(`create resp: ${j2s(depositCreateResp)}`);
+
+ const depositTrackCond = w1.waitForNotificationCond((n) => {
+ return (
+ n.type === NotificationType.TransactionStateTransition &&
+ n.transactionId === depositCreateResp.transactionId &&
+ n.newTxState.major === TransactionMajorState.Pending &&
+ n.newTxState.minor === TransactionMinorState.Track
+ );
+ });
+
+ await w1.call(WalletApiOperation.ApplyDevExperiment, {
+ devExperimentUri: "taler://dev-experiment/stop-block-refresh",
+ });
+
+ await depositTrackCond;
}
-runWalletRefreshBlockedTest.suites = ["wallet"];
+runWalletBlockedDeposit.suites = ["wallet"];
diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts
index 2bca91e45..063aefa43 100644
--- a/packages/taler-harness/src/integrationtests/testrunner.ts
+++ b/packages/taler-harness/src/integrationtests/testrunner.ts
@@ -101,7 +101,7 @@ import { runWalletGenDbTest } from "./test-wallet-gendb.js";
import { runWalletInsufficientBalanceTest } from "./test-wallet-insufficient-balance.js";
import { runWalletNotificationsTest } from "./test-wallet-notifications.js";
import { runWalletObservabilityTest } from "./test-wallet-observability.js";
-import { runWalletRefreshBlockedTest } from "./test-wallet-refresh-blocked.js";
+import { runWalletBlockedDeposit } from "./test-wallet-blocked-deposit.js";
import { runWalletRefreshTest } from "./test-wallet-refresh.js";
import { runWalletWirefeesTest } from "./test-wallet-wirefees.js";
import { runWallettestingTest } from "./test-wallettesting.js";
@@ -213,7 +213,7 @@ const allTests: TestMainFunction[] = [
runWalletWirefeesTest,
runDenomLostTest,
runWalletDenomExpireTest,
- runWalletRefreshBlockedTest,
+ runWalletBlockedDeposit,
];
export interface TestRunSpec {
diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts
index de22d78a8..7b9dfa2a2 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -1787,9 +1787,9 @@ export interface DepositGroupRecord {
contractTermsHash: string;
- payCoinSelection: DbCoinSelection;
+ payCoinSelection?: DbCoinSelection;
- payCoinSelectionUid: string;
+ payCoinSelectionUid?: string;
totalPayCost: AmountString;
@@ -1804,7 +1804,7 @@ export interface DepositGroupRecord {
operationStatus: DepositOperationStatus;
- statusPerCoin: DepositElementStatus[];
+ statusPerCoin?: DepositElementStatus[];
infoPerExchange?: Record<string, DepositInfoPerExchange>;
diff --git a/packages/taler-wallet-core/src/deposits.ts b/packages/taler-wallet-core/src/deposits.ts
index 05a5d780a..5b23d8325 100644
--- a/packages/taler-wallet-core/src/deposits.ts
+++ b/packages/taler-wallet-core/src/deposits.ts
@@ -413,16 +413,28 @@ async function refundDepositGroup(
wex: WalletExecutionContext,
depositGroup: DepositGroupRecord,
): Promise<TaskRunResult> {
- const newTxPerCoin = [...depositGroup.statusPerCoin];
+ const statusPerCoin = depositGroup.statusPerCoin;
+ const payCoinSelection = depositGroup.payCoinSelection;
+ if (!statusPerCoin) {
+ throw Error(
+ "unable to refund deposit group without coin selection (status missing)",
+ );
+ }
+ if (!payCoinSelection) {
+ throw Error(
+ "unable to refund deposit group without coin selection (selection missing)",
+ );
+ }
+ const newTxPerCoin = [...statusPerCoin];
logger.info(`status per coin: ${j2s(depositGroup.statusPerCoin)}`);
- for (let i = 0; i < depositGroup.statusPerCoin.length; i++) {
- const st = depositGroup.statusPerCoin[i];
+ for (let i = 0; i < statusPerCoin.length; i++) {
+ const st = statusPerCoin[i];
switch (st) {
case DepositElementStatus.RefundFailed:
case DepositElementStatus.RefundSuccess:
break;
default: {
- const coinPub = depositGroup.payCoinSelection.coinPubs[i];
+ const coinPub = payCoinSelection.coinPubs[i];
const coinExchange = await wex.db.runReadOnlyTx(
["coins"],
async (tx) => {
@@ -431,7 +443,7 @@ async function refundDepositGroup(
return coinRecord.exchangeBaseUrl;
},
);
- const refundAmount = depositGroup.payCoinSelection.coinContributions[i];
+ const refundAmount = payCoinSelection.coinContributions[i];
// We use a constant refund transaction ID, since there can
// only be one refund.
const rtid = 1;
@@ -503,8 +515,8 @@ async function refundDepositGroup(
const refreshCoins: CoinRefreshRequest[] = [];
for (let i = 0; i < newTxPerCoin.length; i++) {
refreshCoins.push({
- amount: depositGroup.payCoinSelection.coinContributions[i],
- coinPub: depositGroup.payCoinSelection.coinPubs[i],
+ amount: payCoinSelection.coinContributions[i],
+ coinPub: payCoinSelection.coinPubs[i],
});
}
let refreshRes: CreateRefreshGroupResult | undefined = undefined;
@@ -740,9 +752,21 @@ async function processDepositGroupPendingTrack(
wex: WalletExecutionContext,
depositGroup: DepositGroupRecord,
): Promise<TaskRunResult> {
+ const statusPerCoin = depositGroup.statusPerCoin;
+ const payCoinSelection = depositGroup.payCoinSelection;
+ if (!statusPerCoin) {
+ throw Error(
+ "unable to refund deposit group without coin selection (status missing)",
+ );
+ }
+ if (!payCoinSelection) {
+ throw Error(
+ "unable to refund deposit group without coin selection (selection missing)",
+ );
+ }
const { depositGroupId } = depositGroup;
- for (let i = 0; i < depositGroup.statusPerCoin.length; i++) {
- const coinPub = depositGroup.payCoinSelection.coinPubs[i];
+ for (let i = 0; i < statusPerCoin.length; i++) {
+ const coinPub = payCoinSelection.coinPubs[i];
// FIXME: Make the URL part of the coin selection?
const exchangeBaseUrl = await wex.db.runReadWriteTx(
["coins"],
@@ -761,7 +785,7 @@ async function processDepositGroupPendingTrack(
}
| undefined;
- if (depositGroup.statusPerCoin[i] !== DepositElementStatus.Wired) {
+ if (statusPerCoin[i] !== DepositElementStatus.Wired) {
const track = await trackDeposit(
wex,
depositGroup,
@@ -826,6 +850,9 @@ async function processDepositGroupPendingTrack(
if (!dg) {
return;
}
+ if (!dg.statusPerCoin) {
+ return;
+ }
if (updatedTxStatus !== undefined) {
dg.statusPerCoin[i] = updatedTxStatus;
}
@@ -858,9 +885,12 @@ async function processDepositGroupPendingTrack(
if (!dg) {
return undefined;
}
+ if (!dg.statusPerCoin) {
+ return undefined;
+ }
const oldTxState = computeDepositTransactionStatus(dg);
- for (let i = 0; i < depositGroup.statusPerCoin.length; i++) {
- if (depositGroup.statusPerCoin[i] !== DepositElementStatus.Wired) {
+ for (let i = 0; i < dg.statusPerCoin.length; i++) {
+ if (dg.statusPerCoin[i] !== DepositElementStatus.Wired) {
allWired = false;
break;
}
@@ -924,6 +954,87 @@ async function processDepositGroupPendingDeposit(
// Check for cancellation before expensive operations.
cancellationToken?.throwIfCancelled();
+ if (!depositGroup.payCoinSelection) {
+ logger.info("missing coin selection for deposit group, selecting now");
+ // FIXME: Consider doing the coin selection inside the txn
+ const payCoinSel = await selectPayCoins(wex, {
+ restrictExchanges: {
+ auditors: [],
+ exchanges: contractData.allowedExchanges,
+ },
+ restrictWireMethod: contractData.wireMethod,
+ contractTermsAmount: Amounts.parseOrThrow(contractData.amount),
+ depositFeeLimit: Amounts.parseOrThrow(contractData.maxDepositFee),
+ wireFeeAmortization: 1, // FIXME #8653
+ prevPayCoins: [],
+ });
+
+ switch (payCoinSel.type) {
+ case "success":
+ logger.info("coin selection success");
+ break;
+ case "failure":
+ logger.info("coin selection failure");
+ throw TalerError.fromDetail(
+ TalerErrorCode.WALLET_DEPOSIT_GROUP_INSUFFICIENT_BALANCE,
+ {
+ insufficientBalanceDetails: payCoinSel.insufficientBalanceDetails,
+ },
+ );
+ case "prospective":
+ logger.info("coin selection prospective");
+ throw Error("insufficient balance (waiting on pending refresh)");
+ default:
+ assertUnreachable(payCoinSel);
+ }
+
+ const transitionDone = await wex.db.runReadWriteTx(
+ [
+ "depositGroups",
+ "coins",
+ "coinAvailability",
+ "refreshGroups",
+ "refreshSessions",
+ "denominations",
+ ],
+ async (tx) => {
+ const dg = await tx.depositGroups.get(depositGroupId);
+ if (!dg) {
+ return false;
+ }
+ if (dg.statusPerCoin) {
+ return false;
+ }
+ dg.payCoinSelection = {
+ coinContributions: payCoinSel.coinSel.coins.map(
+ (x) => x.contribution,
+ ),
+ coinPubs: payCoinSel.coinSel.coins.map((x) => x.coinPub),
+ };
+ dg.payCoinSelectionUid = encodeCrock(getRandomBytes(32));
+ dg.statusPerCoin = payCoinSel.coinSel.coins.map(
+ () => DepositElementStatus.DepositPending,
+ );
+ await tx.depositGroups.put(dg);
+ await spendCoins(wex, tx, {
+ allocationId: transactionId,
+ coinPubs: dg.payCoinSelection.coinPubs,
+ contributions: dg.payCoinSelection.coinContributions.map((x) =>
+ Amounts.parseOrThrow(x),
+ ),
+ refreshReason: RefreshReason.PayDeposit,
+ });
+ return true;
+ },
+ );
+
+ if (transitionDone) {
+ return TaskRunResult.progress();
+ } else {
+ return TaskRunResult.backoff();
+ }
+ }
+
// FIXME: Cache these!
const depositPermissions = await generateDepositPermissions(
wex,
@@ -990,6 +1101,9 @@ async function processDepositGroupPendingDeposit(
if (!dg) {
return;
}
+ if (!dg.statusPerCoin) {
+ return;
+ }
for (const batchIndex of batchIndexes) {
const coinStatus = dg.statusPerCoin[batchIndex];
switch (coinStatus) {
@@ -1360,8 +1474,11 @@ export async function createDepositGroup(
prevPayCoins: [],
});
+ let coins: SelectedProspectiveCoin[] | undefined = undefined;
+
switch (payCoinSel.type) {
case "success":
+ coins = payCoinSel.coinSel.coins;
break;
case "failure":
throw TalerError.fromDetail(
@@ -1371,17 +1488,13 @@ export async function createDepositGroup(
},
);
case "prospective":
- // FIXME: Here we need to create the deposit group without a full coin selection!
- throw Error("insufficient balance (pending refresh)");
+ coins = payCoinSel.result.prospectiveCoins;
+ break;
default:
assertUnreachable(payCoinSel);
}
- const totalDepositCost = await getTotalPaymentCost(
- wex,
- currency,
- payCoinSel.coinSel.coins,
- );
+ const totalDepositCost = await getTotalPaymentCost(wex, currency, coins);
let depositGroupId: string;
if (req.transactionId) {
@@ -1396,34 +1509,23 @@ export async function createDepositGroup(
const infoPerExchange: Record<string, DepositInfoPerExchange> = {};
- await wex.db.runReadOnlyTx(["coins"], async (tx) => {
- for (let i = 0; i < payCoinSel.coinSel.coins.length; i++) {
- const coin = await tx.coins.get(payCoinSel.coinSel.coins[i].coinPub);
- if (!coin) {
- logger.error("coin not found anymore");
- continue;
- }
- let depPerExchange = infoPerExchange[coin.exchangeBaseUrl];
- if (!depPerExchange) {
- infoPerExchange[coin.exchangeBaseUrl] = depPerExchange = {
- amountEffective: Amounts.stringify(
- Amounts.zeroOfAmount(totalDepositCost),
- ),
- };
- }
- const contrib = payCoinSel.coinSel.coins[i].contribution;
- depPerExchange.amountEffective = Amounts.stringify(
- Amounts.add(depPerExchange.amountEffective, contrib).amount,
- );
+ for (let i = 0; i < coins.length; i++) {
+ let depPerExchange = infoPerExchange[coins[i].exchangeBaseUrl];
+ if (!depPerExchange) {
+ infoPerExchange[coins[i].exchangeBaseUrl] = depPerExchange = {
+ amountEffective: Amounts.stringify(
+ Amounts.zeroOfAmount(totalDepositCost),
+ ),
+ };
}
- });
+ const contrib = coins[i].contribution;
+ depPerExchange.amountEffective = Amounts.stringify(
+ Amounts.add(depPerExchange.amountEffective, contrib).amount,
+ );
+ }
const counterpartyEffectiveDepositAmount =
- await getCounterpartyEffectiveDepositAmount(
- wex,
- p.targetType,
- payCoinSel.coinSel.coins,
- );
+ await getCounterpartyEffectiveDepositAmount(wex, p.targetType, coins);
const depositGroup: DepositGroupRecord = {
contractTermsHash,
@@ -1436,14 +1538,9 @@ export async function createDepositGroup(
AbsoluteTime.toPreciseTimestamp(now),
),
timestampFinished: undefined,
- statusPerCoin: payCoinSel.coinSel.coins.map(
- () => DepositElementStatus.DepositPending,
- ),
- payCoinSelection: {
- coinContributions: payCoinSel.coinSel.coins.map((x) => x.contribution),
- coinPubs: payCoinSel.coinSel.coins.map((x) => x.coinPub),
- },
- payCoinSelectionUid: encodeCrock(getRandomBytes(32)),
+ statusPerCoin: undefined,
+ payCoinSelection: undefined,
+ payCoinSelectionUid: undefined,
merchantPriv: merchantPair.priv,
merchantPub: merchantPair.pub,
totalPayCost: Amounts.stringify(totalDepositCost),
@@ -1461,6 +1558,17 @@ export async function createDepositGroup(
infoPerExchange,
};
+ if (payCoinSel.type === "success") {
+ depositGroup.payCoinSelection = {
+ coinContributions: payCoinSel.coinSel.coins.map((x) => x.contribution),
+ coinPubs: payCoinSel.coinSel.coins.map((x) => x.coinPub),
+ };
+ depositGroup.payCoinSelectionUid = encodeCrock(getRandomBytes(32));
+ depositGroup.statusPerCoin = payCoinSel.coinSel.coins.map(
+ () => DepositElementStatus.DepositPending,
+ );
+ }
+
const ctx = new DepositTransactionContext(wex, depositGroupId);
const transactionId = ctx.transactionId;
@@ -1476,14 +1584,16 @@ export async function createDepositGroup(
"contractTerms",
],
async (tx) => {
- await spendCoins(wex, tx, {
- allocationId: transactionId,
- coinPubs: payCoinSel.coinSel.coins.map((x) => x.coinPub),
- contributions: payCoinSel.coinSel.coins.map((x) =>
- Amounts.parseOrThrow(x.contribution),
- ),
- refreshReason: RefreshReason.PayDeposit,
- });
+ if (depositGroup.payCoinSelection) {
+ await spendCoins(wex, tx, {
+ allocationId: transactionId,
+ coinPubs: depositGroup.payCoinSelection.coinPubs,
+ contributions: depositGroup.payCoinSelection.coinContributions.map(
+ (x) => Amounts.parseOrThrow(x),
+ ),
+ refreshReason: RefreshReason.PayDeposit,
+ });
+ }
await tx.depositGroups.put(depositGroup);
await tx.contractTerms.put({
contractTermsRaw: contractTerms,
diff --git a/packages/taler-wallet-core/src/dev-experiments.ts b/packages/taler-wallet-core/src/dev-experiments.ts
index 57810dbf4..7cf18e36c 100644
--- a/packages/taler-wallet-core/src/dev-experiments.ts
+++ b/packages/taler-wallet-core/src/dev-experiments.ts
@@ -67,52 +67,56 @@ export async function applyDevExperiment(
throw Error("can't handle devmode URI unless devmode is active");
}
- if (parsedUri.devExperimentId === "start-block-refresh") {
- wex.ws.devExperimentState.blockRefreshes = true;
- return;
- }
-
- if (parsedUri.devExperimentId == "insert-pending-refresh") {
- await wex.db.runReadWriteTx(["refreshGroups"], async (tx) => {
- const refreshGroupId = encodeCrock(getRandomBytes(32));
- const newRg: RefreshGroupRecord = {
- currency: "TESTKUDOS",
- expectedOutputPerCoin: [],
- inputPerCoin: [],
- oldCoinPubs: [],
- operationStatus: RefreshOperationStatus.Pending,
- reason: RefreshReason.Manual,
- refreshGroupId,
- statusPerCoin: [],
- timestampCreated: timestampPreciseToDb(TalerPreciseTimestamp.now()),
- timestampFinished: undefined,
- originatingTransactionId: undefined,
- infoPerExchange: {},
- };
- await tx.refreshGroups.put(newRg);
- });
- return;
- }
-
- if (parsedUri.devExperimentId == "insert-denom-loss") {
- await wex.db.runReadWriteTx(["denomLossEvents"], async (tx) => {
- const eventId = encodeCrock(getRandomBytes(32));
- const newRg: DenomLossEventRecord = {
- amount: "TESTKUDOS:42",
- currency: "TESTKUDOS",
- exchangeBaseUrl: "https://exchange.test.taler.net/",
- denomLossEventId: eventId,
- denomPubHashes: [
- encodeCrock(getRandomBytes(64)),
- encodeCrock(getRandomBytes(64)),
- ],
- eventType: DenomLossEventType.DenomExpired,
- status: DenomLossStatus.Done,
- timestampCreated: timestampPreciseToDb(TalerPreciseTimestamp.now()),
- };
- await tx.denomLossEvents.put(newRg);
- });
- return;
+ switch (parsedUri.devExperimentId) {
+ case "start-block-refresh": {
+ wex.ws.devExperimentState.blockRefreshes = true;
+ return;
+ }
+ case "stop-block-refresh": {
+ wex.ws.devExperimentState.blockRefreshes = false;
+ return;
+ }
+ case "insert-pending-refresh": {
+ await wex.db.runReadWriteTx(["refreshGroups"], async (tx) => {
+ const refreshGroupId = encodeCrock(getRandomBytes(32));
+ const newRg: RefreshGroupRecord = {
+ currency: "TESTKUDOS",
+ expectedOutputPerCoin: [],
+ inputPerCoin: [],
+ oldCoinPubs: [],
+ operationStatus: RefreshOperationStatus.Pending,
+ reason: RefreshReason.Manual,
+ refreshGroupId,
+ statusPerCoin: [],
+ timestampCreated: timestampPreciseToDb(TalerPreciseTimestamp.now()),
+ timestampFinished: undefined,
+ originatingTransactionId: undefined,
+ infoPerExchange: {},
+ };
+ await tx.refreshGroups.put(newRg);
+ });
+ return;
+ }
+ case "insert-denom-loss": {
+ await wex.db.runReadWriteTx(["denomLossEvents"], async (tx) => {
+ const eventId = encodeCrock(getRandomBytes(32));
+ const newRg: DenomLossEventRecord = {
+ amount: "TESTKUDOS:42",
+ currency: "TESTKUDOS",
+ exchangeBaseUrl: "https://exchange.test.taler.net/",
+ denomLossEventId: eventId,
+ denomPubHashes: [
+ encodeCrock(getRandomBytes(64)),
+ encodeCrock(getRandomBytes(64)),
+ ],
+ eventType: DenomLossEventType.DenomExpired,
+ status: DenomLossStatus.Done,
+ timestampCreated: timestampPreciseToDb(TalerPreciseTimestamp.now()),
+ };
+ await tx.denomLossEvents.put(newRg);
+ });
+ return;
+ }
}
throw Error(`dev-experiment id not understood ${parsedUri.devExperimentId}`);
diff --git a/packages/taler-wallet-core/src/transactions.ts b/packages/taler-wallet-core/src/transactions.ts
index e404c0354..463aa97ba 100644
--- a/packages/taler-wallet-core/src/transactions.ts
+++ b/packages/taler-wallet-core/src/transactions.ts
@@ -894,10 +894,14 @@ function buildTransactionForDeposit(
ort?: OperationRetryRecord,
): Transaction {
let deposited = true;
- for (const d of dg.statusPerCoin) {
- if (d == DepositElementStatus.DepositPending) {
- deposited = false;
+ if (dg.statusPerCoin) {
+ for (const d of dg.statusPerCoin) {
+ if (d == DepositElementStatus.DepositPending) {
+ deposited = false;
+ }
}
+ } else {
+ deposited = false;
}
const trackingState: DepositTransactionTrackingState[] = [];
@@ -911,6 +915,17 @@ function buildTransactionForDeposit(
});
}
+ let wireTransferProgress = 0;
+ if (dg.statusPerCoin) {
+ wireTransferProgress =
+ (100 *
+ dg.statusPerCoin.reduce(
+ (prev, cur) => prev + (cur === DepositElementStatus.Wired ? 1 : 0),
+ 0,
+ )) /
+ dg.statusPerCoin.length;
+ }
+
const txState = computeDepositTransactionStatus(dg);
return {
type: TransactionType.Deposit,
@@ -927,13 +942,7 @@ function buildTransactionForDeposit(
tag: TransactionType.Deposit,
depositGroupId: dg.depositGroupId,
}),
- wireTransferProgress:
- (100 *
- dg.statusPerCoin.reduce(
- (prev, cur) => prev + (cur === DepositElementStatus.Wired ? 1 : 0),
- 0,
- )) /
- dg.statusPerCoin.length,
+ wireTransferProgress,
depositGroupId: dg.depositGroupId,
trackingState,
deposited,