From ffd2a62c3f7df94365980302fef3bc3376b48182 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 3 Aug 2020 13:00:48 +0530 Subject: modularize repo, use pnpm, improve typechecking --- .../taler-wallet-core/src/operations/balance.ts | 153 +++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 packages/taler-wallet-core/src/operations/balance.ts (limited to 'packages/taler-wallet-core/src/operations/balance.ts') diff --git a/packages/taler-wallet-core/src/operations/balance.ts b/packages/taler-wallet-core/src/operations/balance.ts new file mode 100644 index 000000000..26f0aaeee --- /dev/null +++ b/packages/taler-wallet-core/src/operations/balance.ts @@ -0,0 +1,153 @@ +/* + This file is part of GNU Taler + (C) 2019 GNUnet e.V. + + 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 + */ + +/** + * Imports. + */ +import { BalancesResponse } from "../types/walletTypes"; +import { TransactionHandle } from "../util/query"; +import { InternalWalletState } from "./state"; +import { Stores, CoinStatus } from "../types/dbTypes"; +import * as Amounts from "../util/amounts"; +import { AmountJson } from "../util/amounts"; +import { Logger } from "../util/logging"; + +const logger = new Logger("withdraw.ts"); + +interface WalletBalance { + available: AmountJson; + pendingIncoming: AmountJson; + pendingOutgoing: AmountJson; +} + +/** + * Get balance information. + */ +export async function getBalancesInsideTransaction( + ws: InternalWalletState, + tx: TransactionHandle, +): Promise { + const balanceStore: Record = {}; + + /** + * Add amount to a balance field, both for + * the slicing by exchange and currency. + */ + const initBalance = (currency: string): WalletBalance => { + const b = balanceStore[currency]; + if (!b) { + balanceStore[currency] = { + available: Amounts.getZero(currency), + pendingIncoming: Amounts.getZero(currency), + pendingOutgoing: Amounts.getZero(currency), + }; + } + return balanceStore[currency]; + }; + + // Initialize balance to zero, even if we didn't start withdrawing yet. + await tx.iter(Stores.reserves).forEach((r) => { + const b = initBalance(r.currency); + if (!r.initialWithdrawalStarted) { + b.pendingIncoming = Amounts.add( + b.pendingIncoming, + r.initialDenomSel.totalCoinValue, + ).amount; + } + }); + + await tx.iter(Stores.coins).forEach((c) => { + // Only count fresh coins, as dormant coins will + // already be in a refresh session. + if (c.status === CoinStatus.Fresh) { + const b = initBalance(c.currentAmount.currency); + b.available = Amounts.add(b.available, c.currentAmount).amount; + } + }); + + await tx.iter(Stores.refreshGroups).forEach((r) => { + // Don't count finished refreshes, since the refresh already resulted + // in coins being added to the wallet. + if (r.timestampFinished) { + return; + } + for (let i = 0; i < r.oldCoinPubs.length; i++) { + const session = r.refreshSessionPerCoin[i]; + if (session) { + const b = initBalance(session.amountRefreshOutput.currency); + // We are always assuming the refresh will succeed, thus we + // report the output as available balance. + b.available = Amounts.add(session.amountRefreshOutput).amount; + } + } + }); + + await tx.iter(Stores.withdrawalGroups).forEach((wds) => { + if (wds.timestampFinish) { + return; + } + const b = initBalance(wds.denomsSel.totalWithdrawCost.currency); + b.pendingIncoming = Amounts.add( + b.pendingIncoming, + wds.denomsSel.totalCoinValue, + ).amount; + }); + + const balancesResponse: BalancesResponse = { + balances: [], + }; + + Object.keys(balanceStore) + .sort() + .forEach((c) => { + const v = balanceStore[c]; + balancesResponse.balances.push({ + available: Amounts.stringify(v.available), + pendingIncoming: Amounts.stringify(v.pendingIncoming), + pendingOutgoing: Amounts.stringify(v.pendingOutgoing), + hasPendingTransactions: false, + requiresUserInput: false, + }); + }); + + return balancesResponse; +} + +/** + * Get detailed balance information, sliced by exchange and by currency. + */ +export async function getBalances( + ws: InternalWalletState, +): Promise { + logger.trace("starting to compute balance"); + + const wbal = await ws.db.runWithReadTransaction( + [ + Stores.coins, + Stores.refreshGroups, + Stores.reserves, + Stores.purchases, + Stores.withdrawalGroups, + ], + async (tx) => { + return getBalancesInsideTransaction(ws, tx); + }, + ); + + logger.trace("finished computing wallet balance"); + + return wbal; +} -- cgit v1.2.3