commit 346005d77e4ba8694902b1dac5a5d278da5ea8d8
parent 3f1a938baf71e6219f3856250f6dae3281b96226
Author: Florian Dold <florian@dold.me>
Date: Wed, 20 Nov 2024 18:46:08 +0100
wallet-core: fix various issues found by Marc
- consider p2p and pay transactions in balanceIncoming/balanceOutgoing
- reduce log spam
- reduce unnecessary balance change notifications
Diffstat:
11 files changed, 279 insertions(+), 88 deletions(-)
diff --git a/packages/taler-util/src/codec.ts b/packages/taler-util/src/codec.ts
@@ -74,6 +74,7 @@ type SingletonRecord<K extends keyof any, V> = { [Y in K]: V };
interface Prop {
name: string;
codec: Codec<any>;
+ deprecated?: boolean;
}
interface Alternative {
@@ -83,6 +84,7 @@ interface Alternative {
class ObjectCodecBuilder<OutputType, PartialOutputType> {
private propList: Prop[] = [];
+ private deprecatedProps: Set<string> = new Set();
private _allowExtra: boolean = false;
/**
@@ -99,6 +101,19 @@ class ObjectCodecBuilder<OutputType, PartialOutputType> {
return this as any;
}
+ /**
+ * Define a deprecated property for the object.
+ *
+ * Deprecated properties won't be validated, their presence will
+ * be validated in TRACE mode.
+ */
+ deprecatedProperty(
+ x: string,
+ ): ObjectCodecBuilder<OutputType, PartialOutputType> {
+ this.deprecatedProps.add(x);
+ return this as any;
+ }
+
allowExtra(): ObjectCodecBuilder<OutputType, PartialOutputType> {
this._allowExtra = true;
return this;
@@ -113,6 +128,7 @@ class ObjectCodecBuilder<OutputType, PartialOutputType> {
build(objectDisplayName: string): Codec<PartialOutputType> {
const propList = this.propList;
const allowExtra = this._allowExtra;
+ const deprecatedPros = this.deprecatedProps;
return {
decode(x: any, c?: Context): PartialOutputType {
if (!c) {
@@ -142,6 +158,12 @@ class ObjectCodecBuilder<OutputType, PartialOutputType> {
}
if (allowExtra) {
obj[prop] = x[prop];
+ } else if (deprecatedPros.has(prop)) {
+ logger.warn(
+ `Deprecated operty ${prop} for ${objectDisplayName} at ${renderContext(
+ c,
+ )}`,
+ );
} else {
logger.warn(
`Extra property ${prop} for ${objectDisplayName} at ${renderContext(
diff --git a/packages/taler-util/src/types-taler-common.ts b/packages/taler-util/src/types-taler-common.ts
@@ -198,6 +198,38 @@ export class WithdrawOperationStatusResponse {
confirm_transfer_url?: string;
wire_types: string[];
+
+ // Currency used for the withdrawal.
+ // MUST be present when amount is absent.
+ // @since **v2**, may become mandatory in the future.
+ currency?: string;
+
+ // Minimum amount that the wallet can choose to withdraw.
+ // Only applicable when the amount is not fixed.
+ // @since **v4**.
+ min_amount?: AmountString;
+
+ // Maximum amount that the wallet can choose to withdraw.
+ // Only applicable when the amount is not fixed.
+ // @since **v4**.
+ max_amount?: AmountString;
+
+ // The non-Taler card fees the customer will have
+ // to pay to the bank / payment service provider
+ // they are using to make the withdrawal in addition
+ // to the amount.
+ // @since **v4**
+ card_fees?: AmountString;
+
+ // Exchange account selected by the wallet;
+ // only non-null if status is selected or confirmed.
+ // @since **v1**
+ selected_exchange_account?: string;
+
+ // Reserve public key selected by the exchange,
+ // only non-null if status is selected or confirmed.
+ // @since **v1**
+ selected_reserve_pub?: EddsaPublicKey;
}
export type LitAmountString = `${string}:${number}`;
@@ -225,6 +257,10 @@ export const codecForLibtoolVersion = codecForString;
export const codecForCurrencyName = codecForString;
// FIXME: implement this codec
export const codecForDecimalNumber = codecForString;
+// FIXME: implement this codec
+export const codecForEddsaPublicKey = codecForString;
+// FIXME: implement this codec
+export const codecForEddsaSignature = codecForString;
export const codecForInternationalizedString =
(): Codec<InternationalizedString> => codecForMap(codecForString());
@@ -249,6 +285,12 @@ export const codecForWithdrawOperationStatusResponse =
.property("suggested_exchange", codecOptional(codecForString()))
.property("confirm_transfer_url", codecOptional(codecForString()))
.property("wire_types", codecForList(codecForString()))
+ .property("currency", codecOptional(codecForString()))
+ .property("card_fees", codecOptional(codecForAmountString()))
+ .property("min_amount", codecOptional(codecForAmountString()))
+ .property("max_amount", codecOptional(codecForAmountString()))
+ .property("selected_exchange_account", codecOptional(codecForString()))
+ .property("selected_reserve_pub", codecOptional(codecForEddsaPublicKey()))
.build("WithdrawOperationStatusResponse");
export const codecForCurrencySpecificiation =
@@ -259,6 +301,7 @@ export const codecForCurrencySpecificiation =
.property("num_fractional_normal_digits", codecForNumber())
.property("num_fractional_trailing_zero_digits", codecForNumber())
.property("alt_unit_names", codecForMap(codecForString()))
+ .deprecatedProperty("currency")
.build("CurrencySpecification");
export interface TalerCommonConfigResponse {
diff --git a/packages/taler-util/src/types-taler-exchange.ts b/packages/taler-util/src/types-taler-exchange.ts
@@ -67,6 +67,8 @@ import {
Timestamp,
WireSalt,
codecForAccessToken,
+ codecForEddsaPublicKey,
+ codecForEddsaSignature,
codecForInternationalizedString,
codecForURLString,
} from "./types-taler-common.js";
@@ -469,6 +471,40 @@ export interface ExchangeKeysJson {
// operations.
// Since protocol **v21**.
zero_limits?: ZeroLimitedOperation[];
+
+ // Absolute cost offset for the STEFAN curve used
+ // to (over) approximate fees payable by amount.
+ stefan_abs: AmountString;
+
+ // Factor to multiply the logarithm of the amount
+ // with to (over) approximate fees payable by amount.
+ // Note that the total to be paid is first to be
+ // divided by the smallest denomination to obtain
+ // the value that the logarithm is to be taken of.
+ stefan_log: AmountString;
+
+ // Linear cost factor for the STEFAN curve used
+ // to (over) approximate fees payable by amount.
+ //
+ // Note that this is a scalar, as it is multiplied
+ // with the actual amount.
+ stefan_lin: number;
+
+ // List of exchanges that this exchange is partnering
+ // with to enable wallet-to-wallet transfers.
+ wads: any;
+
+ // Compact EdDSA signature (binary-only) over the
+ // contatentation of all of the master_sigs (in reverse
+ // chronological order by group) in the arrays under
+ // "denominations". Signature of TALER_ExchangeKeySetPS
+ exchange_sig: EddsaSignature;
+
+ // Public EdDSA key of the exchange that was used to generate the signature.
+ // Should match one of the exchange's signing keys from signkeys. It is given
+ // explicitly as the client might otherwise be confused by clock skew as to
+ // which signing key was used for the exchange_sig.
+ exchange_pub: EddsaPublicKey;
}
export interface ExchangeMeltRequest {
@@ -905,12 +941,19 @@ export const codecForExchangeKeysJson = (): Codec<ExchangeKeysJson> =>
"wallet_balance_limit_without_kyc",
codecOptional(codecForList(codecForAmountString())),
)
+ .property("stefan_abs", codecForAmountString())
+ .property("stefan_log", codecForAmountString())
+ .property("stefan_lin", codecForNumber())
+ .property("wads", codecForAny())
+ .deprecatedProperty("rewards_allowed")
+ .property("exchange_pub", codecForEddsaPublicKey())
+ .property("exchange_sig", codecForEddsaSignature())
.build("ExchangeKeysJson");
export const codecForWireFeesJson = (): Codec<WireFeesJson> =>
buildCodecForObject<WireFeesJson>()
- .property("wire_fee", codecForString())
- .property("closing_fee", codecForString())
+ .property("wire_fee", codecForAmountString())
+ .property("closing_fee", codecForAmountString())
.property("sig", codecForString())
.property("start_date", codecForTimestamp)
.property("end_date", codecForTimestamp)
diff --git a/packages/taler-wallet-core/src/balance.ts b/packages/taler-wallet-core/src/balance.ts
@@ -71,7 +71,11 @@ import {
ExchangeEntryDbRecordStatus,
OPERATION_STATUS_NONFINAL_FIRST,
OPERATION_STATUS_NONFINAL_LAST,
+ PeerPullDebitRecordStatus,
+ PeerPullPaymentCreditStatus,
+ PeerPushCreditStatus,
PeerPushDebitStatus,
+ PurchaseStatus,
RefreshGroupRecord,
RefreshOperationStatus,
WalletDbReadOnlyTransaction,
@@ -285,6 +289,11 @@ export async function getBalancesInsideTransaction(
"globalCurrencyAuditors",
"globalCurrencyExchanges",
"peerPushDebit",
+ "peerPushCredit",
+ "peerPullCredit",
+ "peerPullDebit",
+ "purchases",
+ "coins",
]
>,
): Promise<BalancesResponse> {
@@ -319,22 +328,24 @@ export async function getBalancesInsideTransaction(
}
});
- await tx.refreshGroups.iter().forEachAsync(async (r) => {
- switch (r.operationStatus) {
- case RefreshOperationStatus.Pending:
- case RefreshOperationStatus.Suspended:
- break;
- default:
+ await tx.refreshGroups.indexes.byStatus
+ .iter(keyRangeActive)
+ .forEachAsync(async (r) => {
+ switch (r.operationStatus) {
+ case RefreshOperationStatus.Pending:
+ case RefreshOperationStatus.Suspended:
+ break;
+ default:
+ return;
+ }
+ const perExchange = r.infoPerExchange;
+ if (!perExchange) {
return;
- }
- const perExchange = r.infoPerExchange;
- if (!perExchange) {
- return;
- }
- for (const [e, x] of Object.entries(perExchange)) {
- await balanceStore.addAvailable(r.currency, e, x.outputEffective);
- }
- });
+ }
+ for (const [e, x] of Object.entries(perExchange)) {
+ await balanceStore.addAvailable(r.currency, e, x.outputEffective);
+ }
+ });
await tx.withdrawalGroups.indexes.byStatus
.iter(keyRangeActive)
@@ -439,6 +450,94 @@ export async function getBalancesInsideTransaction(
}
});
+ await tx.peerPushCredit.indexes.byStatus
+ .iter(keyRangeActive)
+ .forEachAsync(async (rec) => {
+ switch (rec.status) {
+ case PeerPushCreditStatus.PendingMerge:
+ case PeerPushCreditStatus.PendingBalanceKycInit: {
+ const currency = Amounts.currencyOf(rec.estimatedAmountEffective);
+ const amount = rec.estimatedAmountEffective;
+ await balanceStore.addPendingIncoming(
+ currency,
+ rec.exchangeBaseUrl,
+ amount,
+ );
+ break;
+ }
+ case PeerPushCreditStatus.PendingWithdrawing:
+ case PeerPushCreditStatus.SuspendedWithdrawing: {
+ // Here, incoming balance should be covered by internal withdrawal tx
+ break;
+ }
+ }
+ });
+
+ await tx.peerPullCredit.indexes.byStatus
+ .iter(keyRangeActive)
+ .forEachAsync(async (rec) => {
+ switch (rec.status) {
+ case PeerPullPaymentCreditStatus.PendingReady:
+ case PeerPullPaymentCreditStatus.PendingCreatePurse:
+ case PeerPullPaymentCreditStatus.SuspendedReady:
+ case PeerPullPaymentCreditStatus.SuspendedCreatePurse: {
+ const currency = Amounts.currencyOf(rec.amount);
+ const amount = rec.estimatedAmountEffective;
+ await balanceStore.addPendingIncoming(
+ currency,
+ rec.exchangeBaseUrl,
+ amount,
+ );
+ }
+ }
+ });
+
+ await tx.peerPullDebit.indexes.byStatus
+ .iter(keyRangeActive)
+ .forEachAsync(async (rec) => {
+ switch (rec.status) {
+ case PeerPullDebitRecordStatus.PendingDeposit:
+ case PeerPullDebitRecordStatus.AbortingRefresh:
+ case PeerPullDebitRecordStatus.SuspendedAbortingRefresh:
+ case PeerPullDebitRecordStatus.SuspendedDeposit:
+ const currency = Amounts.currencyOf(rec.amount);
+ const amount = rec.coinSel?.totalCost ?? rec.amount;
+ await balanceStore.addPendingOutgoing(
+ currency,
+ rec.exchangeBaseUrl,
+ amount,
+ );
+ break;
+ }
+ });
+
+ await tx.purchases.indexes.byStatus
+ .iter(keyRangeActive)
+ .forEachAsync(async (rec) => {
+ switch (rec.purchaseStatus) {
+ case PurchaseStatus.SuspendedPaying:
+ case PurchaseStatus.PendingPaying:
+ if (!rec.payInfo || !rec.payInfo.payCoinSelection?.coinPubs) {
+ break;
+ }
+ // FIXME: This is pretty slow, cache?
+ const currency = Amounts.currencyOf(rec.payInfo.totalPayCost);
+ const sel = rec.payInfo.payCoinSelection;
+ for (let i = 0; i < sel.coinPubs.length; i++) {
+ const coinPub = sel.coinPubs[i];
+ const coinRec = await tx.coins.get(coinPub);
+ if (!coinRec) {
+ continue;
+ }
+ await balanceStore.addPendingOutgoing(
+ currency,
+ coinRec.exchangeBaseUrl,
+ sel.coinContributions[i],
+ );
+ }
+ }
+ });
+
await tx.depositGroups.indexes.byStatus
.iter(keyRangeActive)
.forEachAsync(async (dgRecord) => {
@@ -487,26 +586,9 @@ export async function getBalances(
): Promise<BalancesResponse> {
logger.trace("starting to compute balance");
- const wbal = await wex.db.runReadWriteTx(
- {
- storeNames: [
- "coinAvailability",
- "coins",
- "depositGroups",
- "exchangeDetails",
- "exchanges",
- "globalCurrencyAuditors",
- "globalCurrencyExchanges",
- "purchases",
- "refreshGroups",
- "withdrawalGroups",
- "peerPushDebit",
- ],
- },
- async (tx) => {
- return getBalancesInsideTransaction(wex, tx);
- },
- );
+ const wbal = await wex.db.runAllStoresReadOnlyTx({}, async (tx) => {
+ return getBalancesInsideTransaction(wex, tx);
+ });
logger.trace("finished computing wallet balance");
diff --git a/packages/taler-wallet-core/src/exchanges.ts b/packages/taler-wallet-core/src/exchanges.ts
@@ -274,18 +274,23 @@ export async function getScopeForAllCoins(
tx: WalletDbReadOnlyTransaction<
[
"exchanges",
+ "coins",
"exchangeDetails",
"globalCurrencyExchanges",
"globalCurrencyAuditors",
]
>,
- exs: string[],
+ coinPubs: string[],
): Promise<ScopeInfo[]> {
- const queries = exs.map((exchange) => {
- return getExchangeScopeInfoOrUndefined(tx, exchange);
- });
- const rs = await Promise.all(queries);
- return rs.filter((d): d is ScopeInfo => d !== undefined);
+ let exchangeSet = new Set<string>();
+ for (const pub of coinPubs) {
+ const coin = await tx.coins.get(pub);
+ if (!coin) {
+ continue;
+ }
+ exchangeSet.add(coin.exchangeBaseUrl);
+ }
+ return await getScopeForAllExchanges(tx, [...exchangeSet]);
}
/**
@@ -309,29 +314,6 @@ export async function getScopeForAllExchanges(
return rs.filter((d): d is ScopeInfo => d !== undefined);
}
-export async function getCoinScopeInfoOrUndefined(
- tx: WalletDbReadOnlyTransaction<
- [
- "coins",
- "exchanges",
- "exchangeDetails",
- "globalCurrencyExchanges",
- "globalCurrencyAuditors",
- ]
- >,
- coinPub: string,
-): Promise<ScopeInfo | undefined> {
- const coin = await tx.coins.get(coinPub);
- if (!coin) {
- return undefined;
- }
- const det = await getExchangeRecordsInternal(tx, coin.exchangeBaseUrl);
- if (!det) {
- return undefined;
- }
- return internalGetExchangeScopeInfo(tx, det);
-}
-
export async function getExchangeScopeInfoOrUndefined(
tx: WalletDbReadOnlyTransaction<
[
@@ -886,7 +868,7 @@ async function downloadExchangeKeysInfo(
headers,
});
- logger.info("got response to /keys request");
+ logger.trace("got response to /keys request");
// We must make sure to parse out the protocol version
// before we validate the body.
@@ -1149,7 +1131,7 @@ async function startUpdateExchangeEntry(
exchangeBaseUrl: string,
options: { forceUpdate?: boolean } = {},
): Promise<void> {
- logger.info(
+ logger.trace(
`starting update of exchange entry ${exchangeBaseUrl}, forced=${
options.forceUpdate ?? false
}`,
@@ -1295,7 +1277,7 @@ export async function fetchFreshExchange(
forceUpdate?: boolean;
} = {},
): Promise<ReadyExchangeSummary> {
- logger.info(`fetch fresh ${baseUrl} forced ${options.forceUpdate}`);
+ logger.trace(`fetch fresh ${baseUrl} forced ${options.forceUpdate}`);
if (!options.forceUpdate) {
const cachedResp = wex.ws.exchangeCache.get(baseUrl);
@@ -1583,7 +1565,7 @@ export async function updateExchangeFromUrlHandler(
!AbsoluteTime.isNever(nextUpdateStamp) &&
!AbsoluteTime.isExpired(nextUpdateStamp)
) {
- logger.info(
+ logger.trace(
`exchange update for ${exchangeBaseUrl} not necessary, scheduled for ${AbsoluteTime.toIsoString(
nextUpdateStamp,
)}`,
@@ -1595,7 +1577,7 @@ export async function updateExchangeFromUrlHandler(
!AbsoluteTime.isNever(nextRefreshCheckStamp) &&
!AbsoluteTime.isExpired(nextRefreshCheckStamp)
) {
- logger.info(
+ logger.trace(
`exchange refresh check for ${exchangeBaseUrl} not necessary, scheduled for ${AbsoluteTime.toIsoString(
nextRefreshCheckStamp,
)}`,
@@ -2599,7 +2581,7 @@ export async function markExchangeUsed(
tx: WalletDbReadWriteTransaction<["exchanges"]>,
exchangeBaseUrl: string,
): Promise<{ notif: WalletNotification | undefined }> {
- logger.info(`marking exchange ${exchangeBaseUrl} as used`);
+ logger.trace(`marking exchange ${exchangeBaseUrl} as used`);
const exch = await tx.exchanges.get(exchangeBaseUrl);
if (!exch) {
logger.info(`exchange ${exchangeBaseUrl} NOT found`);
@@ -3012,7 +2994,7 @@ export async function checkIncomingAmountLegalUnderKycBalanceThreshold(
exchangeBaseUrl: string,
amountIncoming: AmountLike,
): Promise<BalanceThresholdCheckResult> {
- logger.info(`checking ${exchangeBaseUrl} +${amountIncoming} for KYC`);
+ logger.trace(`checking ${exchangeBaseUrl} +${amountIncoming} for KYC`);
return await wex.db.runReadOnlyTx(
{
storeNames: [
@@ -3078,7 +3060,7 @@ export async function checkIncomingAmountLegalUnderKycBalanceThreshold(
};
}
limits.sort((a, b) => Amounts.cmp(a, b));
- logger.info(`applicable limits: ${j2s(limits)}`);
+ logger.trace(`applicable limits: ${j2s(limits)}`);
let limViolated: AmountString | undefined = undefined;
let limNext: AmountString | undefined = undefined;
for (let i = 0; i < limits.length; i++) {
@@ -3091,7 +3073,7 @@ export async function checkIncomingAmountLegalUnderKycBalanceThreshold(
}
}
if (!limViolated) {
- logger.info("balance limit okay");
+ logger.trace("balance limit okay");
return {
result: "ok",
};
diff --git a/packages/taler-wallet-core/src/pay-merchant.ts b/packages/taler-wallet-core/src/pay-merchant.ts
@@ -146,7 +146,7 @@ import {
WalletDbReadWriteTransaction,
WalletStoresV1,
} from "./db.js";
-import { getScopeForAllExchanges } from "./exchanges.js";
+import { getScopeForAllCoins, getScopeForAllExchanges } from "./exchanges.js";
import { DbReadWriteTransaction, StoreNames } from "./query.js";
import {
calculateRefreshOutput,
@@ -634,7 +634,7 @@ export class RefundTransactionContext implements TransactionContext {
const txState = computeRefundTransactionState(refundRecord);
return {
type: TransactionType.Refund,
- scopes: await getScopeForAllExchanges(
+ scopes: await getScopeForAllCoins(
tx,
!purchaseRecord || !purchaseRecord.payInfo?.payCoinSelection
? []
diff --git a/packages/taler-wallet-core/src/query.ts b/packages/taler-wallet-core/src/query.ts
@@ -313,7 +313,7 @@ export function openDatabase(
cause: req.error,
});
}
- logger.info(
+ logger.trace(
`handling upgradeneeded event on ${databaseName} from ${e.oldVersion} to ${e.newVersion}`,
);
onUpgradeNeeded(db, e.oldVersion, newVersion, transaction);
diff --git a/packages/taler-wallet-core/src/shepherd.ts b/packages/taler-wallet-core/src/shepherd.ts
@@ -868,7 +868,6 @@ async function makeExchangeRetryNotification(
pendingTaskId: string,
e: TalerErrorDetail | undefined,
): Promise<WalletNotification | undefined> {
- logger.info("making exchange retry notification");
const parsedTaskId = parseTaskIdentifier(pendingTaskId);
switch (parsedTaskId.tag) {
case PendingTaskType.ExchangeUpdate:
diff --git a/packages/taler-wallet-core/src/transactions.ts b/packages/taler-wallet-core/src/transactions.ts
@@ -37,6 +37,7 @@ import {
TransactionByIdRequest,
TransactionIdStr,
TransactionMajorState,
+ TransactionMinorState,
TransactionsRequest,
TransactionsResponse,
TransactionState,
@@ -533,7 +534,7 @@ export async function rematerializeTransactions(
wex: WalletExecutionContext,
tx: WalletDbAllStoresReadWriteTransaction,
): Promise<void> {
- logger.info("re-materializing transactions");
+ logger.trace("re-materializing transactions");
const allTxMeta = await tx.transactionsMeta.getAll();
for (const txMeta of allTxMeta) {
@@ -928,11 +929,7 @@ export function notifyTransition(
experimentalUserData,
});
- // As a heuristic, we emit balance-change notifications
- // whenever the major state changes.
- // This sometimes emits more notifications than we need,
- // but makes it much more unlikely that we miss any.
- if (transitionInfo.newTxState.major !== transitionInfo.oldTxState.major) {
+ if (couldChangeBalance(transitionInfo)) {
wex.ws.notify({
type: NotificationType.BalanceChange,
hintTransactionId: transactionId,
@@ -940,3 +937,27 @@ export function notifyTransition(
}
}
}
+
+function couldChangeBalance(ti: TransitionInfo): boolean {
+ // We emit a balance change notification unless we're sure that
+ // the transition does not affect the balance.
+ if (ti.newTxState.major == ti.oldTxState.major) {
+ return false;
+ }
+
+ if (
+ ti.newTxState.major === TransactionMajorState.Dialog &&
+ ti.newTxState.minor === TransactionMinorState.MerchantOrderProposed
+ ) {
+ return false;
+ }
+
+ if (
+ ti.newTxState.major === TransactionMajorState.Pending &&
+ ti.newTxState.minor === TransactionMinorState.ClaimProposal
+ ) {
+ return false;
+ }
+
+ return true;
+}
diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts
@@ -2567,7 +2567,7 @@ export class InternalWalletState {
): Promise<T> {
let rid = this.longpollRequestIdCounter++;
const triggerNextLongpoll = () => {
- logger.info(`cleaning up after long-poll ${rid} request to ${hostname}`);
+ logger.trace(`cleaning up after long-poll ${rid} request to ${hostname}`);
const st = this.longpollStatePerHostname.get(hostname);
if (!st) {
return;
@@ -2608,7 +2608,6 @@ export class InternalWalletState {
}
return doRunLongpoll();
} else {
- logger.info(`directly running long-poll request ${rid} to ${hostname}`);
this.longpollStatePerHostname.set(hostname, {
queue: [],
});
diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts
@@ -3323,7 +3323,7 @@ export async function prepareBankIntegratedWithdrawal(
const externalConfirmation = parsedUri.externalConfirmation;
- logger.info(
+ logger.trace(
`creating withdrawal with externalConfirmation=${externalConfirmation}`,
);