summaryrefslogtreecommitdiff
path: root/src/operations/balance.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/operations/balance.ts')
-rw-r--r--src/operations/balance.ts158
1 files changed, 158 insertions, 0 deletions
diff --git a/src/operations/balance.ts b/src/operations/balance.ts
new file mode 100644
index 000000000..8c8a2a9cf
--- /dev/null
+++ b/src/operations/balance.ts
@@ -0,0 +1,158 @@
+/*
+ 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 <http://www.gnu.org/licenses/>
+ */
+
+/**
+ * Imports.
+ */
+import { WalletBalance, WalletBalanceEntry } from "../types/walletTypes";
+import { runWithReadTransaction } from "../util/query";
+import { InternalWalletState } from "./state";
+import { Stores, TipRecord, 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");
+
+/**
+ * Get detailed balance information, sliced by exchange and by currency.
+ */
+export async function getBalances(
+ ws: InternalWalletState,
+): Promise<WalletBalance> {
+ logger.trace("starting to compute balance");
+ /**
+ * Add amount to a balance field, both for
+ * the slicing by exchange and currency.
+ */
+ function addTo(
+ balance: WalletBalance,
+ field: keyof WalletBalanceEntry,
+ amount: AmountJson,
+ exchange: string,
+ ): void {
+ const z = Amounts.getZero(amount.currency);
+ const balanceIdentity = {
+ available: z,
+ paybackAmount: z,
+ pendingIncoming: z,
+ pendingPayment: z,
+ pendingIncomingDirty: z,
+ pendingIncomingRefresh: z,
+ pendingIncomingWithdraw: z,
+ };
+ let entryCurr = balance.byCurrency[amount.currency];
+ if (!entryCurr) {
+ balance.byCurrency[amount.currency] = entryCurr = {
+ ...balanceIdentity,
+ };
+ }
+ let entryEx = balance.byExchange[exchange];
+ if (!entryEx) {
+ balance.byExchange[exchange] = entryEx = { ...balanceIdentity };
+ }
+ entryCurr[field] = Amounts.add(entryCurr[field], amount).amount;
+ entryEx[field] = Amounts.add(entryEx[field], amount).amount;
+ }
+
+ const balanceStore = {
+ byCurrency: {},
+ byExchange: {},
+ };
+
+ await runWithReadTransaction(
+ ws.db,
+ [Stores.coins, Stores.refresh, Stores.reserves, Stores.purchases, Stores.withdrawalSession],
+ async tx => {
+ await tx.iter(Stores.coins).forEach(c => {
+ if (c.suspended) {
+ return;
+ }
+ if (c.status === CoinStatus.Fresh) {
+ addTo(balanceStore, "available", c.currentAmount, c.exchangeBaseUrl);
+ }
+ if (c.status === CoinStatus.Dirty) {
+ addTo(
+ balanceStore,
+ "pendingIncoming",
+ c.currentAmount,
+ c.exchangeBaseUrl,
+ );
+ addTo(
+ balanceStore,
+ "pendingIncomingDirty",
+ c.currentAmount,
+ c.exchangeBaseUrl,
+ );
+ }
+ });
+ await tx.iter(Stores.refresh).forEach(r => {
+ // Don't count finished refreshes, since the refresh already resulted
+ // in coins being added to the wallet.
+ if (r.finishedTimestamp) {
+ return;
+ }
+ addTo(
+ balanceStore,
+ "pendingIncoming",
+ r.valueOutput,
+ r.exchangeBaseUrl,
+ );
+ addTo(
+ balanceStore,
+ "pendingIncomingRefresh",
+ r.valueOutput,
+ r.exchangeBaseUrl,
+ );
+ });
+
+ await tx.iter(Stores.withdrawalSession).forEach(wds => {
+ let w = wds.totalCoinValue;
+ for (let i = 0; i < wds.planchets.length; i++) {
+ if (wds.withdrawn[i]) {
+ const p = wds.planchets[i];
+ if (p) {
+ w = Amounts.sub(w, p.coinValue).amount;
+ }
+ }
+ }
+ addTo(
+ balanceStore,
+ "pendingIncoming",
+ w,
+ wds.exchangeBaseUrl,
+ );
+ });
+
+ await tx.iter(Stores.purchases).forEach(t => {
+ if (t.firstSuccessfulPayTimestamp) {
+ return;
+ }
+ for (const c of t.payReq.coins) {
+ addTo(
+ balanceStore,
+ "pendingPayment",
+ Amounts.parseOrThrow(c.contribution),
+ c.exchange_url,
+ );
+ }
+ });
+ },
+ );
+
+ logger.trace("computed balances:", balanceStore);
+ return balanceStore;
+}