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: implement and te


From: gnunet
Subject: [taler-wallet-core] branch master updated: wallet-core: implement and test balance reporting with scope info
Date: Mon, 22 Jan 2024 21:29:52 +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 fb5f098f9 wallet-core: implement and test balance reporting with scope 
info
fb5f098f9 is described below

commit fb5f098f9e60f03cdd6f78aba5aa248ec5889485
Author: Florian Dold <florian@dold.me>
AuthorDate: Mon Jan 22 21:29:47 2024 +0100

    wallet-core: implement and test balance reporting with scope info
---
 ...est-multiexchange.ts => test-currency-scope.ts} |  72 +++--
 .../src/integrationtests/test-multiexchange.ts     |  20 +-
 .../src/integrationtests/testrunner.ts             |   4 +-
 packages/taler-util/src/wallet-types.ts            |   2 +
 packages/taler-wallet-core/src/db.ts               |  21 ++
 .../taler-wallet-core/src/operations/balance.ts    | 333 +++++++++++++++------
 .../taler-wallet-core/src/operations/common.ts     |  51 ----
 .../taler-wallet-core/src/operations/exchanges.ts  | 188 ++++++++++--
 .../taler-wallet-core/src/operations/refresh.ts    |  20 +-
 .../taler-wallet-core/src/operations/withdraw.ts   |  69 +++--
 .../taler-wallet-core/src/util/coinSelection.ts    |  15 +-
 packages/taler-wallet-core/src/wallet.ts           |   6 +-
 12 files changed, 556 insertions(+), 245 deletions(-)

diff --git a/packages/taler-harness/src/integrationtests/test-multiexchange.ts 
b/packages/taler-harness/src/integrationtests/test-currency-scope.ts
similarity index 71%
copy from packages/taler-harness/src/integrationtests/test-multiexchange.ts
copy to packages/taler-harness/src/integrationtests/test-currency-scope.ts
index aeda035a8..e07a8f47b 100644
--- a/packages/taler-harness/src/integrationtests/test-multiexchange.ts
+++ b/packages/taler-harness/src/integrationtests/test-currency-scope.ts
@@ -17,7 +17,9 @@
 /**
  * Imports.
  */
-import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import { Duration, j2s } from "@gnu-taler/taler-util";
+import { Wallet, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import { defaultCoinConfig } from "../harness/denomStructures.js";
 import {
   BankService,
   ExchangeService,
@@ -27,18 +29,15 @@ import {
   setupDb,
 } from "../harness/harness.js";
 import {
-  createSimpleTestkudosEnvironmentV2,
-  withdrawViaBankV2,
-  makeTestPaymentV2,
   createWalletDaemonWithClient,
+  makeTestPaymentV2,
+  withdrawViaBankV2,
 } from "../harness/helpers.js";
-import { Duration, j2s } from "@gnu-taler/taler-util";
-import { defaultCoinConfig } from "../harness/denomStructures.js";
 
 /**
  * Run test for basic, bank-integrated withdrawal and payment.
  */
-export async function runMultiExchangeTest(t: GlobalTestState) {
+export async function runCurrencyScopeTest(t: GlobalTestState) {
   // Set up test environment
   const dbDefault = await setupDb(t);
 
@@ -133,34 +132,61 @@ export async function runMultiExchangeTest(t: 
GlobalTestState) {
     ),
   });
 
-  const { walletClient, walletService } = await createWalletDaemonWithClient(
-    t,
-    { name: "wallet" },
-  );
+  const { walletClient } = await createWalletDaemonWithClient(t, {
+    name: "wallet",
+  });
 
   console.log("setup done!");
 
   // Withdraw digital cash into the wallet.
 
-  await withdrawViaBankV2(t, {
+  const w1 = await withdrawViaBankV2(t, {
     walletClient,
     bank,
     exchange: exchangeOne,
-    amount: "TESTKUDOS:20",
+    amount: "TESTKUDOS:6",
   });
 
-  await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {});
+  const w2 = await withdrawViaBankV2(t, {
+    walletClient,
+    bank,
+    exchange: exchangeTwo,
+    amount: "TESTKUDOS:6",
+  });
+
+  await w1.withdrawalFinishedCond;
+  await w2.withdrawalFinishedCond;
+
+  const bal = await walletClient.call(WalletApiOperation.GetBalances, {});
+  console.log(j2s(bal));
 
-  const order = {
-    summary: "Buy me!",
-    amount: "TESTKUDOS:5",
-    fulfillment_url: "taler://fulfillment-success/thx",
-  };
+  // Separate balances, exchange-scope.
+  t.assertDeepEqual(bal.balances.length, 2);
+
+  await walletClient.call(WalletApiOperation.AddGlobalCurrencyExchange, {
+    currency: "TESTKUDOS",
+    exchangeBaseUrl: exchangeOne.baseUrl,
+    exchangeMasterPub: exchangeOne.masterPub,
+  });
+
+  await walletClient.call(WalletApiOperation.AddGlobalCurrencyExchange, {
+    currency: "TESTKUDOS",
+    exchangeBaseUrl: exchangeTwo.baseUrl,
+    exchangeMasterPub: exchangeTwo.masterPub,
+  });
+
+  const ex = walletClient.call(
+    WalletApiOperation.ListGlobalCurrencyExchanges,
+    {},
+  );
+  console.log("global currency exchanges:");
+  console.log(j2s(ex));
 
-  console.log("making test payment");
+  const bal2 = await walletClient.call(WalletApiOperation.GetBalances, {});
+  console.log(j2s(bal2));
 
-  await makeTestPaymentV2(t, { walletClient, merchant, order });
-  await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {});
+  // Global currencies are merged
+  t.assertDeepEqual(bal2.balances.length, 1);
 }
 
-runMultiExchangeTest.suites = ["wallet"];
+runCurrencyScopeTest.suites = ["wallet"];
diff --git a/packages/taler-harness/src/integrationtests/test-multiexchange.ts 
b/packages/taler-harness/src/integrationtests/test-multiexchange.ts
index aeda035a8..e27bccc46 100644
--- a/packages/taler-harness/src/integrationtests/test-multiexchange.ts
+++ b/packages/taler-harness/src/integrationtests/test-multiexchange.ts
@@ -17,7 +17,9 @@
 /**
  * Imports.
  */
+import { Duration } from "@gnu-taler/taler-util";
 import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import { defaultCoinConfig } from "../harness/denomStructures.js";
 import {
   BankService,
   ExchangeService,
@@ -27,13 +29,10 @@ import {
   setupDb,
 } from "../harness/harness.js";
 import {
-  createSimpleTestkudosEnvironmentV2,
-  withdrawViaBankV2,
-  makeTestPaymentV2,
   createWalletDaemonWithClient,
+  makeTestPaymentV2,
+  withdrawViaBankV2,
 } from "../harness/helpers.js";
-import { Duration, j2s } from "@gnu-taler/taler-util";
-import { defaultCoinConfig } from "../harness/denomStructures.js";
 
 /**
  * Run test for basic, bank-integrated withdrawal and payment.
@@ -146,14 +145,21 @@ export async function runMultiExchangeTest(t: 
GlobalTestState) {
     walletClient,
     bank,
     exchange: exchangeOne,
-    amount: "TESTKUDOS:20",
+    amount: "TESTKUDOS:6",
+  });
+
+  await withdrawViaBankV2(t, {
+    walletClient,
+    bank,
+    exchange: exchangeTwo,
+    amount: "TESTKUDOS:6",
   });
 
   await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {});
 
   const order = {
     summary: "Buy me!",
-    amount: "TESTKUDOS:5",
+    amount: "TESTKUDOS:10",
     fulfillment_url: "taler://fulfillment-success/thx",
   };
 
diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts 
b/packages/taler-harness/src/integrationtests/testrunner.ts
index 6ab87c756..1b4bdc218 100644
--- a/packages/taler-harness/src/integrationtests/testrunner.ts
+++ b/packages/taler-harness/src/integrationtests/testrunner.ts
@@ -93,12 +93,13 @@ import { runWithdrawalHugeTest } from 
"./test-withdrawal-huge.js";
 import { runWithdrawalManualTest } from "./test-withdrawal-manual.js";
 import { runWalletGenDbTest } from "./test-wallet-gendb.js";
 import { runLibeufinBankTest } from "./test-libeufin-bank.js";
-import { runMultiExchangeTest } from "./test-multiexchange.js";
+import { runCurrencyScopeTest } from "./test-currency-scope.js";
 import { runAgeRestrictionsDepositTest } from 
"./test-age-restrictions-deposit.js";
 import { runWithdrawalConversionTest } from "./test-withdrawal-conversion.js";
 import { runPaymentDeletedTest } from "./test-payment-deleted.js";
 import { runWithdrawalNotifyBeforeTxTest } from 
"./test-withdrawal-notify-before-tx.js";
 import { runWalletDd48Test } from "./test-wallet-dd48.js";
+import { runMultiExchangeTest } from "./test-multiexchange.js";
 
 /**
  * Test runner.
@@ -187,6 +188,7 @@ const allTests: TestMainFunction[] = [
   runLibeufinBankTest,
   runPaymentDeletedTest,
   runWalletDd48Test,
+  runCurrencyScopeTest,
 ];
 
 export interface TestRunSpec {
diff --git a/packages/taler-util/src/wallet-types.ts 
b/packages/taler-util/src/wallet-types.ts
index c20290287..12231fb2d 100644
--- a/packages/taler-util/src/wallet-types.ts
+++ b/packages/taler-util/src/wallet-types.ts
@@ -1571,6 +1571,8 @@ export interface ExchangeWithdrawalDetails {
    *
    */
   ageRestrictionOptions?: number[];
+
+  scopeInfo: ScopeInfo;
 }
 
 export interface GetExchangeTosResult {
diff --git a/packages/taler-wallet-core/src/db.ts 
b/packages/taler-wallet-core/src/db.ts
index ceca24c82..149d73abc 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -30,6 +30,8 @@ import {
 import {
   AbsoluteTime,
   AgeCommitmentProof,
+  AmountJson,
+  AmountLike,
   AmountString,
   Amounts,
   AttentionInfo,
@@ -1003,6 +1005,13 @@ export interface RefreshReasonDetails {
   proposalId?: string;
 }
 
+export interface RefreshGroupPerExchangeInfo {
+  /**
+   * (Expected) output once the refresh group succeeded.
+   */
+  outputEffective: AmountString;
+}
+
 /**
  * Group of refresh operations.  The refreshed coins do not
  * have to belong to the same exchange, but must have the same
@@ -1038,6 +1047,8 @@ export interface RefreshGroupRecord {
 
   expectedOutputPerCoin: AmountString[];
 
+  infoPerExchange?: Record<string, RefreshGroupPerExchangeInfo>;
+
   /**
    * Flag for each coin whether refreshing finished.
    * If a coin can't be refreshed (remaining value too small),
@@ -1717,6 +1728,14 @@ export interface DepositTrackingInfo {
   exchangePub: string;
 }
 
+export interface DepositInfoPerExchange {
+  /**
+   * Expected effective amount that will be deposited
+   * from coins of this exchange.
+   */
+  amountEffective: AmountJson;
+}
+
 /**
  * Group of deposits made by the wallet.
  */
@@ -1768,6 +1787,8 @@ export interface DepositGroupRecord {
 
   statusPerCoin: DepositElementStatus[];
 
+  infoPerExchange?: Record<string, DepositInfoPerExchange>;
+
   /**
    * When the deposit transaction was aborted and
    * refreshes were tried, we create a refresh
diff --git a/packages/taler-wallet-core/src/operations/balance.ts 
b/packages/taler-wallet-core/src/operations/balance.ts
index 53ca33fe7..a73476e9c 100644
--- a/packages/taler-wallet-core/src/operations/balance.ts
+++ b/packages/taler-wallet-core/src/operations/balance.ts
@@ -54,6 +54,7 @@ import {
   AllowedAuditorInfo,
   AllowedExchangeInfo,
   AmountJson,
+  AmountLike,
   Amounts,
   BalanceFlag,
   BalancesResponse,
@@ -61,6 +62,7 @@ import {
   GetBalanceDetailRequest,
   Logger,
   parsePaytoUri,
+  ScopeInfo,
   ScopeType,
 } from "@gnu-taler/taler-util";
 import {
@@ -68,6 +70,8 @@ import {
   OPERATION_STATUS_ACTIVE_FIRST,
   OPERATION_STATUS_ACTIVE_LAST,
   RefreshGroupRecord,
+  RefreshOperationStatus,
+  WalletDbReadOnlyTransactionArr,
   WalletStoresV1,
   WithdrawalGroupStatus,
 } from "../db.js";
@@ -75,7 +79,10 @@ import { InternalWalletState } from 
"../internal-wallet-state.js";
 import { assertUnreachable } from "../util/assertUnreachable.js";
 import { checkLogicInvariant } from "../util/invariants.js";
 import { GetReadOnlyAccess } from "../util/query.js";
-import { getExchangeWireDetailsInTx } from "./exchanges.js";
+import {
+  getExchangeScopeInfo,
+  getExchangeWireDetailsInTx,
+} from "./exchanges.js";
 
 /**
  * Logger.
@@ -83,6 +90,7 @@ import { getExchangeWireDetailsInTx } from "./exchanges.js";
 const logger = new Logger("operations/balance.ts");
 
 interface WalletBalance {
+  scopeInfo: ScopeInfo;
   available: AmountJson;
   pendingIncoming: AmountJson;
   pendingOutgoing: AmountJson;
@@ -109,67 +117,216 @@ function computeRefreshGroupAvailableAmount(r: 
RefreshGroupRecord): AmountJson {
   return available;
 }
 
-/**
- * Get balance information.
- */
-export async function getBalancesInsideTransaction(
-  ws: InternalWalletState,
-  tx: GetReadOnlyAccess<{
-    coinAvailability: typeof WalletStoresV1.coinAvailability;
-    refreshGroups: typeof WalletStoresV1.refreshGroups;
-    withdrawalGroups: typeof WalletStoresV1.withdrawalGroups;
-    depositGroups: typeof WalletStoresV1.depositGroups;
-  }>,
-): Promise<BalancesResponse> {
-  const balanceStore: Record<string, WalletBalance> = {};
+function getBalanceKey(scopeInfo: ScopeInfo): string {
+  switch (scopeInfo.type) {
+    case ScopeType.Auditor:
+      return `${scopeInfo.type};${scopeInfo.currency};${scopeInfo.url}`;
+    case ScopeType.Exchange:
+      return `${scopeInfo.type};${scopeInfo.currency};${scopeInfo.url}`;
+    case ScopeType.Global:
+      return `${scopeInfo.type};${scopeInfo.currency}`;
+  }
+}
+
+class BalancesStore {
+  private exchangeScopeCache: Record<string, ScopeInfo> = {};
+  private balanceStore: Record<string, WalletBalance> = {};
+
+  constructor(
+    private ws: InternalWalletState,
+    private tx: WalletDbReadOnlyTransactionArr<
+      [
+        "globalCurrencyAuditors",
+        "globalCurrencyExchanges",
+        "exchanges",
+        "exchangeDetails",
+      ]
+    >,
+  ) {}
 
   /**
    * Add amount to a balance field, both for
    * the slicing by exchange and currency.
    */
-  const initBalance = (currency: string): WalletBalance => {
-    const b = balanceStore[currency];
+  private async initBalance(
+    currency: string,
+    exchangeBaseUrl: string,
+  ): Promise<WalletBalance> {
+    let scopeInfo: ScopeInfo | undefined =
+      this.exchangeScopeCache[exchangeBaseUrl];
+    if (!scopeInfo) {
+      scopeInfo = await getExchangeScopeInfo(
+        this.tx,
+        exchangeBaseUrl,
+        currency,
+      );
+      this.exchangeScopeCache[exchangeBaseUrl] = scopeInfo;
+    }
+    const balanceKey = getBalanceKey(scopeInfo);
+    const b = this.balanceStore[balanceKey];
     if (!b) {
-      balanceStore[currency] = {
-        available: Amounts.zeroOfCurrency(currency),
-        pendingIncoming: Amounts.zeroOfCurrency(currency),
-        pendingOutgoing: Amounts.zeroOfCurrency(currency),
+      const zero = Amounts.zeroOfCurrency(currency);
+      this.balanceStore[balanceKey] = {
+        scopeInfo,
+        available: zero,
+        pendingIncoming: zero,
+        pendingOutgoing: zero,
         flagIncomingAml: false,
         flagIncomingConfirmation: false,
         flagIncomingKyc: false,
         flagOutgoingKyc: false,
       };
     }
-    return balanceStore[currency];
-  };
+    return this.balanceStore[balanceKey];
+  }
+
+  async addAvailable(
+    currency: string,
+    exchangeBaseUrl: string,
+    amount: AmountLike,
+  ): Promise<void> {
+    const b = await this.initBalance(currency, exchangeBaseUrl);
+    b.available = Amounts.add(b.available, amount).amount;
+  }
+
+  async addPendingIncoming(
+    currency: string,
+    exchangeBaseUrl: string,
+    amount: AmountLike,
+  ): Promise<void> {
+    const b = await this.initBalance(currency, exchangeBaseUrl);
+    b.pendingIncoming = Amounts.add(b.available, amount).amount;
+  }
+
+  async setFlagIncomingAml(
+    currency: string,
+    exchangeBaseUrl: string,
+  ): Promise<void> {
+    const b = await this.initBalance(currency, exchangeBaseUrl);
+    b.flagIncomingAml = true;
+  }
+
+  async setFlagIncomingKyc(
+    currency: string,
+    exchangeBaseUrl: string,
+  ): Promise<void> {
+    const b = await this.initBalance(currency, exchangeBaseUrl);
+    b.flagIncomingKyc = true;
+  }
+
+  async setFlagIncomingConfirmation(
+    currency: string,
+    exchangeBaseUrl: string,
+  ): Promise<void> {
+    const b = await this.initBalance(currency, exchangeBaseUrl);
+    b.flagIncomingConfirmation = true;
+  }
+
+  async setFlagOutgoingKyc(
+    currency: string,
+    exchangeBaseUrl: string,
+  ): Promise<void> {
+    const b = await this.initBalance(currency, exchangeBaseUrl);
+    b.flagOutgoingKyc = true;
+  }
+
+  toBalancesResponse(): BalancesResponse {
+    const balancesResponse: BalancesResponse = {
+      balances: [],
+    };
+
+    const balanceStore = this.balanceStore;
+
+    Object.keys(balanceStore)
+      .sort()
+      .forEach((c) => {
+        const v = balanceStore[c];
+        const flags: BalanceFlag[] = [];
+        if (v.flagIncomingAml) {
+          flags.push(BalanceFlag.IncomingAml);
+        }
+        if (v.flagIncomingKyc) {
+          flags.push(BalanceFlag.IncomingKyc);
+        }
+        if (v.flagIncomingConfirmation) {
+          flags.push(BalanceFlag.IncomingConfirmation);
+        }
+        if (v.flagOutgoingKyc) {
+          flags.push(BalanceFlag.OutgoingKyc);
+        }
+        balancesResponse.balances.push({
+          scopeInfo: v.scopeInfo,
+          available: Amounts.stringify(v.available),
+          pendingIncoming: Amounts.stringify(v.pendingIncoming),
+          pendingOutgoing: Amounts.stringify(v.pendingOutgoing),
+          // FIXME: This field is basically not implemented, do we even need 
it?
+          hasPendingTransactions: false,
+          // FIXME: This field is basically not implemented, do we even need 
it?
+          requiresUserInput: false,
+          flags,
+        });
+      });
+    return balancesResponse;
+  }
+}
+
+/**
+ * Get balance information.
+ */
+export async function getBalancesInsideTransaction(
+  ws: InternalWalletState,
+  tx: WalletDbReadOnlyTransactionArr<
+    [
+      "exchanges",
+      "exchangeDetails",
+      "coinAvailability",
+      "refreshGroups",
+      "depositGroups",
+      "withdrawalGroups",
+      "globalCurrencyAuditors",
+      "globalCurrencyExchanges",
+    ]
+  >,
+): Promise<BalancesResponse> {
+  const balanceStore: BalancesStore = new BalancesStore(ws, tx);
 
   const keyRangeActive = GlobalIDB.KeyRange.bound(
     OPERATION_STATUS_ACTIVE_FIRST,
     OPERATION_STATUS_ACTIVE_LAST,
   );
 
-  await tx.coinAvailability.iter().forEach((ca) => {
-    const b = initBalance(ca.currency);
+  await tx.coinAvailability.iter().forEachAsync(async (ca) => {
     const count = ca.visibleCoinCount ?? 0;
     for (let i = 0; i < count; i++) {
-      b.available = Amounts.add(b.available, ca.value).amount;
+      await balanceStore.addAvailable(
+        ca.currency,
+        ca.exchangeBaseUrl,
+        ca.value,
+      );
     }
   });
 
-  await tx.refreshGroups.iter().forEach((r) => {
-    const b = initBalance(r.currency);
-    b.available = Amounts.add(
-      b.available,
-      computeRefreshGroupAvailableAmount(r),
-    ).amount;
+  await tx.refreshGroups.iter().forEachAsync(async (r) => {
+    switch (r.operationStatus) {
+      case RefreshOperationStatus.Pending:
+      case RefreshOperationStatus.Suspended:
+        break;
+      default:
+        return;
+    }
+    const perExchange = r.infoPerExchange;
+    if (!perExchange) {
+      return;
+    }
+    for (const [e, x] of Object.entries(perExchange)) {
+      await balanceStore.addAvailable(r.currency, e, x.outputEffective);
+    }
   });
 
   await tx.withdrawalGroups.indexes.byStatus
     .iter(keyRangeActive)
-    .forEach((wgRecord) => {
-      const b = initBalance(
-        Amounts.currencyOf(wgRecord.denomsSel.totalWithdrawCost),
-      );
+    .forEachAsync(async (wgRecord) => {
+      const currency = Amounts.currencyOf(wgRecord.denomsSel.totalCoinValue);
       switch (wgRecord.status) {
         case WithdrawalGroupStatus.AbortedBank:
         case WithdrawalGroupStatus.AbortedExchange:
@@ -190,76 +347,53 @@ export async function getBalancesInsideTransaction(
           break;
         case WithdrawalGroupStatus.SuspendedKyc:
         case WithdrawalGroupStatus.PendingKyc:
-          b.flagIncomingKyc = true;
+          await balanceStore.setFlagIncomingKyc(
+            currency,
+            wgRecord.exchangeBaseUrl,
+          );
           break;
         case WithdrawalGroupStatus.PendingAml:
         case WithdrawalGroupStatus.SuspendedAml:
-          b.flagIncomingAml = true;
+          await balanceStore.setFlagIncomingAml(
+            currency,
+            wgRecord.exchangeBaseUrl,
+          );
           break;
         case WithdrawalGroupStatus.PendingRegisteringBank:
         case WithdrawalGroupStatus.PendingWaitConfirmBank:
-          b.flagIncomingConfirmation = true;
+          await balanceStore.setFlagIncomingConfirmation(
+            currency,
+            wgRecord.exchangeBaseUrl,
+          );
           break;
         default:
           assertUnreachable(wgRecord.status);
       }
-      b.pendingIncoming = Amounts.add(
-        b.pendingIncoming,
+      await balanceStore.addPendingIncoming(
+        currency,
+        wgRecord.exchangeBaseUrl,
         wgRecord.denomsSel.totalCoinValue,
-      ).amount;
+      );
     });
 
-  // FIXME: Use indexing to filter out final transactions.
   await tx.depositGroups.indexes.byStatus
     .iter(keyRangeActive)
-    .forEach((dgRecord) => {
-      const b = initBalance(Amounts.currencyOf(dgRecord.amount));
-      switch (dgRecord.operationStatus) {
-        case DepositOperationStatus.SuspendedKyc:
-        case DepositOperationStatus.PendingKyc:
-          b.flagOutgoingKyc = true;
+    .forEachAsync(async (dgRecord) => {
+      const perExchange = dgRecord.infoPerExchange;
+      if (!perExchange) {
+        return;
       }
-    });
-
-  const balancesResponse: BalancesResponse = {
-    balances: [],
-  };
-
-  Object.keys(balanceStore)
-    .sort()
-    .forEach((c) => {
-      const v = balanceStore[c];
-      const flags: BalanceFlag[] = [];
-      if (v.flagIncomingAml) {
-        flags.push(BalanceFlag.IncomingAml);
-      }
-      if (v.flagIncomingKyc) {
-        flags.push(BalanceFlag.IncomingKyc);
-      }
-      if (v.flagIncomingConfirmation) {
-        flags.push(BalanceFlag.IncomingConfirmation);
-      }
-      if (v.flagOutgoingKyc) {
-        flags.push(BalanceFlag.OutgoingKyc);
+      for (const [e, x] of Object.entries(perExchange)) {
+        const currency = Amounts.currencyOf(dgRecord.amount);
+        switch (dgRecord.operationStatus) {
+          case DepositOperationStatus.SuspendedKyc:
+          case DepositOperationStatus.PendingKyc:
+            await balanceStore.setFlagOutgoingKyc(currency, e);
+        }
       }
-      balancesResponse.balances.push({
-        scopeInfo: {
-          // FIXME: obtain REAL scopeInfo instead of faking a global currency
-          type: ScopeType.Global,
-          currency: Amounts.currencyOf(v.available),
-        },
-        available: Amounts.stringify(v.available),
-        pendingIncoming: Amounts.stringify(v.pendingIncoming),
-        pendingOutgoing: Amounts.stringify(v.pendingOutgoing),
-        // FIXME: This field is basically not implemented, do we even need it?
-        hasPendingTransactions: false,
-        // FIXME: This field is basically not implemented, do we even need it?
-        requiresUserInput: false,
-        flags,
-      });
     });
 
-  return balancesResponse;
+  return balanceStore.toBalancesResponse();
 }
 
 /**
@@ -270,18 +404,23 @@ export async function getBalances(
 ): Promise<BalancesResponse> {
   logger.trace("starting to compute balance");
 
-  const wbal = await ws.db
-    .mktx((x) => [
-      x.coins,
-      x.coinAvailability,
-      x.refreshGroups,
-      x.purchases,
-      x.withdrawalGroups,
-      x.depositGroups,
-    ])
-    .runReadOnly(async (tx) => {
+  const wbal = await ws.db.runReadWriteTx(
+    [
+      "coinAvailability",
+      "coins",
+      "depositGroups",
+      "exchangeDetails",
+      "exchanges",
+      "globalCurrencyAuditors",
+      "globalCurrencyExchanges",
+      "purchases",
+      "refreshGroups",
+      "withdrawalGroups",
+    ],
+    async (tx) => {
       return getBalancesInsideTransaction(ws, tx);
-    });
+    },
+  );
 
   logger.trace("finished computing wallet balance");
 
diff --git a/packages/taler-wallet-core/src/operations/common.ts 
b/packages/taler-wallet-core/src/operations/common.ts
index f34190cef..d626f0056 100644
--- a/packages/taler-wallet-core/src/operations/common.ts
+++ b/packages/taler-wallet-core/src/operations/common.ts
@@ -19,7 +19,6 @@
  */
 import {
   AbsoluteTime,
-  AgeRestriction,
   AmountJson,
   Amounts,
   CancellationToken,
@@ -28,7 +27,6 @@ import {
   Duration,
   ExchangeEntryState,
   ExchangeEntryStatus,
-  ExchangeListItem,
   ExchangeTosStatus,
   ExchangeUpdateStatus,
   getErrorDetailFromException,
@@ -36,10 +34,7 @@ import {
   Logger,
   makeErrorDetail,
   NotificationType,
-  OperationErrorInfo,
   RefreshReason,
-  ScopeInfo,
-  ScopeType,
   TalerError,
   TalerErrorCode,
   TalerErrorDetail,
@@ -55,7 +50,6 @@ import {
   CoinRecord,
   DbPreciseTimestamp,
   DepositGroupRecord,
-  ExchangeDetailsRecord,
   ExchangeEntryDbRecordStatus,
   ExchangeEntryDbUpdateStatus,
   ExchangeEntryRecord,
@@ -653,51 +647,6 @@ export function getExchangeState(r: ExchangeEntryRecord): 
ExchangeEntryState {
   };
 }
 
-/**
- * Mock scope info for an exchange by always returning a regional currency 
scope.
- */
-function mockExchangeScopeInfo(
-  r: ExchangeEntryRecord,
-  exchangeDetails: ExchangeDetailsRecord | undefined,
-): ScopeInfo | undefined {
-  const currency = r.presetCurrencyHint ?? exchangeDetails?.currency;
-  if (currency) {
-    return {
-      currency,
-      type: ScopeType.Exchange,
-      url: r.baseUrl,
-    };
-  }
-  return undefined;
-}
-
-export function makeExchangeListItem(
-  r: ExchangeEntryRecord,
-  exchangeDetails: ExchangeDetailsRecord | undefined,
-  lastError: TalerErrorDetail | undefined,
-): ExchangeListItem {
-  const lastUpdateErrorInfo: OperationErrorInfo | undefined = lastError
-    ? {
-        error: lastError,
-      }
-    : undefined;
-
-  return {
-    exchangeBaseUrl: r.baseUrl,
-    currency: exchangeDetails?.currency ?? r.presetCurrencyHint,
-    exchangeUpdateStatus: getExchangeUpdateStatusFromRecord(r),
-    exchangeEntryStatus: getExchangeEntryStatusFromRecord(r),
-    tosStatus: getExchangeTosStatusFromRecord(r),
-    ageRestrictionOptions: exchangeDetails?.ageMask
-      ? AgeRestriction.getAgeGroupsFromMask(exchangeDetails.ageMask)
-      : [],
-    paytoUris: exchangeDetails?.wireInfo.accounts.map((x) => x.payto_uri) ?? 
[],
-    lastUpdateErrorInfo,
-    // FIXME: Return real scope info in the future!
-    scopeInfo: mockExchangeScopeInfo(r, exchangeDetails),
-  };
-}
-
 export interface LongpollResult {
   ready: boolean;
 }
diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts 
b/packages/taler-wallet-core/src/operations/exchanges.ts
index 67404665c..b4d45db2c 100644
--- a/packages/taler-wallet-core/src/operations/exchanges.ts
+++ b/packages/taler-wallet-core/src/operations/exchanges.ts
@@ -25,6 +25,7 @@
  */
 import {
   AbsoluteTime,
+  AgeRestriction,
   Amounts,
   CancellationToken,
   DeleteExchangeRequest,
@@ -50,7 +51,10 @@ import {
   LibtoolVersion,
   Logger,
   NotificationType,
+  OperationErrorInfo,
   Recoup,
+  ScopeInfo,
+  ScopeType,
   TalerError,
   TalerErrorCode,
   TalerErrorDetail,
@@ -88,7 +92,7 @@ import {
   ExchangeEntryDbRecordStatus,
   ExchangeEntryDbUpdateStatus,
   PendingTaskType,
-  WalletDbReadWriteTransaction,
+  WalletDbReadOnlyTransactionArr,
   WalletDbReadWriteTransactionArr,
   createTimeline,
   isWithdrawableDenom,
@@ -103,7 +107,6 @@ import {
 import { InternalWalletState } from "../internal-wallet-state.js";
 import { checkDbInvariant } from "../util/invariants.js";
 import {
-  DbReadOnlyTransaction,
   DbReadOnlyTransactionArr,
   GetReadOnlyAccess,
   GetReadWriteAccess,
@@ -114,9 +117,10 @@ import {
   TaskRunResult,
   TaskRunResultType,
   constructTaskIdentifier,
+  getExchangeEntryStatusFromRecord,
   getExchangeState,
   getExchangeTosStatusFromRecord,
-  makeExchangeListItem,
+  getExchangeUpdateStatusFromRecord,
   runTaskWithErrorReporting,
 } from "./common.js";
 
@@ -206,6 +210,105 @@ async function getExchangeRecordsInternal(
   ]);
 }
 
+export async function getExchangeScopeInfo(
+  tx: WalletDbReadOnlyTransactionArr<
+    [
+      "exchanges",
+      "exchangeDetails",
+      "globalCurrencyExchanges",
+      "globalCurrencyAuditors",
+    ]
+  >,
+  exchangeBaseUrl: string,
+  currency: string,
+): Promise<ScopeInfo> {
+  const det = await getExchangeRecordsInternal(tx, exchangeBaseUrl);
+  if (!det) {
+    return {
+      type: ScopeType.Exchange,
+      currency: currency,
+      url: exchangeBaseUrl,
+    };
+  }
+  return internalGetExchangeScopeInfo(tx, det);
+}
+
+async function internalGetExchangeScopeInfo(
+  tx: WalletDbReadOnlyTransactionArr<
+    ["globalCurrencyExchanges", "globalCurrencyAuditors"]
+  >,
+  exchangeDetails: ExchangeDetailsRecord,
+): Promise<ScopeInfo> {
+  const globalExchangeRec =
+    await tx.globalCurrencyExchanges.indexes.byCurrencyAndUrlAndPub.get([
+      exchangeDetails.currency,
+      exchangeDetails.exchangeBaseUrl,
+      exchangeDetails.masterPublicKey,
+    ]);
+  if (globalExchangeRec) {
+    return {
+      currency: exchangeDetails.currency,
+      type: ScopeType.Global,
+    };
+  } else {
+    for (const aud of exchangeDetails.auditors) {
+      const globalAuditorRec =
+        await tx.globalCurrencyAuditors.indexes.byCurrencyAndUrlAndPub.get([
+          exchangeDetails.currency,
+          aud.auditor_url,
+          aud.auditor_pub,
+        ]);
+      if (globalAuditorRec) {
+        return {
+          currency: exchangeDetails.currency,
+          type: ScopeType.Auditor,
+          url: aud.auditor_url,
+        };
+      }
+    }
+  }
+  return {
+    currency: exchangeDetails.currency,
+    type: ScopeType.Exchange,
+    url: exchangeDetails.exchangeBaseUrl,
+  };
+}
+
+async function makeExchangeListItem(
+  tx: WalletDbReadOnlyTransactionArr<
+    ["globalCurrencyExchanges", "globalCurrencyAuditors"]
+  >,
+  r: ExchangeEntryRecord,
+  exchangeDetails: ExchangeDetailsRecord | undefined,
+  lastError: TalerErrorDetail | undefined,
+): Promise<ExchangeListItem> {
+  const lastUpdateErrorInfo: OperationErrorInfo | undefined = lastError
+    ? {
+        error: lastError,
+      }
+    : undefined;
+
+  let scopeInfo: ScopeInfo | undefined = undefined;
+
+  if (exchangeDetails) {
+    scopeInfo = await internalGetExchangeScopeInfo(tx, exchangeDetails);
+  }
+
+  return {
+    exchangeBaseUrl: r.baseUrl,
+    currency: exchangeDetails?.currency ?? r.presetCurrencyHint,
+    exchangeUpdateStatus: getExchangeUpdateStatusFromRecord(r),
+    exchangeEntryStatus: getExchangeEntryStatusFromRecord(r),
+    tosStatus: getExchangeTosStatusFromRecord(r),
+    ageRestrictionOptions: exchangeDetails?.ageMask
+      ? AgeRestriction.getAgeGroupsFromMask(exchangeDetails.ageMask)
+      : [],
+    paytoUris: exchangeDetails?.wireInfo.accounts.map((x) => x.payto_uri) ?? 
[],
+    lastUpdateErrorInfo,
+    scopeInfo,
+  };
+}
+
 export interface ExchangeWireDetails {
   currency: string;
   masterPublicKey: EddsaPublicKeyString;
@@ -240,14 +343,15 @@ export async function lookupExchangeByUri(
   ws: InternalWalletState,
   req: GetExchangeEntryByUrlRequest,
 ): Promise<ExchangeListItem> {
-  return await ws.db
-    .mktx((x) => [
-      x.exchanges,
-      x.exchangeDetails,
-      x.denominations,
-      x.operationRetries,
-    ])
-    .runReadOnly(async (tx) => {
+  return await ws.db.runReadOnlyTx(
+    [
+      "exchanges",
+      "exchangeDetails",
+      "operationRetries",
+      "globalCurrencyAuditors",
+      "globalCurrencyExchanges",
+    ],
+    async (tx) => {
       const exchangeRec = await tx.exchanges.get(req.exchangeBaseUrl);
       if (!exchangeRec) {
         throw Error("exchange not found");
@@ -259,12 +363,14 @@ export async function lookupExchangeByUri(
       const opRetryRecord = await tx.operationRetries.get(
         TaskIdentifiers.forExchangeUpdate(exchangeRec),
       );
-      return makeExchangeListItem(
+      return await makeExchangeListItem(
+        tx,
         exchangeRec,
         exchangeDetails,
         opRetryRecord?.lastError,
       );
-    });
+    },
+  );
 }
 
 /**
@@ -800,6 +906,7 @@ export interface ReadyExchangeSummary {
   wireInfo: WireInfo;
   protocolVersionRange: string;
   tosAcceptedTimestamp: TalerPreciseTimestamp | undefined;
+  scopeInfo: ScopeInfo;
 }
 
 /**
@@ -863,14 +970,26 @@ export async function fetchFreshExchange(
     );
   }
 
-  const { exchange, exchangeDetails, retryInfo } = await ws.db
-    .mktx((x) => [x.exchanges, x.exchangeDetails, x.operationRetries])
-    .runReadOnly(async (tx) => {
-      const exchange = await tx.exchanges.get(canonUrl);
-      const exchangeDetails = await getExchangeRecordsInternal(tx, canonUrl);
-      const retryInfo = await tx.operationRetries.get(operationId);
-      return { exchange, exchangeDetails, retryInfo };
-    });
+  const { exchange, exchangeDetails, retryInfo, scopeInfo } =
+    await ws.db.runReadOnlyTx(
+      [
+        "exchanges",
+        "exchangeDetails",
+        "operationRetries",
+        "globalCurrencyAuditors",
+        "globalCurrencyExchanges",
+      ],
+      async (tx) => {
+        const exchange = await tx.exchanges.get(canonUrl);
+        const exchangeDetails = await getExchangeRecordsInternal(tx, canonUrl);
+        const retryInfo = await tx.operationRetries.get(operationId);
+        let scopeInfo: ScopeInfo | undefined = undefined;
+        if (exchange && exchangeDetails) {
+          scopeInfo = await internalGetExchangeScopeInfo(tx, exchangeDetails);
+        }
+        return { exchange, exchangeDetails, retryInfo, scopeInfo };
+      },
+    );
 
   if (!exchange) {
     throw Error("exchange entry does not exist anymore");
@@ -891,6 +1010,10 @@ export async function fetchFreshExchange(
     throw Error("invariant failed");
   }
 
+  if (!scopeInfo) {
+    throw Error("invariant failed");
+  }
+
   const res: ReadyExchangeSummary = {
     currency: exchangeDetails.currency,
     exchangeBaseUrl: canonUrl,
@@ -903,6 +1026,7 @@ export async function fetchFreshExchange(
     tosAcceptedTimestamp: timestampOptionalPreciseFromDb(
       exchange.tosAcceptedTimestamp,
     ),
+    scopeInfo,
   };
 
   if (options.expectedMasterPub) {
@@ -1309,9 +1433,15 @@ export async function listExchanges(
   ws: InternalWalletState,
 ): Promise<ExchangesListResponse> {
   const exchanges: ExchangeListItem[] = [];
-  await ws.db
-    .mktx((x) => [x.exchanges, x.exchangeDetails, x.operationRetries])
-    .runReadOnly(async (tx) => {
+  await ws.db.runReadOnlyTx(
+    [
+      "exchanges",
+      "operationRetries",
+      "exchangeDetails",
+      "globalCurrencyAuditors",
+      "globalCurrencyExchanges",
+    ],
+    async (tx) => {
       const exchangeRecords = await tx.exchanges.iter().toArray();
       for (const r of exchangeRecords) {
         const taskId = constructTaskIdentifier({
@@ -1321,10 +1451,16 @@ export async function listExchanges(
         const exchangeDetails = await getExchangeRecordsInternal(tx, 
r.baseUrl);
         const opRetryRecord = await tx.operationRetries.get(taskId);
         exchanges.push(
-          makeExchangeListItem(r, exchangeDetails, opRetryRecord?.lastError),
+          await makeExchangeListItem(
+            tx,
+            r,
+            exchangeDetails,
+            opRetryRecord?.lastError,
+          ),
         );
       }
-    });
+    },
+  );
   return { exchanges };
 }
 
diff --git a/packages/taler-wallet-core/src/operations/refresh.ts 
b/packages/taler-wallet-core/src/operations/refresh.ts
index d49c9a1cf..974eb1619 100644
--- a/packages/taler-wallet-core/src/operations/refresh.ts
+++ b/packages/taler-wallet-core/src/operations/refresh.ts
@@ -80,6 +80,7 @@ import {
 } from "../db.js";
 import {
   getCandidateWithdrawalDenomsTx,
+  RefreshGroupPerExchangeInfo,
   RefreshSessionRecord,
   timestampPreciseToDb,
   timestampProtocolFromDb,
@@ -1107,6 +1108,7 @@ async function processRefreshSession(
 
 export interface RefreshOutputInfo {
   outputPerCoin: AmountJson[];
+  perExchangeInfo: Record<string, RefreshGroupPerExchangeInfo>;
 }
 
 export async function calculateRefreshOutput(
@@ -1124,6 +1126,8 @@ export async function calculateRefreshOutput(
 
   const denomsPerExchange: Record<string, DenominationRecord[]> = {};
 
+  const infoPerExchange: Record<string, RefreshGroupPerExchangeInfo> = {};
+
   // FIXME: Use denom groups instead of querying all denominations!
   const getDenoms = async (
     exchangeBaseUrl: string,
@@ -1163,11 +1167,21 @@ export async function calculateRefreshOutput(
       ws.config.testing.denomselAllowLate,
     );
     const output = Amounts.sub(refreshAmount, cost).amount;
+    let exchInfo = infoPerExchange[coin.exchangeBaseUrl];
+    if (!exchInfo) {
+      infoPerExchange[coin.exchangeBaseUrl] = exchInfo = {
+        outputEffective: Amounts.stringify(Amounts.zeroOfAmount(cost)),
+      };
+    }
+    exchInfo.outputEffective = Amounts.stringify(
+      Amounts.add(exchInfo.outputEffective, output).amount,
+    );
     estimatedOutputPerCoin.push(output);
   }
 
   return {
     outputPerCoin: estimatedOutputPerCoin,
+    perExchangeInfo: infoPerExchange,
   };
 }
 
@@ -1234,6 +1248,10 @@ async function applyRefresh(
   }
 }
 
+export interface CreateRefreshGroupResult {
+  refreshGroupId: string;
+}
+
 /**
  * Create a refresh group for a list of coins.
  *
@@ -1252,7 +1270,7 @@ export async function createRefreshGroup(
   oldCoinPubs: CoinRefreshRequest[],
   reason: RefreshReason,
   reasonDetails?: RefreshReasonDetails,
-): Promise<RefreshGroupId> {
+): Promise<CreateRefreshGroupResult> {
   const refreshGroupId = encodeCrock(getRandomBytes(32));
 
   const outInfo = await calculateRefreshOutput(ws, tx, currency, oldCoinPubs);
diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts 
b/packages/taler-wallet-core/src/operations/withdraw.ts
index 29c2cae40..d198cf482 100644
--- a/packages/taler-wallet-core/src/operations/withdraw.ts
+++ b/packages/taler-wallet-core/src/operations/withdraw.ts
@@ -557,9 +557,12 @@ export async function getBankWithdrawalInfo(
     throw Error(`can't parse URL ${talerWithdrawUri}`);
   }
 
-  const bankApi = new 
TalerBankIntegrationHttpClient(uriResult.bankIntegrationApiBaseUrl, http);
+  const bankApi = new TalerBankIntegrationHttpClient(
+    uriResult.bankIntegrationApiBaseUrl,
+    http,
+  );
 
-  const { body: config } = await bankApi.getConfig()
+  const { body: config } = await bankApi.getConfig();
 
   if (!bankApi.isCompatible(config.version)) {
     throw TalerError.fromDetail(
@@ -572,12 +575,14 @@ export async function getBankWithdrawalInfo(
     );
   }
 
-  const resp = await 
bankApi.getWithdrawalOperationById(uriResult.withdrawalOperationId)
+  const resp = await bankApi.getWithdrawalOperationById(
+    uriResult.withdrawalOperationId,
+  );
 
   if (resp.type === "fail") {
     throw TalerError.fromUncheckedDetail(resp.detail);
   }
-  const { body: status } = resp
+  const { body: status } = resp;
 
   logger.info(`bank withdrawal operation status: ${j2s(status)}`);
 
@@ -1214,7 +1219,8 @@ export async function updateWithdrawalDenoms(
         denom.verificationStatus === DenominationVerificationStatus.Unverified
       ) {
         logger.trace(
-          `Validating denomination (${current + 1}/${denominations.length
+          `Validating denomination (${current + 1}/${
+            denominations.length
           }) signature of ${denom.denomPubHash}`,
         );
         let valid = false;
@@ -1859,7 +1865,7 @@ export async function getExchangeWithdrawalInfo(
     ) {
       logger.warn(
         `wallet's support for exchange protocol version 
${WALLET_EXCHANGE_PROTOCOL_VERSION} might be outdated ` +
-        `(exchange has ${exchange.protocolVersionRange}), checking for 
updates`,
+          `(exchange has ${exchange.protocolVersionRange}), checking for 
updates`,
       );
     }
   }
@@ -1896,6 +1902,7 @@ export async function getExchangeWithdrawalInfo(
     ageRestrictionOptions: hasDenomWithAgeRestriction
       ? AGE_MASK_GROUPS
       : undefined,
+    scopeInfo: exchange.scopeInfo,
   };
   return ret;
 }
@@ -1907,9 +1914,8 @@ export interface GetWithdrawalDetailsForUriOpts {
 
 type WithdrawalOperationMemoryMap = {
   [uri: string]: boolean | undefined;
-}
-const ongoingChecks: WithdrawalOperationMemoryMap = {
-}
+};
+const ongoingChecks: WithdrawalOperationMemoryMap = {};
 /**
  * Get more information about a taler://withdraw URI.
  *
@@ -1950,28 +1956,41 @@ export async function getWithdrawalDetailsForUri(
     );
   });
 
-  // FIXME: this should be removed after the extended version of 
+  // FIXME: this should be removed after the extended version of
   // withdrawal state machine. issue #8099
-  if (info.status === "pending" && opts.notifyChangeFromPendingTimeoutMs !== 
undefined && !ongoingChecks[talerWithdrawUri]) {
+  if (
+    info.status === "pending" &&
+    opts.notifyChangeFromPendingTimeoutMs !== undefined &&
+    !ongoingChecks[talerWithdrawUri]
+  ) {
     ongoingChecks[talerWithdrawUri] = true;
-    const bankApi = new TalerBankIntegrationHttpClient(info.apiBaseUrl, 
ws.http);
+    const bankApi = new TalerBankIntegrationHttpClient(
+      info.apiBaseUrl,
+      ws.http,
+    );
     console.log(
       `waiting operation (${info.operationId}) to change from pending`,
     );
-    bankApi.getWithdrawalOperationById(info.operationId, {
-      old_state: "pending",
-      timeoutMs: opts.notifyChangeFromPendingTimeoutMs
-    }).then(resp => {
-      console.log(
-        `operation (${info.operationId}) to change to ${JSON.stringify(resp, 
undefined, 2)}`,
-      );
-      ws.notify({
-        type: NotificationType.WithdrawalOperationTransition,
-        operationId: info.operationId,
-        state: resp.type === "fail" ? info.status : resp.body.status,
+    bankApi
+      .getWithdrawalOperationById(info.operationId, {
+        old_state: "pending",
+        timeoutMs: opts.notifyChangeFromPendingTimeoutMs,
+      })
+      .then((resp) => {
+        console.log(
+          `operation (${info.operationId}) to change to ${JSON.stringify(
+            resp,
+            undefined,
+            2,
+          )}`,
+        );
+        ws.notify({
+          type: NotificationType.WithdrawalOperationTransition,
+          operationId: info.operationId,
+          state: resp.type === "fail" ? info.status : resp.body.status,
+        });
+        ongoingChecks[talerWithdrawUri] = false;
       });
-      ongoingChecks[talerWithdrawUri] = false
-    })
   }
 
   return {
diff --git a/packages/taler-wallet-core/src/util/coinSelection.ts 
b/packages/taler-wallet-core/src/util/coinSelection.ts
index d75450a64..e06d7454b 100644
--- a/packages/taler-wallet-core/src/util/coinSelection.ts
+++ b/packages/taler-wallet-core/src/util/coinSelection.ts
@@ -602,14 +602,9 @@ async function selectPayMerchantCandidates(
   ws: InternalWalletState,
   req: SelectPayCoinRequestNg,
 ): Promise<[AvailableDenom[], Record<string, AmountJson>]> {
-  return await ws.db
-    .mktx((x) => [
-      x.exchanges,
-      x.exchangeDetails,
-      x.denominations,
-      x.coinAvailability,
-    ])
-    .runReadOnly(async (tx) => {
+  return await ws.db.runReadOnlyTx(
+    ["exchanges", "exchangeDetails", "denominations", "coinAvailability"],
+    async (tx) => {
       // FIXME: Use the existing helper (from balance.ts) to
       // get acceptable exchanges.
       const denoms: AvailableDenom[] = [];
@@ -721,6 +716,7 @@ async function selectPayMerchantCandidates(
           });
         }
       }
+      logger.info(`available denoms ${j2s(denoms)}`);
       // Sort by available amount (descending),  deposit fee (ascending) and
       // denomPub (ascending) if deposit fee is the same
       // (to guarantee deterministic results)
@@ -731,7 +727,8 @@ async function selectPayMerchantCandidates(
           strcmp(o1.denomPubHash, o2.denomPubHash),
       );
       return [denoms, wfPerExchange];
-    });
+    },
+  );
 }
 
 /**
diff --git a/packages/taler-wallet-core/src/wallet.ts 
b/packages/taler-wallet-core/src/wallet.ts
index 87c5aa995..333e42621 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -1048,11 +1048,7 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
         withdrawalAccountsList: wi.exchangeCreditAccountDetails,
         numCoins,
         // FIXME: Once we have proper scope info support, return correct info 
here.
-        scopeInfo: {
-          type: ScopeType.Exchange,
-          currency: amt.currency,
-          url: req.exchangeBaseUrl,
-        },
+        scopeInfo: wi.scopeInfo,
       };
       return resp;
     }

-- 
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]