summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src/balance.ts
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2024-03-07 11:10:52 +0100
committerFlorian Dold <florian@dold.me>2024-03-07 11:10:52 +0100
commit466e2b7643692aa6b7f76a193b84775008e17350 (patch)
treedd3f9a0b67765e2c7ea6b97c2a7acbbcac71d4b7 /packages/taler-wallet-core/src/balance.ts
parent8eb3e505be967afde0053d5a392e8c6877d8f1dd (diff)
downloadwallet-core-466e2b7643692aa6b7f76a193b84775008e17350.tar.gz
wallet-core-466e2b7643692aa6b7f76a193b84775008e17350.tar.bz2
wallet-core-466e2b7643692aa6b7f76a193b84775008e17350.zip
wallet-core: improve insufficient balance reporting
Diffstat (limited to 'packages/taler-wallet-core/src/balance.ts')
-rw-r--r--packages/taler-wallet-core/src/balance.ts212
1 files changed, 82 insertions, 130 deletions
diff --git a/packages/taler-wallet-core/src/balance.ts b/packages/taler-wallet-core/src/balance.ts
index a77358363..4c58814a1 100644
--- a/packages/taler-wallet-core/src/balance.ts
+++ b/packages/taler-wallet-core/src/balance.ts
@@ -67,6 +67,7 @@ import {
ScopeInfo,
ScopeType,
} from "@gnu-taler/taler-util";
+import { findMatchingWire } from "./coinSelection.js";
import {
DepositOperationStatus,
OPERATION_STATUS_ACTIVE_FIRST,
@@ -80,7 +81,7 @@ import {
getExchangeScopeInfo,
getExchangeWireDetailsInTx,
} from "./exchanges.js";
-import { WalletExecutionContext } from "./wallet.js";
+import { getDenomInfo, WalletExecutionContext } from "./wallet.js";
/**
* Logger.
@@ -460,14 +461,6 @@ export async function getBalances(
return wbal;
}
-/**
- * Information about the balance for a particular payment to a particular
- * merchant.
- */
-export interface MerchantPaymentBalanceDetails {
- balanceAvailable: AmountJson;
-}
-
export interface PaymentRestrictionsForBalance {
currency: string;
minAge: number;
@@ -478,6 +471,7 @@ export interface PaymentRestrictionsForBalance {
}
| undefined;
restrictWireMethods: string[] | undefined;
+ depositPaytoUri: string | undefined;
}
export interface AcceptableExchanges {
@@ -587,7 +581,7 @@ async function getAcceptableExchangeBaseUrlsInTx(
};
}
-export interface MerchantPaymentBalanceDetails {
+export interface PaymentBalanceDetails {
/**
* Balance of type "available" (see balance.ts for definition).
*/
@@ -612,46 +606,110 @@ export interface MerchantPaymentBalanceDetails {
* Balance of type "merchant-depositable" (see balance.ts for definition).
*/
balanceMerchantDepositable: AmountJson;
+
+ /**
+ * Balance that's depositable with the exchange.
+ * This balance is reduced by the exchange's debit restrictions
+ * and wire fee configuration.
+ */
+ balanceExchangeDepositable: AmountJson;
+
+ maxEffectiveSpendAmount: AmountJson;
}
-export async function getMerchantPaymentBalanceDetails(
+export async function getPaymentBalanceDetails(
wex: WalletExecutionContext,
req: PaymentRestrictionsForBalance,
-): Promise<MerchantPaymentBalanceDetails> {
+): Promise<PaymentBalanceDetails> {
return await wex.db.runReadOnlyTx(
- ["coinAvailability", "refreshGroups", "exchanges", "exchangeDetails"],
+ [
+ "coinAvailability",
+ "refreshGroups",
+ "exchanges",
+ "exchangeDetails",
+ "denominations",
+ ],
async (tx) => {
- return getMerchantPaymentBalanceDetailsInTx(wex, tx, req);
+ return getPaymentBalanceDetailsInTx(wex, tx, req);
},
);
}
-export async function getMerchantPaymentBalanceDetailsInTx(
+export async function getPaymentBalanceDetailsInTx(
wex: WalletExecutionContext,
tx: WalletDbReadOnlyTransaction<
- ["coinAvailability", "refreshGroups", "exchanges", "exchangeDetails"]
+ [
+ "coinAvailability",
+ "refreshGroups",
+ "exchanges",
+ "exchangeDetails",
+ "denominations",
+ ]
>,
req: PaymentRestrictionsForBalance,
-): Promise<MerchantPaymentBalanceDetails> {
+): Promise<PaymentBalanceDetails> {
const acceptability = await getAcceptableExchangeBaseUrlsInTx(wex, tx, req);
- const d: MerchantPaymentBalanceDetails = {
+ const d: PaymentBalanceDetails = {
balanceAvailable: Amounts.zeroOfCurrency(req.currency),
balanceMaterial: Amounts.zeroOfCurrency(req.currency),
balanceAgeAcceptable: Amounts.zeroOfCurrency(req.currency),
balanceMerchantAcceptable: Amounts.zeroOfCurrency(req.currency),
balanceMerchantDepositable: Amounts.zeroOfCurrency(req.currency),
+ maxEffectiveSpendAmount: Amounts.zeroOfCurrency(req.currency),
+ balanceExchangeDepositable: Amounts.zeroOfCurrency(req.currency),
};
- await tx.coinAvailability.iter().forEach((ca) => {
+ const availableCoins = await tx.coinAvailability.getAll();
+
+ for (const ca of availableCoins) {
if (ca.currency != req.currency) {
- return;
+ continue;
+ }
+
+ const denom = await getDenomInfo(
+ wex,
+ tx,
+ ca.exchangeBaseUrl,
+ ca.denomPubHash,
+ );
+ if (!denom) {
+ continue;
+ }
+
+ const wireDetails = await getExchangeWireDetailsInTx(
+ tx,
+ ca.exchangeBaseUrl,
+ );
+ if (!wireDetails) {
+ continue;
}
+
const singleCoinAmount: AmountJson = Amounts.parseOrThrow(ca.value);
const coinAmount: AmountJson = Amounts.mult(
singleCoinAmount,
ca.freshCoinCount,
).amount;
+
+ d.maxEffectiveSpendAmount = Amounts.add(
+ d.maxEffectiveSpendAmount,
+ Amounts.mult(ca.value, ca.freshCoinCount).amount,
+ ).amount;
+
+ d.maxEffectiveSpendAmount = Amounts.sub(
+ d.maxEffectiveSpendAmount,
+ Amounts.mult(denom.feeDeposit, ca.freshCoinCount).amount,
+ ).amount;
+
+ let wireOkay = false;
+ if (req.restrictWireMethods == null) {
+ wireOkay = true;
+ } else {
+ for (const wm of req.restrictWireMethods) {
+ const wmf = findMatchingWire(wm, req.depositPaytoUri, wireDetails);
+ }
+ }
+
d.balanceAvailable = Amounts.add(d.balanceAvailable, coinAmount).amount;
d.balanceMaterial = Amounts.add(d.balanceMaterial, coinAmount).amount;
if (ca.maxAge === 0 || ca.maxAge > req.minAge) {
@@ -672,7 +730,7 @@ export async function getMerchantPaymentBalanceDetailsInTx(
}
}
}
- });
+ }
await tx.refreshGroups.iter().forEach((r) => {
if (r.currency != req.currency) {
@@ -690,7 +748,7 @@ export async function getMerchantPaymentBalanceDetailsInTx(
export async function getBalanceDetail(
wex: WalletExecutionContext,
req: GetBalanceDetailRequest,
-): Promise<MerchantPaymentBalanceDetails> {
+): Promise<PaymentBalanceDetails> {
const exchanges: { exchangeBaseUrl: string; exchangePub: string }[] = [];
const wires = new Array<string>();
await wex.db.runReadOnlyTx(["exchanges", "exchangeDetails"], async (tx) => {
@@ -713,7 +771,7 @@ export async function getBalanceDetail(
}
});
- return await getMerchantPaymentBalanceDetails(wex, {
+ return await getPaymentBalanceDetails(wex, {
currency: req.currency,
restrictExchanges: {
auditors: [],
@@ -721,112 +779,6 @@ export async function getBalanceDetail(
},
restrictWireMethods: wires,
minAge: 0,
+ depositPaytoUri: undefined,
});
}
-
-export interface PeerPaymentRestrictionsForBalance {
- currency: string;
- restrictExchangeTo?: string;
-}
-
-export interface PeerPaymentBalanceDetails {
- /**
- * Balance of type "available" (see balance.ts for definition).
- */
- balanceAvailable: AmountJson;
-
- /**
- * Balance of type "material" (see balance.ts for definition).
- */
- balanceMaterial: AmountJson;
-}
-
-export async function getPeerPaymentBalanceDetailsInTx(
- wex: WalletExecutionContext,
- tx: WalletDbReadOnlyTransaction<["coinAvailability", "refreshGroups"]>,
- req: PeerPaymentRestrictionsForBalance,
-): Promise<PeerPaymentBalanceDetails> {
- let balanceAvailable = Amounts.zeroOfCurrency(req.currency);
- let balanceMaterial = Amounts.zeroOfCurrency(req.currency);
-
- await tx.coinAvailability.iter().forEach((ca) => {
- if (ca.currency != req.currency) {
- return;
- }
- if (
- req.restrictExchangeTo &&
- req.restrictExchangeTo !== ca.exchangeBaseUrl
- ) {
- return;
- }
- const singleCoinAmount: AmountJson = Amounts.parseOrThrow(ca.value);
- const coinAmount: AmountJson = Amounts.mult(
- singleCoinAmount,
- ca.freshCoinCount,
- ).amount;
- balanceAvailable = Amounts.add(balanceAvailable, coinAmount).amount;
- balanceMaterial = Amounts.add(balanceMaterial, coinAmount).amount;
- });
-
- await tx.refreshGroups.iter().forEach((r) => {
- if (r.currency != req.currency) {
- return;
- }
- balanceAvailable = Amounts.add(
- balanceAvailable,
- computeRefreshGroupAvailableAmount(r),
- ).amount;
- });
-
- return {
- balanceAvailable,
- balanceMaterial,
- };
-}
-
-/**
- * Get information about the balance at a given exchange
- * with certain restrictions.
- */
-export async function getExchangePaymentBalanceDetailsInTx(
- wex: WalletExecutionContext,
- tx: WalletDbReadOnlyTransaction<["coinAvailability", "refreshGroups"]>,
- req: PeerPaymentRestrictionsForBalance,
-): Promise<PeerPaymentBalanceDetails> {
- let balanceAvailable = Amounts.zeroOfCurrency(req.currency);
- let balanceMaterial = Amounts.zeroOfCurrency(req.currency);
-
- await tx.coinAvailability.iter().forEach((ca) => {
- if (ca.currency != req.currency) {
- return;
- }
- if (
- req.restrictExchangeTo &&
- req.restrictExchangeTo !== ca.exchangeBaseUrl
- ) {
- return;
- }
- const singleCoinAmount: AmountJson = Amounts.parseOrThrow(ca.value);
- const coinAmount: AmountJson = Amounts.mult(
- singleCoinAmount,
- ca.freshCoinCount,
- ).amount;
- balanceAvailable = Amounts.add(balanceAvailable, coinAmount).amount;
- balanceMaterial = Amounts.add(balanceMaterial, coinAmount).amount;
- });
-
- await tx.refreshGroups.iter().forEach((r) => {
- if (r.currency != req.currency) {
- return;
- }
- balanceAvailable = Amounts.add(
- balanceAvailable,
- computeRefreshGroupAvailableAmount(r),
- ).amount;
- });
-
- return {
- balanceAvailable,
- balanceMaterial,
- };
-}