diff options
Diffstat (limited to 'packages')
| -rw-r--r-- | packages/taler-util/src/time.ts | 22 | ||||
| -rw-r--r-- | packages/taler-util/src/wallet-types.ts | 34 | ||||
| -rw-r--r-- | packages/taler-wallet-core/src/internal-wallet-state.ts | 6 | ||||
| -rw-r--r-- | packages/taler-wallet-core/src/operations/backup/import.ts | 27 | ||||
| -rw-r--r-- | packages/taler-wallet-core/src/operations/common.ts | 80 | ||||
| -rw-r--r-- | packages/taler-wallet-core/src/operations/exchanges.ts | 56 | ||||
| -rw-r--r-- | packages/taler-wallet-core/src/operations/pending.ts | 28 | ||||
| -rw-r--r-- | packages/taler-wallet-core/src/operations/refresh.ts | 10 | ||||
| -rw-r--r-- | packages/taler-wallet-core/src/operations/withdraw.ts | 7 | ||||
| -rw-r--r-- | packages/taler-wallet-core/src/wallet.ts | 5 | 
10 files changed, 182 insertions, 93 deletions
| diff --git a/packages/taler-util/src/time.ts b/packages/taler-util/src/time.ts index 75b3c7e94..55cda08a5 100644 --- a/packages/taler-util/src/time.ts +++ b/packages/taler-util/src/time.ts @@ -312,6 +312,14 @@ export namespace Duration {  }  export namespace AbsoluteTime { +  export function getStampMsNow(): number { +    return new Date().getTime(); +  } + +  export function getStampMsNever(): number { +    return Number.MAX_SAFE_INTEGER; +  } +    export function now(): AbsoluteTime {      return {        t_ms: new Date().getTime() + timeshift, @@ -398,6 +406,13 @@ export namespace AbsoluteTime {      };    } +  export function fromStampMs(stampMs: number): AbsoluteTime { +    return { +      t_ms: stampMs, +      [opaque_AbsoluteTime]: true, +    }; +  } +    export function fromPreciseTimestamp(t: TalerPreciseTimestamp): AbsoluteTime {      if (t.t_s === "never") {        return { t_ms: "never", [opaque_AbsoluteTime]: true }; @@ -409,6 +424,13 @@ export namespace AbsoluteTime {      };    } +  export function toStampMs(at: AbsoluteTime): number { +    if (at.t_ms === "never") { +      return Number.MAX_SAFE_INTEGER; +    } +    return at.t_ms; +  } +    export function toPreciseTimestamp(at: AbsoluteTime): TalerPreciseTimestamp {      if (at.t_ms == "never") {        return { diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts index 04fb43ec6..01c1838d5 100644 --- a/packages/taler-util/src/wallet-types.ts +++ b/packages/taler-util/src/wallet-types.ts @@ -1262,17 +1262,25 @@ export interface ExchangeFullDetails {  }  export enum ExchangeTosStatus { -  New = "new", +  Pending = "pending", +  Proposed = "proposed",    Accepted = "accepted", -  Changed = "changed", -  NotFound = "not-found", -  Unknown = "unknown",  }  export enum ExchangeEntryStatus { -  Unknown = "unknown", -  Outdated = "outdated", -  Ok = "ok", +  Preset = "preset", +  Ephemeral = "ephemeral", +  Used = "used", +} + +export enum ExchangeUpdateStatus { +  Initial = "initial", +  InitialUpdate = "initial(update)", +  Suspended = "suspended", +  Failed = "failed", +  OutdatedUpdate = "outdated(update)", +  Ready = "ready", +  ReadyUpdate = "ready(update)",  }  export interface OperationErrorInfo { @@ -1285,13 +1293,9 @@ export interface ExchangeListItem {    currency: string | undefined;    paytoUris: string[];    tosStatus: ExchangeTosStatus; -  exchangeStatus: ExchangeEntryStatus; +  exchangeEntryStatus: ExchangeEntryStatus; +  exchangeUpdateStatus: ExchangeUpdateStatus;    ageRestrictionOptions: number[]; -  /** -   * Permanently added to the wallet, as opposed to just -   * temporarily queried. -   */ -  permanent: boolean;    /**     * Information about the last error that occurred when trying @@ -1370,8 +1374,8 @@ export const codecForExchangeListItem = (): Codec<ExchangeListItem> =>      .property("exchangeBaseUrl", codecForString())      .property("paytoUris", codecForList(codecForString()))      .property("tosStatus", codecForAny()) -    .property("exchangeStatus", codecForAny()) -    .property("permanent", codecForBoolean()) +    .property("exchangeEntryStatus", codecForAny()) +    .property("exchangeUpdateStatus", codecForAny())      .property("ageRestrictionOptions", codecForList(codecForNumber()))      .build("ExchangeListItem"); diff --git a/packages/taler-wallet-core/src/internal-wallet-state.ts b/packages/taler-wallet-core/src/internal-wallet-state.ts index 742af89a8..76aee05bd 100644 --- a/packages/taler-wallet-core/src/internal-wallet-state.ts +++ b/packages/taler-wallet-core/src/internal-wallet-state.ts @@ -42,7 +42,7 @@ import { HttpRequestLibrary } from "@gnu-taler/taler-util/http";  import { TalerCryptoInterface } from "./crypto/cryptoImplementation.js";  import {    ExchangeDetailsRecord, -  ExchangeRecord, +  ExchangeEntryRecord,    RefreshReasonDetails,    WalletStoresV1,  } from "./db.js"; @@ -108,7 +108,7 @@ export interface ExchangeOperations {    ): Promise<ExchangeDetailsRecord | undefined>;    getExchangeTrust(      ws: InternalWalletState, -    exchangeInfo: ExchangeRecord, +    exchangeInfo: ExchangeEntryRecord,    ): Promise<TrustInfo>;    updateExchangeFromUrl(      ws: InternalWalletState, @@ -118,7 +118,7 @@ export interface ExchangeOperations {        cancellationToken?: CancellationToken;      },    ): Promise<{ -    exchange: ExchangeRecord; +    exchange: ExchangeEntryRecord;      exchangeDetails: ExchangeDetailsRecord;    }>;  } diff --git a/packages/taler-wallet-core/src/operations/backup/import.ts b/packages/taler-wallet-core/src/operations/backup/import.ts index a53b624e8..836c65643 100644 --- a/packages/taler-wallet-core/src/operations/backup/import.ts +++ b/packages/taler-wallet-core/src/operations/backup/import.ts @@ -342,20 +342,19 @@ export async function importBackup(          if (existingExchange) {            continue;          } -        await tx.exchanges.put({ -          baseUrl: backupExchange.base_url, -          detailsPointer: { -            currency: backupExchange.currency, -            masterPublicKey: backupExchange.master_public_key, -            updateClock: backupExchange.update_clock, -          }, -          permanent: true, -          lastUpdate: undefined, -          nextUpdate: TalerPreciseTimestamp.now(), -          nextRefreshCheck: TalerPreciseTimestamp.now(), -          lastKeysEtag: undefined, -          lastWireEtag: undefined, -        }); +        // await tx.exchanges.put({ +        //   baseUrl: backupExchange.base_url, +        //   detailsPointer: { +        //     currency: backupExchange.currency, +        //     masterPublicKey: backupExchange.master_public_key, +        //     updateClock: backupExchange.update_clock, +        //   }, +        //   lastUpdate: undefined, +        //   nextUpdate: TalerPreciseTimestamp.now(), +        //   nextRefreshCheck: TalerPreciseTimestamp.now(), +        //   lastKeysEtag: undefined, +        //   lastWireEtag: undefined, +        // });        }        for (const backupExchangeDetails of backupBlob.exchange_details) { diff --git a/packages/taler-wallet-core/src/operations/common.ts b/packages/taler-wallet-core/src/operations/common.ts index 7a8b78b53..e96beb5b2 100644 --- a/packages/taler-wallet-core/src/operations/common.ts +++ b/packages/taler-wallet-core/src/operations/common.ts @@ -30,6 +30,7 @@ import {    ExchangeEntryStatus,    ExchangeListItem,    ExchangeTosStatus, +  ExchangeUpdateStatus,    getErrorDetailFromException,    j2s,    Logger, @@ -47,7 +48,7 @@ import {    WalletStoresV1,    CoinRecord,    ExchangeDetailsRecord, -  ExchangeRecord, +  ExchangeEntryRecord,    BackupProviderRecord,    DepositGroupRecord,    PeerPullPaymentIncomingRecord, @@ -59,6 +60,8 @@ import {    RefreshGroupRecord,    RewardRecord,    WithdrawalGroupRecord, +  ExchangeEntryDbUpdateStatus, +  ExchangeEntryDbRecordStatus,  } from "../db.js";  import { makeErrorDetail, TalerError } from "@gnu-taler/taler-util";  import { InternalWalletState } from "../internal-wallet-state.js"; @@ -529,16 +532,16 @@ export function getExchangeTosStatus(    exchangeDetails: ExchangeDetailsRecord,  ): ExchangeTosStatus {    if (!exchangeDetails.tosAccepted) { -    return ExchangeTosStatus.New; +    return ExchangeTosStatus.Proposed;    }    if (exchangeDetails.tosAccepted?.etag == exchangeDetails.tosCurrentEtag) {      return ExchangeTosStatus.Accepted;    } -  return ExchangeTosStatus.Changed; +  return ExchangeTosStatus.Proposed;  }  export function makeExchangeListItem( -  r: ExchangeRecord, +  r: ExchangeEntryRecord,    exchangeDetails: ExchangeDetailsRecord | undefined,    lastError: TalerErrorDetail | undefined,  ): ExchangeListItem { @@ -547,30 +550,57 @@ export function makeExchangeListItem(          error: lastError,        }      : undefined; -  if (!exchangeDetails) { -    return { -      exchangeBaseUrl: r.baseUrl, -      currency: undefined, -      tosStatus: ExchangeTosStatus.Unknown, -      paytoUris: [], -      exchangeStatus: ExchangeEntryStatus.Unknown, -      permanent: r.permanent, -      ageRestrictionOptions: [], -      lastUpdateErrorInfo, -    }; + +  let exchangeUpdateStatus: ExchangeUpdateStatus; +  switch (r.updateStatus) { +    case ExchangeEntryDbUpdateStatus.Failed: +      exchangeUpdateStatus = ExchangeUpdateStatus.Failed; +      break; +    case ExchangeEntryDbUpdateStatus.Initial: +      exchangeUpdateStatus = ExchangeUpdateStatus.Initial; +      break; +    case ExchangeEntryDbUpdateStatus.InitialUpdate: +      exchangeUpdateStatus = ExchangeUpdateStatus.InitialUpdate; +      break; +    case ExchangeEntryDbUpdateStatus.OutdatedUpdate: +      exchangeUpdateStatus = ExchangeUpdateStatus.OutdatedUpdate; +      break; +    case ExchangeEntryDbUpdateStatus.Ready: +      exchangeUpdateStatus = ExchangeUpdateStatus.Ready; +      break; +    case ExchangeEntryDbUpdateStatus.ReadyUpdate: +      exchangeUpdateStatus = ExchangeUpdateStatus.ReadyUpdate; +      break; +    case ExchangeEntryDbUpdateStatus.Suspended: +      exchangeUpdateStatus = ExchangeUpdateStatus.Suspended; +      break; +  } + +  let exchangeEntryStatus: ExchangeEntryStatus; +  switch (r.entryStatus) { +    case ExchangeEntryDbRecordStatus.Ephemeral: +      exchangeEntryStatus = ExchangeEntryStatus.Ephemeral; +      break; +    case ExchangeEntryDbRecordStatus.Preset: +      exchangeEntryStatus = ExchangeEntryStatus.Preset; +      break; +    case ExchangeEntryDbRecordStatus.Used: +      exchangeEntryStatus = ExchangeEntryStatus.Used; +      break;    } -  let exchangeStatus; -  exchangeStatus = ExchangeEntryStatus.Ok; +    return {      exchangeBaseUrl: r.baseUrl, -    currency: exchangeDetails.currency, -    tosStatus: getExchangeTosStatus(exchangeDetails), -    paytoUris: exchangeDetails.wireInfo.accounts.map((x) => x.payto_uri), -    exchangeStatus, -    permanent: r.permanent, -    ageRestrictionOptions: exchangeDetails.ageMask +    currency: exchangeDetails?.currency, +    exchangeUpdateStatus, +    exchangeEntryStatus, +    tosStatus: exchangeDetails +      ? getExchangeTosStatus(exchangeDetails) +      : ExchangeTosStatus.Pending, +    ageRestrictionOptions: exchangeDetails?.ageMask        ? AgeRestriction.getAgeGroupsFromMask(exchangeDetails.ageMask)        : [], +    paytoUris: exchangeDetails?.wireInfo.accounts.map((x) => x.payto_uri) ?? [],      lastUpdateErrorInfo,    };  } @@ -892,13 +922,13 @@ export namespace TaskIdentifiers {    export function forWithdrawal(wg: WithdrawalGroupRecord): TaskId {      return `${PendingTaskType.Withdraw}:${wg.withdrawalGroupId}` as TaskId;    } -  export function forExchangeUpdate(exch: ExchangeRecord): TaskId { +  export function forExchangeUpdate(exch: ExchangeEntryRecord): TaskId {      return `${PendingTaskType.ExchangeUpdate}:${exch.baseUrl}` as TaskId;    }    export function forExchangeUpdateFromUrl(exchBaseUrl: string): TaskId {      return `${PendingTaskType.ExchangeUpdate}:${exchBaseUrl}` as TaskId;    } -  export function forExchangeCheckRefresh(exch: ExchangeRecord): TaskId { +  export function forExchangeCheckRefresh(exch: ExchangeEntryRecord): TaskId {      return `${PendingTaskType.ExchangeCheckRefresh}:${exch.baseUrl}` as TaskId;    }    export function forTipPickup(tipRecord: RewardRecord): TaskId { diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts b/packages/taler-wallet-core/src/operations/exchanges.ts index c6b46e360..311a71a6e 100644 --- a/packages/taler-wallet-core/src/operations/exchanges.ts +++ b/packages/taler-wallet-core/src/operations/exchanges.ts @@ -32,6 +32,7 @@ import {    encodeCrock,    ExchangeAuditor,    ExchangeDenomination, +  ExchangeEntryStatus,    ExchangeGlobalFees,    ExchangeSignKeyJson,    ExchangeWireJson, @@ -66,10 +67,15 @@ import {    DenominationRecord,    DenominationVerificationStatus,    ExchangeDetailsRecord, -  ExchangeRecord, +  ExchangeEntryRecord,    WalletStoresV1,  } from "../db.js"; -import { isWithdrawableDenom } from "../index.js"; +import { +  ExchangeEntryDbRecordStatus, +  ExchangeEntryDbUpdateStatus, +  isWithdrawableDenom, +  WalletDbReadWriteTransaction, +} from "../index.js";  import { InternalWalletState, TrustInfo } from "../internal-wallet-state.js";  import { checkDbInvariant } from "../util/invariants.js";  import { @@ -326,6 +332,26 @@ export async function downloadExchangeInfo(    };  } +export async function addPresetExchangeEntry( +  tx: WalletDbReadWriteTransaction<"exchanges">, +  exchangeBaseUrl: string, +): Promise<void> { +  let exchange = await tx.exchanges.get(exchangeBaseUrl); +  if (!exchange) { +    const r: ExchangeEntryRecord = { +      entryStatus: ExchangeEntryDbRecordStatus.Preset, +      updateStatus: ExchangeEntryDbUpdateStatus.Initial, +      baseUrl: exchangeBaseUrl, +      detailsPointer: undefined, +      lastUpdate: undefined, +      lastKeysEtag: undefined, +      nextRefreshCheckStampMs: AbsoluteTime.getStampMsNever(), +      nextUpdateStampMs: AbsoluteTime.getStampMsNever(), +    }; +    await tx.exchanges.put(r); +  } +} +  export async function provideExchangeRecordInTx(    ws: InternalWalletState,    tx: GetReadWriteAccess<{ @@ -335,20 +361,20 @@ export async function provideExchangeRecordInTx(    baseUrl: string,    now: AbsoluteTime,  ): Promise<{ -  exchange: ExchangeRecord; +  exchange: ExchangeEntryRecord;    exchangeDetails: ExchangeDetailsRecord | undefined;  }> {    let exchange = await tx.exchanges.get(baseUrl);    if (!exchange) { -    const r: ExchangeRecord = { -      permanent: true, +    const r: ExchangeEntryRecord = { +      entryStatus: ExchangeEntryDbRecordStatus.Ephemeral, +      updateStatus: ExchangeEntryDbUpdateStatus.InitialUpdate,        baseUrl: baseUrl,        detailsPointer: undefined,        lastUpdate: undefined, -      nextUpdate: AbsoluteTime.toPreciseTimestamp(now), -      nextRefreshCheck: AbsoluteTime.toPreciseTimestamp(now), +      nextUpdateStampMs: AbsoluteTime.getStampMsNever(), +      nextRefreshCheckStampMs: AbsoluteTime.getStampMsNever(),        lastKeysEtag: undefined, -      lastWireEtag: undefined,      };      await tx.exchanges.put(r);      exchange = r; @@ -534,6 +560,10 @@ export async function downloadTosFromAcceptedFormat(    );  } +/** + * FIXME: Split this into two parts: (a) triggering the exchange + * to be updated and (b) waiting for the update to finish. + */  export async function updateExchangeFromUrl(    ws: InternalWalletState,    baseUrl: string, @@ -543,7 +573,7 @@ export async function updateExchangeFromUrl(      cancellationToken?: CancellationToken;    } = {},  ): Promise<{ -  exchange: ExchangeRecord; +  exchange: ExchangeEntryRecord;    exchangeDetails: ExchangeDetailsRecord;  }> {    const canonUrl = canonicalizeBaseUrl(baseUrl); @@ -613,7 +643,7 @@ export async function updateExchangeFromUrlHandler(      !forceNow &&      exchangeDetails !== undefined &&      !AbsoluteTime.isExpired( -      AbsoluteTime.fromPreciseTimestamp(exchange.nextUpdate), +      AbsoluteTime.fromStampMs(exchange.nextUpdateStampMs),      )    ) {      logger.trace("using existing exchange info"); @@ -755,11 +785,11 @@ export async function updateExchangeFromUrlHandler(          newDetails.rowId = existingDetails.rowId;        }        r.lastUpdate = TalerPreciseTimestamp.now(); -      r.nextUpdate = AbsoluteTime.toPreciseTimestamp( +      r.nextUpdateStampMs = AbsoluteTime.toStampMs(          AbsoluteTime.fromProtocolTimestamp(keysInfo.expiry),        );        // New denominations might be available. -      r.nextRefreshCheck = TalerPreciseTimestamp.now(); +      r.nextRefreshCheckStampMs = AbsoluteTime.getStampMsNow();        if (detailsPointerChanged) {          r.detailsPointer = {            currency: newDetails.currency, @@ -948,7 +978,7 @@ export async function getExchangePaytoUri(   */  export async function getExchangeTrust(    ws: InternalWalletState, -  exchangeInfo: ExchangeRecord, +  exchangeInfo: ExchangeEntryRecord,  ): Promise<TrustInfo> {    let isTrusted = false;    let isAudited = false; diff --git a/packages/taler-wallet-core/src/operations/pending.ts b/packages/taler-wallet-core/src/operations/pending.ts index 6c6546f83..e37e45c16 100644 --- a/packages/taler-wallet-core/src/operations/pending.ts +++ b/packages/taler-wallet-core/src/operations/pending.ts @@ -45,6 +45,7 @@ import {    PeerPushPaymentIncomingRecord,    RefundGroupRecord,    RefundGroupStatus, +  ExchangeEntryDbUpdateStatus,  } from "../db.js";  import {    PendingOperationsResponse, @@ -81,19 +82,25 @@ async function gatherExchangePending(    ws: InternalWalletState,    tx: GetReadOnlyAccess<{      exchanges: typeof WalletStoresV1.exchanges; -    exchangeDetails: typeof WalletStoresV1.exchangeDetails;      operationRetries: typeof WalletStoresV1.operationRetries;    }>,    now: AbsoluteTime,    resp: PendingOperationsResponse,  ): Promise<void> { -  // FIXME: We should do a range query here based on the update time. +  // FIXME: We should do a range query here based on the update time +  // and/or the entry state.    await tx.exchanges.iter().forEachAsync(async (exch) => { +    switch (exch.updateStatus) { +      case ExchangeEntryDbUpdateStatus.Initial: +      case ExchangeEntryDbUpdateStatus.Suspended: +      case ExchangeEntryDbUpdateStatus.Failed: +        return; +    }      const opTag = TaskIdentifiers.forExchangeUpdate(exch);      let opr = await tx.operationRetries.get(opTag);      const timestampDue =        opr?.retryInfo.nextRetry ?? -      AbsoluteTime.fromPreciseTimestamp(exch.nextUpdate); +      AbsoluteTime.fromStampMs(exch.nextUpdateStampMs);      resp.pendingOperations.push({        type: PendingTaskType.ExchangeUpdate,        ...getPendingCommon(ws, opTag, timestampDue), @@ -108,7 +115,7 @@ async function gatherExchangePending(        resp.pendingOperations.push({          type: PendingTaskType.ExchangeCheckRefresh,          ...getPendingCommon(ws, opTag, timestampDue), -        timestampDue: AbsoluteTime.fromPreciseTimestamp(exch.nextRefreshCheck), +        timestampDue: AbsoluteTime.fromStampMs(exch.nextRefreshCheckStampMs),          givesLifeness: false,          exchangeBaseUrl: exch.baseUrl,        }); @@ -184,8 +191,9 @@ export async function iterRecordsForWithdrawal(        WithdrawalGroupStatus.PendingRegisteringBank,        WithdrawalGroupStatus.PendingAml,      ); -    withdrawalGroupRecords = -      await tx.withdrawalGroups.indexes.byStatus.getAll(range); +    withdrawalGroupRecords = await tx.withdrawalGroups.indexes.byStatus.getAll( +      range, +    );    } else {      withdrawalGroupRecords =        await tx.withdrawalGroups.indexes.byStatus.getAll(); @@ -344,12 +352,8 @@ export async function iterRecordsForRefund(    f: (r: RefundGroupRecord) => Promise<void>,  ): Promise<void> {    if (filter.onlyState === "nonfinal") { -    const keyRange = GlobalIDB.KeyRange.only( -      RefundGroupStatus.Pending -    ); -    await tx.refundGroups.indexes.byStatus -      .iter(keyRange) -      .forEachAsync(f); +    const keyRange = GlobalIDB.KeyRange.only(RefundGroupStatus.Pending); +    await tx.refundGroups.indexes.byStatus.iter(keyRange).forEachAsync(f);    } else {      await tx.refundGroups.iter().forEachAsync(f);    } diff --git a/packages/taler-wallet-core/src/operations/refresh.ts b/packages/taler-wallet-core/src/operations/refresh.ts index 72d1a2725..fb356f0fc 100644 --- a/packages/taler-wallet-core/src/operations/refresh.ts +++ b/packages/taler-wallet-core/src/operations/refresh.ts @@ -1190,14 +1190,14 @@ export async function autoRefresh(            `created refresh group for auto-refresh (${res.refreshGroupId})`,          );        } -//      logger.trace( -//        `current wallet time: ${AbsoluteTime.toIsoString(AbsoluteTime.now())}`, -//      ); +      //      logger.trace( +      //        `current wallet time: ${AbsoluteTime.toIsoString(AbsoluteTime.now())}`, +      //      );        logger.trace(          `next refresh check at ${AbsoluteTime.toIsoString(minCheckThreshold)}`,        ); -      exchange.nextRefreshCheck = -        AbsoluteTime.toPreciseTimestamp(minCheckThreshold); +      exchange.nextRefreshCheckStampMs = +        AbsoluteTime.toStampMs(minCheckThreshold);        await tx.exchanges.put(exchange);      });    return TaskRunResult.finished(); diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts index 040d191e1..d8ce0a9a2 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.ts @@ -128,6 +128,8 @@ import {  } from "../util/coinSelection.js";  import {    ExchangeDetailsRecord, +  ExchangeEntryDbRecordStatus, +  ExchangeEntryDbUpdateStatus,    PendingTaskType,    isWithdrawableDenom,  } from "../index.js"; @@ -2341,10 +2343,6 @@ export async function internalPerformCreateWithdrawalGroup(    }>,    prep: PrepareCreateWithdrawalGroupResult,  ): Promise<PerformCreateWithdrawalGroupResult> { -  const transactionId = constructTransactionIdentifier({ -    tag: TransactionType.Withdrawal, -    withdrawalGroupId: prep.withdrawalGroup.withdrawalGroupId, -  });    const { withdrawalGroup } = prep;    if (!prep.creationInfo) {      return { withdrawalGroup, transitionInfo: undefined }; @@ -2361,6 +2359,7 @@ export async function internalPerformCreateWithdrawalGroup(    const exchange = await tx.exchanges.get(withdrawalGroup.exchangeBaseUrl);    if (exchange) {      exchange.lastWithdrawal = TalerPreciseTimestamp.now(); +    exchange.entryStatus = ExchangeEntryDbRecordStatus.Used;      await tx.exchanges.put(exchange);    } diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index 194894e52..c9ccda20d 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -189,6 +189,7 @@ import {  } from "./operations/deposits.js";  import {    acceptExchangeTermsOfService, +  addPresetExchangeEntry,    downloadTosFromAcceptedFormat,    getExchangeDetails,    getExchangeRequestTimeout, @@ -533,6 +534,7 @@ async function fillDefaults(ws: InternalWalletState): Promise<void> {          await tx.auditorTrust.put(c);        }        for (const baseUrl of ws.config.builtin.exchanges) { +        await addPresetExchangeEntry(tx, baseUrl);          const now = AbsoluteTime.now();          provideExchangeRecordInTx(ws, tx, baseUrl, now);        } @@ -1688,8 +1690,7 @@ export class Wallet {    public static defaultConfig: Readonly<WalletConfig> = {      builtin: { -      //exchanges: ["https://exchange.demo.taler.net/"], -      exchanges: [], +      exchanges: ["https://exchange.demo.taler.net/"],        auditors: [          {            currency: "KUDOS", | 
