diff options
author | Florian Dold <florian@dold.me> | 2024-03-07 11:10:52 +0100 |
---|---|---|
committer | Florian Dold <florian@dold.me> | 2024-03-07 11:10:52 +0100 |
commit | 466e2b7643692aa6b7f76a193b84775008e17350 (patch) | |
tree | dd3f9a0b67765e2c7ea6b97c2a7acbbcac71d4b7 /packages/taler-wallet-core/src/balance.ts | |
parent | 8eb3e505be967afde0053d5a392e8c6877d8f1dd (diff) | |
download | wallet-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.ts | 212 |
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, - }; -} |