gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] branch master updated: wallet-core: improve insuffic


From: gnunet
Subject: [taler-wallet-core] branch master updated: wallet-core: improve insufficient balance reporting
Date: Thu, 07 Mar 2024 11:10:57 +0100

This is an automated email from the git hooks/post-receive script.

dold pushed a commit to branch master
in repository wallet-core.

The following commit(s) were added to refs/heads/master by this push:
     new 466e2b764 wallet-core: improve insufficient balance reporting
466e2b764 is described below

commit 466e2b7643692aa6b7f76a193b84775008e17350
Author: Florian Dold <florian@dold.me>
AuthorDate: Thu Mar 7 11:10:52 2024 +0100

    wallet-core: improve insufficient balance reporting
---
 packages/taler-harness/src/harness/harness.ts      |  62 +++---
 .../test-wallet-insufficient-balance.ts            | 166 ++++++++++++++++
 .../src/integrationtests/testrunner.ts             |   2 +
 packages/taler-util/src/errors.ts                  |   6 +-
 packages/taler-util/src/wallet-types.ts            |  14 +-
 packages/taler-wallet-core/src/balance.ts          | 212 ++++++++-------------
 packages/taler-wallet-core/src/coinSelection.ts    | 114 ++++-------
 packages/taler-wallet-core/src/wallet-api-types.ts |   4 +-
 .../src/components/PaymentButtons.tsx              |   4 +-
 9 files changed, 342 insertions(+), 242 deletions(-)

diff --git a/packages/taler-harness/src/harness/harness.ts 
b/packages/taler-harness/src/harness/harness.ts
index 291cd4c6d..3d9e7fb8a 100644
--- a/packages/taler-harness/src/harness/harness.ts
+++ b/packages/taler-harness/src/harness/harness.ts
@@ -605,6 +605,11 @@ export interface HarnessExchangeBankAccount {
 
   debitRestrictions?: AccountRestriction[];
   creditRestrictions?: AccountRestriction[];
+
+  /**
+   * If set, the harness will not automatically configure the wire fee for 
this account.
+   */
+  skipWireFeeCreation?: boolean;
 }
 
 /**
@@ -1320,7 +1325,6 @@ export class ExchangeService implements 
ExchangeServiceInterface {
       if (!p) {
         throw Error(`invalid payto uri in exchange config: ${paytoUri}`);
       }
-      accountTargetTypes.add(p?.targetType);
       const optArgs: string[] = [];
       if (acct.conversionUrl != null) {
         optArgs.push("conversion-url", acct.conversionUrl);
@@ -1339,33 +1343,43 @@ export class ExchangeService implements 
ExchangeServiceInterface {
           "upload",
         ],
       );
-    }
 
-    const year = new Date().getFullYear();
-    for (const accTargetType of accountTargetTypes.values()) {
-      for (let i = year; i < year + 5; i++) {
-        await runCommand(
-          this.globalState,
-          "exchange-offline",
-          "taler-exchange-offline",
-          [
-            "-c",
-            this.configFilename,
-            "wire-fee",
-            // Year
-            `${i}`,
-            // Wire method
-            accTargetType,
-            // Wire fee
-            `${this.exchangeConfig.currency}:0.01`,
-            // Closing fee
-            `${this.exchangeConfig.currency}:0.01`,
-            "upload",
-          ],
-        );
+      const accTargetType = p.targetType;
+
+      const covered = accountTargetTypes.has(p.targetType);
+      if (!covered && !acct.skipWireFeeCreation) {
+        const year = new Date().getFullYear();
+
+        for (let i = year; i < year + 5; i++) {
+          await runCommand(
+            this.globalState,
+            "exchange-offline",
+            "taler-exchange-offline",
+            [
+              "-c",
+              this.configFilename,
+              "wire-fee",
+              // Year
+              `${i}`,
+              // Wire method
+              accTargetType,
+              // Wire fee
+              `${this.exchangeConfig.currency}:0.01`,
+              // Closing fee
+              `${this.exchangeConfig.currency}:0.01`,
+              "upload",
+            ],
+          );
+          accountTargetTypes.add(accTargetType);
+        }
       }
     }
 
+    const wireTypeConfigured: Set<string> = new Set();
+
+    for (const acct of this.exchangeBankAccounts) {
+    }
+
     await runCommand(
       this.globalState,
       "exchange-offline",
diff --git 
a/packages/taler-harness/src/integrationtests/test-wallet-insufficient-balance.ts
 
b/packages/taler-harness/src/integrationtests/test-wallet-insufficient-balance.ts
new file mode 100644
index 000000000..ac1244446
--- /dev/null
+++ 
b/packages/taler-harness/src/integrationtests/test-wallet-insufficient-balance.ts
@@ -0,0 +1,166 @@
+/*
+ This file is part of GNU Taler
+ (C) 2020 Taler Systems S.A.
+
+ 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 {
+  AmountString,
+  Duration,
+  PaymentInsufficientBalanceDetails,
+  TalerErrorCode,
+  WalletNotification,
+} from "@gnu-taler/taler-util";
+import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
+import {
+  ExchangeService,
+  FakebankService,
+  GlobalTestState,
+  MerchantService,
+  WalletClient,
+  WalletService,
+  generateRandomPayto,
+  setupDb,
+} from "../harness/harness.js";
+import { withdrawViaBankV2 } from "../harness/helpers.js";
+
+export async function runWalletInsufficientBalanceTest(t: GlobalTestState) {
+  // Set up test environment
+
+  const db = await setupDb(t);
+
+  const bank = await FakebankService.create(t, {
+    allowRegistrations: true,
+    currency: "TESTKUDOS",
+    database: db.connStr,
+    httpPort: 8082,
+  });
+
+  const exchange = ExchangeService.create(t, {
+    name: "testexchange-1",
+    currency: "TESTKUDOS",
+    httpPort: 8081,
+    database: db.connStr,
+  });
+
+  const merchant = await MerchantService.create(t, {
+    name: "testmerchant-1",
+    currency: "TESTKUDOS",
+    httpPort: 8083,
+    database: db.connStr,
+  });
+
+  const exchangeBankAccount = await bank.createExchangeAccount(
+    "myexchange",
+    "x",
+  );
+  exchangeBankAccount.skipWireFeeCreation = true;
+  exchange.addBankAccount("1", exchangeBankAccount);
+
+  bank.setSuggestedExchange(exchange, exchangeBankAccount.accountPaytoUri);
+
+  await bank.start();
+
+  await bank.pingUntilAvailable();
+
+  const coinConfig: CoinConfig[] = defaultCoinConfig.map((x) => 
x("TESTKUDOS"));
+  exchange.addCoinConfigList(coinConfig);
+
+  await exchange.start();
+  await exchange.pingUntilAvailable();
+
+  merchant.addExchange(exchange);
+
+  await merchant.start();
+  await merchant.pingUntilAvailable();
+
+  await merchant.addInstanceWithWireAccount({
+    id: "default",
+    name: "Default Instance",
+    paytoUris: [generateRandomPayto("merchant-default")],
+    defaultWireTransferDelay: Duration.toTalerProtocolDuration(
+      Duration.fromSpec({ minutes: 1 }),
+    ),
+  });
+
+  await merchant.addInstanceWithWireAccount({
+    id: "minst1",
+    name: "minst1",
+    paytoUris: [generateRandomPayto("minst1")],
+    defaultWireTransferDelay: Duration.toTalerProtocolDuration(
+      Duration.fromSpec({ minutes: 1 }),
+    ),
+  });
+
+  const walletService = new WalletService(t, {
+    name: "wallet",
+    useInMemoryDb: true,
+  });
+  await walletService.start();
+  await walletService.pingUntilAvailable();
+
+  const allNotifications: WalletNotification[] = [];
+
+  const walletClient = new WalletClient({
+    name: "wallet",
+    unixPath: walletService.socketPath,
+    onNotification(n) {
+      console.log("got notification", n);
+      allNotifications.push(n);
+    },
+  });
+  await walletClient.connect();
+  await walletClient.client.call(WalletApiOperation.InitWallet, {
+    config: {
+      testing: {
+        skipDefaults: true,
+      },
+    },
+  });
+
+  const wres = await withdrawViaBankV2(t, {
+    amount: "TESTKUDOS:10",
+    bank,
+    exchange,
+    walletClient,
+  });
+  await wres.withdrawalFinishedCond;
+
+  const exc = await t.assertThrowsTalerErrorAsync(async () => {
+    await walletClient.call(WalletApiOperation.PrepareDeposit, {
+      amount: "TESTKUDOS:5" as AmountString,
+      depositPaytoUri: "payto://x-taler-bank/localhost/foobar",
+    });
+  });
+  t.assertDeepEqual(
+    exc.errorDetail.code,
+    TalerErrorCode.WALLET_DEPOSIT_GROUP_INSUFFICIENT_BALANCE,
+  );
+  const insufficientBalanceDetails: PaymentInsufficientBalanceDetails =
+    exc.errorDetail.insufficientBalanceDetails;
+
+  t.assertAmountEquals(
+    insufficientBalanceDetails.balanceAvailable,
+    "TESTKUDOS:9.72",
+  );
+  t.assertAmountEquals(
+    insufficientBalanceDetails.balanceExchangeDepositable,
+    "TESTKUDOS:0",
+  );
+}
+
+runWalletInsufficientBalanceTest.suites = ["wallet"];
diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts 
b/packages/taler-harness/src/integrationtests/testrunner.ts
index 803e68e6b..0bfb245ab 100644
--- a/packages/taler-harness/src/integrationtests/testrunner.ts
+++ b/packages/taler-harness/src/integrationtests/testrunner.ts
@@ -96,6 +96,7 @@ import { runWalletDblessTest } from "./test-wallet-dbless.js";
 import { runWalletDd48Test } from "./test-wallet-dd48.js";
 import { runWalletDevExperimentsTest } from "./test-wallet-dev-experiments.js";
 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 { runWalletRefreshTest } from "./test-wallet-refresh.js";
@@ -204,6 +205,7 @@ const allTests: TestMainFunction[] = [
   runWalletObservabilityTest,
   runWalletDevExperimentsTest,
   runWalletBalanceZeroTest,
+  runWalletInsufficientBalanceTest,
 ];
 
 export interface TestRunSpec {
diff --git a/packages/taler-util/src/errors.ts 
b/packages/taler-util/src/errors.ts
index c4733a194..3ada34d63 100644
--- a/packages/taler-util/src/errors.ts
+++ b/packages/taler-util/src/errors.ts
@@ -25,7 +25,7 @@
  */
 import {
   AbsoluteTime,
-  PayMerchantInsufficientBalanceDetails,
+  PaymentInsufficientBalanceDetails,
   TalerErrorCode,
   TalerErrorDetail,
   TransactionType,
@@ -132,10 +132,10 @@ export interface DetailsMap {
     kycUrl: string;
   };
   [TalerErrorCode.WALLET_DEPOSIT_GROUP_INSUFFICIENT_BALANCE]: {
-    insufficientBalanceDetails: PayMerchantInsufficientBalanceDetails;
+    insufficientBalanceDetails: PaymentInsufficientBalanceDetails;
   };
   [TalerErrorCode.WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE]: {
-    insufficientBalanceDetails: PayMerchantInsufficientBalanceDetails;
+    insufficientBalanceDetails: PaymentInsufficientBalanceDetails;
   };
   [TalerErrorCode.WALLET_REFRESH_GROUP_INCOMPLETE]: {
     numErrors: number;
diff --git a/packages/taler-util/src/wallet-types.ts 
b/packages/taler-util/src/wallet-types.ts
index cb4374648..8be8fc4a0 100644
--- a/packages/taler-util/src/wallet-types.ts
+++ b/packages/taler-util/src/wallet-types.ts
@@ -833,10 +833,12 @@ export const codecForPreparePayResultPaymentPossible =
       )
       .build("PreparePayResultPaymentPossible");
 
+export interface BalanceDetails {}
+
 /**
  * Detailed reason for why the wallet's balance is insufficient.
  */
-export interface PayMerchantInsufficientBalanceDetails {
+export interface PaymentInsufficientBalanceDetails {
   /**
    * Amount requested by the merchant.
    */
@@ -867,6 +869,8 @@ export interface PayMerchantInsufficientBalanceDetails {
    */
   balanceMerchantDepositable: AmountString;
 
+  balanceExchangeDepositable: AmountString;
+
   /**
    * Maximum effective amount that the wallet can spend,
    * when all fees are paid by the wallet.
@@ -877,19 +881,21 @@ export interface PayMerchantInsufficientBalanceDetails {
     [url: string]: {
       balanceAvailable: AmountString;
       balanceMaterial: AmountString;
+      balanceExchangeDepositable: AmountString;
     };
   };
 }
 
 export const codecForPayMerchantInsufficientBalanceDetails =
-  (): Codec<PayMerchantInsufficientBalanceDetails> =>
-    buildCodecForObject<PayMerchantInsufficientBalanceDetails>()
+  (): Codec<PaymentInsufficientBalanceDetails> =>
+    buildCodecForObject<PaymentInsufficientBalanceDetails>()
       .property("amountRequested", codecForAmountString())
       .property("balanceAgeAcceptable", codecForAmountString())
       .property("balanceAvailable", codecForAmountString())
       .property("balanceMaterial", codecForAmountString())
       .property("balanceMerchantAcceptable", codecForAmountString())
       .property("balanceMerchantDepositable", codecForAmountString())
+      .property("balanceExchangeDepositable", codecForAmountString())
       .property("perExchange", codecForAny())
       .property("maxEffectiveSpendAmount", codecForAmountString())
       .build("PayMerchantInsufficientBalanceDetails");
@@ -981,7 +987,7 @@ export interface PreparePayResultInsufficientBalance {
   contractTerms: MerchantContractTerms;
   amountRaw: AmountString;
   talerUri: string;
-  balanceDetails: PayMerchantInsufficientBalanceDetails;
+  balanceDetails: PaymentInsufficientBalanceDetails;
 }
 
 export interface PreparePayResultAlreadyConfirmed {
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,
-  };
-}
diff --git a/packages/taler-wallet-core/src/coinSelection.ts 
b/packages/taler-wallet-core/src/coinSelection.ts
index 5ac52e1d3..695be79ac 100644
--- a/packages/taler-wallet-core/src/coinSelection.ts
+++ b/packages/taler-wallet-core/src/coinSelection.ts
@@ -42,15 +42,12 @@ import {
   Logger,
   parsePaytoUri,
   PayCoinSelection,
-  PayMerchantInsufficientBalanceDetails,
+  PaymentInsufficientBalanceDetails,
   SelectedCoin,
   strcmp,
   TalerProtocolTimestamp,
 } from "@gnu-taler/taler-util";
-import {
-  getExchangePaymentBalanceDetailsInTx,
-  getMerchantPaymentBalanceDetailsInTx,
-} from "./balance.js";
+import { getPaymentBalanceDetailsInTx } from "./balance.js";
 import { getAutoRefreshExecuteThreshold } from "./common.js";
 import { DenominationRecord, WalletDbReadOnlyTransaction } from "./db.js";
 import {
@@ -171,7 +168,7 @@ function tallyFees(
 export type SelectPayCoinsResult =
   | {
       type: "failure";
-      insufficientBalanceDetails: PayMerchantInsufficientBalanceDetails;
+      insufficientBalanceDetails: PaymentInsufficientBalanceDetails;
     }
   | { type: "success"; coinSel: PayCoinSelection };
 
@@ -264,6 +261,7 @@ export async function selectPayCoins(
               instructedAmount: req.contractTermsAmount,
               requiredMinimumAge: req.requiredMinimumAge,
               wireMethod: req.restrictWireMethod,
+              depositPaytoUri: req.depositPaytoUri,
             },
           ),
         } satisfies SelectPayCoinsResult;
@@ -273,7 +271,6 @@ export async function selectPayCoins(
         tx,
         selectedDenom,
         coinRes,
-        req.contractTermsAmount,
         tally,
       );
 
@@ -334,7 +331,6 @@ async function assembleSelectPayCoinsSuccessResult(
   tx: WalletDbReadOnlyTransaction<["coins"]>,
   finalSel: SelResult,
   coinRes: SelectedCoin[],
-  contractTermsAmount: AmountJson,
   tally: CoinSelectionTally,
 ): Promise<PayCoinSelection> {
   for (const dph of Object.keys(finalSel)) {
@@ -378,6 +374,7 @@ interface ReportInsufficientBalanceRequest {
   requiredMinimumAge: number | undefined;
   restrictExchanges: ExchangeRestrictionSpec | undefined;
   wireMethod: string | undefined;
+  depositPaytoUri: string | undefined;
 }
 
 export async function reportInsufficientBalanceDetails(
@@ -392,82 +389,42 @@ export async function reportInsufficientBalanceDetails(
     ]
   >,
   req: ReportInsufficientBalanceRequest,
-): Promise<PayMerchantInsufficientBalanceDetails> {
-  const currency = Amounts.currencyOf(req.instructedAmount);
-  const details = await getMerchantPaymentBalanceDetailsInTx(wex, tx, {
+): Promise<PaymentInsufficientBalanceDetails> {
+  const details = await getPaymentBalanceDetailsInTx(wex, tx, {
     restrictExchanges: req.restrictExchanges,
     restrictWireMethods: req.wireMethod ? [req.wireMethod] : [],
     currency: Amounts.currencyOf(req.instructedAmount),
     minAge: req.requiredMinimumAge ?? 0,
+    depositPaytoUri: req.depositPaytoUri,
   });
-  let feeGapEstimate: AmountJson;
-
-  // FIXME: need fee gap estimate
-  // FIXME: We can probably give a better estimate.
-  // feeGapEstimate = Amounts.add(
-  //   tally.amountPayRemaining,
-  //   tally.lastDepositFee,
-  // ).amount;
-
-  feeGapEstimate = Amounts.zeroOfAmount(req.instructedAmount);
-
-  const perExchange: PayMerchantInsufficientBalanceDetails["perExchange"] = {};
-
-  const exchanges = await tx.exchanges.iter().toArray();
-
-  let maxEffectiveSpendAmount = Amounts.zeroOfAmount(req.instructedAmount);
+  const perExchange: PaymentInsufficientBalanceDetails["perExchange"] = {};
+  const exchanges = await tx.exchanges.getAll();
 
   for (const exch of exchanges) {
-    if (exch.detailsPointer?.currency !== currency) {
+    if (!exch.detailsPointer) {
       continue;
     }
-
-    // We now see how much we could spend if we paid all the fees ourselves
-    // in a worst-case estimate.
-
-    const exchangeBaseUrl = exch.baseUrl;
-    let ageLower = 0;
-    let ageUpper = AgeRestriction.AGE_UNRESTRICTED;
-    if (req.requiredMinimumAge) {
-      ageLower = req.requiredMinimumAge;
-    }
-
-    const myExchangeCoins =
-      await tx.coinAvailability.indexes.byExchangeAgeAvailability.getAll(
-        GlobalIDB.KeyRange.bound(
-          [exchangeBaseUrl, ageLower, 1],
-          [exchangeBaseUrl, ageUpper, Number.MAX_SAFE_INTEGER],
-        ),
-      );
-
-    for (const ec of myExchangeCoins) {
-      maxEffectiveSpendAmount = Amounts.add(
-        maxEffectiveSpendAmount,
-        Amounts.mult(ec.value, ec.freshCoinCount).amount,
-      ).amount;
-
-      const denom = await getDenomInfo(
-        wex,
-        tx,
-        exchangeBaseUrl,
-        ec.denomPubHash,
-      );
-      if (!denom) {
-        continue;
-      }
-      maxEffectiveSpendAmount = Amounts.sub(
-        maxEffectiveSpendAmount,
-        Amounts.mult(denom.feeDeposit, ec.freshCoinCount).amount,
-      ).amount;
-    }
-
-    const infoExchange = await getExchangePaymentBalanceDetailsInTx(wex, tx, {
-      currency,
-      restrictExchangeTo: exch.baseUrl,
+    const exchDet = await getPaymentBalanceDetailsInTx(wex, tx, {
+      restrictExchanges: {
+        exchanges: [
+          {
+            exchangeBaseUrl: exch.baseUrl,
+            exchangePub: exch.detailsPointer?.masterPublicKey,
+          },
+        ],
+        auditors: [],
+      },
+      restrictWireMethods: req.wireMethod ? [req.wireMethod] : [],
+      currency: Amounts.currencyOf(req.instructedAmount),
+      minAge: req.requiredMinimumAge ?? 0,
+      depositPaytoUri: req.depositPaytoUri,
     });
     perExchange[exch.baseUrl] = {
-      balanceAvailable: Amounts.stringify(infoExchange.balanceAvailable),
-      balanceMaterial: Amounts.stringify(infoExchange.balanceMaterial),
+      balanceAvailable: Amounts.stringify(exchDet.balanceAvailable),
+      balanceMaterial: Amounts.stringify(exchDet.balanceMaterial),
+      balanceExchangeDepositable: Amounts.stringify(
+        exchDet.balanceExchangeDepositable,
+      ),
     };
   }
 
@@ -479,10 +436,13 @@ export async function reportInsufficientBalanceDetails(
     balanceMerchantAcceptable: Amounts.stringify(
       details.balanceMerchantAcceptable,
     ),
+    balanceExchangeDepositable: Amounts.stringify(
+      details.balanceExchangeDepositable,
+    ),
     balanceMerchantDepositable: Amounts.stringify(
       details.balanceMerchantDepositable,
     ),
-    maxEffectiveSpendAmount: Amounts.stringify(maxEffectiveSpendAmount),
+    maxEffectiveSpendAmount: 
Amounts.stringify(details.maxEffectiveSpendAmount),
     perExchange,
   };
 }
@@ -682,7 +642,7 @@ export type AvailableDenom = DenominationInfo & {
   numAvailable: number;
 };
 
-function findMatchingWire(
+export function findMatchingWire(
   wireMethod: string,
   depositPaytoUri: string | undefined,
   exchangeWireDetails: ExchangeWireDetails,
@@ -876,7 +836,7 @@ export type SelectPeerCoinsResult =
   | { type: "success"; result: PeerCoinSelectionDetails }
   | {
       type: "failure";
-      insufficientBalanceDetails: PayMerchantInsufficientBalanceDetails;
+      insufficientBalanceDetails: PaymentInsufficientBalanceDetails;
     };
 
 export interface PeerCoinSelectionRequest {
@@ -1017,7 +977,6 @@ export async function selectPeerCoins(
             tx,
             selectedDenom,
             resCoins,
-            req.instructedAmount,
             tally,
           );
 
@@ -1046,6 +1005,7 @@ export async function selectPeerCoins(
           instructedAmount: req.instructedAmount,
           requiredMinimumAge: undefined,
           wireMethod: undefined,
+          depositPaytoUri: undefined,
         },
       );
       return {
diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts 
b/packages/taler-wallet-core/src/wallet-api-types.ts
index 240012bca..ace702e88 100644
--- a/packages/taler-wallet-core/src/wallet-api-types.ts
+++ b/packages/taler-wallet-core/src/wallet-api-types.ts
@@ -148,7 +148,7 @@ import {
   RemoveBackupProviderRequest,
   RunBackupCycleRequest,
 } from "./backup/index.js";
-import { MerchantPaymentBalanceDetails } from "./balance.js";
+import { PaymentBalanceDetails } from "./balance.js";
 
 export enum WalletApiOperation {
   InitWallet = "initWallet",
@@ -290,7 +290,7 @@ export type GetBalancesOp = {
 export type GetBalancesDetailOp = {
   op: WalletApiOperation.GetBalanceDetail;
   request: GetBalanceDetailRequest;
-  response: MerchantPaymentBalanceDetails;
+  response: PaymentBalanceDetails;
 };
 
 export type GetPlanForOperationOp = {
diff --git 
a/packages/taler-wallet-webextension/src/components/PaymentButtons.tsx 
b/packages/taler-wallet-webextension/src/components/PaymentButtons.tsx
index 731bcfed9..e7c4fbba4 100644
--- a/packages/taler-wallet-webextension/src/components/PaymentButtons.tsx
+++ b/packages/taler-wallet-webextension/src/components/PaymentButtons.tsx
@@ -17,7 +17,7 @@
 import {
   AmountJson,
   Amounts,
-  PayMerchantInsufficientBalanceDetails,
+  PaymentInsufficientBalanceDetails,
   PreparePayResult,
   PreparePayResultType,
   TranslatedString,
@@ -221,7 +221,7 @@ type NoEnoughBalanceReason =
   | "fee-gap";
 
 function getReason(
-  info: PayMerchantInsufficientBalanceDetails,
+  info: PaymentInsufficientBalanceDetails,
 ): NoEnoughBalanceReason {
   if (Amounts.cmp(info.amountRequested, info.balanceAvailable) > 0) {
     return "available";

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]