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: thread through w


From: gnunet
Subject: [taler-wallet-core] branch master updated: wallet-core: thread through wallet execution context
Date: Tue, 27 Feb 2024 17:41:59 +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 523280b38 wallet-core: thread through wallet execution context
523280b38 is described below

commit 523280b3862b528512ff93c651bc0d9ed632fbf6
Author: Florian Dold <florian@dold.me>
AuthorDate: Tue Feb 27 17:39:58 2024 +0100

    wallet-core: thread through wallet execution context
---
 packages/taler-wallet-core/src/attention.ts        |  26 +-
 packages/taler-wallet-core/src/backup/index.ts     | 117 +++---
 packages/taler-wallet-core/src/balance.ts          |  32 +-
 packages/taler-wallet-core/src/coinSelection.ts    |  32 +-
 packages/taler-wallet-core/src/common.ts           |  16 +-
 packages/taler-wallet-core/src/deposits.ts         | 221 +++++------
 packages/taler-wallet-core/src/dev-experiments.ts  |   8 +-
 packages/taler-wallet-core/src/exchanges.ts        | 142 ++++---
 .../src/instructedAmountConversion.ts              |  24 +-
 packages/taler-wallet-core/src/pay-merchant.ts     | 428 ++++++++++-----------
 packages/taler-wallet-core/src/pay-peer-common.ts  |  24 +-
 .../taler-wallet-core/src/pay-peer-pull-credit.ts  | 157 ++++----
 .../taler-wallet-core/src/pay-peer-pull-debit.ts   |  93 +++--
 .../taler-wallet-core/src/pay-peer-push-credit.ts  | 120 +++---
 .../taler-wallet-core/src/pay-peer-push-debit.ts   | 177 ++++-----
 packages/taler-wallet-core/src/recoup.ts           |  79 ++--
 packages/taler-wallet-core/src/refresh.ts          | 222 +++++------
 packages/taler-wallet-core/src/reward.ts           |  24 +-
 packages/taler-wallet-core/src/shepherd.ts         |  71 ++--
 packages/taler-wallet-core/src/testing.ts          | 174 ++++-----
 packages/taler-wallet-core/src/transactions.ts     |  86 ++---
 packages/taler-wallet-core/src/wallet.ts           | 273 ++++++-------
 packages/taler-wallet-core/src/withdraw.ts         | 414 ++++++++++----------
 23 files changed, 1430 insertions(+), 1530 deletions(-)

diff --git a/packages/taler-wallet-core/src/attention.ts 
b/packages/taler-wallet-core/src/attention.ts
index 61f95350d..60d2117f1 100644
--- a/packages/taler-wallet-core/src/attention.ts
+++ b/packages/taler-wallet-core/src/attention.ts
@@ -29,15 +29,15 @@ import {
   UserAttentionsResponse,
 } from "@gnu-taler/taler-util";
 import { timestampPreciseFromDb, timestampPreciseToDb } from "./db.js";
-import { InternalWalletState } from "./wallet.js";
+import { InternalWalletState, WalletExecutionContext } from "./wallet.js";
 
 const logger = new Logger("operations/attention.ts");
 
 export async function getUserAttentionsUnreadCount(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: UserAttentionsRequest,
 ): Promise<UserAttentionsCountResponse> {
-  const total = await ws.db.runReadOnlyTx(["userAttention"], async (tx) => {
+  const total = await wex.db.runReadOnlyTx(["userAttention"], async (tx) => {
     let count = 0;
     await tx.userAttention.iter().forEach((x) => {
       if (
@@ -56,10 +56,10 @@ export async function getUserAttentionsUnreadCount(
 }
 
 export async function getUserAttentions(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: UserAttentionsRequest,
 ): Promise<UserAttentionsResponse> {
-  return await ws.db.runReadOnlyTx(["userAttention"], async (tx) => {
+  return await wex.db.runReadOnlyTx(["userAttention"], async (tx) => {
     const pending: UserAttentionUnreadList = [];
     await tx.userAttention.iter().forEach((x) => {
       if (
@@ -79,10 +79,10 @@ export async function getUserAttentions(
 }
 
 export async function markAttentionRequestAsRead(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: UserAttentionByIdRequest,
 ): Promise<void> {
-  await ws.db.runReadWriteTx(["userAttention"], async (tx) => {
+  await wex.db.runReadWriteTx(["userAttention"], async (tx) => {
     const ua = await tx.userAttention.get([req.entityId, req.type]);
     if (!ua) throw Error("attention request not found");
     tx.userAttention.put({
@@ -96,15 +96,15 @@ export async function markAttentionRequestAsRead(
  * the wallet need the user attention to complete a task
  * internal API
  *
- * @param ws
+ * @param wex
  * @param info
  */
 export async function addAttentionRequest(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   info: AttentionInfo,
   entityId: string,
 ): Promise<void> {
-  await ws.db.runReadWriteTx(["userAttention"], async (tx) => {
+  await wex.db.runReadWriteTx(["userAttention"], async (tx) => {
     await tx.userAttention.put({
       info,
       entityId,
@@ -118,14 +118,14 @@ export async function addAttentionRequest(
  * user completed the task, attention request is not needed
  * internal API
  *
- * @param ws
+ * @param wex
  * @param created
  */
 export async function removeAttentionRequest(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: UserAttentionByIdRequest,
 ): Promise<void> {
-  await ws.db.runReadWriteTx(["userAttention"], async (tx) => {
+  await wex.db.runReadWriteTx(["userAttention"], async (tx) => {
     const ua = await tx.userAttention.get([req.entityId, req.type]);
     if (!ua) throw Error("attention request not found");
     await tx.userAttention.delete([req.entityId, req.type]);
diff --git a/packages/taler-wallet-core/src/backup/index.ts 
b/packages/taler-wallet-core/src/backup/index.ts
index a5d7eee80..04c53526d 100644
--- a/packages/taler-wallet-core/src/backup/index.ts
+++ b/packages/taler-wallet-core/src/backup/index.ts
@@ -90,7 +90,7 @@ import {
   timestampPreciseToDb,
 } from "../db.js";
 import { preparePayForUri } from "../pay-merchant.js";
-import { InternalWalletState } from "../wallet.js";
+import { InternalWalletState, WalletExecutionContext } from "../wallet.js";
 
 const logger = new Logger("operations/backup.ts");
 
@@ -179,10 +179,10 @@ function getNextBackupTimestamp(): TalerPreciseTimestamp {
 }
 
 async function runBackupCycleForProvider(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   args: BackupForProviderArgs,
 ): Promise<TaskRunResult> {
-  const provider = await ws.db.runReadOnlyTx(
+  const provider = await wex.db.runReadOnlyTx(
     ["backupProviders"],
     async (tx) => {
       return tx.backupProviders.get(args.backupProviderBaseUrl);
@@ -197,7 +197,7 @@ async function runBackupCycleForProvider(
   //const backupJson = await exportBackup(ws);
   // FIXME: re-implement backup
   const backupJson = {};
-  const backupConfig = await provideBackupState(ws);
+  const backupConfig = await provideBackupState(wex);
   const encBackup = await encryptBackup(backupConfig, backupJson);
   const currentBackupHash = hash(encBackup);
 
@@ -209,7 +209,7 @@ async function runBackupCycleForProvider(
   logger.trace(`trying to upload backup to ${provider.baseUrl}`);
   logger.trace(`old hash ${oldHash}, new hash ${newHash}`);
 
-  const syncSigResp = await ws.cryptoApi.makeSyncSignature({
+  const syncSigResp = await wex.cryptoApi.makeSyncSignature({
     newHash: encodeCrock(currentBackupHash),
     oldHash: provider.lastBackupHash,
     accountPriv: encodeCrock(accountKeyPair.eddsaPriv),
@@ -226,7 +226,7 @@ async function runBackupCycleForProvider(
     accountBackupUrl.searchParams.set("fresh", "yes");
   }
 
-  const resp = await ws.http.fetch(accountBackupUrl.href, {
+  const resp = await wex.http.fetch(accountBackupUrl.href, {
     method: "POST",
     body: encBackup,
     headers: {
@@ -244,7 +244,7 @@ async function runBackupCycleForProvider(
   logger.trace(`sync response status: ${resp.status}`);
 
   if (resp.status === HttpStatusCode.NotModified) {
-    await ws.db.runReadWriteTx(["backupProviders"], async (tx) => {
+    await wex.db.runReadWriteTx(["backupProviders"], async (tx) => {
       const prov = await tx.backupProviders.get(provider.baseUrl);
       if (!prov) {
         return;
@@ -259,7 +259,7 @@ async function runBackupCycleForProvider(
       await tx.backupProviders.put(prov);
     });
 
-    removeAttentionRequest(ws, {
+    removeAttentionRequest(wex, {
       entityId: provider.baseUrl,
       type: AttentionType.BackupUnpaid,
     });
@@ -279,7 +279,7 @@ async function runBackupCycleForProvider(
     //FIXME: check download errors
     let res: PreparePayResult | undefined = undefined;
     try {
-      res = await preparePayForUri(ws, talerUri);
+      res = await preparePayForUri(wex, talerUri);
     } catch (e) {
       const error = TalerError.fromException(e);
       if (!error.hasErrorCode(TalerErrorCode.WALLET_ORDER_ALREADY_CLAIMED)) {
@@ -290,7 +290,7 @@ async function runBackupCycleForProvider(
     if (res === undefined) {
       //claimed
 
-      await ws.db.runReadWriteTx(["backupProviders"], async (tx) => {
+      await wex.db.runReadWriteTx(["backupProviders"], async (tx) => {
         const prov = await tx.backupProviders.get(provider.baseUrl);
         if (!prov) {
           logger.warn("backup provider not found anymore");
@@ -310,7 +310,7 @@ async function runBackupCycleForProvider(
     }
     const result = res;
 
-    await ws.db.runReadWriteTx(["backupProviders"], async (tx) => {
+    await wex.db.runReadWriteTx(["backupProviders"], async (tx) => {
       const prov = await tx.backupProviders.get(provider.baseUrl);
       if (!prov) {
         logger.warn("backup provider not found anymore");
@@ -327,7 +327,7 @@ async function runBackupCycleForProvider(
     });
 
     addAttentionRequest(
-      ws,
+      wex,
       {
         type: AttentionType.BackupUnpaid,
         provider_base_url: provider.baseUrl,
@@ -343,7 +343,7 @@ async function runBackupCycleForProvider(
   }
 
   if (resp.status === HttpStatusCode.NoContent) {
-    await ws.db.runReadWriteTx(["backupProviders"], async (tx) => {
+    await wex.db.runReadWriteTx(["backupProviders"], async (tx) => {
       const prov = await tx.backupProviders.get(provider.baseUrl);
       if (!prov) {
         return;
@@ -359,7 +359,7 @@ async function runBackupCycleForProvider(
       await tx.backupProviders.put(prov);
     });
 
-    removeAttentionRequest(ws, {
+    removeAttentionRequest(wex, {
       entityId: provider.baseUrl,
       type: AttentionType.BackupUnpaid,
     });
@@ -372,11 +372,11 @@ async function runBackupCycleForProvider(
   if (resp.status === HttpStatusCode.Conflict) {
     logger.info("conflicting backup found");
     const backupEnc = new Uint8Array(await resp.bytes());
-    const backupConfig = await provideBackupState(ws);
+    const backupConfig = await provideBackupState(wex);
     // const blob = await decryptBackup(backupConfig, backupEnc);
     // FIXME: Re-implement backup import with merging
     // await importBackup(ws, blob, cryptoData);
-    await ws.db.runReadWriteTx(["backupProviders"], async (tx) => {
+    await wex.db.runReadWriteTx(["backupProviders"], async (tx) => {
       const prov = await tx.backupProviders.get(provider.baseUrl);
       if (!prov) {
         logger.warn("backup provider not found anymore");
@@ -394,7 +394,7 @@ async function runBackupCycleForProvider(
     });
     logger.info("processed existing backup");
     // Now upload our own, merged backup.
-    return await runBackupCycleForProvider(ws, args);
+    return await runBackupCycleForProvider(wex, args);
   }
 
   // Some other response that we did not expect!
@@ -410,10 +410,10 @@ async function runBackupCycleForProvider(
 }
 
 export async function processBackupForProvider(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   backupProviderBaseUrl: string,
 ): Promise<TaskRunResult> {
-  const provider = await ws.db.runReadOnlyTx(
+  const provider = await wex.db.runReadOnlyTx(
     ["backupProviders"],
     async (tx) => {
       return await tx.backupProviders.get(backupProviderBaseUrl);
@@ -425,7 +425,7 @@ export async function processBackupForProvider(
 
   logger.info(`running backup for provider ${backupProviderBaseUrl}`);
 
-  return await runBackupCycleForProvider(ws, {
+  return await runBackupCycleForProvider(wex, {
     backupProviderBaseUrl: provider.baseUrl,
   });
 }
@@ -441,10 +441,10 @@ export const codecForRemoveBackupProvider =
       .build("RemoveBackupProviderRequest");
 
 export async function removeBackupProvider(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: RemoveBackupProviderRequest,
 ): Promise<void> {
-  await ws.db.runReadWriteTx(["backupProviders"], async (tx) => {
+  await wex.db.runReadWriteTx(["backupProviders"], async (tx) => {
     await tx.backupProviders.delete(req.provider);
   });
 }
@@ -469,10 +469,10 @@ export const codecForRunBackupCycle = (): 
Codec<RunBackupCycleRequest> =>
  * 3. Upload the updated backup blob.
  */
 export async function runBackupCycle(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: RunBackupCycleRequest,
 ): Promise<void> {
-  const providers = await ws.db.runReadOnlyTx(
+  const providers = await wex.db.runReadOnlyTx(
     ["backupProviders"],
     async (tx) => {
       if (req.providers) {
@@ -486,7 +486,7 @@ export async function runBackupCycle(
   );
 
   for (const provider of providers) {
-    await runBackupCycleForProvider(ws, {
+    await runBackupCycleForProvider(wex, {
       backupProviderBaseUrl: provider.baseUrl,
     });
   }
@@ -547,13 +547,13 @@ export const codecForAddBackupProviderResponse =
       .build("AddBackupProviderResponse");
 
 export async function addBackupProvider(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: AddBackupProviderRequest,
 ): Promise<AddBackupProviderResponse> {
   logger.info(`adding backup provider ${j2s(req)}`);
-  await provideBackupState(ws);
+  await provideBackupState(wex);
   const canonUrl = canonicalizeBaseUrl(req.backupProviderBaseUrl);
-  await ws.db.runReadWriteTx(["backupProviders"], async (tx) => {
+  await wex.db.runReadWriteTx(["backupProviders"], async (tx) => {
     const oldProv = await tx.backupProviders.get(canonUrl);
     if (oldProv) {
       logger.info("old backup provider found");
@@ -571,12 +571,12 @@ export async function addBackupProvider(
     }
   });
   const termsUrl = new URL("config", canonUrl);
-  const resp = await ws.http.fetch(termsUrl.href);
+  const resp = await wex.http.fetch(termsUrl.href);
   const terms = await readSuccessResponseJsonOrThrow(
     resp,
     codecForSyncTermsOfServiceResponse(),
   );
-  await ws.db.runReadWriteTx(["backupProviders"], async (tx) => {
+  await wex.db.runReadWriteTx(["backupProviders"], async (tx) => {
     let state: BackupProviderState;
     //FIXME: what is the difference provisional and ready?
     if (req.activate) {
@@ -604,13 +604,13 @@ export async function addBackupProvider(
     });
   });
 
-  return await runFirstBackupCycleForProvider(ws, {
+  return await runFirstBackupCycleForProvider(wex, {
     backupProviderBaseUrl: canonUrl,
   });
 }
 
 async function runFirstBackupCycleForProvider(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   args: BackupForProviderArgs,
 ): Promise<AddBackupProviderResponse> {
   throw Error("not implemented");
@@ -647,7 +647,7 @@ export interface BackupInfo {
 }
 
 async function getProviderPaymentInfo(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   provider: BackupProviderRecord,
 ): Promise<ProviderPaymentStatus> {
   throw Error("not implemented");
@@ -702,10 +702,10 @@ async function getProviderPaymentInfo(
  * Get information about the current state of wallet backups.
  */
 export async function getBackupInfo(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
 ): Promise<BackupInfo> {
-  const backupConfig = await provideBackupState(ws);
-  const providerRecords = await ws.db.runReadOnlyTx(
+  const backupConfig = await provideBackupState(wex);
+  const providerRecords = await wex.db.runReadOnlyTx(
     ["backupProviders", "operationRetries"],
     async (tx) => {
       return await tx.backupProviders.iter().mapAsync(async (bp) => {
@@ -731,7 +731,7 @@ export async function getBackupInfo(
         x.provider.state.tag === BackupProviderStateTag.Retrying
           ? x.retryRecord?.lastError
           : undefined,
-      paymentStatus: await getProviderPaymentInfo(ws, x.provider),
+      paymentStatus: await getProviderPaymentInfo(wex, x.provider),
       terms: x.provider.terms,
       name: x.provider.name,
     });
@@ -748,10 +748,10 @@ export async function getBackupInfo(
  * private key.
  */
 export async function getBackupRecovery(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
 ): Promise<BackupRecovery> {
-  const bs = await provideBackupState(ws);
-  const providers = await ws.db.runReadOnlyTx(
+  const bs = await provideBackupState(wex);
+  const providers = await wex.db.runReadOnlyTx(
     ["backupProviders"],
     async (tx) => {
       return await tx.backupProviders.iter().toArray();
@@ -771,10 +771,10 @@ export async function getBackupRecovery(
 }
 
 async function backupRecoveryTheirs(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   br: BackupRecovery,
 ) {
-  await ws.db.runReadWriteTx(["backupProviders", "config"], async (tx) => {
+  await wex.db.runReadWriteTx(["backupProviders", "config"], async (tx) => {
     let backupStateEntry: ConfigRecord | undefined = await tx.config.get(
       ConfigRecordKey.WalletBackupState,
     );
@@ -818,16 +818,19 @@ async function backupRecoveryTheirs(
   });
 }
 
-async function backupRecoveryOurs(ws: InternalWalletState, br: BackupRecovery) 
{
+async function backupRecoveryOurs(
+  wex: WalletExecutionContext,
+  br: BackupRecovery,
+) {
   throw Error("not implemented");
 }
 
 export async function loadBackupRecovery(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   br: RecoveryLoadRequest,
 ): Promise<void> {
-  const bs = await provideBackupState(ws);
-  const providers = await ws.db.runReadOnlyTx(
+  const bs = await provideBackupState(wex);
+  const providers = await wex.db.runReadOnlyTx(
     ["backupProviders"],
     async (tx) => {
       return await tx.backupProviders.iter().toArray();
@@ -847,9 +850,9 @@ export async function loadBackupRecovery(
     strategy = RecoveryMergeStrategy.Theirs;
   }
   if (strategy === RecoveryMergeStrategy.Theirs) {
-    return backupRecoveryTheirs(ws, br.recovery);
+    return backupRecoveryTheirs(wex, br.recovery);
   } else {
-    return backupRecoveryOurs(ws, br.recovery);
+    return backupRecoveryOurs(wex, br.recovery);
   }
 }
 
@@ -873,9 +876,9 @@ export async function decryptBackup(
 }
 
 export async function provideBackupState(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
 ): Promise<WalletBackupConfState> {
-  const bs: ConfigRecord | undefined = await ws.db.runReadOnlyTx(
+  const bs: ConfigRecord | undefined = await wex.db.runReadOnlyTx(
     ["config"],
     async (tx) => {
       return await tx.config.get(ConfigRecordKey.WalletBackupState);
@@ -887,12 +890,12 @@ export async function provideBackupState(
   }
   // We need to generate the key outside of the transaction
   // due to how IndexedDB works.
-  const k = await ws.cryptoApi.createEddsaKeypair({});
+  const k = await wex.cryptoApi.createEddsaKeypair({});
   const d = getRandomBytes(5);
   // FIXME: device ID should be configured when wallet is initialized
   // and be based on hostname
   const deviceId = `wallet-core-${encodeCrock(d)}`;
-  return await ws.db.runReadWriteTx(["config"], async (tx) => {
+  return await wex.db.runReadWriteTx(["config"], async (tx) => {
     let backupStateEntry: ConfigRecord | undefined = await tx.config.get(
       ConfigRecordKey.WalletBackupState,
     );
@@ -926,11 +929,11 @@ export async function getWalletBackupState(
 }
 
 export async function setWalletDeviceId(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   deviceId: string,
 ): Promise<void> {
-  await provideBackupState(ws);
-  await ws.db.runReadWriteTx(["config"], async (tx) => {
+  await provideBackupState(wex);
+  await wex.db.runReadWriteTx(["config"], async (tx) => {
     let backupStateEntry: ConfigRecord | undefined = await tx.config.get(
       ConfigRecordKey.WalletBackupState,
     );
@@ -946,8 +949,8 @@ export async function setWalletDeviceId(
 }
 
 export async function getWalletDeviceId(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
 ): Promise<string> {
-  const bs = await provideBackupState(ws);
+  const bs = await provideBackupState(wex);
   return bs.deviceId;
 }
diff --git a/packages/taler-wallet-core/src/balance.ts 
b/packages/taler-wallet-core/src/balance.ts
index a287748f1..3b53699ac 100644
--- a/packages/taler-wallet-core/src/balance.ts
+++ b/packages/taler-wallet-core/src/balance.ts
@@ -80,7 +80,7 @@ import {
   getExchangeScopeInfo,
   getExchangeWireDetailsInTx,
 } from "./exchanges.js";
-import { InternalWalletState } from "./wallet.js";
+import { InternalWalletState, WalletExecutionContext } from "./wallet.js";
 
 /**
  * Logger.
@@ -131,7 +131,7 @@ class BalancesStore {
   private balanceStore: Record<string, WalletBalance> = {};
 
   constructor(
-    private ws: InternalWalletState,
+    private wex: WalletExecutionContext,
     private tx: WalletDbReadOnlyTransaction<
       [
         "globalCurrencyAuditors",
@@ -281,7 +281,7 @@ class BalancesStore {
  * Get balance information.
  */
 export async function getBalancesInsideTransaction(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   tx: WalletDbReadOnlyTransaction<
     [
       "exchanges",
@@ -295,7 +295,7 @@ export async function getBalancesInsideTransaction(
     ]
   >,
 ): Promise<BalancesResponse> {
-  const balanceStore: BalancesStore = new BalancesStore(ws, tx);
+  const balanceStore: BalancesStore = new BalancesStore(wex, tx);
 
   const keyRangeActive = GlobalIDB.KeyRange.bound(
     OPERATION_STATUS_ACTIVE_FIRST,
@@ -428,11 +428,11 @@ export async function getBalancesInsideTransaction(
  * Get detailed balance information, sliced by exchange and by currency.
  */
 export async function getBalances(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
 ): Promise<BalancesResponse> {
   logger.trace("starting to compute balance");
 
-  const wbal = await ws.db.runReadWriteTx(
+  const wbal = await wex.db.runReadWriteTx(
     [
       "coinAvailability",
       "coins",
@@ -446,7 +446,7 @@ export async function getBalances(
       "withdrawalGroups",
     ],
     async (tx) => {
-      return getBalancesInsideTransaction(ws, tx);
+      return getBalancesInsideTransaction(wex, tx);
     },
   );
 
@@ -488,12 +488,12 @@ export interface AcceptableExchanges {
  * Get all exchanges that are acceptable for a particular payment.
  */
 export async function getAcceptableExchangeBaseUrls(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: MerchantPaymentRestrictionsForBalance,
 ): Promise<AcceptableExchanges> {
   const acceptableExchangeUrls = new Set<string>();
   const depositableExchangeUrls = new Set<string>();
-  await ws.db.runReadOnlyTx(["exchanges", "exchangeDetails"], async (tx) => {
+  await wex.db.runReadOnlyTx(["exchanges", "exchangeDetails"], async (tx) => {
     // FIXME: We should have a DB index to look up all exchanges
     // for a particular auditor ...
 
@@ -600,10 +600,10 @@ export interface MerchantPaymentBalanceDetails {
 }
 
 export async function getMerchantPaymentBalanceDetails(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: MerchantPaymentRestrictionsForBalance,
 ): Promise<MerchantPaymentBalanceDetails> {
-  const acceptability = await getAcceptableExchangeBaseUrls(ws, req);
+  const acceptability = await getAcceptableExchangeBaseUrls(wex, req);
 
   const d: MerchantPaymentBalanceDetails = {
     balanceAvailable: Amounts.zeroOfCurrency(req.currency),
@@ -613,7 +613,7 @@ export async function getMerchantPaymentBalanceDetails(
     balanceMerchantDepositable: Amounts.zeroOfCurrency(req.currency),
   };
 
-  await ws.db.runReadOnlyTx(
+  await wex.db.runReadOnlyTx(
     ["coinAvailability", "refreshGroups"],
     async (tx) => {
       await tx.coinAvailability.iter().forEach((ca) => {
@@ -665,12 +665,12 @@ export async function getMerchantPaymentBalanceDetails(
 }
 
 export async function getBalanceDetail(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: GetBalanceDetailRequest,
 ): Promise<MerchantPaymentBalanceDetails> {
   const exchanges: { exchangeBaseUrl: string; exchangePub: string }[] = [];
   const wires = new Array<string>();
-  await ws.db.runReadOnlyTx(["exchanges", "exchangeDetails"], async (tx) => {
+  await wex.db.runReadOnlyTx(["exchanges", "exchangeDetails"], async (tx) => {
     const allExchanges = await tx.exchanges.iter().toArray();
     for (const e of allExchanges) {
       const details = await getExchangeWireDetailsInTx(tx, e.baseUrl);
@@ -690,7 +690,7 @@ export async function getBalanceDetail(
     }
   });
 
-  return await getMerchantPaymentBalanceDetails(ws, {
+  return await getMerchantPaymentBalanceDetails(wex, {
     currency: req.currency,
     acceptedAuditors: [],
     acceptedExchanges: exchanges,
@@ -717,7 +717,7 @@ export interface PeerPaymentBalanceDetails {
 }
 
 export async function getPeerPaymentBalanceDetailsInTx(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   tx: WalletDbReadOnlyTransaction<["coinAvailability", "refreshGroups"]>,
   req: PeerPaymentRestrictionsForBalance,
 ): Promise<PeerPaymentBalanceDetails> {
diff --git a/packages/taler-wallet-core/src/coinSelection.ts 
b/packages/taler-wallet-core/src/coinSelection.ts
index e1ae613bc..680e5faa1 100644
--- a/packages/taler-wallet-core/src/coinSelection.ts
+++ b/packages/taler-wallet-core/src/coinSelection.ts
@@ -64,7 +64,11 @@ import { getAutoRefreshExecuteThreshold } from "./common.js";
 import { DenominationRecord, WalletDbReadOnlyTransaction } from "./db.js";
 import { isWithdrawableDenom } from "./denominations.js";
 import { getExchangeWireDetailsInTx } from "./exchanges.js";
-import { getDenomInfo, InternalWalletState } from "./wallet.js";
+import {
+  getDenomInfo,
+  InternalWalletState,
+  WalletExecutionContext,
+} from "./wallet.js";
 
 const logger = new Logger("coinSelection.ts");
 
@@ -247,7 +251,7 @@ export type SelectPayCoinsResult =
  * This function is only exported for the sake of unit tests.
  */
 export async function selectPayCoinsNew(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: SelectPayCoinRequestNg,
 ): Promise<SelectPayCoinsResult> {
   const {
@@ -259,7 +263,7 @@ export async function selectPayCoinsNew(
 
   // FIXME: Why don't we do this in a transaction?
   const [candidateDenoms, wireFeesPerExchange] =
-    await selectPayMerchantCandidates(ws, req);
+    await selectPayMerchantCandidates(wex, req);
 
   const coinPubs: string[] = [];
   const coinContributions: AmountJson[] = [];
@@ -312,7 +316,7 @@ export async function selectPayCoinsNew(
   }
 
   if (!selectedDenom) {
-    const details = await getMerchantPaymentBalanceDetails(ws, {
+    const details = await getMerchantPaymentBalanceDetails(wex, {
       acceptedAuditors: req.auditors,
       acceptedExchanges: req.exchanges,
       acceptedWireMethods: [req.wireMethod],
@@ -357,7 +361,7 @@ export async function selectPayCoinsNew(
   logger.trace(`coin selection request ${j2s(req)}`);
   logger.trace(`selected coins (via denoms) for payment: ${j2s(finalSel)}`);
 
-  await ws.db.runReadOnlyTx(["coins", "denominations"], async (tx) => {
+  await wex.db.runReadOnlyTx(["coins", "denominations"], async (tx) => {
     for (const dph of Object.keys(finalSel)) {
       const selInfo = finalSel[dph];
       const numRequested = selInfo.contributions.length;
@@ -594,10 +598,10 @@ export type AvailableDenom = DenominationInfo & {
 };
 
 async function selectPayMerchantCandidates(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: SelectPayCoinRequestNg,
 ): Promise<[AvailableDenom[], Record<string, AmountJson>]> {
-  return await ws.db.runReadOnlyTx(
+  return await wex.db.runReadOnlyTx(
     ["exchanges", "exchangeDetails", "denominations", "coinAvailability"],
     async (tx) => {
       // FIXME: Use the existing helper (from balance.ts) to
@@ -906,7 +910,7 @@ export interface PeerCoinSelectionRequest {
  * Get coin availability information for a certain exchange.
  */
 async function selectPayPeerCandidatesForExchange(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   tx: WalletDbReadOnlyTransaction<["coinAvailability", "denominations"]>,
   exchangeBaseUrl: string,
 ): Promise<AvailableDenom[]> {
@@ -1035,7 +1039,7 @@ function greedySelectPeer(
 }
 
 export async function selectPeerCoins(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: PeerCoinSelectionRequest,
 ): Promise<SelectPeerCoinsResult> {
   const instructedAmount = req.instructedAmount;
@@ -1044,7 +1048,7 @@ export async function selectPeerCoins(
     // one coin to spend.
     throw new Error("amount of zero not allowed");
   }
-  return await ws.db.runReadWriteTx(
+  return await wex.db.runReadWriteTx(
     [
       "exchanges",
       "contractTerms",
@@ -1063,7 +1067,7 @@ export async function selectPeerCoins(
           continue;
         }
         const candidates = await selectPayPeerCandidatesForExchange(
-          ws,
+          wex,
           tx,
           exch.baseUrl,
         );
@@ -1089,7 +1093,7 @@ export async function selectPeerCoins(
               throw Error("repair not possible, coin not found");
             }
             const denom = await getDenomInfo(
-              ws,
+              wex,
               tx,
               coin.exchangeBaseUrl,
               coin.denomPubHash,
@@ -1188,7 +1192,7 @@ export async function selectPeerCoins(
       // We were unable to select coins.
       // Now we need to produce error details.
 
-      const infoGeneral = await getPeerPaymentBalanceDetailsInTx(ws, tx, {
+      const infoGeneral = await getPeerPaymentBalanceDetailsInTx(wex, tx, {
         currency,
       });
 
@@ -1200,7 +1204,7 @@ export async function selectPeerCoins(
         if (exch.detailsPointer?.currency !== currency) {
           continue;
         }
-        const infoExchange = await getPeerPaymentBalanceDetailsInTx(ws, tx, {
+        const infoExchange = await getPeerPaymentBalanceDetailsInTx(wex, tx, {
           currency,
           restrictExchangeTo: exch.baseUrl,
         });
diff --git a/packages/taler-wallet-core/src/common.ts 
b/packages/taler-wallet-core/src/common.ts
index 08adb2515..5acdeeba4 100644
--- a/packages/taler-wallet-core/src/common.ts
+++ b/packages/taler-wallet-core/src/common.ts
@@ -61,7 +61,11 @@ import {
   timestampPreciseToDb,
 } from "./db.js";
 import { createRefreshGroup } from "./refresh.js";
-import { InternalWalletState, getDenomInfo } from "./wallet.js";
+import {
+  InternalWalletState,
+  WalletExecutionContext,
+  getDenomInfo,
+} from "./wallet.js";
 
 const logger = new Logger("operations/common.ts");
 
@@ -76,7 +80,7 @@ export interface CoinsSpendInfo {
 }
 
 export async function makeCoinsVisible(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   tx: WalletDbReadWriteTransaction<["coins", "coinAvailability"]>,
   transactionId: string,
 ): Promise<void> {
@@ -104,7 +108,7 @@ export async function makeCoinsVisible(
 }
 
 export async function makeCoinAvailable(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   tx: WalletDbReadWriteTransaction<
     ["coins", "coinAvailability", "denominations"]
   >,
@@ -143,7 +147,7 @@ export async function makeCoinAvailable(
 }
 
 export async function spendCoins(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   tx: WalletDbReadWriteTransaction<
     ["coins", "coinAvailability", "refreshGroups", "denominations"]
   >,
@@ -162,7 +166,7 @@ export async function spendCoins(
       throw Error("coin allocated for payment doesn't exist anymore");
     }
     const denom = await getDenomInfo(
-      ws,
+      wex,
       tx,
       coin.exchangeBaseUrl,
       coin.denomPubHash,
@@ -224,7 +228,7 @@ export async function spendCoins(
   }
 
   await createRefreshGroup(
-    ws,
+    wex,
     tx,
     Amounts.currencyOf(csi.contributions[0]),
     refreshCoinPubs,
diff --git a/packages/taler-wallet-core/src/deposits.ts 
b/packages/taler-wallet-core/src/deposits.ts
index 9db8cfc27..3abb614bd 100644
--- a/packages/taler-wallet-core/src/deposits.ts
+++ b/packages/taler-wallet-core/src/deposits.ts
@@ -109,7 +109,11 @@ import {
   notifyTransition,
   parseTransactionIdentifier,
 } from "./transactions.js";
-import { InternalWalletState, getDenomInfo } from "./wallet.js";
+import {
+  InternalWalletState,
+  WalletExecutionContext,
+  getDenomInfo,
+} from "./wallet.js";
 import { getCandidateWithdrawalDenomsTx } from "./withdraw.js";
 
 /**
@@ -122,7 +126,7 @@ export class DepositTransactionContext implements 
TransactionContext {
   readonly taskId: TaskIdStr;
 
   constructor(
-    public ws: InternalWalletState,
+    public wex: WalletExecutionContext,
     public depositGroupId: string,
   ) {
     this.transactionId = constructTransactionIdentifier({
@@ -137,7 +141,7 @@ export class DepositTransactionContext implements 
TransactionContext {
 
   async deleteTransaction(): Promise<void> {
     const depositGroupId = this.depositGroupId;
-    const ws = this.ws;
+    const ws = this.wex;
     // FIXME: We should check first if we are in a final state
     // where deletion is allowed.
     await ws.db.runReadWriteTx(["depositGroups", "tombstones"], async (tx) => {
@@ -153,8 +157,8 @@ export class DepositTransactionContext implements 
TransactionContext {
   }
 
   async suspendTransaction(): Promise<void> {
-    const { ws, depositGroupId, transactionId, taskId: retryTag } = this;
-    const transitionInfo = await ws.db.runReadWriteTx(
+    const { wex, depositGroupId, transactionId, taskId: retryTag } = this;
+    const transitionInfo = await wex.db.runReadWriteTx(
       ["depositGroups"],
       async (tx) => {
         const dg = await tx.depositGroups.get(depositGroupId);
@@ -191,13 +195,13 @@ export class DepositTransactionContext implements 
TransactionContext {
         };
       },
     );
-    ws.taskScheduler.stopShepherdTask(retryTag);
-    notifyTransition(ws, transactionId, transitionInfo);
+    wex.taskScheduler.stopShepherdTask(retryTag);
+    notifyTransition(wex, transactionId, transitionInfo);
   }
 
   async abortTransaction(): Promise<void> {
-    const { ws, depositGroupId, transactionId, taskId: retryTag } = this;
-    const transitionInfo = await ws.db.runReadWriteTx(
+    const { wex, depositGroupId, transactionId, taskId: retryTag } = this;
+    const transitionInfo = await wex.db.runReadWriteTx(
       ["depositGroups"],
       async (tx) => {
         const dg = await tx.depositGroups.get(depositGroupId);
@@ -224,17 +228,17 @@ export class DepositTransactionContext implements 
TransactionContext {
         return undefined;
       },
     );
-    ws.taskScheduler.stopShepherdTask(retryTag);
-    notifyTransition(ws, transactionId, transitionInfo);
-    ws.taskScheduler.startShepherdTask(retryTag);
-    ws.notify({
+    wex.taskScheduler.stopShepherdTask(retryTag);
+    notifyTransition(wex, transactionId, transitionInfo);
+    wex.taskScheduler.startShepherdTask(retryTag);
+    wex.ws.notify({
       type: NotificationType.BalanceChange,
       hintTransactionId: transactionId,
     });
   }
 
   async resumeTransaction(): Promise<void> {
-    const { ws, depositGroupId, transactionId, taskId: retryTag } = this;
+    const { wex: ws, depositGroupId, transactionId, taskId: retryTag } = this;
     const transitionInfo = await ws.db.runReadWriteTx(
       ["depositGroups"],
       async (tx) => {
@@ -277,8 +281,8 @@ export class DepositTransactionContext implements 
TransactionContext {
   }
 
   async failTransaction(): Promise<void> {
-    const { ws, depositGroupId, transactionId, taskId: retryTag } = this;
-    const transitionInfo = await ws.db.runReadWriteTx(
+    const { wex, depositGroupId, transactionId, taskId } = this;
+    const transitionInfo = await wex.db.runReadWriteTx(
       ["depositGroups"],
       async (tx) => {
         const dg = await tx.depositGroups.get(depositGroupId);
@@ -303,9 +307,9 @@ export class DepositTransactionContext implements 
TransactionContext {
         return undefined;
       },
     );
-    ws.taskScheduler.stopShepherdTask(retryTag);
-    notifyTransition(ws, transactionId, transitionInfo);
-    ws.notify({
+    wex.taskScheduler.stopShepherdTask(taskId);
+    notifyTransition(wex, transactionId, transitionInfo);
+    wex.ws.notify({
       type: NotificationType.BalanceChange,
       hintTransactionId: transactionId,
     });
@@ -410,9 +414,8 @@ export function computeDepositTransactionActions(
 }
 
 async function refundDepositGroup(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   depositGroup: DepositGroupRecord,
-  cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
   const newTxPerCoin = [...depositGroup.statusPerCoin];
   logger.info(`status per coin: ${j2s(depositGroup.statusPerCoin)}`);
@@ -424,7 +427,7 @@ async function refundDepositGroup(
         break;
       default: {
         const coinPub = depositGroup.payCoinSelection.coinPubs[i];
-        const coinExchange = await ws.db.runReadOnlyTx(
+        const coinExchange = await wex.db.runReadOnlyTx(
           ["coins"],
           async (tx) => {
             const coinRecord = await tx.coins.get(coinPub);
@@ -436,7 +439,7 @@ async function refundDepositGroup(
         // We use a constant refund transaction ID, since there can
         // only be one refund.
         const rtid = 1;
-        const sig = await ws.cryptoApi.signRefund({
+        const sig = await wex.cryptoApi.signRefund({
           coinPub,
           contractTermsHash: depositGroup.contractTermsHash,
           merchantPriv: depositGroup.merchantPriv,
@@ -452,10 +455,10 @@ async function refundDepositGroup(
           rtransaction_id: rtid,
         };
         const refundUrl = new URL(`coins/${coinPub}/refund`, coinExchange);
-        const httpResp = await ws.http.fetch(refundUrl.href, {
+        const httpResp = await wex.http.fetch(refundUrl.href, {
           method: "POST",
           body: refundReq,
-          cancellationToken,
+          cancellationToken: wex.cancellationToken,
         });
         logger.info(
           `coin ${i} refund HTTP status for coin: ${httpResp.status}`,
@@ -486,7 +489,7 @@ async function refundDepositGroup(
 
   const currency = Amounts.currencyOf(depositGroup.totalPayCost);
 
-  const res = await ws.db.runReadWriteTx(
+  const res = await wex.db.runReadWriteTx(
     [
       "depositGroups",
       "refreshGroups",
@@ -510,7 +513,7 @@ async function refundDepositGroup(
       let refreshRes: CreateRefreshGroupResult | undefined = undefined;
       if (isDone) {
         refreshRes = await createRefreshGroup(
-          ws,
+          wex,
           tx,
           currency,
           refreshCoins,
@@ -529,7 +532,7 @@ async function refundDepositGroup(
 
   if (res?.refreshRes) {
     for (const notif of res.refreshRes.notifications) {
-      ws.notify(notif);
+      wex.ws.notify(notif);
     }
   }
 
@@ -550,7 +553,7 @@ async function refundDepositGroup(
  * transaction of processDepositGroup?
  */
 async function waitForRefreshOnDepositGroup(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   depositGroup: DepositGroupRecord,
 ): Promise<TaskRunResult> {
   const abortRefreshGroupId = depositGroup.abortRefreshGroupId;
@@ -559,7 +562,7 @@ async function waitForRefreshOnDepositGroup(
     tag: TransactionType.Deposit,
     depositGroupId: depositGroup.depositGroupId,
   });
-  const transitionInfo = await ws.db.runReadWriteTx(
+  const transitionInfo = await wex.db.runReadWriteTx(
     ["depositGroups", "refreshGroups"],
     async (tx) => {
       const refreshGroup = await tx.refreshGroups.get(abortRefreshGroupId);
@@ -593,8 +596,8 @@ async function waitForRefreshOnDepositGroup(
     },
   );
 
-  notifyTransition(ws, transactionId, transitionInfo);
-  ws.notify({
+  notifyTransition(wex, transactionId, transitionInfo);
+  wex.ws.notify({
     type: NotificationType.BalanceChange,
     hintTransactionId: transactionId,
   });
@@ -602,24 +605,22 @@ async function waitForRefreshOnDepositGroup(
 }
 
 async function processDepositGroupAborting(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   depositGroup: DepositGroupRecord,
-  cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
   logger.info("processing deposit tx in 'aborting'");
   const abortRefreshGroupId = depositGroup.abortRefreshGroupId;
   if (!abortRefreshGroupId) {
     logger.info("refunding deposit group");
-    return refundDepositGroup(ws, depositGroup, cancellationToken);
+    return refundDepositGroup(wex, depositGroup);
   }
   logger.info("waiting for refresh");
-  return waitForRefreshOnDepositGroup(ws, depositGroup);
+  return waitForRefreshOnDepositGroup(wex, depositGroup);
 }
 
 async function processDepositGroupPendingKyc(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   depositGroup: DepositGroupRecord,
-  cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
   const { depositGroupId } = depositGroup;
   const transactionId = constructTransactionIdentifier({
@@ -640,9 +641,9 @@ async function processDepositGroupPendingKyc(
   );
   url.searchParams.set("timeout_ms", "10000");
   logger.info(`kyc url ${url.href}`);
-  const kycStatusRes = await ws.http.fetch(url.href, {
+  const kycStatusRes = await wex.http.fetch(url.href, {
     method: "GET",
-    cancellationToken,
+    cancellationToken: wex.cancellationToken,
   });
   if (
     kycStatusRes.status === HttpStatusCode.Ok ||
@@ -650,7 +651,7 @@ async function processDepositGroupPendingKyc(
     // remove after the exchange is fixed or clarified
     kycStatusRes.status === HttpStatusCode.NoContent
   ) {
-    const transitionInfo = await ws.db.runReadWriteTx(
+    const transitionInfo = await wex.db.runReadWriteTx(
       ["depositGroups"],
       async (tx) => {
         const newDg = await tx.depositGroups.get(depositGroupId);
@@ -667,7 +668,7 @@ async function processDepositGroupPendingKyc(
         return { oldTxState, newTxState };
       },
     );
-    notifyTransition(ws, transactionId, transitionInfo);
+    notifyTransition(wex, transactionId, transitionInfo);
   } else if (kycStatusRes.status === HttpStatusCode.Accepted) {
     // FIXME: Do we have to update the URL here?
   } else {
@@ -682,7 +683,7 @@ async function processDepositGroupPendingKyc(
  * and transition the transaction to the KYC required state.
  */
 async function transitionToKycRequired(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   depositGroup: DepositGroupRecord,
   kycInfo: KycPendingInfo,
   exchangeUrl: string,
@@ -700,7 +701,7 @@ async function transitionToKycRequired(
     exchangeUrl,
   );
   logger.info(`kyc url ${url.href}`);
-  const kycStatusReq = await ws.http.fetch(url.href, {
+  const kycStatusReq = await wex.http.fetch(url.href, {
     method: "GET",
   });
   if (kycStatusReq.status === HttpStatusCode.Ok) {
@@ -709,7 +710,7 @@ async function transitionToKycRequired(
   } else if (kycStatusReq.status === HttpStatusCode.Accepted) {
     const kycStatus = await kycStatusReq.json();
     logger.info(`kyc status: ${j2s(kycStatus)}`);
-    const transitionInfo = await ws.db.runReadWriteTx(
+    const transitionInfo = await wex.db.runReadWriteTx(
       ["depositGroups"],
       async (tx) => {
         const dg = await tx.depositGroups.get(depositGroupId);
@@ -731,7 +732,7 @@ async function transitionToKycRequired(
         return { oldTxState, newTxState };
       },
     );
-    notifyTransition(ws, transactionId, transitionInfo);
+    notifyTransition(wex, transactionId, transitionInfo);
     return TaskRunResult.finished();
   } else {
     throw Error(`unexpected response from kyc-check (${kycStatusReq.status})`);
@@ -739,15 +740,14 @@ async function transitionToKycRequired(
 }
 
 async function processDepositGroupPendingTrack(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   depositGroup: DepositGroupRecord,
-  cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
   const { depositGroupId } = depositGroup;
   for (let i = 0; i < depositGroup.statusPerCoin.length; i++) {
     const coinPub = depositGroup.payCoinSelection.coinPubs[i];
     // FIXME: Make the URL part of the coin selection?
-    const exchangeBaseUrl = await ws.db.runReadWriteTx(
+    const exchangeBaseUrl = await wex.db.runReadWriteTx(
       ["coins"],
       async (tx) => {
         const coinRecord = await tx.coins.get(coinPub);
@@ -766,11 +766,10 @@ async function processDepositGroupPendingTrack(
 
     if (depositGroup.statusPerCoin[i] !== DepositElementStatus.Wired) {
       const track = await trackDeposit(
-        ws,
+        wex,
         depositGroup,
         coinPub,
         exchangeBaseUrl,
-        cancellationToken,
       );
 
       if (track.type === "accepted") {
@@ -784,7 +783,7 @@ async function processDepositGroupPendingTrack(
             requirementRow,
           };
           return transitionToKycRequired(
-            ws,
+            wex,
             depositGroup,
             kycInfo,
             exchangeBaseUrl,
@@ -801,7 +800,7 @@ async function processDepositGroupPendingTrack(
         }
 
         const fee = await getExchangeWireFee(
-          ws,
+          wex,
           payto.targetType,
           exchangeBaseUrl,
           track.execution_time,
@@ -825,7 +824,7 @@ async function processDepositGroupPendingTrack(
     }
 
     if (updatedTxStatus !== undefined) {
-      await ws.db.runReadWriteTx(["depositGroups"], async (tx) => {
+      await wex.db.runReadWriteTx(["depositGroups"], async (tx) => {
         const dg = await tx.depositGroups.get(depositGroupId);
         if (!dg) {
           return;
@@ -855,7 +854,7 @@ async function processDepositGroupPendingTrack(
 
   let allWired = true;
 
-  const transitionInfo = await ws.db.runReadWriteTx(
+  const transitionInfo = await wex.db.runReadWriteTx(
     ["depositGroups"],
     async (tx) => {
       const dg = await tx.depositGroups.get(depositGroupId);
@@ -884,9 +883,9 @@ async function processDepositGroupPendingTrack(
     tag: TransactionType.Deposit,
     depositGroupId,
   });
-  notifyTransition(ws, transactionId, transitionInfo);
+  notifyTransition(wex, transactionId, transitionInfo);
   if (allWired) {
-    ws.notify({
+    wex.ws.notify({
       type: NotificationType.BalanceChange,
       hintTransactionId: transactionId,
     });
@@ -899,13 +898,13 @@ async function processDepositGroupPendingTrack(
 }
 
 async function processDepositGroupPendingDeposit(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   depositGroup: DepositGroupRecord,
   cancellationToken?: CancellationToken,
 ): Promise<TaskRunResult> {
   logger.info("processing deposit group in pending(deposit)");
   const depositGroupId = depositGroup.depositGroupId;
-  const contractTermsRec = await ws.db.runReadOnlyTx(
+  const contractTermsRec = await wex.db.runReadOnlyTx(
     ["contractTerms"],
     async (tx) => {
       return tx.contractTerms.get(depositGroup.contractTermsHash);
@@ -932,7 +931,7 @@ async function processDepositGroupPendingDeposit(
 
   // FIXME: Cache these!
   const depositPermissions = await generateDepositPermissions(
-    ws,
+    wex,
     depositGroup.payCoinSelection,
     contractData,
   );
@@ -981,7 +980,7 @@ async function processDepositGroupPendingDeposit(
     const url = new URL(`batch-deposit`, exchangeUrl);
     logger.info(`depositing to ${url.href}`);
     logger.trace(`deposit request: ${j2s(batchReq)}`);
-    const httpResp = await ws.http.fetch(url.href, {
+    const httpResp = await wex.http.fetch(url.href, {
       method: "POST",
       body: batchReq,
       cancellationToken: cancellationToken,
@@ -991,7 +990,7 @@ async function processDepositGroupPendingDeposit(
       codecForBatchDepositSuccess(),
     );
 
-    await ws.db.runReadWriteTx(["depositGroups"], async (tx) => {
+    await wex.db.runReadWriteTx(["depositGroups"], async (tx) => {
       const dg = await tx.depositGroups.get(depositGroupId);
       if (!dg) {
         return;
@@ -1007,7 +1006,7 @@ async function processDepositGroupPendingDeposit(
     });
   }
 
-  const transitionInfo = await ws.db.runReadWriteTx(
+  const transitionInfo = await wex.db.runReadWriteTx(
     ["depositGroups"],
     async (tx) => {
       const dg = await tx.depositGroups.get(depositGroupId);
@@ -1022,7 +1021,7 @@ async function processDepositGroupPendingDeposit(
     },
   );
 
-  notifyTransition(ws, transactionId, transitionInfo);
+  notifyTransition(wex, transactionId, transitionInfo);
   return TaskRunResult.progress();
 }
 
@@ -1030,11 +1029,10 @@ async function processDepositGroupPendingDeposit(
  * Process a deposit group that is not in its final state yet.
  */
 export async function processDepositGroup(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   depositGroupId: string,
-  cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
-  const depositGroup = await ws.db.runReadOnlyTx(
+  const depositGroup = await wex.db.runReadOnlyTx(
     ["depositGroups"],
     async (tx) => {
       return tx.depositGroups.get(depositGroupId);
@@ -1047,21 +1045,13 @@ export async function processDepositGroup(
 
   switch (depositGroup.operationStatus) {
     case DepositOperationStatus.PendingTrack:
-      return processDepositGroupPendingTrack(
-        ws,
-        depositGroup,
-        cancellationToken,
-      );
+      return processDepositGroupPendingTrack(wex, depositGroup);
     case DepositOperationStatus.PendingKyc:
-      return processDepositGroupPendingKyc(ws, depositGroup, 
cancellationToken);
+      return processDepositGroupPendingKyc(wex, depositGroup);
     case DepositOperationStatus.PendingDeposit:
-      return processDepositGroupPendingDeposit(
-        ws,
-        depositGroup,
-        cancellationToken,
-      );
+      return processDepositGroupPendingDeposit(wex, depositGroup);
     case DepositOperationStatus.Aborting:
-      return processDepositGroupAborting(ws, depositGroup, cancellationToken);
+      return processDepositGroupAborting(wex, depositGroup);
   }
 
   return TaskRunResult.finished();
@@ -1071,12 +1061,12 @@ export async function processDepositGroup(
  * FIXME: Consider moving this to exchanges.ts.
  */
 async function getExchangeWireFee(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   wireType: string,
   baseUrl: string,
   time: TalerProtocolTimestamp,
 ): Promise<WireFee> {
-  const exchangeDetails = await ws.db.runReadOnlyTx(
+  const exchangeDetails = await wex.db.runReadOnlyTx(
     ["exchangeDetails", "exchanges"],
     async (tx) => {
       const ex = await tx.exchanges.get(baseUrl);
@@ -1116,11 +1106,10 @@ async function getExchangeWireFee(
 }
 
 async function trackDeposit(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   depositGroup: DepositGroupRecord,
   coinPub: string,
   exchangeUrl: string,
-  cancellationToken: CancellationToken,
 ): Promise<TrackTransaction> {
   const wireHash = hashWire(
     depositGroup.wire.payto_uri,
@@ -1131,7 +1120,7 @@ async function trackDeposit(
     
`deposits/${wireHash}/${depositGroup.merchantPub}/${depositGroup.contractTermsHash}/${coinPub}`,
     exchangeUrl,
   );
-  const sigResp = await ws.cryptoApi.signTrackTransaction({
+  const sigResp = await wex.cryptoApi.signTrackTransaction({
     coinPub,
     contractTermsHash: depositGroup.contractTermsHash,
     merchantPriv: depositGroup.merchantPriv,
@@ -1141,9 +1130,9 @@ async function trackDeposit(
   url.searchParams.set("merchant_sig", sigResp.sig);
   // Not doing long-polling yet as it looks like it's broken in the exchange 
(2024-02-20)
   // url.searchParams.set("timeout_ms", "30000");
-  const httpResp = await ws.http.fetch(url.href, {
+  const httpResp = await wex.http.fetch(url.href, {
     method: "GET",
-    cancellationToken,
+    cancellationToken: wex.cancellationToken,
   });
   logger.trace(`deposits response status: ${httpResp.status}`);
   switch (httpResp.status) {
@@ -1177,7 +1166,7 @@ async function trackDeposit(
  * as it doesn't prepare anything
  */
 export async function prepareDepositGroup(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: PrepareDepositRequest,
 ): Promise<PrepareDepositResponse> {
   const p = parsePaytoUri(req.depositPaytoUri);
@@ -1188,7 +1177,7 @@ export async function prepareDepositGroup(
 
   const exchangeInfos: { url: string; master_pub: string }[] = [];
 
-  await ws.db.runReadOnlyTx(["exchangeDetails", "exchanges"], async (tx) => {
+  await wex.db.runReadOnlyTx(["exchangeDetails", "exchanges"], async (tx) => {
     const allExchanges = await tx.exchanges.iter().toArray();
     for (const e of allExchanges) {
       const details = await getExchangeWireDetailsInTx(tx, e.baseUrl);
@@ -1227,7 +1216,7 @@ export async function prepareDepositGroup(
     refund_deadline: TalerProtocolTimestamp.zero(),
   };
 
-  const { h: contractTermsHash } = await ws.cryptoApi.hashString({
+  const { h: contractTermsHash } = await wex.cryptoApi.hashString({
     str: canonicalJson(contractTerms),
   });
 
@@ -1237,7 +1226,7 @@ export async function prepareDepositGroup(
     "",
   );
 
-  const payCoinSel = await selectPayCoinsNew(ws, {
+  const payCoinSel = await selectPayCoinsNew(wex, {
     auditors: [],
     exchanges: contractData.allowedExchanges,
     wireMethod: contractData.wireMethod,
@@ -1257,16 +1246,16 @@ export async function prepareDepositGroup(
     );
   }
 
-  const totalDepositCost = await getTotalPaymentCost(ws, payCoinSel.coinSel);
+  const totalDepositCost = await getTotalPaymentCost(wex, payCoinSel.coinSel);
 
   const effectiveDepositAmount = await getCounterpartyEffectiveDepositAmount(
-    ws,
+    wex,
     p.targetType,
     payCoinSel.coinSel,
   );
 
   const fees = await getTotalFeesForDepositAmount(
-    ws,
+    wex,
     p.targetType,
     amount,
     payCoinSel.coinSel,
@@ -1288,7 +1277,7 @@ export function generateDepositGroupTxId(): string {
 }
 
 export async function createDepositGroup(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: CreateDepositGroupRequest,
 ): Promise<CreateDepositGroupResponse> {
   const p = parsePaytoUri(req.depositPaytoUri);
@@ -1300,7 +1289,7 @@ export async function createDepositGroup(
 
   const exchangeInfos: { url: string; master_pub: string }[] = [];
 
-  await ws.db.runReadOnlyTx(["exchanges", "exchangeDetails"], async (tx) => {
+  await wex.db.runReadOnlyTx(["exchanges", "exchangeDetails"], async (tx) => {
     const allExchanges = await tx.exchanges.iter().toArray();
     for (const e of allExchanges) {
       const details = await getExchangeWireDetailsInTx(tx, e.baseUrl);
@@ -1319,8 +1308,8 @@ export async function createDepositGroup(
     AbsoluteTime.addDuration(now, Duration.fromSpec({ minutes: 5 })),
   );
   const nowRounded = AbsoluteTime.toProtocolTimestamp(now);
-  const noncePair = await ws.cryptoApi.createEddsaKeypair({});
-  const merchantPair = await ws.cryptoApi.createEddsaKeypair({});
+  const noncePair = await wex.cryptoApi.createEddsaKeypair({});
+  const merchantPair = await wex.cryptoApi.createEddsaKeypair({});
   const wireSalt = encodeCrock(getRandomBytes(16));
   const wireHash = hashWire(req.depositPaytoUri, wireSalt);
   const contractTerms: MerchantContractTerms = {
@@ -1346,7 +1335,7 @@ export async function createDepositGroup(
     refund_deadline: TalerProtocolTimestamp.zero(),
   };
 
-  const { h: contractTermsHash } = await ws.cryptoApi.hashString({
+  const { h: contractTermsHash } = await wex.cryptoApi.hashString({
     str: canonicalJson(contractTerms),
   });
 
@@ -1356,7 +1345,7 @@ export async function createDepositGroup(
     "",
   );
 
-  const payCoinSel = await selectPayCoinsNew(ws, {
+  const payCoinSel = await selectPayCoinsNew(wex, {
     auditors: [],
     exchanges: contractData.allowedExchanges,
     wireMethod: contractData.wireMethod,
@@ -1376,7 +1365,7 @@ export async function createDepositGroup(
     );
   }
 
-  const totalDepositCost = await getTotalPaymentCost(ws, payCoinSel.coinSel);
+  const totalDepositCost = await getTotalPaymentCost(wex, payCoinSel.coinSel);
 
   let depositGroupId: string;
   if (req.transactionId) {
@@ -1391,7 +1380,7 @@ export async function createDepositGroup(
 
   const infoPerExchange: Record<string, DepositInfoPerExchange> = {};
 
-  await ws.db.runReadOnlyTx(["coins"], async (tx) => {
+  await wex.db.runReadOnlyTx(["coins"], async (tx) => {
     for (let i = 0; i < payCoinSel.coinSel.coinPubs.length; i++) {
       const coin = await tx.coins.get(payCoinSel.coinSel.coinPubs[i]);
       if (!coin) {
@@ -1415,7 +1404,7 @@ export async function createDepositGroup(
 
   const counterpartyEffectiveDepositAmount =
     await getCounterpartyEffectiveDepositAmount(
-      ws,
+      wex,
       p.targetType,
       payCoinSel.coinSel,
     );
@@ -1453,10 +1442,10 @@ export async function createDepositGroup(
     infoPerExchange,
   };
 
-  const ctx = new DepositTransactionContext(ws, depositGroupId);
+  const ctx = new DepositTransactionContext(wex, depositGroupId);
   const transactionId = ctx.transactionId;
 
-  const newTxState = await ws.db.runReadWriteTx(
+  const newTxState = await wex.db.runReadWriteTx(
     [
       "depositGroups",
       "coins",
@@ -1467,7 +1456,7 @@ export async function createDepositGroup(
       "contractTerms",
     ],
     async (tx) => {
-      await spendCoins(ws, tx, {
+      await spendCoins(wex, tx, {
         allocationId: transactionId,
         coinPubs: payCoinSel.coinSel.coinPubs,
         contributions: payCoinSel.coinSel.coinContributions.map((x) =>
@@ -1484,7 +1473,7 @@ export async function createDepositGroup(
     },
   );
 
-  ws.notify({
+  wex.ws.notify({
     type: NotificationType.TransactionStateTransition,
     transactionId,
     oldTxState: {
@@ -1493,12 +1482,12 @@ export async function createDepositGroup(
     newTxState,
   });
 
-  ws.notify({
+  wex.ws.notify({
     type: NotificationType.BalanceChange,
     hintTransactionId: transactionId,
   });
 
-  ws.taskScheduler.startShepherdTask(ctx.taskId);
+  wex.taskScheduler.startShepherdTask(ctx.taskId);
 
   return {
     depositGroupId,
@@ -1511,7 +1500,7 @@ export async function createDepositGroup(
  * account after depositing, not considering aggregation.
  */
 export async function getCounterpartyEffectiveDepositAmount(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   wireType: string,
   pcs: PayCoinSelection,
 ): Promise<AmountJson> {
@@ -1519,7 +1508,7 @@ export async function 
getCounterpartyEffectiveDepositAmount(
   const fees: AmountJson[] = [];
   const exchangeSet: Set<string> = new Set();
 
-  await ws.db.runReadOnlyTx(
+  await wex.db.runReadOnlyTx(
     ["coins", "denominations", "exchangeDetails", "exchanges"],
     async (tx) => {
       for (let i = 0; i < pcs.coinPubs.length; i++) {
@@ -1528,7 +1517,7 @@ export async function 
getCounterpartyEffectiveDepositAmount(
           throw Error("can't calculate deposit amount, coin not found");
         }
         const denom = await getDenomInfo(
-          ws,
+          wex,
           tx,
           coin.exchangeBaseUrl,
           coin.denomPubHash,
@@ -1574,7 +1563,7 @@ export async function 
getCounterpartyEffectiveDepositAmount(
  * specified amount using the selected coins and the wire method.
  */
 async function getTotalFeesForDepositAmount(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   wireType: string,
   total: AmountJson,
   pcs: PayCoinSelection,
@@ -1585,7 +1574,7 @@ async function getTotalFeesForDepositAmount(
   const exchangeSet: Set<string> = new Set();
   const currency = Amounts.currencyOf(total);
 
-  await ws.db.runReadOnlyTx(
+  await wex.db.runReadOnlyTx(
     ["coins", "denominations", "exchanges", "exchangeDetails"],
     async (tx) => {
       for (let i = 0; i < pcs.coinPubs.length; i++) {
@@ -1594,7 +1583,7 @@ async function getTotalFeesForDepositAmount(
           throw Error("can't calculate deposit amount, coin not found");
         }
         const denom = await getDenomInfo(
-          ws,
+          wex,
           tx,
           coin.exchangeBaseUrl,
           coin.denomPubHash,
@@ -1606,7 +1595,7 @@ async function getTotalFeesForDepositAmount(
         exchangeSet.add(coin.exchangeBaseUrl);
 
         const allDenoms = await getCandidateWithdrawalDenomsTx(
-          ws,
+          wex,
           tx,
           coin.exchangeBaseUrl,
           currency,
@@ -1619,7 +1608,7 @@ async function getTotalFeesForDepositAmount(
           allDenoms,
           denom,
           amountLeft,
-          ws.config.testing.denomselAllowLate,
+          wex.ws.config.testing.denomselAllowLate,
         );
         refreshFee.push(refreshCost);
       }
diff --git a/packages/taler-wallet-core/src/dev-experiments.ts 
b/packages/taler-wallet-core/src/dev-experiments.ts
index b48aa716b..c94571ff8 100644
--- a/packages/taler-wallet-core/src/dev-experiments.ts
+++ b/packages/taler-wallet-core/src/dev-experiments.ts
@@ -43,7 +43,7 @@ import {
   RefreshOperationStatus,
   timestampPreciseToDb,
 } from "./db.js";
-import { InternalWalletState } from "./wallet.js";
+import { WalletExecutionContext } from "./wallet.js";
 
 const logger = new Logger("dev-experiments.ts");
 
@@ -51,7 +51,7 @@ const logger = new Logger("dev-experiments.ts");
  * Apply a dev experiment to the wallet database / state.
  */
 export async function applyDevExperiment(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   uri: string,
 ): Promise<void> {
   logger.info(`applying dev experiment ${uri}`);
@@ -60,14 +60,14 @@ export async function applyDevExperiment(
     logger.info("unable to parse dev experiment URI");
     return;
   }
-  if (!ws.config.testing.devModeActive) {
+  if (!wex.ws.config.testing.devModeActive) {
     throw Error(
       "can't handle devmode URI (other than enable-devmode) unless devmode is 
active",
     );
   }
 
   if (parsedUri.devExperimentId == "insert-pending-refresh") {
-    await ws.db.runReadWriteTx(["refreshGroups"], async (tx) => {
+    await wex.db.runReadWriteTx(["refreshGroups"], async (tx) => {
       const refreshGroupId = encodeCrock(getRandomBytes(32));
       const newRg: RefreshGroupRecord = {
         currency: "TESTKUDOS",
diff --git a/packages/taler-wallet-core/src/exchanges.ts 
b/packages/taler-wallet-core/src/exchanges.ts
index 932df721d..a26b3f5ca 100644
--- a/packages/taler-wallet-core/src/exchanges.ts
+++ b/packages/taler-wallet-core/src/exchanges.ts
@@ -128,7 +128,7 @@ import { DbReadOnlyTransaction } from "./query.js";
 import { createRecoupGroup } from "./recoup.js";
 import { createRefreshGroup } from "./refresh.js";
 import { WALLET_EXCHANGE_PROTOCOL_VERSION } from "./versions.js";
-import { InternalWalletState } from "./wallet.js";
+import { InternalWalletState, WalletExecutionContext } from "./wallet.js";
 
 const logger = new Logger("exchanges.ts");
 
@@ -147,10 +147,10 @@ interface ExchangeTosDownloadResult {
 }
 
 async function downloadExchangeWithTermsOfService(
+  wex: WalletExecutionContext,
   exchangeBaseUrl: string,
   http: HttpRequestLibrary,
   timeout: Duration,
-  cancellationToken: CancellationToken,
   acceptFormat: string,
   acceptLanguage: string | undefined,
 ): Promise<ExchangeTosDownloadResult> {
@@ -170,7 +170,7 @@ async function downloadExchangeWithTermsOfService(
   const resp = await http.fetch(reqUrl.href, {
     headers,
     timeout,
-    cancellationToken,
+    cancellationToken: wex.cancellationToken,
   });
   const tosText = await readSuccessResponseTextOrThrow(resp);
   const tosEtag = resp.headers.get("etag") || "unknown";
@@ -344,10 +344,10 @@ export async function getExchangeWireDetailsInTx(
 }
 
 export async function lookupExchangeByUri(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: GetExchangeEntryByUrlRequest,
 ): Promise<ExchangeListItem> {
-  return await ws.db.runReadOnlyTx(
+  return await wex.db.runReadOnlyTx(
     [
       "exchanges",
       "exchangeDetails",
@@ -381,10 +381,10 @@ export async function lookupExchangeByUri(
  * Mark the current ToS version as accepted by the user.
  */
 export async function acceptExchangeTermsOfService(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   exchangeBaseUrl: string,
 ): Promise<void> {
-  const notif = await ws.db.runReadWriteTx(
+  const notif = await wex.db.runReadWriteTx(
     ["exchangeDetails", "exchanges"],
     async (tx) => {
       const exch = await tx.exchanges.get(exchangeBaseUrl);
@@ -407,7 +407,7 @@ export async function acceptExchangeTermsOfService(
     },
   );
   if (notif) {
-    ws.notify(notif);
+    wex.ws.notify(notif);
   }
 }
 
@@ -449,7 +449,7 @@ export async function forgetExchangeTermsOfService(
  * Throw an exception if they are invalid.
  */
 async function validateWireInfo(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   versionCurrent: number,
   wireInfo: ExchangeKeysDownloadResult,
   masterPublicKey: string,
@@ -457,10 +457,10 @@ async function validateWireInfo(
   for (const a of wireInfo.accounts) {
     logger.trace("validating exchange acct");
     let isValid = false;
-    if (ws.config.testing.insecureTrustExchange) {
+    if (wex.ws.config.testing.insecureTrustExchange) {
       isValid = true;
     } else {
-      const { valid: v } = await ws.cryptoApi.isValidWireAccount({
+      const { valid: v } = await wex.ws.cryptoApi.isValidWireAccount({
         masterPub: masterPublicKey,
         paytoUri: a.payto_uri,
         sig: a.master_sig,
@@ -490,10 +490,10 @@ async function validateWireInfo(
         wireFee: Amounts.stringify(x.wire_fee),
       };
       let isValid = false;
-      if (ws.config.testing.insecureTrustExchange) {
+      if (wex.ws.config.testing.insecureTrustExchange) {
         isValid = true;
       } else {
-        const { valid: v } = await ws.cryptoApi.isValidWireFee({
+        const { valid: v } = await wex.ws.cryptoApi.isValidWireFee({
           masterPub: masterPublicKey,
           type: wireMethod,
           wf: fee,
@@ -520,7 +520,7 @@ async function validateWireInfo(
  * Throw an exception if they are invalid.
  */
 async function validateGlobalFees(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   fees: GlobalFees[],
   masterPub: string,
 ): Promise<ExchangeGlobalFees[]> {
@@ -528,10 +528,10 @@ async function validateGlobalFees(
   for (const gf of fees) {
     logger.trace("validating exchange global fees");
     let isValid = false;
-    if (ws.config.testing.insecureTrustExchange) {
+    if (wex.ws.config.testing.insecureTrustExchange) {
       isValid = true;
     } else {
-      const { valid: v } = await ws.cryptoApi.isValidGlobalFees({
+      const { valid: v } = await wex.cryptoApi.isValidGlobalFees({
         masterPub,
         gf,
       });
@@ -832,10 +832,9 @@ async function downloadExchangeKeysInfo(
 }
 
 async function downloadTosFromAcceptedFormat(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   baseUrl: string,
   timeout: Duration,
-  cancellationToken: CancellationToken,
   acceptedFormat?: string[],
   acceptLanguage?: string,
 ): Promise<ExchangeTosDownloadResult> {
@@ -844,10 +843,10 @@ async function downloadTosFromAcceptedFormat(
   if (acceptedFormat)
     for (const format of acceptedFormat) {
       const resp = await downloadExchangeWithTermsOfService(
+        wex,
         baseUrl,
-        ws.http,
+        wex.http,
         timeout,
-        cancellationToken,
         format,
         acceptLanguage,
       );
@@ -861,10 +860,10 @@ async function downloadTosFromAcceptedFormat(
   }
   // If none of the specified format was found try text/plain
   return await downloadExchangeWithTermsOfService(
+    wex,
     baseUrl,
-    ws.http,
+    wex.http,
     timeout,
-    cancellationToken,
     "text/plain",
     acceptLanguage,
   );
@@ -880,7 +879,7 @@ async function downloadTosFromAcceptedFormat(
  * a new ephemeral entry is created.
  */
 async function startUpdateExchangeEntry(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   exchangeBaseUrl: string,
   options: { forceUpdate?: boolean } = {},
 ): Promise<void> {
@@ -892,19 +891,19 @@ async function startUpdateExchangeEntry(
     }`,
   );
 
-  const { notification } = await ws.db.runReadWriteTx(
+  const { notification } = await wex.db.runReadWriteTx(
     ["exchanges", "exchangeDetails"],
     async (tx) => {
-      return provideExchangeRecordInTx(ws, tx, exchangeBaseUrl);
+      return provideExchangeRecordInTx(wex.ws, tx, exchangeBaseUrl);
     },
   );
 
   if (notification) {
-    ws.notify(notification);
+    wex.ws.notify(notification);
   }
 
   const { oldExchangeState, newExchangeState, taskId } =
-    await ws.db.runReadWriteTx(
+    await wex.db.runReadWriteTx(
       ["exchanges", "operationRetries"],
       async (tx) => {
         const r = await tx.exchanges.get(canonBaseUrl);
@@ -952,13 +951,13 @@ async function startUpdateExchangeEntry(
         return { oldExchangeState, newExchangeState, taskId };
       },
     );
-  ws.notify({
+  wex.ws.notify({
     type: NotificationType.ExchangeStateTransition,
     exchangeBaseUrl: canonBaseUrl,
     newExchangeState: newExchangeState,
     oldExchangeState: oldExchangeState,
   });
-  await ws.taskScheduler.resetTaskRetries(taskId);
+  await wex.ws.taskScheduler.resetTaskRetries(taskId);
 }
 
 /**
@@ -978,7 +977,7 @@ export interface ReadyExchangeSummary {
 }
 
 async function internalWaitReadyExchange(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   canonUrl: string,
   exchangeNotifFlag: AsyncFlag,
   options: {
@@ -994,7 +993,7 @@ async function internalWaitReadyExchange(
   while (true) {
     logger.info(`waiting for ready exchange ${canonUrl}`);
     const { exchange, exchangeDetails, retryInfo, scopeInfo } =
-      await ws.db.runReadOnlyTx(
+      await wex.db.runReadOnlyTx(
         [
           "exchanges",
           "exchangeDetails",
@@ -1105,7 +1104,7 @@ async function internalWaitReadyExchange(
  * will still have been added as an ephemeral exchange entry.
  */
 export async function fetchFreshExchange(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   baseUrl: string,
   options: {
     cancellationToken?: CancellationToken;
@@ -1115,17 +1114,17 @@ export async function fetchFreshExchange(
 ): Promise<ReadyExchangeSummary> {
   const canonUrl = canonicalizeBaseUrl(baseUrl);
 
-  ws.taskScheduler.ensureRunning();
+  wex.ws.taskScheduler.ensureRunning();
 
-  await startUpdateExchangeEntry(ws, canonUrl, {
+  await startUpdateExchangeEntry(wex, canonUrl, {
     forceUpdate: options.forceUpdate,
   });
 
-  return waitReadyExchange(ws, canonUrl, options);
+  return waitReadyExchange(wex, canonUrl, options);
 }
 
 async function waitReadyExchange(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   canonUrl: string,
   options: {
     cancellationToken?: CancellationToken;
@@ -1138,7 +1137,7 @@ async function waitReadyExchange(
   const exchangeNotifFlag = new AsyncFlag();
   // Raise exchangeNotifFlag whenever we get a notification
   // about our exchange.
-  const cancelNotif = ws.addNotificationListener((notif) => {
+  const cancelNotif = wex.ws.addNotificationListener((notif) => {
     if (
       notif.type === NotificationType.ExchangeStateTransition &&
       notif.exchangeBaseUrl === canonUrl
@@ -1150,7 +1149,7 @@ async function waitReadyExchange(
 
   try {
     const res = await internalWaitReadyExchange(
-      ws,
+      wex,
       canonUrl,
       exchangeNotifFlag,
       options,
@@ -1169,14 +1168,13 @@ async function waitReadyExchange(
  * exchange entry in then DB.
  */
 export async function updateExchangeFromUrlHandler(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   exchangeBaseUrl: string,
-  cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
   logger.trace(`updating exchange info for ${exchangeBaseUrl}`);
   exchangeBaseUrl = canonicalizeBaseUrl(exchangeBaseUrl);
 
-  const oldExchangeRec = await ws.db.runReadOnlyTx(
+  const oldExchangeRec = await wex.db.runReadOnlyTx(
     ["exchanges"],
     async (tx) => {
       return tx.exchanges.get(exchangeBaseUrl);
@@ -1264,9 +1262,9 @@ export async function updateExchangeFromUrlHandler(
 
   const keysInfo = await downloadExchangeKeysInfo(
     exchangeBaseUrl,
-    ws.http,
+    wex.http,
     timeout,
-    cancellationToken,
+    wex.cancellationToken,
     oldExchangeRec.cachebreakNextUpdate ?? false,
   );
 
@@ -1279,14 +1277,14 @@ export async function updateExchangeFromUrlHandler(
   }
 
   const wireInfo = await validateWireInfo(
-    ws,
+    wex,
     version.current,
     keysInfo,
     keysInfo.masterPublicKey,
   );
 
   const globalFees = await validateGlobalFees(
-    ws,
+    wex,
     keysInfo.globalFees,
     keysInfo.masterPublicKey,
   );
@@ -1311,10 +1309,9 @@ export async function updateExchangeFromUrlHandler(
   // because that one needs to exist, and we
   // will get the current etag from the response.
   const tosDownload = await downloadTosFromAcceptedFormat(
-    ws,
+    wex,
     exchangeBaseUrl,
     timeout,
-    cancellationToken,
     ["text/plain"],
   );
 
@@ -1327,7 +1324,7 @@ export async function updateExchangeFromUrlHandler(
   let ageMask = 0;
   for (const x of keysInfo.currentDenominations) {
     if (
-      isWithdrawableDenom(x, ws.config.testing.denomselAllowLate) &&
+      isWithdrawableDenom(x, wex.ws.config.testing.denomselAllowLate) &&
       x.denomPub.age_mask != 0
     ) {
       ageMask = x.denomPub.age_mask;
@@ -1335,7 +1332,7 @@ export async function updateExchangeFromUrlHandler(
     }
   }
 
-  const updated = await ws.db.runReadWriteTx(
+  const updated = await wex.db.runReadWriteTx(
     [
       "exchanges",
       "exchangeDetails",
@@ -1492,7 +1489,7 @@ export async function updateExchangeFromUrlHandler(
       if (newlyRevokedCoinPubs.length != 0) {
         logger.info("recouping coins", newlyRevokedCoinPubs);
         recoupGroupId = await createRecoupGroup(
-          ws,
+          wex,
           tx,
           exchangeBaseUrl,
           newlyRevokedCoinPubs,
@@ -1517,7 +1514,7 @@ export async function updateExchangeFromUrlHandler(
     });
     // Asynchronously start recoup.  This doesn't need to finish
     // for the exchange update to be considered finished.
-    ws.taskScheduler.startShepherdTask(recoupTaskId);
+    wex.ws.taskScheduler.startShepherdTask(recoupTaskId);
   }
 
   if (!updated) {
@@ -1535,7 +1532,7 @@ export async function updateExchangeFromUrlHandler(
 
   if (refreshCheckNecessary) {
     // Do auto-refresh.
-    await ws.db.runReadWriteTx(
+    await wex.db.runReadWriteTx(
       [
         "coins",
         "denominations",
@@ -1581,7 +1578,7 @@ export async function updateExchangeFromUrlHandler(
         }
         if (refreshCoins.length > 0) {
           const res = await createRefreshGroup(
-            ws,
+            wex,
             tx,
             exchange.detailsPointer?.currency,
             refreshCoins,
@@ -1605,7 +1602,7 @@ export async function updateExchangeFromUrlHandler(
     );
   }
 
-  ws.notify({
+  wex.ws.notify({
     type: NotificationType.ExchangeStateTransition,
     exchangeBaseUrl,
     newExchangeState: updated.newExchangeState,
@@ -1648,13 +1645,13 @@ function getAutoRefreshCheckThreshold(d: 
DenominationRecord): AbsoluteTime {
  * Throws if no matching account was found.
  */
 export async function getExchangePaytoUri(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   exchangeBaseUrl: string,
   supportedTargetTypes: string[],
 ): Promise<string> {
   // We do the update here, since the exchange might not even exist
   // yet in our database.
-  const details = await ws.db.runReadOnlyTx(
+  const details = await wex.db.runReadOnlyTx(
     ["exchanges", "exchangeDetails"],
     async (tx) => {
       return getExchangeRecordsInternal(tx, exchangeBaseUrl);
@@ -1682,23 +1679,22 @@ export async function getExchangePaytoUri(
  * Try to download in the accepted format not cached.
  */
 export async function getExchangeTos(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   exchangeBaseUrl: string,
   acceptedFormat?: string[],
   acceptLanguage?: string,
 ): Promise<GetExchangeTosResult> {
-  const exch = await fetchFreshExchange(ws, exchangeBaseUrl);
+  const exch = await fetchFreshExchange(wex, exchangeBaseUrl);
 
   const tosDownload = await downloadTosFromAcceptedFormat(
-    ws,
+    wex,
     exchangeBaseUrl,
     getExchangeRequestTimeout(),
-    CancellationToken.CONTINUE,
     acceptedFormat,
     acceptLanguage,
   );
 
-  await ws.db.runReadWriteTx(["exchanges"], async (tx) => {
+  await wex.db.runReadWriteTx(["exchanges"], async (tx) => {
     const updateExchangeEntry = await tx.exchanges.get(exchangeBaseUrl);
     if (updateExchangeEntry) {
       updateExchangeEntry.tosCurrentEtag = tosDownload.tosEtag;
@@ -1750,10 +1746,10 @@ export async function downloadExchangeInfo(
  * List all exchange entries known to the wallet.
  */
 export async function listExchanges(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
 ): Promise<ExchangesListResponse> {
   const exchanges: ExchangeListItem[] = [];
-  await ws.db.runReadOnlyTx(
+  await wex.db.runReadOnlyTx(
     [
       "exchanges",
       "operationRetries",
@@ -1793,7 +1789,7 @@ export async function listExchanges(
  * succeeded.
  */
 export async function markExchangeUsed(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   tx: WalletDbReadWriteTransaction<["exchanges"]>,
   exchangeBaseUrl: string,
 ): Promise<{ notif: WalletNotification | undefined }> {
@@ -1833,10 +1829,10 @@ export async function markExchangeUsed(
  * for the fees charged by the exchange.
  */
 export async function getExchangeDetailedInfo(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   exchangeBaseurl: string,
 ): Promise<ExchangeDetailedResponse> {
-  const exchange = await ws.db.runReadOnlyTx(
+  const exchange = await wex.db.runReadOnlyTx(
     ["exchanges", "exchangeDetails", "denominations"],
     async (tx) => {
       const ex = await tx.exchanges.get(exchangeBaseurl);
@@ -1986,7 +1982,7 @@ export async function getExchangeDetailedInfo(
 }
 
 async function internalGetExchangeResources(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   tx: DbReadOnlyTransaction<
     typeof WalletStoresV1,
     ["exchanges", "coins", "withdrawalGroups"]
@@ -2005,12 +2001,12 @@ async function internalGetExchangeResources(
 }
 
 export async function deleteExchange(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: DeleteExchangeRequest,
 ): Promise<void> {
   let inUse: boolean = false;
   const exchangeBaseUrl = canonicalizeBaseUrl(req.exchangeBaseUrl);
-  await ws.db.runReadWriteTx(
+  await wex.db.runReadWriteTx(
     ["exchanges", "coins", "withdrawalGroups", "exchangeDetails"],
     async (tx) => {
       const exchangeRec = await tx.exchanges.get(exchangeBaseUrl);
@@ -2019,7 +2015,7 @@ export async function deleteExchange(
         logger.info("no exchange found to delete");
         return;
       }
-      const res = await internalGetExchangeResources(ws, tx, exchangeBaseUrl);
+      const res = await internalGetExchangeResources(wex, tx, exchangeBaseUrl);
       if (res.hasResources) {
         if (req.purge) {
           const detRecs =
@@ -2050,18 +2046,18 @@ export async function deleteExchange(
 }
 
 export async function getExchangeResources(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   exchangeBaseUrl: string,
 ): Promise<GetExchangeResourcesResponse> {
   // Withdrawals include internal withdrawals from peer transactions
-  const res = await ws.db.runReadOnlyTx(
+  const res = await wex.db.runReadOnlyTx(
     ["exchanges", "withdrawalGroups", "coins"],
     async (tx) => {
       const exchangeRecord = await tx.exchanges.get(exchangeBaseUrl);
       if (!exchangeRecord) {
         return undefined;
       }
-      return internalGetExchangeResources(ws, tx, exchangeBaseUrl);
+      return internalGetExchangeResources(wex, tx, exchangeBaseUrl);
     },
   );
   if (!res) {
diff --git a/packages/taler-wallet-core/src/instructedAmountConversion.ts 
b/packages/taler-wallet-core/src/instructedAmountConversion.ts
index 2250188b7..ccad050bf 100644
--- a/packages/taler-wallet-core/src/instructedAmountConversion.ts
+++ b/packages/taler-wallet-core/src/instructedAmountConversion.ts
@@ -34,7 +34,7 @@ import {
 import { CoinInfo } from "./coinSelection.js";
 import { DenominationRecord, timestampProtocolFromDb } from "./db.js";
 import { getExchangeWireDetailsInTx } from "./exchanges.js";
-import { InternalWalletState } from "./wallet.js";
+import { InternalWalletState, WalletExecutionContext } from "./wallet.js";
 
 /**
  * If the operation going to be plan subtracts
@@ -129,14 +129,14 @@ interface AvailableCoins {
  * of being cached
  */
 async function getAvailableDenoms(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   op: TransactionType,
   currency: string,
   filters: CoinsFilter = {},
 ): Promise<AvailableCoins> {
   const operationType = getOperationType(TransactionType.Deposit);
 
-  return await ws.db.runReadOnlyTx(
+  return await wex.db.runReadOnlyTx(
     ["exchanges", "exchangeDetails", "denominations", "coinAvailability"],
     async (tx) => {
       const list: CoinInfo[] = [];
@@ -328,14 +328,14 @@ function buildCoinInfoFromDenom(
 }
 
 export async function convertDepositAmount(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: ConvertAmountRequest,
 ): Promise<AmountResponse> {
   const amount = Amounts.parseOrThrow(req.amount);
   // const filter = getCoinsFilter(req);
 
   const denoms = await getAvailableDenoms(
-    ws,
+    wex,
     TransactionType.Deposit,
     amount.currency,
     {},
@@ -430,13 +430,13 @@ export function convertDepositAmountForAvailableCoins(
 }
 
 export async function getMaxDepositAmount(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: GetAmountRequest,
 ): Promise<AmountResponse> {
   // const filter = getCoinsFilter(req);
 
   const denoms = await getAvailableDenoms(
-    ws,
+    wex,
     TransactionType.Deposit,
     req.currency,
     {},
@@ -473,25 +473,27 @@ export function getMaxDepositAmountForAvailableCoins(
 }
 
 export async function convertPeerPushAmount(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: ConvertAmountRequest,
 ): Promise<AmountResponse> {
   throw Error("to be implemented after 1.0");
 }
+
 export async function getMaxPeerPushAmount(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: GetAmountRequest,
 ): Promise<AmountResponse> {
   throw Error("to be implemented after 1.0");
 }
+
 export async function convertWithdrawalAmount(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: ConvertAmountRequest,
 ): Promise<AmountResponse> {
   const amount = Amounts.parseOrThrow(req.amount);
 
   const denoms = await getAvailableDenoms(
-    ws,
+    wex,
     TransactionType.Withdrawal,
     amount.currency,
     {},
diff --git a/packages/taler-wallet-core/src/pay-merchant.ts 
b/packages/taler-wallet-core/src/pay-merchant.ts
index 73dc59ba2..a576930ba 100644
--- a/packages/taler-wallet-core/src/pay-merchant.ts
+++ b/packages/taler-wallet-core/src/pay-merchant.ts
@@ -33,7 +33,6 @@ import {
   AmountString,
   assertUnreachable,
   AsyncFlag,
-  CancellationToken,
   checkDbInvariant,
   codecForAbortResponse,
   codecForMerchantContractTerms,
@@ -142,6 +141,7 @@ import {
   EXCHANGE_COINS_LOCK,
   getDenomInfo,
   InternalWalletState,
+  WalletExecutionContext,
 } from "./wallet.js";
 import { getCandidateWithdrawalDenomsTx } from "./withdraw.js";
 
@@ -155,7 +155,7 @@ export class PayMerchantTransactionContext implements 
TransactionContext {
   readonly taskId: TaskIdStr;
 
   constructor(
-    public ws: InternalWalletState,
+    public wex: WalletExecutionContext,
     public proposalId: string,
   ) {
     this.transactionId = constructTransactionIdentifier({
@@ -198,7 +198,7 @@ export class PayMerchantTransactionContext implements 
TransactionContext {
       >,
     ) => Promise<TransitionResult>,
   ): Promise<void> {
-    const ws = this.ws;
+    const ws = this.wex;
     const extraStores = opts.extraStores ?? [];
     const transitionInfo = await ws.db.runReadWriteTx(
       ["purchases", ...extraStores],
@@ -227,7 +227,7 @@ export class PayMerchantTransactionContext implements 
TransactionContext {
   }
 
   async deleteTransaction(): Promise<void> {
-    const { ws, proposalId } = this;
+    const { wex: ws, proposalId } = this;
     await ws.db.runReadWriteTx(["purchases", "tombstones"], async (tx) => {
       let found = false;
       const purchase = await tx.purchases.get(proposalId);
@@ -244,7 +244,7 @@ export class PayMerchantTransactionContext implements 
TransactionContext {
   }
 
   async suspendTransaction(): Promise<void> {
-    const { ws, proposalId, transactionId } = this;
+    const { wex: ws, proposalId, transactionId } = this;
     ws.taskScheduler.stopShepherdTask(this.taskId);
     const transitionInfo = await ws.db.runReadWriteTx(
       ["purchases"],
@@ -267,8 +267,8 @@ export class PayMerchantTransactionContext implements 
TransactionContext {
   }
 
   async abortTransaction(): Promise<void> {
-    const { ws, proposalId, transactionId } = this;
-    const transitionInfo = await ws.db.runReadWriteTx(
+    const { wex, proposalId, transactionId } = this;
+    const transitionInfo = await wex.db.runReadWriteTx(
       [
         "purchases",
         "refreshGroups",
@@ -308,7 +308,7 @@ export class PayMerchantTransactionContext implements 
TransactionContext {
                 });
               }
               await createRefreshGroup(
-                ws,
+                wex,
                 tx,
                 currency,
                 refreshCoins,
@@ -328,13 +328,13 @@ export class PayMerchantTransactionContext implements 
TransactionContext {
         return { oldTxState, newTxState };
       },
     );
-    ws.taskScheduler.stopShepherdTask(this.taskId);
-    notifyTransition(ws, transactionId, transitionInfo);
-    ws.taskScheduler.startShepherdTask(this.taskId);
+    wex.taskScheduler.stopShepherdTask(this.taskId);
+    notifyTransition(wex, transactionId, transitionInfo);
+    wex.taskScheduler.startShepherdTask(this.taskId);
   }
 
   async resumeTransaction(): Promise<void> {
-    const { ws, proposalId, transactionId, taskId: retryTag } = this;
+    const { wex: ws, proposalId, transactionId, taskId: retryTag } = this;
     const transitionInfo = await ws.db.runReadWriteTx(
       ["purchases"],
       async (tx) => {
@@ -357,7 +357,7 @@ export class PayMerchantTransactionContext implements 
TransactionContext {
   }
 
   async failTransaction(): Promise<void> {
-    const { ws, proposalId, transactionId } = this;
+    const { wex: ws, proposalId, transactionId } = this;
     const transitionInfo = await ws.db.runReadWriteTx(
       [
         "purchases",
@@ -396,7 +396,7 @@ export class RefundTransactionContext implements 
TransactionContext {
   public transactionId: TransactionIdStr;
   public taskId: TaskIdStr | undefined = undefined;
   constructor(
-    public ws: InternalWalletState,
+    public wex: WalletExecutionContext,
     public refundGroupId: string,
   ) {
     this.transactionId = constructTransactionIdentifier({
@@ -406,8 +406,8 @@ export class RefundTransactionContext implements 
TransactionContext {
   }
 
   async deleteTransaction(): Promise<void> {
-    const { ws, refundGroupId, transactionId } = this;
-    await ws.db.runReadWriteTx(["refundGroups", "tombstones"], async (tx) => {
+    const { wex, refundGroupId, transactionId } = this;
+    await wex.db.runReadWriteTx(["refundGroups", "tombstones"], async (tx) => {
       const refundRecord = await tx.refundGroups.get(refundGroupId);
       if (!refundRecord) {
         return;
@@ -443,11 +443,11 @@ export class RefundTransactionContext implements 
TransactionContext {
  * of coins that are too small to spend.
  */
 export async function getTotalPaymentCost(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   pcs: PayCoinSelection,
 ): Promise<AmountJson> {
   const currency = Amounts.currencyOf(pcs.paymentAmount);
-  return ws.db.runReadOnlyTx(["coins", "denominations"], async (tx) => {
+  return wex.db.runReadOnlyTx(["coins", "denominations"], async (tx) => {
     const costs: AmountJson[] = [];
     for (let i = 0; i < pcs.coinPubs.length; i++) {
       const coin = await tx.coins.get(pcs.coinPubs[i]);
@@ -464,7 +464,7 @@ export async function getTotalPaymentCost(
         );
       }
       const allDenoms = await getCandidateWithdrawalDenomsTx(
-        ws,
+        wex,
         tx,
         coin.exchangeBaseUrl,
         currency,
@@ -477,7 +477,7 @@ export async function getTotalPaymentCost(
         allDenoms,
         DenominationRecord.toDenomInfo(denom),
         amountLeft,
-        ws.config.testing.denomselAllowLate,
+        wex.ws.config.testing.denomselAllowLate,
       );
       costs.push(Amounts.parseOrThrow(pcs.coinContributions[i]));
       costs.push(refreshCost);
@@ -488,7 +488,7 @@ export async function getTotalPaymentCost(
 }
 
 async function failProposalPermanently(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   proposalId: string,
   err: TalerErrorDetail,
 ): Promise<void> {
@@ -496,7 +496,7 @@ async function failProposalPermanently(
     tag: TransactionType.Payment,
     proposalId,
   });
-  const transitionInfo = await ws.db.runReadWriteTx(
+  const transitionInfo = await wex.db.runReadWriteTx(
     ["purchases"],
     async (tx) => {
       const p = await tx.purchases.get(proposalId);
@@ -511,7 +511,7 @@ async function failProposalPermanently(
       return { oldTxState, newTxState };
     },
   );
-  notifyTransition(ws, transactionId, transitionInfo);
+  notifyTransition(wex, transactionId, transitionInfo);
 }
 
 function getPayRequestTimeout(purchase: PurchaseRecord): Duration {
@@ -525,7 +525,7 @@ function getPayRequestTimeout(purchase: PurchaseRecord): 
Duration {
  * Return the proposal download data for a purchase, throw if not available.
  */
 export async function expectProposalDownload(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   p: PurchaseRecord,
   parentTx?: WalletDbReadOnlyTransaction<["contractTerms"]>,
 ): Promise<{
@@ -559,7 +559,7 @@ export async function expectProposalDownload(
   if (parentTx) {
     return getFromTransaction(parentTx);
   }
-  return await ws.db.runReadOnlyTx(["contractTerms"], getFromTransaction);
+  return await wex.db.runReadOnlyTx(["contractTerms"], getFromTransaction);
 }
 
 export function extractContractData(
@@ -603,11 +603,10 @@ export function extractContractData(
 }
 
 async function processDownloadProposal(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   proposalId: string,
-  cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
-  const proposal = await ws.db.runReadOnlyTx(["purchases"], async (tx) => {
+  const proposal = await wex.db.runReadOnlyTx(["purchases"], async (tx) => {
     return await tx.purchases.get(proposalId);
   });
 
@@ -615,7 +614,7 @@ async function processDownloadProposal(
     return TaskRunResult.finished();
   }
 
-  const ctx = new PayMerchantTransactionContext(ws, proposalId);
+  const ctx = new PayMerchantTransactionContext(wex, proposalId);
 
   if (proposal.purchaseStatus != PurchaseStatus.PendingDownloadingProposal) {
     logger.error(
@@ -644,10 +643,10 @@ async function processDownloadProposal(
     requestBody.token = proposal.claimToken;
   }
 
-  const httpResponse = await ws.http.fetch(orderClaimUrl, {
+  const httpResponse = await wex.http.fetch(orderClaimUrl, {
     method: "POST",
     body: requestBody,
-    cancellationToken,
+    cancellationToken: wex.cancellationToken,
   });
   const r = await readSuccessResponseJsonOrErrorCode(
     httpResponse,
@@ -692,7 +691,7 @@ async function processDownloadProposal(
       {},
       "validation for well-formedness failed",
     );
-    await failProposalPermanently(ws, proposalId, err);
+    await failProposalPermanently(wex, proposalId, err);
     throw makePendingOperationFailedError(
       err,
       TransactionType.Payment,
@@ -718,7 +717,7 @@ async function processDownloadProposal(
       {},
       `schema validation failed: ${e}`,
     );
-    await failProposalPermanently(ws, proposalId, err);
+    await failProposalPermanently(wex, proposalId, err);
     throw makePendingOperationFailedError(
       err,
       TransactionType.Payment,
@@ -726,7 +725,7 @@ async function processDownloadProposal(
     );
   }
 
-  const sigValid = await ws.cryptoApi.isValidContractTermsSignature({
+  const sigValid = await wex.cryptoApi.isValidContractTermsSignature({
     contractTermsHash,
     merchantPub: parsedContractTerms.merchant_pub,
     sig: proposalResp.sig,
@@ -741,7 +740,7 @@ async function processDownloadProposal(
       },
       "merchant's signature on contract terms is invalid",
     );
-    await failProposalPermanently(ws, proposalId, err);
+    await failProposalPermanently(wex, proposalId, err);
     throw makePendingOperationFailedError(
       err,
       TransactionType.Payment,
@@ -763,7 +762,7 @@ async function processDownloadProposal(
       },
       "merchant base URL mismatch",
     );
-    await failProposalPermanently(ws, proposalId, err);
+    await failProposalPermanently(wex, proposalId, err);
     throw makePendingOperationFailedError(
       err,
       TransactionType.Payment,
@@ -779,7 +778,7 @@ async function processDownloadProposal(
 
   logger.trace(`extracted contract data: ${j2s(contractData)}`);
 
-  const transitionInfo = await ws.db.runReadWriteTx(
+  const transitionInfo = await wex.db.runReadWriteTx(
     ["purchases", "contractTerms"],
     async (tx) => {
       const p = await tx.purchases.get(proposalId);
@@ -835,7 +834,7 @@ async function processDownloadProposal(
     },
   );
 
-  notifyTransition(ws, transactionId, transitionInfo);
+  notifyTransition(wex, transactionId, transitionInfo);
 
   return TaskRunResult.progress();
 }
@@ -846,15 +845,14 @@ async function processDownloadProposal(
  * return the old proposal ID.
  */
 async function createOrReusePurchase(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   merchantBaseUrl: string,
   orderId: string,
   sessionId: string | undefined,
   claimToken: string | undefined,
   noncePriv: string | undefined,
-  cancellationToken: CancellationToken,
 ): Promise<string> {
-  const oldProposals = await ws.db.runReadOnlyTx(["purchases"], async (tx) => {
+  const oldProposals = await wex.db.runReadOnlyTx(["purchases"], async (tx) => 
{
     return tx.purchases.indexes.byUrlAndOrderId.getAll([
       merchantBaseUrl,
       orderId,
@@ -882,17 +880,13 @@ async function createOrReusePurchase(
       }) for order ${orderId} at ${merchantBaseUrl}`,
     );
     if (oldProposal.purchaseStatus === PurchaseStatus.DialogShared) {
-      const download = await expectProposalDownload(ws, oldProposal);
-      const paid = await checkIfOrderIsAlreadyPaid(
-        ws,
-        download.contractData,
-        cancellationToken,
-      );
+      const download = await expectProposalDownload(wex, oldProposal);
+      const paid = await checkIfOrderIsAlreadyPaid(wex, download.contractData);
       logger.info(`old proposal paid: ${paid}`);
       if (paid) {
         // if this transaction was shared and the order is paid then it
         // means that another wallet already paid the proposal
-        const transitionInfo = await ws.db.runReadWriteTx(
+        const transitionInfo = await wex.db.runReadWriteTx(
           ["purchases"],
           async (tx) => {
             const p = await tx.purchases.get(oldProposal.proposalId);
@@ -912,7 +906,7 @@ async function createOrReusePurchase(
           tag: TransactionType.Payment,
           proposalId: oldProposal.proposalId,
         });
-        notifyTransition(ws, transactionId, transitionInfo);
+        notifyTransition(wex, transactionId, transitionInfo);
       }
     }
     return oldProposal.proposalId;
@@ -924,10 +918,10 @@ async function createOrReusePurchase(
     shared = true;
     noncePair = {
       priv: noncePriv,
-      pub: (await ws.cryptoApi.eddsaGetPublic({ priv: noncePriv })).pub,
+      pub: (await wex.cryptoApi.eddsaGetPublic({ priv: noncePriv })).pub,
     };
   } else {
-    noncePair = await ws.cryptoApi.createEddsaKeypair({});
+    noncePair = await wex.cryptoApi.createEddsaKeypair({});
   }
 
   const { priv, pub } = noncePair;
@@ -958,7 +952,7 @@ async function createOrReusePurchase(
     shared: shared,
   };
 
-  const transitionInfo = await ws.db.runReadWriteTx(
+  const transitionInfo = await wex.db.runReadWriteTx(
     ["purchases"],
     async (tx) => {
       await tx.purchases.put(proposalRecord);
@@ -977,12 +971,12 @@ async function createOrReusePurchase(
     tag: TransactionType.Payment,
     proposalId,
   });
-  notifyTransition(ws, transactionId, transitionInfo);
+  notifyTransition(wex, transactionId, transitionInfo);
   return proposalId;
 }
 
 async function storeFirstPaySuccess(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   proposalId: string,
   sessionId: string | undefined,
   payResponse: MerchantPayResponse,
@@ -992,7 +986,7 @@ async function storeFirstPaySuccess(
     proposalId,
   });
   const now = AbsoluteTime.toPreciseTimestamp(AbsoluteTime.now());
-  const transitionInfo = await ws.db.runReadWriteTx(
+  const transitionInfo = await wex.db.runReadWriteTx(
     ["contractTerms", "purchases"],
     async (tx) => {
       const purchase = await tx.purchases.get(proposalId);
@@ -1044,11 +1038,11 @@ async function storeFirstPaySuccess(
       };
     },
   );
-  notifyTransition(ws, transactionId, transitionInfo);
+  notifyTransition(wex, transactionId, transitionInfo);
 }
 
 async function storePayReplaySuccess(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   proposalId: string,
   sessionId: string | undefined,
 ): Promise<void> {
@@ -1056,7 +1050,7 @@ async function storePayReplaySuccess(
     tag: TransactionType.Payment,
     proposalId,
   });
-  const transitionInfo = await ws.db.runReadWriteTx(
+  const transitionInfo = await wex.db.runReadWriteTx(
     ["purchases"],
     async (tx) => {
       const purchase = await tx.purchases.get(proposalId);
@@ -1082,7 +1076,7 @@ async function storePayReplaySuccess(
       return { oldTxState, newTxState };
     },
   );
-  notifyTransition(ws, transactionId, transitionInfo);
+  notifyTransition(wex, transactionId, transitionInfo);
 }
 
 /**
@@ -1094,13 +1088,13 @@ async function storePayReplaySuccess(
  * (3) re-do coin selection with the bad coin removed
  */
 async function handleInsufficientFunds(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   proposalId: string,
   err: TalerErrorDetail,
 ): Promise<void> {
   logger.trace("handling insufficient funds, trying to re-select coins");
 
-  const proposal = await ws.db.runReadOnlyTx(["purchases"], async (tx) => {
+  const proposal = await wex.db.runReadOnlyTx(["purchases"], async (tx) => {
     return tx.purchases.get(proposalId);
   });
   if (!proposal) {
@@ -1128,7 +1122,7 @@ async function handleInsufficientFunds(
     throw new TalerProtocolViolationError();
   }
 
-  const { contractData } = await expectProposalDownload(ws, proposal);
+  const { contractData } = await expectProposalDownload(wex, proposal);
 
   const prevPayCoins: PreviousPayCoins = [];
 
@@ -1139,7 +1133,7 @@ async function handleInsufficientFunds(
 
   const payCoinSelection = payInfo.payCoinSelection;
 
-  await ws.db.runReadOnlyTx(["coins", "denominations"], async (tx) => {
+  await wex.db.runReadOnlyTx(["coins", "denominations"], async (tx) => {
     for (let i = 0; i < payCoinSelection.coinPubs.length; i++) {
       const coinPub = payCoinSelection.coinPubs[i];
       if (coinPub === brokenCoinPub) {
@@ -1166,7 +1160,7 @@ async function handleInsufficientFunds(
     }
   });
 
-  const res = await selectPayCoinsNew(ws, {
+  const res = await selectPayCoinsNew(wex, {
     auditors: [],
     exchanges: contractData.allowedExchanges,
     wireMethod: contractData.wireMethod,
@@ -1185,7 +1179,7 @@ async function handleInsufficientFunds(
 
   logger.trace("re-selected coins");
 
-  await ws.db.runReadWriteTx(
+  await wex.db.runReadWriteTx(
     [
       "purchases",
       "coins",
@@ -1205,7 +1199,7 @@ async function handleInsufficientFunds(
       payInfo.payCoinSelection = res.coinSel;
       payInfo.payCoinSelectionUid = encodeCrock(getRandomBytes(32));
       await tx.purchases.put(p);
-      await spendCoins(ws, tx, {
+      await spendCoins(wex, tx, {
         // allocationId: `txn:proposal:${p.proposalId}`,
         allocationId: constructTransactionIdentifier({
           tag: TransactionType.Payment,
@@ -1220,7 +1214,7 @@ async function handleInsufficientFunds(
     },
   );
 
-  ws.notify({
+  wex.ws.notify({
     type: NotificationType.BalanceChange,
     hintTransactionId: constructTransactionIdentifier({
       tag: TransactionType.Payment,
@@ -1233,11 +1227,11 @@ async function handleInsufficientFunds(
 // FIXME: Does way more than checking the payment
 // FIXME: Should return immediately.
 async function checkPaymentByProposalId(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   proposalId: string,
   sessionId?: string,
 ): Promise<PreparePayResult> {
-  let proposal = await ws.db.runReadOnlyTx(["purchases"], async (tx) => {
+  let proposal = await wex.db.runReadOnlyTx(["purchases"], async (tx) => {
     return tx.purchases.get(proposalId);
   });
   if (!proposal) {
@@ -1247,7 +1241,7 @@ async function checkPaymentByProposalId(
     const existingProposalId = proposal.repurchaseProposalId;
     if (existingProposalId) {
       logger.trace("using existing purchase for same product");
-      const oldProposal = await ws.db.runReadOnlyTx(
+      const oldProposal = await wex.db.runReadOnlyTx(
         ["purchases"],
         async (tx) => {
           return tx.purchases.get(existingProposalId);
@@ -1258,7 +1252,7 @@ async function checkPaymentByProposalId(
       }
     }
   }
-  const d = await expectProposalDownload(ws, proposal);
+  const d = await expectProposalDownload(wex, proposal);
   const contractData = d.contractData;
   const merchantSig = d.contractData.merchantSig;
   if (!merchantSig) {
@@ -1267,7 +1261,7 @@ async function checkPaymentByProposalId(
 
   proposalId = proposal.proposalId;
 
-  const ctx = new PayMerchantTransactionContext(ws, proposalId);
+  const ctx = new PayMerchantTransactionContext(wex, proposalId);
 
   const transactionId = ctx.transactionId;
 
@@ -1280,7 +1274,7 @@ async function checkPaymentByProposalId(
   });
 
   // First check if we already paid for it.
-  const purchase = await ws.db.runReadOnlyTx(["purchases"], async (tx) => {
+  const purchase = await wex.db.runReadOnlyTx(["purchases"], async (tx) => {
     return tx.purchases.get(proposalId);
   });
 
@@ -1290,7 +1284,7 @@ async function checkPaymentByProposalId(
     purchase.purchaseStatus === PurchaseStatus.DialogShared
   ) {
     // If not already paid, check if we could pay for it.
-    const res = await selectPayCoinsNew(ws, {
+    const res = await selectPayCoinsNew(wex, {
       auditors: [],
       exchanges: contractData.allowedExchanges,
       contractTermsAmount: Amounts.parseOrThrow(contractData.amount),
@@ -1318,7 +1312,7 @@ async function checkPaymentByProposalId(
       };
     }
 
-    const totalCost = await getTotalPaymentCost(ws, res.coinSel);
+    const totalCost = await getTotalPaymentCost(wex, res.coinSel);
     logger.trace("costInfo", totalCost);
     logger.trace("coinsForPayment", res);
 
@@ -1342,7 +1336,7 @@ async function checkPaymentByProposalId(
       "automatically re-submitting payment with different session ID",
     );
     logger.trace(`last: ${purchase.lastSessionId}, current: ${sessionId}`);
-    const transitionInfo = await ws.db.runReadWriteTx(
+    const transitionInfo = await wex.db.runReadWriteTx(
       ["purchases"],
       async (tx) => {
         const p = await tx.purchases.get(proposalId);
@@ -1357,14 +1351,14 @@ async function checkPaymentByProposalId(
         return { oldTxState, newTxState };
       },
     );
-    notifyTransition(ws, transactionId, transitionInfo);
-    ws.taskScheduler.startShepherdTask(ctx.taskId);
+    notifyTransition(wex, transactionId, transitionInfo);
+    wex.taskScheduler.startShepherdTask(ctx.taskId);
 
     // FIXME: Consider changing the API here so that we don't have to
     // wait inline for the repurchase.
 
-    await waitPaymentResult(ws, proposalId, sessionId);
-    const download = await expectProposalDownload(ws, purchase);
+    await waitPaymentResult(wex, proposalId, sessionId);
+    const download = await expectProposalDownload(wex, purchase);
     return {
       status: PreparePayResultType.AlreadyConfirmed,
       contractTerms: download.contractTermsRaw,
@@ -1379,7 +1373,7 @@ async function checkPaymentByProposalId(
       talerUri,
     };
   } else if (!purchase.timestampFirstSuccessfulPay) {
-    const download = await expectProposalDownload(ws, purchase);
+    const download = await expectProposalDownload(wex, purchase);
     return {
       status: PreparePayResultType.AlreadyConfirmed,
       contractTerms: download.contractTermsRaw,
@@ -1398,7 +1392,7 @@ async function checkPaymentByProposalId(
       purchase.purchaseStatus === PurchaseStatus.Done ||
       purchase.purchaseStatus === PurchaseStatus.PendingQueryingRefund ||
       purchase.purchaseStatus === PurchaseStatus.PendingQueryingAutoRefund;
-    const download = await expectProposalDownload(ws, purchase);
+    const download = await expectProposalDownload(wex, purchase);
     return {
       status: PreparePayResultType.AlreadyConfirmed,
       contractTerms: download.contractTermsRaw,
@@ -1417,10 +1411,10 @@ async function checkPaymentByProposalId(
 }
 
 export async function getContractTermsDetails(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   proposalId: string,
 ): Promise<WalletContractData> {
-  const proposal = await ws.db.runReadOnlyTx(["purchases"], async (tx) => {
+  const proposal = await wex.db.runReadOnlyTx(["purchases"], async (tx) => {
     return tx.purchases.get(proposalId);
   });
 
@@ -1428,7 +1422,7 @@ export async function getContractTermsDetails(
     throw Error(`proposal with id ${proposalId} not found`);
   }
 
-  const d = await expectProposalDownload(ws, proposal);
+  const d = await expectProposalDownload(wex, proposal);
 
   return d.contractData;
 }
@@ -1440,7 +1434,7 @@ export async function getContractTermsDetails(
  * yet send to the merchant.
  */
 export async function preparePayForUri(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   talerPayUri: string,
 ): Promise<PreparePayResult> {
   const uriResult = parsePayUri(talerPayUri);
@@ -1456,39 +1450,38 @@ export async function preparePayForUri(
   }
 
   const proposalId = await createOrReusePurchase(
-    ws,
+    wex,
     uriResult.merchantBaseUrl,
     uriResult.orderId,
     uriResult.sessionId,
     uriResult.claimToken,
     uriResult.noncePriv,
-    CancellationToken.CONTINUE,
   );
 
-  await waitProposalDownloaded(ws, proposalId);
+  await waitProposalDownloaded(wex, proposalId);
 
-  return checkPaymentByProposalId(ws, proposalId, uriResult.sessionId);
+  return checkPaymentByProposalId(wex, proposalId, uriResult.sessionId);
 }
 
 /**
  * Wait until a proposal is at least downloaded.
  */
 async function waitProposalDownloaded(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   proposalId: string,
 ): Promise<void> {
-  const ctx = new PayMerchantTransactionContext(ws, proposalId);
+  const ctx = new PayMerchantTransactionContext(wex, proposalId);
 
   logger.info(`waiting for ${ctx.transactionId} to be downloaded`);
 
-  ws.taskScheduler.startShepherdTask(ctx.taskId);
+  wex.taskScheduler.startShepherdTask(ctx.taskId);
 
   // FIXME: We should use Symbol.dispose magic here for cleanup!
 
   const payNotifFlag = new AsyncFlag();
   // Raise exchangeNotifFlag whenever we get a notification
   // about our exchange.
-  const cancelNotif = ws.addNotificationListener((notif) => {
+  const cancelNotif = wex.ws.addNotificationListener((notif) => {
     if (
       notif.type === NotificationType.TransactionStateTransition &&
       notif.transactionId === ctx.transactionId
@@ -1511,7 +1504,7 @@ async function internalWaitProposalDownloaded(
   payNotifFlag: AsyncFlag,
 ): Promise<void> {
   while (true) {
-    const { purchase, retryInfo } = await ctx.ws.db.runReadOnlyTx(
+    const { purchase, retryInfo } = await ctx.wex.db.runReadOnlyTx(
       ["purchases", "operationRetries"],
       async (tx) => {
         return {
@@ -1539,7 +1532,7 @@ async function internalWaitProposalDownloaded(
 }
 
 export async function preparePayForTemplate(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: PreparePayTemplateRequest,
 ): Promise<PreparePayResult> {
   const parsedUri = parsePayTemplateUri(req.talerPayTemplateUri);
@@ -1575,7 +1568,7 @@ export async function preparePayForTemplate(
     `templates/${parsedUri.templateId}`,
     parsedUri.merchantBaseUrl,
   );
-  const httpReq = await ws.http.fetch(reqUrl.href, {
+  const httpReq = await wex.http.fetch(reqUrl.href, {
     method: "POST",
     body: templateDetails,
   });
@@ -1591,7 +1584,7 @@ export async function preparePayForTemplate(
     claimToken: resp.token,
   });
 
-  return await preparePayForUri(ws, payUri);
+  return await preparePayForUri(wex, payUri);
 }
 
 /**
@@ -1600,7 +1593,7 @@ export async function preparePayForTemplate(
  * Accesses the database and the crypto worker.
  */
 export async function generateDepositPermissions(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   payCoinSel: PayCoinSelection,
   contractData: WalletContractData,
 ): Promise<CoinDepositPermission[]> {
@@ -1609,7 +1602,7 @@ export async function generateDepositPermissions(
     coin: CoinRecord;
     denom: DenominationRecord;
   }> = [];
-  await ws.db.runReadOnlyTx(["coins", "denominations"], async (tx) => {
+  await wex.db.runReadOnlyTx(["coins", "denominations"], async (tx) => {
     for (let i = 0; i < payCoinSel.coinPubs.length; i++) {
       const coin = await tx.coins.get(payCoinSel.coinPubs[i]);
       if (!coin) {
@@ -1637,7 +1630,7 @@ export async function generateDepositPermissions(
         coin.ageCommitmentProof,
       )}`,
     );
-    const dp = await ws.cryptoApi.signDepositPermission({
+    const dp = await wex.cryptoApi.signDepositPermission({
       coinPriv: coin.coinPriv,
       coinPub: coin.coinPub,
       contractTermsHash: contractData.contractTermsHash,
@@ -1665,7 +1658,7 @@ async function internalWaitPaymentResult(
   waitSessionId?: string,
 ): Promise<ConfirmPayResult> {
   while (true) {
-    const txRes = await ctx.ws.db.runReadOnlyTx(
+    const txRes = await ctx.wex.db.runReadOnlyTx(
       ["purchases", "operationRetries"],
       async (tx) => {
         const purchase = await tx.purchases.get(ctx.proposalId);
@@ -1684,7 +1677,7 @@ async function internalWaitPaymentResult(
       `purchase is in state ${PurchaseStatus[purchase.purchaseStatus]}`,
     );
 
-    const d = await expectProposalDownload(ctx.ws, purchase);
+    const d = await expectProposalDownload(ctx.wex, purchase);
 
     if (txRes.purchase.timestampFirstSuccessfulPay) {
       if (
@@ -1726,20 +1719,20 @@ async function internalWaitPaymentResult(
  * b) the attempt to pay failed (merchant unavailable, etc.)
  */
 async function waitPaymentResult(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   proposalId: string,
   waitSessionId?: string,
 ): Promise<ConfirmPayResult> {
-  const ctx = new PayMerchantTransactionContext(ws, proposalId);
+  const ctx = new PayMerchantTransactionContext(wex, proposalId);
 
-  ws.taskScheduler.ensureRunning();
-  ws.taskScheduler.startShepherdTask(ctx.taskId);
+  wex.taskScheduler.ensureRunning();
+  wex.taskScheduler.startShepherdTask(ctx.taskId);
 
   // FIXME: Clean up using the new JS "using" / Symbol.dispose syntax.
   const purchaseNotifFlag = new AsyncFlag();
   // Raise purchaseNotifFlag whenever we get a notification
   // about our purchase.
-  const cancelNotif = ws.addNotificationListener((notif) => {
+  const cancelNotif = wex.ws.addNotificationListener((notif) => {
     if (
       notif.type === NotificationType.TransactionStateTransition &&
       notif.transactionId === ctx.transactionId
@@ -1768,7 +1761,7 @@ async function waitPaymentResult(
  * Confirm payment for a proposal previously claimed by the wallet.
  */
 export async function confirmPay(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   transactionId: string,
   sessionIdOverride?: string,
   forcedCoinSel?: ForcedCoinSel,
@@ -1781,7 +1774,7 @@ export async function confirmPay(
   logger.trace(
     `executing confirmPay with proposalId ${proposalId} and sessionIdOverride 
${sessionIdOverride}`,
   );
-  const proposal = await ws.db.runReadOnlyTx(["purchases"], async (tx) => {
+  const proposal = await wex.db.runReadOnlyTx(["purchases"], async (tx) => {
     return tx.purchases.get(proposalId);
   });
 
@@ -1789,12 +1782,12 @@ export async function confirmPay(
     throw Error(`proposal with id ${proposalId} not found`);
   }
 
-  const d = await expectProposalDownload(ws, proposal);
+  const d = await expectProposalDownload(wex, proposal);
   if (!d) {
     throw Error("proposal is in invalid state");
   }
 
-  const existingPurchase = await ws.db.runReadWriteTx(
+  const existingPurchase = await wex.db.runReadWriteTx(
     ["purchases"],
     async (tx) => {
       const purchase = await tx.purchases.get(proposalId);
@@ -1817,18 +1810,18 @@ export async function confirmPay(
   if (existingPurchase && existingPurchase.payInfo) {
     logger.trace("confirmPay: submitting payment for existing purchase");
     const ctx = new PayMerchantTransactionContext(
-      ws,
+      wex,
       existingPurchase.proposalId,
     );
-    await ws.taskScheduler.resetTaskRetries(ctx.taskId);
-    return waitPaymentResult(ws, proposalId);
+    await wex.taskScheduler.resetTaskRetries(ctx.taskId);
+    return waitPaymentResult(wex, proposalId);
   }
 
   logger.trace("confirmPay: purchase record does not exist yet");
 
   const contractData = d.contractData;
 
-  const selectCoinsResult = await selectPayCoinsNew(ws, {
+  const selectCoinsResult = await selectPayCoinsNew(wex, {
     auditors: [],
     exchanges: contractData.allowedExchanges,
     wireMethod: contractData.wireMethod,
@@ -1852,7 +1845,7 @@ export async function confirmPay(
   }
 
   const coinSelection = selectCoinsResult.coinSel;
-  const payCostInfo = await getTotalPaymentCost(ws, coinSelection);
+  const payCostInfo = await getTotalPaymentCost(wex, coinSelection);
 
   let sessionId: string | undefined;
   if (sessionIdOverride) {
@@ -1865,7 +1858,7 @@ export async function confirmPay(
     `recording payment on ${proposal.orderId} with session ID ${sessionId}`,
   );
 
-  const transitionInfo = await ws.db.runReadWriteTx(
+  const transitionInfo = await wex.db.runReadWriteTx(
     [
       "purchases",
       "coins",
@@ -1891,7 +1884,7 @@ export async function confirmPay(
           p.timestampAccept = 
timestampPreciseToDb(TalerPreciseTimestamp.now());
           p.purchaseStatus = PurchaseStatus.PendingPaying;
           await tx.purchases.put(p);
-          await spendCoins(ws, tx, {
+          await spendCoins(wex, tx, {
             //`txn:proposal:${p.proposalId}`
             allocationId: constructTransactionIdentifier({
               tag: TransactionType.Payment,
@@ -1914,22 +1907,21 @@ export async function confirmPay(
     },
   );
 
-  notifyTransition(ws, transactionId, transitionInfo);
-  ws.notify({
+  notifyTransition(wex, transactionId, transitionInfo);
+  wex.ws.notify({
     type: NotificationType.BalanceChange,
     hintTransactionId: transactionId,
   });
 
   // Wait until we have completed the first attempt to pay.
-  return waitPaymentResult(ws, proposalId);
+  return waitPaymentResult(wex, proposalId);
 }
 
 export async function processPurchase(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   proposalId: string,
-  cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
-  const purchase = await ws.db.runReadOnlyTx(["purchases"], async (tx) => {
+  const purchase = await wex.db.runReadOnlyTx(["purchases"], async (tx) => {
     return tx.purchases.get(proposalId);
   });
   if (!purchase) {
@@ -1947,20 +1939,20 @@ export async function processPurchase(
 
   switch (purchase.purchaseStatus) {
     case PurchaseStatus.PendingDownloadingProposal:
-      return processDownloadProposal(ws, proposalId, cancellationToken);
+      return processDownloadProposal(wex, proposalId);
     case PurchaseStatus.PendingPaying:
     case PurchaseStatus.PendingPayingReplay:
-      return processPurchasePay(ws, proposalId, cancellationToken);
+      return processPurchasePay(wex, proposalId);
     case PurchaseStatus.PendingQueryingRefund:
-      return processPurchaseQueryRefund(ws, purchase, cancellationToken);
+      return processPurchaseQueryRefund(wex, purchase);
     case PurchaseStatus.PendingQueryingAutoRefund:
-      return processPurchaseAutoRefund(ws, purchase, cancellationToken);
+      return processPurchaseAutoRefund(wex, purchase);
     case PurchaseStatus.AbortingWithRefund:
-      return processPurchaseAbortingRefund(ws, purchase, cancellationToken);
+      return processPurchaseAbortingRefund(wex, purchase);
     case PurchaseStatus.PendingAcceptRefund:
-      return processPurchaseAcceptRefund(ws, purchase, cancellationToken);
+      return processPurchaseAcceptRefund(wex, purchase);
     case PurchaseStatus.DialogShared:
-      return processPurchaseDialogShared(ws, purchase, cancellationToken);
+      return processPurchaseDialogShared(wex, purchase);
     case PurchaseStatus.FailedClaim:
     case PurchaseStatus.Done:
     case PurchaseStatus.DoneRepurchaseDetected:
@@ -1984,11 +1976,10 @@ export async function processPurchase(
 }
 
 async function processPurchasePay(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   proposalId: string,
-  cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
-  const purchase = await ws.db.runReadOnlyTx(["purchases"], async (tx) => {
+  const purchase = await wex.db.runReadOnlyTx(["purchases"], async (tx) => {
     return tx.purchases.get(proposalId);
   });
   if (!purchase) {
@@ -2018,17 +2009,13 @@ async function processPurchasePay(
   const payInfo = purchase.payInfo;
   checkDbInvariant(!!payInfo, "payInfo");
 
-  const download = await expectProposalDownload(ws, purchase);
+  const download = await expectProposalDownload(wex, purchase);
 
   if (purchase.shared) {
-    const paid = await checkIfOrderIsAlreadyPaid(
-      ws,
-      download.contractData,
-      cancellationToken,
-    );
+    const paid = await checkIfOrderIsAlreadyPaid(wex, download.contractData);
 
     if (paid) {
-      const transitionInfo = await ws.db.runReadWriteTx(
+      const transitionInfo = await wex.db.runReadWriteTx(
         ["purchases"],
         async (tx) => {
           const p = await tx.purchases.get(purchase.proposalId);
@@ -2048,7 +2035,7 @@ async function processPurchasePay(
         proposalId,
       });
 
-      notifyTransition(ws, transactionId, transitionInfo);
+      notifyTransition(wex, transactionId, transitionInfo);
 
       return {
         type: TaskRunResultType.Error,
@@ -2069,7 +2056,7 @@ async function processPurchasePay(
     let depositPermissions: CoinDepositPermission[];
     // FIXME: Cache!
     depositPermissions = await generateDepositPermissions(
-      ws,
+      wex,
       payInfo.payCoinSelection,
       download.contractData,
     );
@@ -2084,12 +2071,12 @@ async function processPurchasePay(
       JSON.stringify(reqBody, undefined, 2),
     );
 
-    const resp = await ws.runSequentialized([EXCHANGE_COINS_LOCK], () =>
-      ws.http.fetch(payUrl, {
+    const resp = await wex.ws.runSequentialized([EXCHANGE_COINS_LOCK], () =>
+      wex.http.fetch(payUrl, {
         method: "POST",
         body: reqBody,
         timeout: getPayRequestTimeout(purchase),
-        cancellationToken,
+        cancellationToken: wex.cancellationToken,
       }),
     );
 
@@ -2115,7 +2102,7 @@ async function processPurchasePay(
         TalerErrorCode.MERCHANT_POST_ORDERS_ID_PAY_INSUFFICIENT_FUNDS
       ) {
         // Do this in the background, as it might take some time
-        handleInsufficientFunds(ws, proposalId, err).catch(async (e) => {
+        handleInsufficientFunds(wex, proposalId, err).catch(async (e) => {
           logger.error("handling insufficient funds failed");
           logger.error(`${e.toString()}`);
         });
@@ -2140,7 +2127,7 @@ async function processPurchasePay(
     logger.trace("got success from pay URL", merchantResp);
 
     const merchantPub = download.contractData.merchantPub;
-    const { valid } = await ws.cryptoApi.isValidPaymentSignature({
+    const { valid } = await wex.cryptoApi.isValidPaymentSignature({
       contractHash: download.contractData.contractTermsHash,
       merchantPub,
       sig: merchantResp.sig,
@@ -2152,7 +2139,7 @@ async function processPurchasePay(
       throw Error("merchant payment signature invalid");
     }
 
-    await storeFirstPaySuccess(ws, proposalId, sessionId, merchantResp);
+    await storeFirstPaySuccess(wex, proposalId, sessionId, merchantResp);
   } else {
     const payAgainUrl = new URL(
       `orders/${download.contractData.orderId}/paid`,
@@ -2164,11 +2151,11 @@ async function processPurchasePay(
       session_id: sessionId ?? "",
     };
     logger.trace(`/paid request body: ${j2s(reqBody)}`);
-    const resp = await ws.runSequentialized([EXCHANGE_COINS_LOCK], () =>
-      ws.http.fetch(payAgainUrl, {
+    const resp = await wex.ws.runSequentialized([EXCHANGE_COINS_LOCK], () =>
+      wex.http.fetch(payAgainUrl, {
         method: "POST",
         body: reqBody,
-        cancellationToken,
+        cancellationToken: wex.cancellationToken,
       }),
     );
     logger.trace(`/paid response status: ${resp.status}`);
@@ -2182,21 +2169,21 @@ async function processPurchasePay(
         "/paid failed",
       );
     }
-    await storePayReplaySuccess(ws, proposalId, sessionId);
+    await storePayReplaySuccess(wex, proposalId, sessionId);
   }
 
   return TaskRunResult.progress();
 }
 
 export async function refuseProposal(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   proposalId: string,
 ): Promise<void> {
   const transactionId = constructTransactionIdentifier({
     tag: TransactionType.Payment,
     proposalId,
   });
-  const transitionInfo = await ws.db.runReadWriteTx(
+  const transitionInfo = await wex.db.runReadWriteTx(
     ["purchases"],
     async (tx) => {
       const proposal = await tx.purchases.get(proposalId);
@@ -2218,7 +2205,7 @@ export async function refuseProposal(
     },
   );
 
-  notifyTransition(ws, transactionId, transitionInfo);
+  notifyTransition(wex, transactionId, transitionInfo);
 }
 
 const transitionSuspend: {
@@ -2456,11 +2443,11 @@ export function computePayMerchantTransactionActions(
 }
 
 export async function sharePayment(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   merchantBaseUrl: string,
   orderId: string,
 ): Promise<SharePaymentResult> {
-  const result = await ws.db.runReadWriteTx(["purchases"], async (tx) => {
+  const result = await wex.db.runReadWriteTx(["purchases"], async (tx) => {
     const p = await tx.purchases.indexes.byUrlAndOrderId.get([
       merchantBaseUrl,
       orderId,
@@ -2503,9 +2490,8 @@ export async function sharePayment(
 }
 
 async function checkIfOrderIsAlreadyPaid(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   contract: WalletContractData,
-  cancellationToken: CancellationToken,
 ) {
   const requestUrl = new URL(
     `orders/${contract.orderId}`,
@@ -2515,8 +2501,8 @@ async function checkIfOrderIsAlreadyPaid(
 
   requestUrl.searchParams.set("timeout_ms", "1000");
 
-  const resp = await ws.http.fetch(requestUrl.href, {
-    cancellationToken,
+  const resp = await wex.http.fetch(requestUrl.href, {
+    cancellationToken: wex.cancellationToken,
   });
   if (
     resp.status === HttpStatusCode.Ok ||
@@ -2532,25 +2518,20 @@ async function checkIfOrderIsAlreadyPaid(
 }
 
 async function processPurchaseDialogShared(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   purchase: PurchaseRecord,
-  cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
   const proposalId = purchase.proposalId;
   logger.trace(`processing dialog-shared for proposal ${proposalId}`);
-  const download = await expectProposalDownload(ws, purchase);
+  const download = await expectProposalDownload(wex, purchase);
 
   if (purchase.purchaseStatus !== PurchaseStatus.DialogShared) {
     return TaskRunResult.finished();
   }
 
-  const paid = await checkIfOrderIsAlreadyPaid(
-    ws,
-    download.contractData,
-    cancellationToken,
-  );
+  const paid = await checkIfOrderIsAlreadyPaid(wex, download.contractData);
   if (paid) {
-    const transitionInfo = await ws.db.runReadWriteTx(
+    const transitionInfo = await wex.db.runReadWriteTx(
       ["purchases"],
       async (tx) => {
         const p = await tx.purchases.get(purchase.proposalId);
@@ -2570,31 +2551,25 @@ async function processPurchaseDialogShared(
       proposalId,
     });
 
-    notifyTransition(ws, transactionId, transitionInfo);
+    notifyTransition(wex, transactionId, transitionInfo);
   }
 
   return TaskRunResult.backoff();
 }
 
 async function processPurchaseAutoRefund(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   purchase: PurchaseRecord,
-  cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
   const proposalId = purchase.proposalId;
   logger.trace(`processing auto-refund for proposal ${proposalId}`);
 
-  const taskId = constructTaskIdentifier({
-    tag: PendingTaskType.Purchase,
-    proposalId,
-  });
-
   const transactionId = constructTransactionIdentifier({
     tag: TransactionType.Payment,
     proposalId,
   });
 
-  const download = await expectProposalDownload(ws, purchase);
+  const download = await expectProposalDownload(wex, purchase);
 
   if (
     !purchase.autoRefundDeadline ||
@@ -2604,7 +2579,7 @@ async function processPurchaseAutoRefund(
       ),
     )
   ) {
-    const transitionInfo = await ws.db.runReadWriteTx(
+    const transitionInfo = await wex.db.runReadWriteTx(
       ["purchases"],
       async (tx) => {
         const p = await tx.purchases.get(purchase.proposalId);
@@ -2623,7 +2598,7 @@ async function processPurchaseAutoRefund(
         return { oldTxState, newTxState };
       },
     );
-    notifyTransition(ws, transactionId, transitionInfo);
+    notifyTransition(wex, transactionId, transitionInfo);
     return TaskRunResult.finished();
   }
 
@@ -2639,8 +2614,8 @@ async function processPurchaseAutoRefund(
   requestUrl.searchParams.set("timeout_ms", "1000");
   requestUrl.searchParams.set("await_refund_obtained", "yes");
 
-  const resp = await ws.http.fetch(requestUrl.href, {
-    cancellationToken,
+  const resp = await wex.http.fetch(requestUrl.href, {
+    cancellationToken: wex.cancellationToken,
   });
 
   // FIXME: Check other status codes!
@@ -2651,7 +2626,7 @@ async function processPurchaseAutoRefund(
   );
 
   if (orderStatus.refund_pending) {
-    const transitionInfo = await ws.db.runReadWriteTx(
+    const transitionInfo = await wex.db.runReadWriteTx(
       ["purchases"],
       async (tx) => {
         const p = await tx.purchases.get(purchase.proposalId);
@@ -2669,19 +2644,18 @@ async function processPurchaseAutoRefund(
         return { oldTxState, newTxState };
       },
     );
-    notifyTransition(ws, transactionId, transitionInfo);
+    notifyTransition(wex, transactionId, transitionInfo);
   }
 
   return TaskRunResult.backoff();
 }
 
 async function processPurchaseAbortingRefund(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   purchase: PurchaseRecord,
-  cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
   const proposalId = purchase.proposalId;
-  const download = await expectProposalDownload(ws, purchase);
+  const download = await expectProposalDownload(wex, purchase);
   logger.trace(`processing aborting-refund for proposal ${proposalId}`);
 
   const requestUrl = new URL(
@@ -2696,7 +2670,7 @@ async function processPurchaseAbortingRefund(
     throw Error("can't abort, no coins selected");
   }
 
-  await ws.db.runReadOnlyTx(["coins"], async (tx) => {
+  await wex.db.runReadOnlyTx(["coins"], async (tx) => {
     for (let i = 0; i < payCoinSelection.coinPubs.length; i++) {
       const coinPub = payCoinSelection.coinPubs[i];
       const coin = await tx.coins.get(coinPub);
@@ -2716,10 +2690,10 @@ async function processPurchaseAbortingRefund(
 
   logger.trace(`making order abort request to ${requestUrl.href}`);
 
-  const abortHttpResp = await ws.http.fetch(requestUrl.href, {
+  const abortHttpResp = await wex.http.fetch(requestUrl.href, {
     method: "POST",
     body: abortReq,
-    cancellationToken,
+    cancellationToken: wex.cancellationToken,
   });
 
   if (abortHttpResp.status === HttpStatusCode.NotFound) {
@@ -2728,7 +2702,7 @@ async function processPurchaseAbortingRefund(
       err.code ===
       TalerErrorCode.MERCHANT_POST_ORDERS_ID_ABORT_CONTRACT_NOT_FOUND
     ) {
-      const ctx = new PayMerchantTransactionContext(ws, proposalId);
+      const ctx = new PayMerchantTransactionContext(wex, proposalId);
       await ctx.transition(async (rec) => {
         if (rec.purchaseStatus === PurchaseStatus.AbortingWithRefund) {
           rec.purchaseStatus = PurchaseStatus.AbortedOrderDeleted;
@@ -2766,18 +2740,17 @@ async function processPurchaseAbortingRefund(
       ),
     });
   }
-  return await storeRefunds(ws, purchase, refunds, RefundReason.AbortRefund);
+  return await storeRefunds(wex, purchase, refunds, RefundReason.AbortRefund);
 }
 
 async function processPurchaseQueryRefund(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   purchase: PurchaseRecord,
-  cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
   const proposalId = purchase.proposalId;
   logger.trace(`processing query-refund for proposal ${proposalId}`);
 
-  const download = await expectProposalDownload(ws, purchase);
+  const download = await expectProposalDownload(wex, purchase);
 
   const requestUrl = new URL(
     `orders/${download.contractData.orderId}`,
@@ -2788,8 +2761,8 @@ async function processPurchaseQueryRefund(
     download.contractData.contractTermsHash,
   );
 
-  const resp = await ws.http.fetch(requestUrl.href, {
-    cancellationToken,
+  const resp = await wex.http.fetch(requestUrl.href, {
+    cancellationToken: wex.cancellationToken,
   });
   const orderStatus = await readSuccessResponseJsonOrThrow(
     resp,
@@ -2802,7 +2775,7 @@ async function processPurchaseQueryRefund(
   });
 
   if (!orderStatus.refund_pending) {
-    const transitionInfo = await ws.db.runReadWriteTx(
+    const transitionInfo = await wex.db.runReadWriteTx(
       ["purchases"],
       async (tx) => {
         const p = await tx.purchases.get(purchase.proposalId);
@@ -2821,7 +2794,7 @@ async function processPurchaseQueryRefund(
         return { oldTxState, newTxState };
       },
     );
-    notifyTransition(ws, transactionId, transitionInfo);
+    notifyTransition(wex, transactionId, transitionInfo);
     return TaskRunResult.progress();
   } else {
     const refundAwaiting = Amounts.sub(
@@ -2829,7 +2802,7 @@ async function processPurchaseQueryRefund(
       Amounts.parseOrThrow(orderStatus.refund_taken),
     ).amount;
 
-    const transitionInfo = await ws.db.runReadWriteTx(
+    const transitionInfo = await wex.db.runReadWriteTx(
       ["purchases"],
       async (tx) => {
         const p = await tx.purchases.get(purchase.proposalId);
@@ -2848,17 +2821,16 @@ async function processPurchaseQueryRefund(
         return { oldTxState, newTxState };
       },
     );
-    notifyTransition(ws, transactionId, transitionInfo);
+    notifyTransition(wex, transactionId, transitionInfo);
     return TaskRunResult.progress();
   }
 }
 
 async function processPurchaseAcceptRefund(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   purchase: PurchaseRecord,
-  cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
-  const download = await expectProposalDownload(ws, purchase);
+  const download = await expectProposalDownload(wex, purchase);
 
   const requestUrl = new URL(
     `orders/${download.contractData.orderId}/refund`,
@@ -2867,12 +2839,12 @@ async function processPurchaseAcceptRefund(
 
   logger.trace(`making refund request to ${requestUrl.href}`);
 
-  const request = await ws.http.fetch(requestUrl.href, {
+  const request = await wex.http.fetch(requestUrl.href, {
     method: "POST",
     body: {
       h_contract: download.contractData.contractTermsHash,
     },
-    cancellationToken,
+    cancellationToken: wex.cancellationToken,
   });
 
   const refundResponse = await readSuccessResponseJsonOrThrow(
@@ -2880,7 +2852,7 @@ async function processPurchaseAcceptRefund(
     codecForMerchantOrderRefundPickupResponse(),
   );
   return await storeRefunds(
-    ws,
+    wex,
     purchase,
     refundResponse.refunds,
     RefundReason.AbortRefund,
@@ -2888,7 +2860,7 @@ async function processPurchaseAcceptRefund(
 }
 
 export async function startRefundQueryForUri(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   talerUri: string,
 ): Promise<StartRefundQueryForUriResponse> {
   const parsedUri = parseTalerUri(talerUri);
@@ -2898,7 +2870,7 @@ export async function startRefundQueryForUri(
   if (parsedUri.type !== TalerUriAction.Refund) {
     throw Error("expected taler://refund URI");
   }
-  const purchaseRecord = await ws.db.runReadOnlyTx(
+  const purchaseRecord = await wex.db.runReadOnlyTx(
     ["purchases"],
     async (tx) => {
       return tx.purchases.indexes.byUrlAndOrderId.get([
@@ -2918,18 +2890,18 @@ export async function startRefundQueryForUri(
     tag: TransactionType.Payment,
     proposalId,
   });
-  await startQueryRefund(ws, proposalId);
+  await startQueryRefund(wex, proposalId);
   return {
     transactionId,
   };
 }
 
 export async function startQueryRefund(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   proposalId: string,
 ): Promise<void> {
-  const ctx = new PayMerchantTransactionContext(ws, proposalId);
-  const transitionInfo = await ws.db.runReadWriteTx(
+  const ctx = new PayMerchantTransactionContext(wex, proposalId);
+  const transitionInfo = await wex.db.runReadWriteTx(
     ["purchases"],
     async (tx) => {
       const p = await tx.purchases.get(proposalId);
@@ -2947,12 +2919,12 @@ export async function startQueryRefund(
       return { oldTxState, newTxState };
     },
   );
-  notifyTransition(ws, ctx.transactionId, transitionInfo);
-  ws.taskScheduler.startShepherdTask(ctx.taskId);
+  notifyTransition(wex, ctx.transactionId, transitionInfo);
+  wex.taskScheduler.startShepherdTask(ctx.taskId);
 }
 
 async function computeRefreshRequest(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   tx: WalletDbReadWriteTransaction<["coins", "denominations"]>,
   items: RefundItemRecord[],
 ): Promise<CoinRefreshRequest[]> {
@@ -2963,7 +2935,7 @@ async function computeRefreshRequest(
       throw Error("coin not found");
     }
     const denomInfo = await getDenomInfo(
-      ws,
+      wex,
       tx,
       coin.exchangeBaseUrl,
       coin.denomPubHash,
@@ -3004,7 +2976,7 @@ function getItemStatus(rf: MerchantCoinRefundStatus): 
RefundItemStatus {
  * Store refunds, possibly creating a new refund group.
  */
 async function storeRefunds(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   purchase: PurchaseRecord,
   refunds: MerchantCoinRefundStatus[],
   reason: RefundReason,
@@ -3019,10 +2991,10 @@ async function storeRefunds(
   const newRefundGroupId = encodeCrock(randomBytes(32));
   const now = TalerPreciseTimestamp.now();
 
-  const download = await expectProposalDownload(ws, purchase);
+  const download = await expectProposalDownload(wex, purchase);
   const currency = Amounts.currencyOf(download.contractData.amount);
 
-  const result = await ws.db.runReadWriteTx(
+  const result = await wex.db.runReadWriteTx(
     [
       "coins",
       "denominations",
@@ -3116,12 +3088,12 @@ async function storeRefunds(
       if (newGroup) {
         const amountsRaw = newGroupRefunds.map((x) => x.refundAmount);
         const refreshCoins = await computeRefreshRequest(
-          ws,
+          wex,
           tx,
           newGroupRefunds,
         );
         const outInfo = await calculateRefreshOutput(
-          ws,
+          wex,
           tx,
           currency,
           refreshCoins,
@@ -3172,9 +3144,9 @@ async function storeRefunds(
             refundGroup.status = RefundGroupStatus.Failed;
           }
           await tx.refundGroups.put(refundGroup);
-          const refreshCoins = await computeRefreshRequest(ws, tx, items);
+          const refreshCoins = await computeRefreshRequest(wex, tx, items);
           await createRefreshGroup(
-            ws,
+            wex,
             tx,
             Amounts.currencyOf(download.contractData.amount),
             refreshCoins,
@@ -3215,7 +3187,7 @@ async function storeRefunds(
     return TaskRunResult.finished();
   }
 
-  notifyTransition(ws, transactionId, result.transitionInfo);
+  notifyTransition(wex, transactionId, result.transitionInfo);
 
   if (result.numPendingItemsTotal > 0) {
     return TaskRunResult.backoff();
diff --git a/packages/taler-wallet-core/src/pay-peer-common.ts 
b/packages/taler-wallet-core/src/pay-peer-common.ts
index dbc3376b4..ff035d5e5 100644
--- a/packages/taler-wallet-core/src/pay-peer-common.ts
+++ b/packages/taler-wallet-core/src/pay-peer-common.ts
@@ -33,25 +33,25 @@ import type { SelectedPeerCoin } from "./coinSelection.js";
 import { SpendCoinDetails } from "./crypto/cryptoImplementation.js";
 import { PeerPushPaymentCoinSelection, ReserveRecord } from "./db.js";
 import { getTotalRefreshCost } from "./refresh.js";
-import { InternalWalletState, getDenomInfo } from "./wallet.js";
+import { WalletExecutionContext, getDenomInfo } from "./wallet.js";
 import { getCandidateWithdrawalDenomsTx } from "./withdraw.js";
 
 /**
  * Get information about the coin selected for signatures.
  */
 export async function queryCoinInfosForSelection(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   csel: PeerPushPaymentCoinSelection,
 ): Promise<SpendCoinDetails[]> {
   let infos: SpendCoinDetails[] = [];
-  await ws.db.runReadOnlyTx(["coins", "denominations"], async (tx) => {
+  await wex.db.runReadOnlyTx(["coins", "denominations"], async (tx) => {
     for (let i = 0; i < csel.coinPubs.length; i++) {
       const coin = await tx.coins.get(csel.coinPubs[i]);
       if (!coin) {
         throw Error("coin not found anymore");
       }
       const denom = await getDenomInfo(
-        ws,
+        wex,
         tx,
         coin.exchangeBaseUrl,
         coin.denomPubHash,
@@ -73,11 +73,11 @@ export async function queryCoinInfosForSelection(
 }
 
 export async function getTotalPeerPaymentCost(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   pcs: SelectedPeerCoin[],
 ): Promise<AmountJson> {
   const currency = Amounts.currencyOf(pcs[0].contribution);
-  return ws.db.runReadOnlyTx(["coins", "denominations"], async (tx) => {
+  return wex.db.runReadOnlyTx(["coins", "denominations"], async (tx) => {
     const costs: AmountJson[] = [];
     for (let i = 0; i < pcs.length; i++) {
       const coin = await tx.coins.get(pcs[i].coinPub);
@@ -85,7 +85,7 @@ export async function getTotalPeerPaymentCost(
         throw Error("can't calculate payment cost, coin not found");
       }
       const denomInfo = await getDenomInfo(
-        ws,
+        wex,
         tx,
         coin.exchangeBaseUrl,
         coin.denomPubHash,
@@ -96,7 +96,7 @@ export async function getTotalPeerPaymentCost(
         );
       }
       const allDenoms = await getCandidateWithdrawalDenomsTx(
-        ws,
+        wex,
         tx,
         coin.exchangeBaseUrl,
         currency,
@@ -109,7 +109,7 @@ export async function getTotalPeerPaymentCost(
         allDenoms,
         denomInfo,
         amountLeft,
-        ws.config.testing.denomselAllowLate,
+        wex.ws.config.testing.denomselAllowLate,
       );
       costs.push(Amounts.parseOrThrow(pcs[i].contribution));
       costs.push(refreshCost);
@@ -133,16 +133,16 @@ export const codecForExchangePurseStatus = (): 
Codec<ExchangePurseStatus> =>
     .build("ExchangePurseStatus");
 
 export async function getMergeReserveInfo(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: {
     exchangeBaseUrl: string;
   },
 ): Promise<ReserveRecord> {
   // We have to eagerly create the key pair outside of the transaction,
   // due to the async crypto API.
-  const newReservePair = await ws.cryptoApi.createEddsaKeypair({});
+  const newReservePair = await wex.cryptoApi.createEddsaKeypair({});
 
-  const mergeReserveRecord: ReserveRecord = await ws.db.runReadWriteTx(
+  const mergeReserveRecord: ReserveRecord = await wex.db.runReadWriteTx(
     ["exchanges", "reserves"],
     async (tx) => {
       const ex = await tx.exchanges.get(req.exchangeBaseUrl);
diff --git a/packages/taler-wallet-core/src/pay-peer-pull-credit.ts 
b/packages/taler-wallet-core/src/pay-peer-pull-credit.ts
index 7774dfd5f..c999a8d1f 100644
--- a/packages/taler-wallet-core/src/pay-peer-pull-credit.ts
+++ b/packages/taler-wallet-core/src/pay-peer-pull-credit.ts
@@ -17,7 +17,6 @@
 import {
   AbsoluteTime,
   Amounts,
-  CancellationToken,
   CheckPeerPullCreditRequest,
   CheckPeerPullCreditResponse,
   ContractTermsUtil,
@@ -81,7 +80,7 @@ import {
   constructTransactionIdentifier,
   notifyTransition,
 } from "./transactions.js";
-import { InternalWalletState } from "./wallet.js";
+import { WalletExecutionContext } from "./wallet.js";
 import {
   getExchangeWithdrawalInfo,
   internalCreateWithdrawalGroup,
@@ -94,7 +93,7 @@ export class PeerPullCreditTransactionContext implements 
TransactionContext {
   readonly taskId: TaskIdStr;
 
   constructor(
-    public ws: InternalWalletState,
+    public wex: WalletExecutionContext,
     public pursePub: string,
   ) {
     this.taskId = constructTaskIdentifier({
@@ -108,7 +107,7 @@ export class PeerPullCreditTransactionContext implements 
TransactionContext {
   }
 
   async deleteTransaction(): Promise<void> {
-    const { ws, pursePub } = this;
+    const { wex: ws, pursePub } = this;
     await ws.db.runReadWriteTx(
       ["withdrawalGroups", "peerPullCredit", "tombstones"],
       async (tx) => {
@@ -138,7 +137,7 @@ export class PeerPullCreditTransactionContext implements 
TransactionContext {
   }
 
   async suspendTransaction(): Promise<void> {
-    const { ws, pursePub, taskId: retryTag, transactionId } = this;
+    const { wex: ws, pursePub, taskId: retryTag, transactionId } = this;
     const transitionInfo = await ws.db.runReadWriteTx(
       ["peerPullCredit"],
       async (tx) => {
@@ -198,7 +197,7 @@ export class PeerPullCreditTransactionContext implements 
TransactionContext {
   }
 
   async failTransaction(): Promise<void> {
-    const { ws, pursePub, taskId: retryTag, transactionId } = this;
+    const { wex: ws, pursePub, taskId: retryTag, transactionId } = this;
     const transitionInfo = await ws.db.runReadWriteTx(
       ["peerPullCredit"],
       async (tx) => {
@@ -249,7 +248,7 @@ export class PeerPullCreditTransactionContext implements 
TransactionContext {
   }
 
   async resumeTransaction(): Promise<void> {
-    const { ws, pursePub, taskId: retryTag, transactionId } = this;
+    const { wex: ws, pursePub, taskId: retryTag, transactionId } = this;
     const transitionInfo = await ws.db.runReadWriteTx(
       ["peerPullCredit"],
       async (tx) => {
@@ -308,7 +307,7 @@ export class PeerPullCreditTransactionContext implements 
TransactionContext {
   }
 
   async abortTransaction(): Promise<void> {
-    const { ws, pursePub, taskId: retryTag, transactionId } = this;
+    const { wex: ws, pursePub, taskId: retryTag, transactionId } = this;
     const transitionInfo = await ws.db.runReadWriteTx(
       ["peerPullCredit"],
       async (tx) => {
@@ -364,9 +363,8 @@ export class PeerPullCreditTransactionContext implements 
TransactionContext {
 }
 
 async function queryPurseForPeerPullCredit(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   pullIni: PeerPullCreditRecord,
-  cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
   const purseDepositUrl = new URL(
     `purses/${pullIni.pursePub}/deposit`,
@@ -374,9 +372,9 @@ async function queryPurseForPeerPullCredit(
   );
   purseDepositUrl.searchParams.set("timeout_ms", "30000");
   logger.info(`querying purse status via ${purseDepositUrl.href}`);
-  const resp = await ws.http.fetch(purseDepositUrl.href, {
+  const resp = await wex.http.fetch(purseDepositUrl.href, {
     timeout: { d_ms: 60000 },
-    cancellationToken,
+    cancellationToken: wex.cancellationToken,
   });
   const transactionId = constructTransactionIdentifier({
     tag: TransactionType.PeerPullCredit,
@@ -388,7 +386,7 @@ async function queryPurseForPeerPullCredit(
   switch (resp.status) {
     case HttpStatusCode.Gone: {
       // Exchange says that purse doesn't exist anymore => expired!
-      const transitionInfo = await ws.db.runReadWriteTx(
+      const transitionInfo = await wex.db.runReadWriteTx(
         ["peerPullCredit"],
         async (tx) => {
           const finPi = await tx.peerPullCredit.get(pullIni.pursePub);
@@ -405,7 +403,7 @@ async function queryPurseForPeerPullCredit(
           return { oldTxState, newTxState };
         },
       );
-      notifyTransition(ws, transactionId, transitionInfo);
+      notifyTransition(wex, transactionId, transitionInfo);
       return TaskRunResult.backoff();
     }
     case HttpStatusCode.NotFound:
@@ -427,7 +425,7 @@ async function queryPurseForPeerPullCredit(
     return TaskRunResult.backoff();
   }
 
-  const reserve = await ws.db.runReadOnlyTx(["reserves"], async (tx) => {
+  const reserve = await wex.db.runReadOnlyTx(["reserves"], async (tx) => {
     return await tx.reserves.get(pullIni.mergeReserveRowId);
   });
 
@@ -435,7 +433,7 @@ async function queryPurseForPeerPullCredit(
     throw Error("reserve for peer pull credit not found in wallet DB");
   }
 
-  await internalCreateWithdrawalGroup(ws, {
+  await internalCreateWithdrawalGroup(wex, {
     amount: Amounts.parseOrThrow(pullIni.amount),
     wgInfo: {
       withdrawalType: WithdrawalRecordType.PeerPullCredit,
@@ -449,7 +447,7 @@ async function queryPurseForPeerPullCredit(
       pub: reserve.reservePub,
     },
   });
-  const transitionInfo = await ws.db.runReadWriteTx(
+  const transitionInfo = await wex.db.runReadWriteTx(
     ["peerPullCredit"],
     async (tx) => {
       const finPi = await tx.peerPullCredit.get(pullIni.pursePub);
@@ -466,17 +464,16 @@ async function queryPurseForPeerPullCredit(
       return { oldTxState, newTxState };
     },
   );
-  notifyTransition(ws, transactionId, transitionInfo);
+  notifyTransition(wex, transactionId, transitionInfo);
   return TaskRunResult.backoff();
 }
 
 async function longpollKycStatus(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   pursePub: string,
   exchangeUrl: string,
   kycInfo: KycPendingInfo,
   userType: KycUserType,
-  cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
   const transactionId = constructTransactionIdentifier({
     tag: TransactionType.PeerPullCredit,
@@ -488,9 +485,9 @@ async function longpollKycStatus(
   );
   url.searchParams.set("timeout_ms", "10000");
   logger.info(`kyc url ${url.href}`);
-  const kycStatusRes = await ws.http.fetch(url.href, {
+  const kycStatusRes = await wex.http.fetch(url.href, {
     method: "GET",
-    cancellationToken,
+    cancellationToken: wex.cancellationToken,
   });
   if (
     kycStatusRes.status === HttpStatusCode.Ok ||
@@ -498,7 +495,7 @@ async function longpollKycStatus(
     // remove after the exchange is fixed or clarified
     kycStatusRes.status === HttpStatusCode.NoContent
   ) {
-    const transitionInfo = await ws.db.runReadWriteTx(
+    const transitionInfo = await wex.db.runReadWriteTx(
       ["peerPullCredit"],
       async (tx) => {
         const peerIni = await tx.peerPullCredit.get(pursePub);
@@ -517,7 +514,7 @@ async function longpollKycStatus(
         return { oldTxState, newTxState };
       },
     );
-    notifyTransition(ws, transactionId, transitionInfo);
+    notifyTransition(wex, transactionId, transitionInfo);
     return TaskRunResult.progress();
   } else if (kycStatusRes.status === HttpStatusCode.Accepted) {
     return TaskRunResult.longpollReturnedPending();
@@ -527,9 +524,8 @@ async function longpollKycStatus(
 }
 
 async function processPeerPullCreditAbortingDeletePurse(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   peerPullIni: PeerPullCreditRecord,
-  cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
   const { pursePub, pursePriv } = peerPullIni;
   const transactionId = constructTransactionIdentifier({
@@ -537,20 +533,20 @@ async function processPeerPullCreditAbortingDeletePurse(
     pursePub,
   });
 
-  const sigResp = await ws.cryptoApi.signDeletePurse({
+  const sigResp = await wex.cryptoApi.signDeletePurse({
     pursePriv,
   });
   const purseUrl = new URL(`purses/${pursePub}`, peerPullIni.exchangeBaseUrl);
-  const resp = await ws.http.fetch(purseUrl.href, {
+  const resp = await wex.http.fetch(purseUrl.href, {
     method: "DELETE",
     headers: {
       "taler-purse-signature": sigResp.sig,
     },
-    cancellationToken,
+    cancellationToken: wex.cancellationToken,
   });
   logger.info(`deleted purse with response status ${resp.status}`);
 
-  const transitionInfo = await ws.db.runReadWriteTx(
+  const transitionInfo = await wex.db.runReadWriteTx(
     [
       "peerPullCredit",
       "refreshGroups",
@@ -576,13 +572,13 @@ async function processPeerPullCreditAbortingDeletePurse(
       };
     },
   );
-  notifyTransition(ws, transactionId, transitionInfo);
+  notifyTransition(wex, transactionId, transitionInfo);
 
   return TaskRunResult.backoff();
 }
 
 async function handlePeerPullCreditWithdrawing(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   pullIni: PeerPullCreditRecord,
 ): Promise<TaskRunResult> {
   if (!pullIni.withdrawalGroupId) {
@@ -594,7 +590,7 @@ async function handlePeerPullCreditWithdrawing(
   });
   const wgId = pullIni.withdrawalGroupId;
   let finished: boolean = false;
-  const transitionInfo = await ws.db.runReadWriteTx(
+  const transitionInfo = await wex.db.runReadWriteTx(
     ["peerPullCredit", "withdrawalGroups"],
     async (tx) => {
       const ppi = await tx.peerPullCredit.get(pullIni.pursePub);
@@ -627,7 +623,7 @@ async function handlePeerPullCreditWithdrawing(
       };
     },
   );
-  notifyTransition(ws, transactionId, transitionInfo);
+  notifyTransition(wex, transactionId, transitionInfo);
   if (finished) {
     return TaskRunResult.finished();
   } else {
@@ -637,13 +633,12 @@ async function handlePeerPullCreditWithdrawing(
 }
 
 async function handlePeerPullCreditCreatePurse(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   pullIni: PeerPullCreditRecord,
-  cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
   const purseFee = Amounts.stringify(Amounts.zeroOfAmount(pullIni.amount));
   const pursePub = pullIni.pursePub;
-  const mergeReserve = await ws.db.runReadOnlyTx(["reserves"], async (tx) => {
+  const mergeReserve = await wex.db.runReadOnlyTx(["reserves"], async (tx) => {
     return tx.reserves.get(pullIni.mergeReserveRowId);
   });
 
@@ -651,7 +646,7 @@ async function handlePeerPullCreditCreatePurse(
     throw Error("merge reserve for peer pull payment not found in database");
   }
 
-  const contractTermsRecord = await ws.db.runReadOnlyTx(
+  const contractTermsRecord = await wex.db.runReadOnlyTx(
     ["contractTerms"],
     async (tx) => {
       return tx.contractTerms.get(pullIni.contractTermsHash);
@@ -669,7 +664,7 @@ async function handlePeerPullCreditCreatePurse(
     mergeReserve.reservePub,
   );
 
-  const econtractResp = await ws.cryptoApi.encryptContractForDeposit({
+  const econtractResp = await wex.cryptoApi.encryptContractForDeposit({
     contractPriv: pullIni.contractPriv,
     contractPub: pullIni.contractPub,
     contractTerms: contractTermsRecord.contractTermsRaw,
@@ -681,7 +676,7 @@ async function handlePeerPullCreditCreatePurse(
   const mergeTimestamp = timestampPreciseFromDb(pullIni.mergeTimestamp);
 
   const purseExpiration = contractTerms.purse_expiration;
-  const sigRes = await ws.cryptoApi.signReservePurseCreate({
+  const sigRes = await wex.cryptoApi.signReservePurseCreate({
     contractTermsHash: pullIni.contractTermsHash,
     flags: WalletAccountMergeFlags.CreateWithPurseFee,
     mergePriv: pullIni.mergePriv,
@@ -717,22 +712,17 @@ async function handlePeerPullCreditCreatePurse(
     pullIni.exchangeBaseUrl,
   );
 
-  const httpResp = await ws.http.fetch(reservePurseMergeUrl.href, {
+  const httpResp = await wex.http.fetch(reservePurseMergeUrl.href, {
     method: "POST",
     body: reservePurseReqBody,
-    cancellationToken,
+    cancellationToken: wex.cancellationToken,
   });
 
   if (httpResp.status === HttpStatusCode.UnavailableForLegalReasons) {
     const respJson = await httpResp.json();
     const kycPending = codecForWalletKycUuid().decode(respJson);
     logger.info(`kyc uuid response: ${j2s(kycPending)}`);
-    return processPeerPullCreditKycRequired(
-      ws,
-      pullIni,
-      kycPending,
-      cancellationToken,
-    );
+    return processPeerPullCreditKycRequired(wex, pullIni, kycPending);
   }
 
   const resp = await readSuccessResponseJsonOrThrow(httpResp, codecForAny());
@@ -744,7 +734,7 @@ async function handlePeerPullCreditCreatePurse(
     pursePub: pullIni.pursePub,
   });
 
-  const transitionInfo = await ws.db.runReadWriteTx(
+  const transitionInfo = await wex.db.runReadWriteTx(
     ["peerPullCredit"],
     async (tx) => {
       const pi2 = await tx.peerPullCredit.get(pursePub);
@@ -758,16 +748,15 @@ async function handlePeerPullCreditCreatePurse(
       return { oldTxState, newTxState };
     },
   );
-  notifyTransition(ws, transactionId, transitionInfo);
+  notifyTransition(wex, transactionId, transitionInfo);
   return TaskRunResult.backoff();
 }
 
 export async function processPeerPullCredit(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   pursePub: string,
-  cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
-  const pullIni = await ws.db.runReadOnlyTx(["peerPullCredit"], async (tx) => {
+  const pullIni = await wex.db.runReadOnlyTx(["peerPullCredit"], async (tx) => 
{
     return tx.peerPullCredit.get(pursePub);
   });
   if (!pullIni) {
@@ -786,30 +775,25 @@ export async function processPeerPullCredit(
       return TaskRunResult.finished();
     }
     case PeerPullPaymentCreditStatus.PendingReady:
-      return queryPurseForPeerPullCredit(ws, pullIni, cancellationToken);
+      return queryPurseForPeerPullCredit(wex, pullIni);
     case PeerPullPaymentCreditStatus.PendingMergeKycRequired: {
       if (!pullIni.kycInfo) {
         throw Error("invalid state, kycInfo required");
       }
       return await longpollKycStatus(
-        ws,
+        wex,
         pursePub,
         pullIni.exchangeBaseUrl,
         pullIni.kycInfo,
         "individual",
-        cancellationToken,
       );
     }
     case PeerPullPaymentCreditStatus.PendingCreatePurse:
-      return handlePeerPullCreditCreatePurse(ws, pullIni, cancellationToken);
+      return handlePeerPullCreditCreatePurse(wex, pullIni);
     case PeerPullPaymentCreditStatus.AbortingDeletePurse:
-      return await processPeerPullCreditAbortingDeletePurse(
-        ws,
-        pullIni,
-        cancellationToken,
-      );
+      return await processPeerPullCreditAbortingDeletePurse(wex, pullIni);
     case PeerPullPaymentCreditStatus.PendingWithdrawing:
-      return handlePeerPullCreditWithdrawing(ws, pullIni);
+      return handlePeerPullCreditWithdrawing(wex, pullIni);
     case PeerPullPaymentCreditStatus.Aborted:
     case PeerPullPaymentCreditStatus.Failed:
     case PeerPullPaymentCreditStatus.Expired:
@@ -827,10 +811,9 @@ export async function processPeerPullCredit(
 }
 
 async function processPeerPullCreditKycRequired(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   peerIni: PeerPullCreditRecord,
   kycPending: WalletKycUuid,
-  cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
   const transactionId = constructTransactionIdentifier({
     tag: TransactionType.PeerPullCredit,
@@ -845,9 +828,9 @@ async function processPeerPullCreditKycRequired(
   );
 
   logger.info(`kyc url ${url.href}`);
-  const kycStatusRes = await ws.http.fetch(url.href, {
+  const kycStatusRes = await wex.http.fetch(url.href, {
     method: "GET",
-    cancellationToken,
+    cancellationToken: wex.cancellationToken,
   });
 
   if (
@@ -861,7 +844,7 @@ async function processPeerPullCreditKycRequired(
   } else if (kycStatusRes.status === HttpStatusCode.Accepted) {
     const kycStatus = await kycStatusRes.json();
     logger.info(`kyc status: ${j2s(kycStatus)}`);
-    const { transitionInfo, result } = await ws.db.runReadWriteTx(
+    const { transitionInfo, result } = await wex.db.runReadWriteTx(
       ["peerPullCredit"],
       async (tx) => {
         const peerInc = await tx.peerPullCredit.get(pursePub);
@@ -897,7 +880,7 @@ async function processPeerPullCreditKycRequired(
         };
       },
     );
-    notifyTransition(ws, transactionId, transitionInfo);
+    notifyTransition(wex, transactionId, transitionInfo);
     return TaskRunResult.backoff();
   } else {
     throw Error(`unexpected response from kyc-check (${kycStatusRes.status})`);
@@ -908,7 +891,7 @@ async function processPeerPullCreditKycRequired(
  * Check fees and available exchanges for a peer push payment initiation.
  */
 export async function checkPeerPullPaymentInitiation(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: CheckPeerPullCreditRequest,
 ): Promise<CheckPeerPullCreditResponse> {
   // FIXME: We don't support exchanges with purse fees yet.
@@ -922,7 +905,7 @@ export async function checkPeerPullPaymentInitiation(
   if (req.exchangeBaseUrl) {
     exchangeUrl = req.exchangeBaseUrl;
   } else {
-    exchangeUrl = await getPreferredExchangeForCurrency(ws, currency);
+    exchangeUrl = await getPreferredExchangeForCurrency(wex, currency);
   }
 
   if (!exchangeUrl) {
@@ -932,7 +915,7 @@ export async function checkPeerPullPaymentInitiation(
   logger.trace(`found ${exchangeUrl} as preferred exchange`);
 
   const wi = await getExchangeWithdrawalInfo(
-    ws,
+    wex,
     exchangeUrl,
     Amounts.parseOrThrow(req.amount),
     undefined,
@@ -957,12 +940,12 @@ export async function checkPeerPullPaymentInitiation(
  * Find a preferred exchange based on when we withdrew last from this exchange.
  */
 async function getPreferredExchangeForCurrency(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   currency: string,
 ): Promise<string | undefined> {
   // Find an exchange with the matching currency.
   // Prefer exchanges with the most recent withdrawal.
-  const url = await ws.db.runReadOnlyTx(["exchanges"], async (tx) => {
+  const url = await wex.db.runReadOnlyTx(["exchanges"], async (tx) => {
     const exchanges = await tx.exchanges.iter().toArray();
     let candidate = undefined;
     for (const e of exchanges) {
@@ -1005,7 +988,7 @@ async function getPreferredExchangeForCurrency(
  * Initiate a peer pull payment.
  */
 export async function initiatePeerPullPayment(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: InitiatePeerPullCreditRequest,
 ): Promise<InitiatePeerPullCreditResponse> {
   const currency = Amounts.currencyOf(req.partialContractTerms.amount);
@@ -1013,7 +996,7 @@ export async function initiatePeerPullPayment(
   if (req.exchangeBaseUrl) {
     maybeExchangeBaseUrl = req.exchangeBaseUrl;
   } else {
-    maybeExchangeBaseUrl = await getPreferredExchangeForCurrency(ws, currency);
+    maybeExchangeBaseUrl = await getPreferredExchangeForCurrency(wex, 
currency);
   }
 
   if (!maybeExchangeBaseUrl) {
@@ -1022,20 +1005,20 @@ export async function initiatePeerPullPayment(
 
   const exchangeBaseUrl = maybeExchangeBaseUrl;
 
-  await fetchFreshExchange(ws, exchangeBaseUrl);
+  await fetchFreshExchange(wex, exchangeBaseUrl);
 
-  const mergeReserveInfo = await getMergeReserveInfo(ws, {
+  const mergeReserveInfo = await getMergeReserveInfo(wex, {
     exchangeBaseUrl: exchangeBaseUrl,
   });
 
-  const pursePair = await ws.cryptoApi.createEddsaKeypair({});
-  const mergePair = await ws.cryptoApi.createEddsaKeypair({});
+  const pursePair = await wex.cryptoApi.createEddsaKeypair({});
+  const mergePair = await wex.cryptoApi.createEddsaKeypair({});
 
   const contractTerms = req.partialContractTerms;
 
   const hContractTerms = ContractTermsUtil.hashContractTerms(contractTerms);
 
-  const contractKeyPair = await ws.cryptoApi.createEddsaKeypair({});
+  const contractKeyPair = await wex.cryptoApi.createEddsaKeypair({});
 
   const withdrawalGroupId = encodeCrock(getRandomBytes(32));
 
@@ -1045,7 +1028,7 @@ export async function initiatePeerPullPayment(
   const contractEncNonce = encodeCrock(getRandomBytes(24));
 
   const wi = await getExchangeWithdrawalInfo(
-    ws,
+    wex,
     exchangeBaseUrl,
     Amounts.parseOrThrow(req.partialContractTerms.amount),
     undefined,
@@ -1053,7 +1036,7 @@ export async function initiatePeerPullPayment(
 
   const mergeTimestamp = TalerPreciseTimestamp.now();
 
-  const transitionInfo = await ws.db.runReadWriteTx(
+  const transitionInfo = await wex.db.runReadWriteTx(
     ["peerPullCredit", "contractTerms"],
     async (tx) => {
       const ppi: PeerPullCreditRecord = {
@@ -1086,16 +1069,16 @@ export async function initiatePeerPullPayment(
     },
   );
 
-  const ctx = new PeerPullCreditTransactionContext(ws, pursePair.pub);
+  const ctx = new PeerPullCreditTransactionContext(wex, pursePair.pub);
 
   // The pending-incoming balance has changed.
-  ws.notify({
+  wex.ws.notify({
     type: NotificationType.BalanceChange,
     hintTransactionId: ctx.transactionId,
   });
 
-  notifyTransition(ws, ctx.transactionId, transitionInfo);
-  ws.taskScheduler.startShepherdTask(ctx.taskId);
+  notifyTransition(wex, ctx.transactionId, transitionInfo);
+  wex.taskScheduler.startShepherdTask(ctx.taskId);
 
   return {
     talerUri: stringifyTalerUri({
diff --git a/packages/taler-wallet-core/src/pay-peer-pull-debit.ts 
b/packages/taler-wallet-core/src/pay-peer-pull-debit.ts
index 30bd1a2c8..828f68113 100644
--- a/packages/taler-wallet-core/src/pay-peer-pull-debit.ts
+++ b/packages/taler-wallet-core/src/pay-peer-pull-debit.ts
@@ -95,7 +95,7 @@ import {
   notifyTransition,
   parseTransactionIdentifier,
 } from "./transactions.js";
-import { InternalWalletState } from "./wallet.js";
+import { InternalWalletState, WalletExecutionContext } from "./wallet.js";
 
 const logger = new Logger("pay-peer-pull-debit.ts");
 
@@ -103,13 +103,13 @@ const logger = new Logger("pay-peer-pull-debit.ts");
  * Common context for a peer-pull-debit transaction.
  */
 export class PeerPullDebitTransactionContext implements TransactionContext {
-  ws: InternalWalletState;
+  wex: WalletExecutionContext;
   readonly transactionId: TransactionIdStr;
   readonly taskId: TaskIdStr;
   peerPullDebitId: string;
 
-  constructor(ws: InternalWalletState, peerPullDebitId: string) {
-    this.ws = ws;
+  constructor(wex: WalletExecutionContext, peerPullDebitId: string) {
+    this.wex = wex;
     this.transactionId = constructTransactionIdentifier({
       tag: TransactionType.PeerPullDebit,
       peerPullDebitId,
@@ -123,7 +123,7 @@ export class PeerPullDebitTransactionContext implements 
TransactionContext {
 
   async deleteTransaction(): Promise<void> {
     const transactionId = this.transactionId;
-    const ws = this.ws;
+    const ws = this.wex;
     const peerPullDebitId = this.peerPullDebitId;
     await ws.db.runReadWriteTx(["peerPullDebit", "tombstones"], async (tx) => {
       const debit = await tx.peerPullDebit.get(peerPullDebitId);
@@ -137,9 +137,9 @@ export class PeerPullDebitTransactionContext implements 
TransactionContext {
   async suspendTransaction(): Promise<void> {
     const taskId = this.taskId;
     const transactionId = this.transactionId;
-    const ws = this.ws;
+    const wex = this.wex;
     const peerPullDebitId = this.peerPullDebitId;
-    const transitionInfo = await ws.db.runReadWriteTx(
+    const transitionInfo = await wex.db.runReadWriteTx(
       ["peerPullDebit"],
       async (tx) => {
         const pullDebitRec = await tx.peerPullDebit.get(peerPullDebitId);
@@ -183,8 +183,8 @@ export class PeerPullDebitTransactionContext implements 
TransactionContext {
         return undefined;
       },
     );
-    notifyTransition(ws, transactionId, transitionInfo);
-    ws.taskScheduler.stopShepherdTask(taskId);
+    notifyTransition(wex, transactionId, transitionInfo);
+    wex.taskScheduler.stopShepherdTask(taskId);
   }
 
   async resumeTransaction(): Promise<void> {
@@ -206,7 +206,7 @@ export class PeerPullDebitTransactionContext implements 
TransactionContext {
           return TransitionResult.Stay;
       }
     });
-    this.ws.taskScheduler.startShepherdTask(this.taskId);
+    this.wex.taskScheduler.startShepherdTask(this.taskId);
   }
 
   async failTransaction(): Promise<void> {
@@ -224,7 +224,7 @@ export class PeerPullDebitTransactionContext implements 
TransactionContext {
           return TransitionResult.Stay;
       }
     });
-    this.ws.taskScheduler.stopShepherdTask(this.taskId);
+    this.wex.taskScheduler.stopShepherdTask(this.taskId);
   }
 
   async abortTransaction(): Promise<void> {
@@ -262,7 +262,7 @@ export class PeerPullDebitTransactionContext implements 
TransactionContext {
         }
 
         const refresh = await createRefreshGroup(
-          ctx.ws,
+          ctx.wex,
           tx,
           currency,
           coinPubs,
@@ -300,7 +300,7 @@ export class PeerPullDebitTransactionContext implements 
TransactionContext {
       >,
     ) => Promise<TransitionResult>,
   ): Promise<void> {
-    const ws = this.ws;
+    const ws = this.wex;
     const extraStores = opts.extraStores ?? [];
     const transitionInfo = await ws.db.runReadWriteTx(
       ["peerPullDebit", ...extraStores],
@@ -336,7 +336,7 @@ async function handlePurseCreationConflict(
   peerPullInc: PeerPullPaymentIncomingRecord,
   resp: HttpResponse,
 ): Promise<TaskRunResult> {
-  const ws = ctx.ws;
+  const ws = ctx.wex;
   const errResp = await readTalerErrorResponse(resp);
   if (errResp.code !== TalerErrorCode.EXCHANGE_GENERIC_INSUFFICIENT_FUNDS) {
     await ctx.failTransaction();
@@ -411,9 +411,8 @@ async function handlePurseCreationConflict(
 }
 
 async function processPeerPullDebitPendingDeposit(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   peerPullInc: PeerPullPaymentIncomingRecord,
-  cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
   const pursePub = peerPullInc.pursePub;
 
@@ -422,9 +421,9 @@ async function processPeerPullDebitPendingDeposit(
     throw Error("invalid state, no coins selected");
   }
 
-  const coins = await queryCoinInfosForSelection(ws, coinSel);
+  const coins = await queryCoinInfosForSelection(wex, coinSel);
 
-  const depositSigsResp = await ws.cryptoApi.signPurseDeposits({
+  const depositSigsResp = await wex.cryptoApi.signPurseDeposits({
     exchangeBaseUrl: peerPullInc.exchangeBaseUrl,
     pursePub: peerPullInc.pursePub,
     coins,
@@ -443,14 +442,14 @@ async function processPeerPullDebitPendingDeposit(
     logger.trace(`purse deposit payload: ${j2s(depositPayload)}`);
   }
 
-  const httpResp = await ws.http.fetch(purseDepositUrl.href, {
+  const httpResp = await wex.http.fetch(purseDepositUrl.href, {
     method: "POST",
     body: depositPayload,
-    cancellationToken,
+    cancellationToken: wex.cancellationToken,
   });
 
   const ctx = new PeerPullDebitTransactionContext(
-    ws,
+    wex,
     peerPullInc.peerPullDebitId,
   );
 
@@ -489,9 +488,8 @@ async function processPeerPullDebitPendingDeposit(
 }
 
 async function processPeerPullDebitAbortingRefresh(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   peerPullInc: PeerPullPaymentIncomingRecord,
-  _cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
   const peerPullDebitId = peerPullInc.peerPullDebitId;
   const abortRefreshGroupId = peerPullInc.abortRefreshGroupId;
@@ -500,7 +498,7 @@ async function processPeerPullDebitAbortingRefresh(
     tag: TransactionType.PeerPullDebit,
     peerPullDebitId,
   });
-  const transitionInfo = await ws.db.runReadWriteTx(
+  const transitionInfo = await wex.db.runReadWriteTx(
     ["peerPullDebit", "refreshGroups"],
     async (tx) => {
       const refreshGroup = await tx.refreshGroups.get(abortRefreshGroupId);
@@ -533,17 +531,16 @@ async function processPeerPullDebitAbortingRefresh(
       return undefined;
     },
   );
-  notifyTransition(ws, transactionId, transitionInfo);
+  notifyTransition(wex, transactionId, transitionInfo);
   // FIXME: Shouldn't this be finished in some cases?!
   return TaskRunResult.backoff();
 }
 
 export async function processPeerPullDebit(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   peerPullDebitId: string,
-  cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
-  const peerPullInc = await ws.db.runReadOnlyTx(
+  const peerPullInc = await wex.db.runReadOnlyTx(
     ["peerPullDebit"],
     async (tx) => {
       return tx.peerPullDebit.get(peerPullDebitId);
@@ -556,22 +553,20 @@ export async function processPeerPullDebit(
   switch (peerPullInc.status) {
     case PeerPullDebitRecordStatus.PendingDeposit:
       return await processPeerPullDebitPendingDeposit(
-        ws,
+        wex,
         peerPullInc,
-        cancellationToken,
       );
     case PeerPullDebitRecordStatus.AbortingRefresh:
       return await processPeerPullDebitAbortingRefresh(
-        ws,
+        wex,
         peerPullInc,
-        cancellationToken,
       );
   }
   return TaskRunResult.finished();
 }
 
 export async function confirmPeerPullDebit(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: ConfirmPeerPullDebitRequest,
 ): Promise<AcceptPeerPullPaymentResponse> {
   let peerPullDebitId: string;
@@ -588,7 +583,7 @@ export async function confirmPeerPullDebit(
     throw Error("invalid request, transactionId or peerPullDebitId required");
   }
 
-  const peerPullInc = await ws.db.runReadOnlyTx(
+  const peerPullInc = await wex.db.runReadOnlyTx(
     ["peerPullDebit"],
     async (tx) => {
       return tx.peerPullDebit.get(peerPullDebitId);
@@ -603,7 +598,7 @@ export async function confirmPeerPullDebit(
 
   const instructedAmount = Amounts.parseOrThrow(peerPullInc.amount);
 
-  const coinSelRes = await selectPeerCoins(ws, { instructedAmount });
+  const coinSelRes = await selectPeerCoins(wex, { instructedAmount });
   if (logger.shouldLogTrace()) {
     logger.trace(`selected p2p coins (pull): ${j2s(coinSelRes)}`);
   }
@@ -620,11 +615,11 @@ export async function confirmPeerPullDebit(
   const sel = coinSelRes.result;
 
   const totalAmount = await getTotalPeerPaymentCost(
-    ws,
+    wex,
     coinSelRes.result.coins,
   );
 
-  await ws.db.runReadWriteTx(
+  await wex.db.runReadWriteTx(
     [
       "exchanges",
       "coins",
@@ -634,7 +629,7 @@ export async function confirmPeerPullDebit(
       "coinAvailability",
     ],
     async (tx) => {
-      await spendCoins(ws, tx, {
+      await spendCoins(wex, tx, {
         // allocationId: `txn:peer-pull-debit:${req.peerPullDebitId}`,
         allocationId: constructTransactionIdentifier({
           tag: TransactionType.PeerPullDebit,
@@ -663,16 +658,16 @@ export async function confirmPeerPullDebit(
     },
   );
 
-  const ctx = new PeerPullDebitTransactionContext(ws, peerPullDebitId);
+  const ctx = new PeerPullDebitTransactionContext(wex, peerPullDebitId);
 
   const transactionId = ctx.transactionId;
 
-  ws.notify({
+  wex.ws.notify({
     type: NotificationType.BalanceChange,
     hintTransactionId: transactionId,
   });
 
-  ws.taskScheduler.startShepherdTask(ctx.taskId);
+  wex.taskScheduler.startShepherdTask(ctx.taskId);
 
   return {
     transactionId,
@@ -684,7 +679,7 @@ export async function confirmPeerPullDebit(
  * Store the results in the wallet DB.
  */
 export async function preparePeerPullDebit(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: PreparePeerPullDebitRequest,
 ): Promise<PreparePeerPullDebitResponse> {
   const uri = parsePayPullUri(req.talerUri);
@@ -693,7 +688,7 @@ export async function preparePeerPullDebit(
     throw Error("got invalid taler://pay-pull URI");
   }
 
-  const existing = await ws.db.runReadOnlyTx(
+  const existing = await wex.db.runReadOnlyTx(
     ["peerPullDebit", "contractTerms"],
     async (tx) => {
       const peerPullDebitRecord =
@@ -734,7 +729,7 @@ export async function preparePeerPullDebit(
 
   const getContractUrl = new URL(`contracts/${contractPub}`, exchangeBaseUrl);
 
-  const contractHttpResp = await ws.http.fetch(getContractUrl.href);
+  const contractHttpResp = await wex.http.fetch(getContractUrl.href);
 
   const contractResp = await readSuccessResponseJsonOrThrow(
     contractHttpResp,
@@ -743,7 +738,7 @@ export async function preparePeerPullDebit(
 
   const pursePub = contractResp.purse_pub;
 
-  const dec = await ws.cryptoApi.decryptContractForDeposit({
+  const dec = await wex.cryptoApi.decryptContractForDeposit({
     ciphertext: contractResp.econtract,
     contractPriv: contractPriv,
     pursePub: pursePub,
@@ -751,7 +746,7 @@ export async function preparePeerPullDebit(
 
   const getPurseUrl = new URL(`purses/${pursePub}/merge`, exchangeBaseUrl);
 
-  const purseHttpResp = await ws.http.fetch(getPurseUrl.href);
+  const purseHttpResp = await wex.http.fetch(getPurseUrl.href);
 
   const purseStatus = await readSuccessResponseJsonOrThrow(
     purseHttpResp,
@@ -777,7 +772,7 @@ export async function preparePeerPullDebit(
 
   const instructedAmount = Amounts.parseOrThrow(contractTerms.amount);
 
-  const coinSelRes = await selectPeerCoins(ws, { instructedAmount });
+  const coinSelRes = await selectPeerCoins(wex, { instructedAmount });
   if (logger.shouldLogTrace()) {
     logger.trace(`selected p2p coins (pull): ${j2s(coinSelRes)}`);
   }
@@ -792,11 +787,11 @@ export async function preparePeerPullDebit(
   }
 
   const totalAmount = await getTotalPeerPaymentCost(
-    ws,
+    wex,
     coinSelRes.result.coins,
   );
 
-  await ws.db.runReadWriteTx(["peerPullDebit", "contractTerms"], async (tx) => 
{
+  await wex.db.runReadWriteTx(["peerPullDebit", "contractTerms"], async (tx) 
=> {
     await tx.contractTerms.put({
       h: contractTermsHash,
       contractTermsRaw: contractTerms,
diff --git a/packages/taler-wallet-core/src/pay-peer-push-credit.ts 
b/packages/taler-wallet-core/src/pay-peer-push-credit.ts
index e629bffe4..772007bb6 100644
--- a/packages/taler-wallet-core/src/pay-peer-push-credit.ts
+++ b/packages/taler-wallet-core/src/pay-peer-push-credit.ts
@@ -83,7 +83,7 @@ import {
   notifyTransition,
   parseTransactionIdentifier,
 } from "./transactions.js";
-import { InternalWalletState } from "./wallet.js";
+import { InternalWalletState, WalletExecutionContext } from "./wallet.js";
 import {
   PerformCreateWithdrawalGroupResult,
   getExchangeWithdrawalInfo,
@@ -98,7 +98,7 @@ export class PeerPushCreditTransactionContext implements 
TransactionContext {
   readonly taskId: TaskIdStr;
 
   constructor(
-    public ws: InternalWalletState,
+    public wex: WalletExecutionContext,
     public peerPushCreditId: string,
   ) {
     this.transactionId = constructTransactionIdentifier({
@@ -112,8 +112,8 @@ export class PeerPushCreditTransactionContext implements 
TransactionContext {
   }
 
   async deleteTransaction(): Promise<void> {
-    const { ws, peerPushCreditId } = this;
-    await ws.db.runReadWriteTx(
+    const { wex, peerPushCreditId } = this;
+    await wex.db.runReadWriteTx(
       ["withdrawalGroups", "peerPushCredit", "tombstones"],
       async (tx) => {
         const pushInc = await tx.peerPushCredit.get(peerPushCreditId);
@@ -141,8 +141,8 @@ export class PeerPushCreditTransactionContext implements 
TransactionContext {
   }
 
   async suspendTransaction(): Promise<void> {
-    const { ws, peerPushCreditId, taskId: retryTag, transactionId } = this;
-    const transitionInfo = await ws.db.runReadWriteTx(
+    const { wex, peerPushCreditId, taskId: retryTag, transactionId } = this;
+    const transitionInfo = await wex.db.runReadWriteTx(
       ["peerPushCredit"],
       async (tx) => {
         const pushCreditRec = await tx.peerPushCredit.get(peerPushCreditId);
@@ -190,13 +190,13 @@ export class PeerPushCreditTransactionContext implements 
TransactionContext {
         return undefined;
       },
     );
-    notifyTransition(ws, transactionId, transitionInfo);
-    ws.taskScheduler.stopShepherdTask(retryTag);
+    notifyTransition(wex, transactionId, transitionInfo);
+    wex.taskScheduler.stopShepherdTask(retryTag);
   }
 
   async abortTransaction(): Promise<void> {
-    const { ws, peerPushCreditId, taskId: retryTag, transactionId } = this;
-    const transitionInfo = await ws.db.runReadWriteTx(
+    const { wex, peerPushCreditId, taskId: retryTag, transactionId } = this;
+    const transitionInfo = await wex.db.runReadWriteTx(
       ["peerPushCredit"],
       async (tx) => {
         const pushCreditRec = await tx.peerPushCredit.get(peerPushCreditId);
@@ -247,12 +247,12 @@ export class PeerPushCreditTransactionContext implements 
TransactionContext {
         return undefined;
       },
     );
-    notifyTransition(ws, transactionId, transitionInfo);
-    ws.taskScheduler.startShepherdTask(retryTag);
+    notifyTransition(wex, transactionId, transitionInfo);
+    wex.taskScheduler.startShepherdTask(retryTag);
   }
 
   async resumeTransaction(): Promise<void> {
-    const { ws, peerPushCreditId, taskId: retryTag, transactionId } = this;
+    const { wex: ws, peerPushCreditId, taskId: retryTag, transactionId } = 
this;
     const transitionInfo = await ws.db.runReadWriteTx(
       ["peerPushCredit"],
       async (tx) => {
@@ -305,7 +305,7 @@ export class PeerPushCreditTransactionContext implements 
TransactionContext {
   }
 
   async failTransaction(): Promise<void> {
-    const { ws, peerPushCreditId, taskId: retryTag, transactionId } = this;
+    const { wex: ws, peerPushCreditId, taskId: retryTag, transactionId } = 
this;
     const transitionInfo = await ws.db.runReadWriteTx(
       ["peerPushCredit"],
       async (tx) => {
@@ -355,7 +355,7 @@ export class PeerPushCreditTransactionContext implements 
TransactionContext {
 }
 
 export async function preparePeerPushCredit(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: PreparePeerPushCreditRequest,
 ): Promise<PreparePeerPushCreditResponse> {
   const uri = parsePayPushUri(req.talerUri);
@@ -364,7 +364,7 @@ export async function preparePeerPushCredit(
     throw Error("got invalid taler://pay-push URI");
   }
 
-  const existing = await ws.db.runReadOnlyTx(
+  const existing = await wex.db.runReadOnlyTx(
     ["contractTerms", "peerPushCredit"],
     async (tx) => {
       const existingPushInc =
@@ -407,14 +407,14 @@ export async function preparePeerPushCredit(
 
   const exchangeBaseUrl = uri.exchangeBaseUrl;
 
-  await fetchFreshExchange(ws, exchangeBaseUrl);
+  await fetchFreshExchange(wex, exchangeBaseUrl);
 
   const contractPriv = uri.contractPriv;
   const contractPub = encodeCrock(eddsaGetPublic(decodeCrock(contractPriv)));
 
   const getContractUrl = new URL(`contracts/${contractPub}`, exchangeBaseUrl);
 
-  const contractHttpResp = await ws.http.fetch(getContractUrl.href);
+  const contractHttpResp = await wex.http.fetch(getContractUrl.href);
 
   const contractResp = await readSuccessResponseJsonOrThrow(
     contractHttpResp,
@@ -423,7 +423,7 @@ export async function preparePeerPushCredit(
 
   const pursePub = contractResp.purse_pub;
 
-  const dec = await ws.cryptoApi.decryptContractForMerge({
+  const dec = await wex.cryptoApi.decryptContractForMerge({
     ciphertext: contractResp.econtract,
     contractPriv: contractPriv,
     pursePub: pursePub,
@@ -431,7 +431,7 @@ export async function preparePeerPushCredit(
 
   const getPurseUrl = new URL(`purses/${pursePub}/deposit`, exchangeBaseUrl);
 
-  const purseHttpResp = await ws.http.fetch(getPurseUrl.href);
+  const purseHttpResp = await wex.http.fetch(getPurseUrl.href);
 
   const contractTerms = codecForPeerContractTerms().decode(dec.contractTerms);
 
@@ -453,13 +453,13 @@ export async function preparePeerPushCredit(
   const withdrawalGroupId = encodeCrock(getRandomBytes(32));
 
   const wi = await getExchangeWithdrawalInfo(
-    ws,
+    wex,
     exchangeBaseUrl,
     Amounts.parseOrThrow(purseStatus.balance),
     undefined,
   );
 
-  const transitionInfo = await ws.db.runReadWriteTx(
+  const transitionInfo = await wex.db.runReadWriteTx(
     ["contractTerms", "peerPushCredit"],
     async (tx) => {
       const rec: PeerPushPaymentIncomingRecord = {
@@ -499,9 +499,9 @@ export async function preparePeerPushCredit(
     peerPushCreditId,
   });
 
-  notifyTransition(ws, transactionId, transitionInfo);
+  notifyTransition(wex, transactionId, transitionInfo);
 
-  ws.notify({
+  wex.ws.notify({
     type: NotificationType.BalanceChange,
     hintTransactionId: transactionId,
   });
@@ -518,12 +518,11 @@ export async function preparePeerPushCredit(
 }
 
 async function longpollKycStatus(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   peerPushCreditId: string,
   exchangeUrl: string,
   kycInfo: KycPendingInfo,
   userType: KycUserType,
-  cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
   const transactionId = constructTransactionIdentifier({
     tag: TransactionType.PeerPushCredit,
@@ -535,9 +534,9 @@ async function longpollKycStatus(
   );
   url.searchParams.set("timeout_ms", "30000");
   logger.info(`kyc url ${url.href}`);
-  const kycStatusRes = await ws.http.fetch(url.href, {
+  const kycStatusRes = await wex.http.fetch(url.href, {
     method: "GET",
-    cancellationToken,
+    cancellationToken: wex.cancellationToken,
   });
   if (
     kycStatusRes.status === HttpStatusCode.Ok ||
@@ -545,7 +544,7 @@ async function longpollKycStatus(
     // remove after the exchange is fixed or clarified
     kycStatusRes.status === HttpStatusCode.NoContent
   ) {
-    const transitionInfo = await ws.db.runReadWriteTx(
+    const transitionInfo = await wex.db.runReadWriteTx(
       ["peerPushCredit"],
       async (tx) => {
         const peerInc = await tx.peerPushCredit.get(peerPushCreditId);
@@ -562,7 +561,7 @@ async function longpollKycStatus(
         return { oldTxState, newTxState };
       },
     );
-    notifyTransition(ws, transactionId, transitionInfo);
+    notifyTransition(wex, transactionId, transitionInfo);
     return TaskRunResult.progress();
   } else if (kycStatusRes.status === HttpStatusCode.Accepted) {
     // FIXME: Do we have to update the URL here?
@@ -573,10 +572,9 @@ async function longpollKycStatus(
 }
 
 async function processPeerPushCreditKycRequired(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   peerInc: PeerPushPaymentIncomingRecord,
   kycPending: WalletKycUuid,
-  cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
   const transactionId = constructTransactionIdentifier({
     tag: TransactionType.PeerPushCredit,
@@ -591,9 +589,9 @@ async function processPeerPushCreditKycRequired(
   );
 
   logger.info(`kyc url ${url.href}`);
-  const kycStatusRes = await ws.http.fetch(url.href, {
+  const kycStatusRes = await wex.http.fetch(url.href, {
     method: "GET",
-    cancellationToken,
+    cancellationToken: wex.cancellationToken,
   });
 
   if (
@@ -607,7 +605,7 @@ async function processPeerPushCreditKycRequired(
   } else if (kycStatusRes.status === HttpStatusCode.Accepted) {
     const kycStatus = await kycStatusRes.json();
     logger.info(`kyc status: ${j2s(kycStatus)}`);
-    const { transitionInfo, result } = await ws.db.runReadWriteTx(
+    const { transitionInfo, result } = await wex.db.runReadWriteTx(
       ["peerPushCredit"],
       async (tx) => {
         const peerInc = await tx.peerPushCredit.get(peerPushCreditId);
@@ -643,7 +641,7 @@ async function processPeerPushCreditKycRequired(
         };
       },
     );
-    notifyTransition(ws, transactionId, transitionInfo);
+    notifyTransition(wex, transactionId, transitionInfo);
     return result;
   } else {
     throw Error(`unexpected response from kyc-check (${kycStatusRes.status})`);
@@ -651,10 +649,9 @@ async function processPeerPushCreditKycRequired(
 }
 
 async function handlePendingMerge(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   peerInc: PeerPushPaymentIncomingRecord,
   contractTerms: PeerContractTerms,
-  cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
   const { peerPushCreditId } = peerInc;
   const transactionId = constructTransactionIdentifier({
@@ -664,7 +661,7 @@ async function handlePendingMerge(
 
   const amount = Amounts.parseOrThrow(contractTerms.amount);
 
-  const mergeReserveInfo = await getMergeReserveInfo(ws, {
+  const mergeReserveInfo = await getMergeReserveInfo(wex, {
     exchangeBaseUrl: peerInc.exchangeBaseUrl,
   });
 
@@ -675,7 +672,7 @@ async function handlePendingMerge(
     mergeReserveInfo.reservePub,
   );
 
-  const sigRes = await ws.cryptoApi.signPurseMerge({
+  const sigRes = await wex.cryptoApi.signPurseMerge({
     contractTermsHash: ContractTermsUtil.hashContractTerms(contractTerms),
     flags: WalletAccountMergeFlags.MergeFullyPaidPurse,
     mergePriv: peerInc.mergePriv,
@@ -700,7 +697,7 @@ async function handlePendingMerge(
     reserve_sig: sigRes.accountSig,
   };
 
-  const mergeHttpResp = await ws.http.fetch(mergePurseUrl.href, {
+  const mergeHttpResp = await wex.http.fetch(mergePurseUrl.href, {
     method: "POST",
     body: mergeReq,
   });
@@ -710,10 +707,9 @@ async function handlePendingMerge(
     const kycPending = codecForWalletKycUuid().decode(respJson);
     logger.info(`kyc uuid response: ${j2s(kycPending)}`);
     return processPeerPushCreditKycRequired(
-      ws,
+      wex,
       peerInc,
       kycPending,
-      cancellationToken,
     );
   }
 
@@ -724,7 +720,7 @@ async function handlePendingMerge(
   );
   logger.trace(`merge response: ${j2s(res)}`);
 
-  const withdrawalGroupPrep = await internalPrepareCreateWithdrawalGroup(ws, {
+  const withdrawalGroupPrep = await internalPrepareCreateWithdrawalGroup(wex, {
     amount,
     wgInfo: {
       withdrawalType: WithdrawalRecordType.PeerPushCredit,
@@ -738,7 +734,7 @@ async function handlePendingMerge(
     },
   });
 
-  const txRes = await ws.db.runReadWriteTx(
+  const txRes = await wex.db.runReadWriteTx(
     [
       "contractTerms",
       "peerPushCredit",
@@ -760,7 +756,7 @@ async function handlePendingMerge(
         case PeerPushCreditStatus.PendingMergeKycRequired: {
           peerInc.status = PeerPushCreditStatus.PendingWithdrawing;
           wgCreateRes = await internalPerformCreateWithdrawalGroup(
-            ws,
+            wex,
             tx,
             withdrawalGroupPrep,
           );
@@ -779,20 +775,20 @@ async function handlePendingMerge(
   );
   // Transaction was committed, now we can emit notifications.
   if (txRes?.wgCreateRes?.exchangeNotif) {
-    ws.notify(txRes.wgCreateRes.exchangeNotif);
+    wex.ws.notify(txRes.wgCreateRes.exchangeNotif);
   }
   notifyTransition(
-    ws,
+    wex,
     withdrawalGroupPrep.transactionId,
     txRes?.wgCreateRes?.transitionInfo,
   );
-  notifyTransition(ws, transactionId, txRes?.peerPushCreditTransition);
+  notifyTransition(wex, transactionId, txRes?.peerPushCreditTransition);
 
   return TaskRunResult.backoff();
 }
 
 async function handlePendingWithdrawing(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   peerInc: PeerPushPaymentIncomingRecord,
 ): Promise<TaskRunResult> {
   if (!peerInc.withdrawalGroupId) {
@@ -804,7 +800,7 @@ async function handlePendingWithdrawing(
   });
   const wgId = peerInc.withdrawalGroupId;
   let finished: boolean = false;
-  const transitionInfo = await ws.db.runReadWriteTx(
+  const transitionInfo = await wex.db.runReadWriteTx(
     ["peerPushCredit", "withdrawalGroups"],
     async (tx) => {
       const ppi = await tx.peerPushCredit.get(peerInc.peerPushCreditId);
@@ -837,7 +833,7 @@ async function handlePendingWithdrawing(
       };
     },
   );
-  notifyTransition(ws, transactionId, transitionInfo);
+  notifyTransition(wex, transactionId, transitionInfo);
   if (finished) {
     return TaskRunResult.finished();
   } else {
@@ -847,13 +843,12 @@ async function handlePendingWithdrawing(
 }
 
 export async function processPeerPushCredit(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   peerPushCreditId: string,
-  cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
   let peerInc: PeerPushPaymentIncomingRecord | undefined;
   let contractTerms: PeerContractTerms | undefined;
-  await ws.db.runReadWriteTx(
+  await wex.db.runReadWriteTx(
     ["contractTerms", "peerPushCredit"],
     async (tx) => {
       peerInc = await tx.peerPushCredit.get(peerPushCreditId);
@@ -886,20 +881,19 @@ export async function processPeerPushCredit(
         throw Error("invalid state, kycInfo required");
       }
       return await longpollKycStatus(
-        ws,
+        wex,
         peerPushCreditId,
         peerInc.exchangeBaseUrl,
         peerInc.kycInfo,
         "individual",
-        cancellationToken,
       );
     }
 
     case PeerPushCreditStatus.PendingMerge:
-      return handlePendingMerge(ws, peerInc, contractTerms, cancellationToken);
+      return handlePendingMerge(wex, peerInc, contractTerms);
 
     case PeerPushCreditStatus.PendingWithdrawing:
-      return handlePendingWithdrawing(ws, peerInc);
+      return handlePendingWithdrawing(wex, peerInc);
 
     default:
       return TaskRunResult.finished();
@@ -907,7 +901,7 @@ export async function processPeerPushCredit(
 }
 
 export async function confirmPeerPushCredit(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: ConfirmPeerPushCreditRequest,
 ): Promise<AcceptPeerPushPaymentResponse> {
   let peerInc: PeerPushPaymentIncomingRecord | undefined;
@@ -927,7 +921,7 @@ export async function confirmPeerPushCredit(
     throw Error("no transaction ID (or deprecated peerPushCreditId) provided");
   }
 
-  await ws.db.runReadWriteTx(
+  await wex.db.runReadWriteTx(
     ["contractTerms", "peerPushCredit"],
     async (tx) => {
       peerInc = await tx.peerPushCredit.get(peerPushCreditId);
@@ -947,9 +941,9 @@ export async function confirmPeerPushCredit(
     );
   }
 
-  const ctx = new PeerPushCreditTransactionContext(ws, peerPushCreditId);
+  const ctx = new PeerPushCreditTransactionContext(wex, peerPushCreditId);
 
-  ws.taskScheduler.startShepherdTask(ctx.taskId);
+  wex.taskScheduler.startShepherdTask(ctx.taskId);
 
   const transactionId = constructTransactionIdentifier({
     tag: TransactionType.PeerPushCredit,
diff --git a/packages/taler-wallet-core/src/pay-peer-push-debit.ts 
b/packages/taler-wallet-core/src/pay-peer-push-debit.ts
index 40a5d97a4..5ee4d642b 100644
--- a/packages/taler-wallet-core/src/pay-peer-push-debit.ts
+++ b/packages/taler-wallet-core/src/pay-peer-push-debit.ts
@@ -16,7 +16,6 @@
 
 import {
   Amounts,
-  CancellationToken,
   CheckPeerPushDebitRequest,
   CheckPeerPushDebitResponse,
   CoinRefreshRequest,
@@ -78,7 +77,7 @@ import {
   constructTransactionIdentifier,
   notifyTransition,
 } from "./transactions.js";
-import { InternalWalletState } from "./wallet.js";
+import { WalletExecutionContext } from "./wallet.js";
 
 const logger = new Logger("pay-peer-push-debit.ts");
 
@@ -87,7 +86,7 @@ export class PeerPushDebitTransactionContext implements 
TransactionContext {
   readonly taskId: TaskIdStr;
 
   constructor(
-    public ws: InternalWalletState,
+    public wex: WalletExecutionContext,
     public pursePub: string,
   ) {
     this.transactionId = constructTransactionIdentifier({
@@ -101,8 +100,8 @@ export class PeerPushDebitTransactionContext implements 
TransactionContext {
   }
 
   async deleteTransaction(): Promise<void> {
-    const { ws, pursePub, transactionId } = this;
-    await ws.db.runReadWriteTx(["peerPushDebit", "tombstones"], async (tx) => {
+    const { wex, pursePub, transactionId } = this;
+    await wex.db.runReadWriteTx(["peerPushDebit", "tombstones"], async (tx) => 
{
       const debit = await tx.peerPushDebit.get(pursePub);
       if (debit) {
         await tx.peerPushDebit.delete(pursePub);
@@ -112,8 +111,8 @@ export class PeerPushDebitTransactionContext implements 
TransactionContext {
   }
 
   async suspendTransaction(): Promise<void> {
-    const { ws, pursePub, transactionId, taskId: retryTag } = this;
-    const transitionInfo = await ws.db.runReadWriteTx(
+    const { wex, pursePub, transactionId, taskId: retryTag } = this;
+    const transitionInfo = await wex.db.runReadWriteTx(
       ["peerPushDebit"],
       async (tx) => {
         const pushDebitRec = await tx.peerPushDebit.get(pursePub);
@@ -165,13 +164,13 @@ export class PeerPushDebitTransactionContext implements 
TransactionContext {
         return undefined;
       },
     );
-    ws.taskScheduler.stopShepherdTask(retryTag);
-    notifyTransition(ws, transactionId, transitionInfo);
+    wex.taskScheduler.stopShepherdTask(retryTag);
+    notifyTransition(wex, transactionId, transitionInfo);
   }
 
   async abortTransaction(): Promise<void> {
-    const { ws, pursePub, transactionId, taskId: retryTag } = this;
-    const transitionInfo = await ws.db.runReadWriteTx(
+    const { wex, pursePub, transactionId, taskId: retryTag } = this;
+    const transitionInfo = await wex.db.runReadWriteTx(
       ["peerPushDebit"],
       async (tx) => {
         const pushDebitRec = await tx.peerPushDebit.get(pursePub);
@@ -218,14 +217,14 @@ export class PeerPushDebitTransactionContext implements 
TransactionContext {
         return undefined;
       },
     );
-    ws.taskScheduler.stopShepherdTask(retryTag);
-    notifyTransition(ws, transactionId, transitionInfo);
-    ws.taskScheduler.startShepherdTask(retryTag);
+    wex.taskScheduler.stopShepherdTask(retryTag);
+    notifyTransition(wex, transactionId, transitionInfo);
+    wex.taskScheduler.startShepherdTask(retryTag);
   }
 
   async resumeTransaction(): Promise<void> {
-    const { ws, pursePub, transactionId, taskId: retryTag } = this;
-    const transitionInfo = await ws.db.runReadWriteTx(
+    const { wex, pursePub, transactionId, taskId: retryTag } = this;
+    const transitionInfo = await wex.db.runReadWriteTx(
       ["peerPushDebit"],
       async (tx) => {
         const pushDebitRec = await tx.peerPushDebit.get(pursePub);
@@ -277,12 +276,12 @@ export class PeerPushDebitTransactionContext implements 
TransactionContext {
         return undefined;
       },
     );
-    ws.taskScheduler.startShepherdTask(retryTag);
-    notifyTransition(ws, transactionId, transitionInfo);
+    wex.taskScheduler.startShepherdTask(retryTag);
+    notifyTransition(wex, transactionId, transitionInfo);
   }
 
   async failTransaction(): Promise<void> {
-    const { ws, pursePub, transactionId, taskId: retryTag } = this;
+    const { wex: ws, pursePub, transactionId, taskId: retryTag } = this;
     const transitionInfo = await ws.db.runReadWriteTx(
       ["peerPushDebit"],
       async (tx) => {
@@ -337,14 +336,14 @@ export class PeerPushDebitTransactionContext implements 
TransactionContext {
 }
 
 export async function checkPeerPushDebit(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: CheckPeerPushDebitRequest,
 ): Promise<CheckPeerPushDebitResponse> {
   const instructedAmount = Amounts.parseOrThrow(req.amount);
   logger.trace(
     `checking peer push debit for ${Amounts.stringify(instructedAmount)}`,
   );
-  const coinSelRes = await selectPeerCoins(ws, { instructedAmount });
+  const coinSelRes = await selectPeerCoins(wex, { instructedAmount });
   if (coinSelRes.type === "failure") {
     throw TalerError.fromDetail(
       TalerErrorCode.WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE,
@@ -355,7 +354,7 @@ export async function checkPeerPushDebit(
   }
   logger.trace(`selected peer coins (len=${coinSelRes.result.coins.length})`);
   const totalAmount = await getTotalPeerPaymentCost(
-    ws,
+    wex,
     coinSelRes.result.coins,
   );
   logger.trace("computed total peer payment cost");
@@ -368,13 +367,13 @@ export async function checkPeerPushDebit(
 }
 
 async function handlePurseCreationConflict(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   peerPushInitiation: PeerPushDebitRecord,
   resp: HttpResponse,
 ): Promise<TaskRunResult> {
   const pursePub = peerPushInitiation.pursePub;
   const errResp = await readTalerErrorResponse(resp);
-  const ctx = new PeerPushDebitTransactionContext(ws, pursePub);
+  const ctx = new PeerPushDebitTransactionContext(wex, pursePub);
   if (errResp.code !== TalerErrorCode.EXCHANGE_GENERIC_INSUFFICIENT_FUNDS) {
     await ctx.failTransaction();
     return TaskRunResult.finished();
@@ -405,7 +404,7 @@ async function handlePurseCreationConflict(
     }
   }
 
-  const coinSelRes = await selectPeerCoins(ws, { instructedAmount, repair });
+  const coinSelRes = await selectPeerCoins(wex, { instructedAmount, repair });
 
   if (coinSelRes.type == "failure") {
     // FIXME: Details!
@@ -414,7 +413,7 @@ async function handlePurseCreationConflict(
     );
   }
 
-  await ws.db.runReadWriteTx(["peerPushDebit"], async (tx) => {
+  await wex.db.runReadWriteTx(["peerPushDebit"], async (tx) => {
     const myPpi = await tx.peerPushDebit.get(peerPushInitiation.pursePub);
     if (!myPpi) {
       return;
@@ -438,19 +437,18 @@ async function handlePurseCreationConflict(
 }
 
 async function processPeerPushDebitCreateReserve(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   peerPushInitiation: PeerPushDebitRecord,
-  cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
   const pursePub = peerPushInitiation.pursePub;
   const purseExpiration = peerPushInitiation.purseExpiration;
   const hContractTerms = peerPushInitiation.contractTermsHash;
-  const ctx = new PeerPushDebitTransactionContext(ws, pursePub);
+  const ctx = new PeerPushDebitTransactionContext(wex, pursePub);
   const transactionId = ctx.transactionId;
 
   logger.trace(`processing ${transactionId} pending(create-reserve)`);
 
-  const contractTermsRecord = await ws.db.runReadOnlyTx(
+  const contractTermsRecord = await wex.db.runReadOnlyTx(
     ["contractTerms"],
     async (tx) => {
       return tx.contractTerms.get(hContractTerms);
@@ -463,7 +461,7 @@ async function processPeerPushDebitCreateReserve(
     );
   }
 
-  const purseSigResp = await ws.cryptoApi.signPurseCreation({
+  const purseSigResp = await wex.cryptoApi.signPurseCreation({
     hContractTerms,
     mergePub: peerPushInitiation.mergePub,
     minAge: 0,
@@ -473,11 +471,11 @@ async function processPeerPushDebitCreateReserve(
   });
 
   const coins = await queryCoinInfosForSelection(
-    ws,
+    wex,
     peerPushInitiation.coinSel,
   );
 
-  const depositSigsResp = await ws.cryptoApi.signPurseDeposits({
+  const depositSigsResp = await wex.cryptoApi.signPurseDeposits({
     exchangeBaseUrl: peerPushInitiation.exchangeBaseUrl,
     pursePub: peerPushInitiation.pursePub,
     coins,
@@ -495,7 +493,7 @@ async function processPeerPushDebitCreateReserve(
 
   logger.trace(`encrypt contract request: ${j2s(encryptContractRequest)}`);
 
-  const econtractResp = await ws.cryptoApi.encryptContractForMerge(
+  const econtractResp = await wex.cryptoApi.encryptContractForMerge(
     encryptContractRequest,
   );
 
@@ -517,10 +515,10 @@ async function processPeerPushDebitCreateReserve(
 
   logger.trace(`request body: ${j2s(reqBody)}`);
 
-  const httpResp = await ws.http.fetch(createPurseUrl.href, {
+  const httpResp = await wex.http.fetch(createPurseUrl.href, {
     method: "POST",
     body: reqBody,
-    cancellationToken,
+    cancellationToken: wex.cancellationToken,
   });
 
   {
@@ -538,7 +536,7 @@ async function processPeerPushDebitCreateReserve(
     }
     case HttpStatusCode.Conflict: {
       // Handle double-spending
-      return handlePurseCreationConflict(ws, peerPushInitiation, httpResp);
+      return handlePurseCreationConflict(wex, peerPushInitiation, httpResp);
     }
     default: {
       const errResp = await readTalerErrorResponse(httpResp);
@@ -554,7 +552,7 @@ async function processPeerPushDebitCreateReserve(
     throw Error("got error response from exchange");
   }
 
-  await transitionPeerPushDebitTransaction(ws, pursePub, {
+  await transitionPeerPushDebitTransaction(wex, pursePub, {
     stFrom: PeerPushDebitStatus.PendingCreatePurse,
     stTo: PeerPushDebitStatus.PendingReady,
   });
@@ -563,9 +561,8 @@ async function processPeerPushDebitCreateReserve(
 }
 
 async function processPeerPushDebitAbortingDeletePurse(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   peerPushInitiation: PeerPushDebitRecord,
-  cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
   const { pursePub, pursePriv } = peerPushInitiation;
   const transactionId = constructTransactionIdentifier({
@@ -573,23 +570,23 @@ async function processPeerPushDebitAbortingDeletePurse(
     pursePub,
   });
 
-  const sigResp = await ws.cryptoApi.signDeletePurse({
+  const sigResp = await wex.cryptoApi.signDeletePurse({
     pursePriv,
   });
   const purseUrl = new URL(
     `purses/${pursePub}`,
     peerPushInitiation.exchangeBaseUrl,
   );
-  const resp = await ws.http.fetch(purseUrl.href, {
+  const resp = await wex.http.fetch(purseUrl.href, {
     method: "DELETE",
     headers: {
       "taler-purse-signature": sigResp.sig,
     },
-    cancellationToken,
+    cancellationToken: wex.cancellationToken,
   });
   logger.info(`deleted purse with response status ${resp.status}`);
 
-  const transitionInfo = await ws.db.runReadWriteTx(
+  const transitionInfo = await wex.db.runReadWriteTx(
     [
       "peerPushDebit",
       "refreshGroups",
@@ -617,7 +614,7 @@ async function processPeerPushDebitAbortingDeletePurse(
       }
 
       const refresh = await createRefreshGroup(
-        ws,
+        wex,
         tx,
         currency,
         coinPubs,
@@ -634,7 +631,7 @@ async function processPeerPushDebitAbortingDeletePurse(
       };
     },
   );
-  notifyTransition(ws, transactionId, transitionInfo);
+  notifyTransition(wex, transactionId, transitionInfo);
 
   return TaskRunResult.backoff();
 }
@@ -645,7 +642,7 @@ interface SimpleTransition {
 }
 
 async function transitionPeerPushDebitTransaction(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   pursePub: string,
   transitionSpec: SimpleTransition,
 ): Promise<void> {
@@ -653,7 +650,7 @@ async function transitionPeerPushDebitTransaction(
     tag: TransactionType.PeerPushDebit,
     pursePub,
   });
-  const transitionInfo = await ws.db.runReadWriteTx(
+  const transitionInfo = await wex.db.runReadWriteTx(
     ["peerPushDebit"],
     async (tx) => {
       const ppiRec = await tx.peerPushDebit.get(pursePub);
@@ -673,11 +670,11 @@ async function transitionPeerPushDebitTransaction(
       };
     },
   );
-  notifyTransition(ws, transactionId, transitionInfo);
+  notifyTransition(wex, transactionId, transitionInfo);
 }
 
 async function processPeerPushDebitAbortingRefreshDeleted(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   peerPushInitiation: PeerPushDebitRecord,
 ): Promise<TaskRunResult> {
   const pursePub = peerPushInitiation.pursePub;
@@ -687,7 +684,7 @@ async function processPeerPushDebitAbortingRefreshDeleted(
     tag: TransactionType.PeerPushDebit,
     pursePub: peerPushInitiation.pursePub,
   });
-  const transitionInfo = await ws.db.runReadWriteTx(
+  const transitionInfo = await wex.db.runReadWriteTx(
     ["refreshGroups", "peerPushDebit"],
     async (tx) => {
       const refreshGroup = await tx.refreshGroups.get(abortRefreshGroupId);
@@ -720,13 +717,13 @@ async function processPeerPushDebitAbortingRefreshDeleted(
       return undefined;
     },
   );
-  notifyTransition(ws, transactionId, transitionInfo);
+  notifyTransition(wex, transactionId, transitionInfo);
   // FIXME: Shouldn't this be finished in some cases?!
   return TaskRunResult.backoff();
 }
 
 async function processPeerPushDebitAbortingRefreshExpired(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   peerPushInitiation: PeerPushDebitRecord,
 ): Promise<TaskRunResult> {
   const pursePub = peerPushInitiation.pursePub;
@@ -736,7 +733,7 @@ async function processPeerPushDebitAbortingRefreshExpired(
     tag: TransactionType.PeerPushDebit,
     pursePub: peerPushInitiation.pursePub,
   });
-  const transitionInfo = await ws.db.runReadWriteTx(
+  const transitionInfo = await wex.db.runReadWriteTx(
     ["peerPushDebit", "refreshGroups"],
     async (tx) => {
       const refreshGroup = await tx.refreshGroups.get(abortRefreshGroupId);
@@ -769,7 +766,7 @@ async function processPeerPushDebitAbortingRefreshExpired(
       return undefined;
     },
   );
-  notifyTransition(ws, transactionId, transitionInfo);
+  notifyTransition(wex, transactionId, transitionInfo);
   // FIXME: Shouldn't this be finished in some cases?!
   return TaskRunResult.backoff();
 }
@@ -778,9 +775,8 @@ async function processPeerPushDebitAbortingRefreshExpired(
  * Process the "pending(ready)" state of a peer-push-debit transaction.
  */
 async function processPeerPushDebitReady(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   peerPushInitiation: PeerPushDebitRecord,
-  cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
   logger.trace("processing peer-push-debit pending(ready)");
   const pursePub = peerPushInitiation.pursePub;
@@ -794,9 +790,9 @@ async function processPeerPushDebitReady(
   );
   mergeUrl.searchParams.set("timeout_ms", "30000");
   logger.info(`long-polling on purse status at ${mergeUrl.href}`);
-  const resp = await ws.http.fetch(mergeUrl.href, {
+  const resp = await wex.http.fetch(mergeUrl.href, {
     // timeout: getReserveRequestTimeout(withdrawalGroup),
-    cancellationToken,
+    cancellationToken: wex.cancellationToken,
   });
   if (resp.status === HttpStatusCode.Ok) {
     const purseStatus = await readSuccessResponseJsonOrThrow(
@@ -809,7 +805,7 @@ async function processPeerPushDebitReady(
       return TaskRunResult.backoff();
     } else {
       await transitionPeerPushDebitTransaction(
-        ws,
+        wex,
         peerPushInitiation.pursePub,
         {
           stFrom: PeerPushDebitStatus.PendingReady,
@@ -820,7 +816,7 @@ async function processPeerPushDebitReady(
     }
   } else if (resp.status === HttpStatusCode.Gone) {
     logger.info(`purse ${pursePub} is gone, aborting peer-push-debit`);
-    const transitionInfo = await ws.db.runReadWriteTx(
+    const transitionInfo = await wex.db.runReadWriteTx(
       [
         "peerPushDebit",
         "refreshGroups",
@@ -848,7 +844,7 @@ async function processPeerPushDebitReady(
         }
 
         const refresh = await createRefreshGroup(
-          ws,
+          wex,
           tx,
           currency,
           coinPubs,
@@ -865,7 +861,7 @@ async function processPeerPushDebitReady(
         };
       },
     );
-    notifyTransition(ws, transactionId, transitionInfo);
+    notifyTransition(wex, transactionId, transitionInfo);
     return TaskRunResult.backoff();
   } else {
     logger.warn(`unexpected HTTP status for purse: ${resp.status}`);
@@ -874,11 +870,10 @@ async function processPeerPushDebitReady(
 }
 
 export async function processPeerPushDebit(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   pursePub: string,
-  cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
-  const peerPushInitiation = await ws.db.runReadOnlyTx(
+  const peerPushInitiation = await wex.db.runReadOnlyTx(
     ["peerPushDebit"],
     async (tx) => {
       return tx.peerPushDebit.get(pursePub);
@@ -890,27 +885,21 @@ export async function processPeerPushDebit(
 
   switch (peerPushInitiation.status) {
     case PeerPushDebitStatus.PendingCreatePurse:
-      return processPeerPushDebitCreateReserve(
-        ws,
-        peerPushInitiation,
-        cancellationToken,
-      );
+      return processPeerPushDebitCreateReserve(wex, peerPushInitiation);
     case PeerPushDebitStatus.PendingReady:
-      return processPeerPushDebitReady(
-        ws,
-        peerPushInitiation,
-        cancellationToken,
-      );
+      return processPeerPushDebitReady(wex, peerPushInitiation);
     case PeerPushDebitStatus.AbortingDeletePurse:
-      return processPeerPushDebitAbortingDeletePurse(
-        ws,
+      return processPeerPushDebitAbortingDeletePurse(wex, peerPushInitiation);
+    case PeerPushDebitStatus.AbortingRefreshDeleted:
+      return processPeerPushDebitAbortingRefreshDeleted(
+        wex,
         peerPushInitiation,
-        cancellationToken,
       );
-    case PeerPushDebitStatus.AbortingRefreshDeleted:
-      return processPeerPushDebitAbortingRefreshDeleted(ws, 
peerPushInitiation);
     case PeerPushDebitStatus.AbortingRefreshExpired:
-      return processPeerPushDebitAbortingRefreshExpired(ws, 
peerPushInitiation);
+      return processPeerPushDebitAbortingRefreshExpired(
+        wex,
+        peerPushInitiation,
+      );
     default: {
       const txState = computePeerPushDebitTransactionState(peerPushInitiation);
       logger.warn(
@@ -926,7 +915,7 @@ export async function processPeerPushDebit(
  * Initiate sending a peer-to-peer push payment.
  */
 export async function initiatePeerPushDebit(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: InitiatePeerPushDebitRequest,
 ): Promise<InitiatePeerPushDebitResponse> {
   const instructedAmount = Amounts.parseOrThrow(
@@ -935,14 +924,14 @@ export async function initiatePeerPushDebit(
   const purseExpiration = req.partialContractTerms.purse_expiration;
   const contractTerms = req.partialContractTerms;
 
-  const pursePair = await ws.cryptoApi.createEddsaKeypair({});
-  const mergePair = await ws.cryptoApi.createEddsaKeypair({});
+  const pursePair = await wex.cryptoApi.createEddsaKeypair({});
+  const mergePair = await wex.cryptoApi.createEddsaKeypair({});
 
   const hContractTerms = ContractTermsUtil.hashContractTerms(contractTerms);
 
-  const contractKeyPair = await ws.cryptoApi.createEddsaKeypair({});
+  const contractKeyPair = await wex.cryptoApi.createEddsaKeypair({});
 
-  const coinSelRes = await selectPeerCoins(ws, { instructedAmount });
+  const coinSelRes = await selectPeerCoins(wex, { instructedAmount });
 
   if (coinSelRes.type !== "success") {
     throw TalerError.fromDetail(
@@ -959,7 +948,7 @@ export async function initiatePeerPushDebit(
   logger.trace(`${j2s(coinSelRes)}`);
 
   const totalAmount = await getTotalPeerPaymentCost(
-    ws,
+    wex,
     coinSelRes.result.coins,
   );
 
@@ -967,13 +956,13 @@ export async function initiatePeerPushDebit(
 
   const pursePub = pursePair.pub;
 
-  const ctx = new PeerPushDebitTransactionContext(ws, pursePub);
+  const ctx = new PeerPushDebitTransactionContext(wex, pursePub);
 
   const transactionId = ctx.transactionId;
 
   const contractEncNonce = encodeCrock(getRandomBytes(24));
 
-  const transitionInfo = await ws.db.runReadWriteTx(
+  const transitionInfo = await wex.db.runReadWriteTx(
     [
       "exchanges",
       "contractTerms",
@@ -987,7 +976,7 @@ export async function initiatePeerPushDebit(
       // FIXME: Instead of directly doing a spendCoin here,
       // we might want to mark the coins as used and spend them
       // after we've been able to create the purse.
-      await spendCoins(ws, tx, {
+      await spendCoins(wex, tx, {
         allocationId: constructTransactionIdentifier({
           tag: TransactionType.PeerPushDebit,
           pursePub: pursePair.pub,
@@ -1034,13 +1023,13 @@ export async function initiatePeerPushDebit(
       };
     },
   );
-  notifyTransition(ws, transactionId, transitionInfo);
-  ws.notify({
+  notifyTransition(wex, transactionId, transitionInfo);
+  wex.ws.notify({
     type: NotificationType.BalanceChange,
     hintTransactionId: transactionId,
   });
 
-  ws.taskScheduler.startShepherdTask(ctx.taskId);
+  wex.taskScheduler.startShepherdTask(ctx.taskId);
 
   return {
     contractPriv: contractKeyPair.priv,
diff --git a/packages/taler-wallet-core/src/recoup.ts 
b/packages/taler-wallet-core/src/recoup.ts
index 3ef494c3a..8d5d3dd1f 100644
--- a/packages/taler-wallet-core/src/recoup.ts
+++ b/packages/taler-wallet-core/src/recoup.ts
@@ -63,7 +63,11 @@ import {
 } from "./db.js";
 import { createRefreshGroup } from "./refresh.js";
 import { constructTransactionIdentifier } from "./transactions.js";
-import { getDenomInfo, type InternalWalletState } from "./wallet.js";
+import {
+  WalletExecutionContext,
+  getDenomInfo,
+  type InternalWalletState,
+} from "./wallet.js";
 import { internalCreateWithdrawalGroup } from "./withdraw.js";
 
 export const logger = new Logger("operations/recoup.ts");
@@ -73,7 +77,7 @@ export const logger = new Logger("operations/recoup.ts");
  * a coin in the group as finished.
  */
 export async function putGroupAsFinished(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   tx: WalletDbReadWriteTransaction<
     ["recoupGroups", "denominations", "refreshGroups", "coins"]
   >,
@@ -91,7 +95,7 @@ export async function putGroupAsFinished(
 }
 
 async function recoupRewardCoin(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   recoupGroupId: string,
   coinIdx: number,
   coin: CoinRecord,
@@ -99,7 +103,7 @@ async function recoupRewardCoin(
   // We can't really recoup a coin we got via tipping.
   // Thus we just put the coin to sleep.
   // FIXME: somehow report this to the user
-  await ws.db.runReadWriteTx(
+  await wex.db.runReadWriteTx(
     ["recoupGroups", "denominations", "refreshGroups", "coins"],
     async (tx) => {
       const recoupGroup = await tx.recoupGroups.get(recoupGroupId);
@@ -109,23 +113,23 @@ async function recoupRewardCoin(
       if (recoupGroup.recoupFinishedPerCoin[coinIdx]) {
         return;
       }
-      await putGroupAsFinished(ws, tx, recoupGroup, coinIdx);
+      await putGroupAsFinished(wex, tx, recoupGroup, coinIdx);
     },
   );
 }
 
 async function recoupRefreshCoin(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   recoupGroupId: string,
   coinIdx: number,
   coin: CoinRecord,
   cs: RefreshCoinSource,
 ): Promise<void> {
-  const d = await ws.db.runReadOnlyTx(
+  const d = await wex.db.runReadOnlyTx(
     ["coins", "denominations"],
     async (tx) => {
       const denomInfo = await getDenomInfo(
-        ws,
+        wex,
         tx,
         coin.exchangeBaseUrl,
         coin.denomPubHash,
@@ -141,7 +145,7 @@ async function recoupRefreshCoin(
     return;
   }
 
-  const recoupRequest = await ws.cryptoApi.createRecoupRefreshRequest({
+  const recoupRequest = await wex.cryptoApi.createRecoupRefreshRequest({
     blindingKey: coin.blindingKey,
     coinPriv: coin.coinPriv,
     coinPub: coin.coinPub,
@@ -155,7 +159,7 @@ async function recoupRefreshCoin(
   );
   logger.trace(`making recoup request for ${coin.coinPub}`);
 
-  const resp = await ws.http.fetch(reqUrl.href, {
+  const resp = await wex.http.fetch(reqUrl.href, {
     method: "POST",
     body: recoupRequest,
   });
@@ -168,7 +172,7 @@ async function recoupRefreshCoin(
     throw Error(`Coin's oldCoinPub doesn't match reserve on recoup`);
   }
 
-  await ws.db.runReadWriteTx(
+  await wex.db.runReadWriteTx(
     ["coins", "denominations", "recoupGroups", "refreshGroups"],
     async (tx) => {
       const recoupGroup = await tx.recoupGroups.get(recoupGroupId);
@@ -189,13 +193,13 @@ async function recoupRefreshCoin(
         return;
       }
       const oldCoinDenom = await getDenomInfo(
-        ws,
+        wex,
         tx,
         oldCoin.exchangeBaseUrl,
         oldCoin.denomPubHash,
       );
       const revokedCoinDenom = await getDenomInfo(
-        ws,
+        wex,
         tx,
         revokedCoin.exchangeBaseUrl,
         revokedCoin.denomPubHash,
@@ -220,22 +224,22 @@ async function recoupRefreshCoin(
       }
       await tx.coins.put(revokedCoin);
       await tx.coins.put(oldCoin);
-      await putGroupAsFinished(ws, tx, recoupGroup, coinIdx);
+      await putGroupAsFinished(wex, tx, recoupGroup, coinIdx);
     },
   );
 }
 
 export async function recoupWithdrawCoin(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   recoupGroupId: string,
   coinIdx: number,
   coin: CoinRecord,
   cs: WithdrawCoinSource,
 ): Promise<void> {
   const reservePub = cs.reservePub;
-  const denomInfo = await ws.db.runReadOnlyTx(["denominations"], async (tx) => 
{
+  const denomInfo = await wex.db.runReadOnlyTx(["denominations"], async (tx) 
=> {
     const denomInfo = await getDenomInfo(
-      ws,
+      wex,
       tx,
       coin.exchangeBaseUrl,
       coin.denomPubHash,
@@ -247,7 +251,7 @@ export async function recoupWithdrawCoin(
     return;
   }
 
-  const recoupRequest = await ws.cryptoApi.createRecoupRequest({
+  const recoupRequest = await wex.cryptoApi.createRecoupRequest({
     blindingKey: coin.blindingKey,
     coinPriv: coin.coinPriv,
     coinPub: coin.coinPub,
@@ -257,7 +261,7 @@ export async function recoupWithdrawCoin(
   });
   const reqUrl = new URL(`/coins/${coin.coinPub}/recoup`, 
coin.exchangeBaseUrl);
   logger.trace(`requesting recoup via ${reqUrl.href}`);
-  const resp = await ws.http.fetch(reqUrl.href, {
+  const resp = await wex.http.fetch(reqUrl.href, {
     method: "POST",
     body: recoupRequest,
   });
@@ -273,7 +277,7 @@ export async function recoupWithdrawCoin(
   }
 
   // FIXME: verify that our expectations about the amount match
-  await ws.db.runReadWriteTx(
+  await wex.db.runReadWriteTx(
     ["coins", "denominations", "recoupGroups", "refreshGroups"],
     async (tx) => {
       const recoupGroup = await tx.recoupGroups.get(recoupGroupId);
@@ -289,17 +293,16 @@ export async function recoupWithdrawCoin(
       }
       updatedCoin.status = CoinStatus.Dormant;
       await tx.coins.put(updatedCoin);
-      await putGroupAsFinished(ws, tx, recoupGroup, coinIdx);
+      await putGroupAsFinished(wex, tx, recoupGroup, coinIdx);
     },
   );
 }
 
 export async function processRecoupGroup(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   recoupGroupId: string,
-  cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
-  let recoupGroup = await ws.db.runReadOnlyTx(["recoupGroups"], async (tx) => {
+  let recoupGroup = await wex.db.runReadOnlyTx(["recoupGroups"], async (tx) => 
{
     return tx.recoupGroups.get(recoupGroupId);
   });
   if (!recoupGroup) {
@@ -311,7 +314,7 @@ export async function processRecoupGroup(
   }
   const ps = recoupGroup.coinPubs.map(async (x, i) => {
     try {
-      await processRecoupForCoin(ws, recoupGroupId, i);
+      await processRecoupForCoin(wex, recoupGroupId, i);
     } catch (e) {
       logger.warn(`processRecoup failed: ${e}`);
       throw e;
@@ -319,7 +322,7 @@ export async function processRecoupGroup(
   });
   await Promise.all(ps);
 
-  recoupGroup = await ws.db.runReadOnlyTx(["recoupGroups"], async (tx) => {
+  recoupGroup = await wex.db.runReadOnlyTx(["recoupGroups"], async (tx) => {
     return tx.recoupGroups.get(recoupGroupId);
   });
   if (!recoupGroup) {
@@ -338,7 +341,7 @@ export async function processRecoupGroup(
   const reservePrivMap: Record<string, string> = {};
   for (let i = 0; i < recoupGroup.coinPubs.length; i++) {
     const coinPub = recoupGroup.coinPubs[i];
-    await ws.db.runReadOnlyTx(["coins", "reserves"], async (tx) => {
+    await wex.db.runReadOnlyTx(["coins", "reserves"], async (tx) => {
       const coin = await tx.coins.get(coinPub);
       if (!coin) {
         throw Error(`Coin ${coinPub} not found, can't request recoup`);
@@ -363,13 +366,13 @@ export async function processRecoupGroup(
     );
     logger.info(`querying reserve status for recoup via ${reserveUrl}`);
 
-    const resp = await ws.http.fetch(reserveUrl.href);
+    const resp = await wex.http.fetch(reserveUrl.href);
 
     const result = await readSuccessResponseJsonOrThrow(
       resp,
       codecForReserveStatus(),
     );
-    await internalCreateWithdrawalGroup(ws, {
+    await internalCreateWithdrawalGroup(wex, {
       amount: Amounts.parseOrThrow(result.balance),
       exchangeBaseUrl: recoupGroup.exchangeBaseUrl,
       reserveStatus: WithdrawalGroupStatus.PendingQueryingStatus,
@@ -383,7 +386,7 @@ export async function processRecoupGroup(
     });
   }
 
-  await ws.db.runReadWriteTx(
+  await wex.db.runReadWriteTx(
     [
       "recoupGroups",
       "coinAvailability",
@@ -400,7 +403,7 @@ export async function processRecoupGroup(
       rg2.operationStatus = RecoupOperationStatus.Finished;
       if (rg2.scheduleRefreshCoins.length > 0) {
         await createRefreshGroup(
-          ws,
+          wex,
           tx,
           Amounts.currencyOf(rg2.scheduleRefreshCoins[0].amount),
           rg2.scheduleRefreshCoins,
@@ -452,7 +455,7 @@ export class RewardTransactionContext implements 
TransactionContext {
 }
 
 export async function createRecoupGroup(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   tx: WalletDbReadWriteTransaction<
     ["recoupGroups", "denominations", "refreshGroups", "coins"]
   >,
@@ -476,7 +479,7 @@ export async function createRecoupGroup(
     const coinPub = coinPubs[coinIdx];
     const coin = await tx.coins.get(coinPub);
     if (!coin) {
-      await putGroupAsFinished(ws, tx, recoupGroup, coinIdx);
+      await putGroupAsFinished(wex, tx, recoupGroup, coinIdx);
       continue;
     }
     await tx.coins.put(coin);
@@ -491,11 +494,11 @@ export async function createRecoupGroup(
  * Run the recoup protocol for a single coin in a recoup group.
  */
 async function processRecoupForCoin(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   recoupGroupId: string,
   coinIdx: number,
 ): Promise<void> {
-  const coin = await ws.db.runReadOnlyTx(
+  const coin = await wex.db.runReadOnlyTx(
     ["coins", "recoupGroups"],
     async (tx) => {
       const recoupGroup = await tx.recoupGroups.get(recoupGroupId);
@@ -527,11 +530,11 @@ async function processRecoupForCoin(
 
   switch (cs.type) {
     case CoinSourceType.Reward:
-      return recoupRewardCoin(ws, recoupGroupId, coinIdx, coin);
+      return recoupRewardCoin(wex, recoupGroupId, coinIdx, coin);
     case CoinSourceType.Refresh:
-      return recoupRefreshCoin(ws, recoupGroupId, coinIdx, coin, cs);
+      return recoupRefreshCoin(wex, recoupGroupId, coinIdx, coin, cs);
     case CoinSourceType.Withdraw:
-      return recoupWithdrawCoin(ws, recoupGroupId, coinIdx, coin, cs);
+      return recoupWithdrawCoin(wex, recoupGroupId, coinIdx, coin, cs);
     default:
       throw Error("unknown coin source type");
   }
diff --git a/packages/taler-wallet-core/src/refresh.ts 
b/packages/taler-wallet-core/src/refresh.ts
index cc5eff12c..b467a1c47 100644
--- a/packages/taler-wallet-core/src/refresh.ts
+++ b/packages/taler-wallet-core/src/refresh.ts
@@ -21,7 +21,6 @@ import {
   Amounts,
   amountToPretty,
   assertUnreachable,
-  CancellationToken,
   checkDbInvariant,
   codecForExchangeMeltResponse,
   codecForExchangeRevealResponse,
@@ -103,6 +102,7 @@ import {
   EXCHANGE_COINS_LOCK,
   getDenomInfo,
   InternalWalletState,
+  WalletExecutionContext,
 } from "./wallet.js";
 import { getCandidateWithdrawalDenomsTx } from "./withdraw.js";
 
@@ -113,7 +113,7 @@ export class RefreshTransactionContext implements 
TransactionContext {
   readonly taskId: TaskIdStr;
 
   constructor(
-    public ws: InternalWalletState,
+    public wex: WalletExecutionContext,
     public refreshGroupId: string,
   ) {
     this.transactionId = constructTransactionIdentifier({
@@ -128,7 +128,7 @@ export class RefreshTransactionContext implements 
TransactionContext {
 
   async deleteTransaction(): Promise<void> {
     const refreshGroupId = this.refreshGroupId;
-    const ws = this.ws;
+    const ws = this.wex;
     await ws.db.runReadWriteTx(["refreshGroups", "tombstones"], async (tx) => {
       const rg = await tx.refreshGroups.get(refreshGroupId);
       if (rg) {
@@ -141,8 +141,8 @@ export class RefreshTransactionContext implements 
TransactionContext {
   }
 
   async suspendTransaction(): Promise<void> {
-    const { ws, refreshGroupId, transactionId } = this;
-    let res = await ws.db.runReadWriteTx(["refreshGroups"], async (tx) => {
+    const { wex, refreshGroupId, transactionId } = this;
+    let res = await wex.db.runReadWriteTx(["refreshGroups"], async (tx) => {
       const dg = await tx.refreshGroups.get(refreshGroupId);
       if (!dg) {
         logger.warn(
@@ -168,7 +168,7 @@ export class RefreshTransactionContext implements 
TransactionContext {
       return undefined;
     });
     if (res) {
-      ws.notify({
+      wex.ws.notify({
         type: NotificationType.TransactionStateTransition,
         transactionId,
         oldTxState: res.oldTxState,
@@ -183,7 +183,7 @@ export class RefreshTransactionContext implements 
TransactionContext {
   }
 
   async resumeTransaction(): Promise<void> {
-    const { ws, refreshGroupId, transactionId } = this;
+    const { wex: ws, refreshGroupId, transactionId } = this;
     const transitionInfo = await ws.db.runReadWriteTx(
       ["refreshGroups"],
       async (tx) => {
@@ -217,7 +217,7 @@ export class RefreshTransactionContext implements 
TransactionContext {
   }
 
   async failTransaction(): Promise<void> {
-    const { ws, refreshGroupId, transactionId } = this;
+    const { wex: ws, refreshGroupId, transactionId } = this;
     const transitionInfo = await ws.db.runReadWriteTx(
       ["refreshGroups"],
       async (tx) => {
@@ -331,7 +331,7 @@ function updateGroupStatus(rg: RefreshGroupRecord): { 
final: boolean } {
  * finished), return undefined.
  */
 async function provideRefreshSession(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   refreshGroupId: string,
   coinIndex: number,
 ): Promise<RefreshSessionRecord | undefined> {
@@ -339,7 +339,7 @@ async function provideRefreshSession(
     `creating refresh session for coin ${coinIndex} in refresh group 
${refreshGroupId}`,
   );
 
-  const d = await ws.db.runReadWriteTx(
+  const d = await wex.db.runReadWriteTx(
     ["coins", "refreshGroups", "refreshSessions"],
     async (tx) => {
       const refreshGroup = await tx.refreshGroups.get(refreshGroupId);
@@ -374,16 +374,16 @@ async function provideRefreshSession(
 
   const { refreshGroup, coin } = d;
 
-  const exch = await fetchFreshExchange(ws, coin.exchangeBaseUrl);
+  const exch = await fetchFreshExchange(wex, coin.exchangeBaseUrl);
 
   // FIXME: use helper functions from withdraw.ts
   // to update and filter withdrawable denoms.
 
-  const { availableAmount, availableDenoms } = await ws.db.runReadOnlyTx(
+  const { availableAmount, availableDenoms } = await wex.db.runReadOnlyTx(
     ["denominations"],
     async (tx) => {
       const oldDenom = await getDenomInfo(
-        ws,
+        wex,
         tx,
         exch.exchangeBaseUrl,
         coin.denomPubHash,
@@ -410,7 +410,7 @@ async function provideRefreshSession(
   const newCoinDenoms = selectWithdrawalDenominations(
     availableAmount,
     availableDenoms,
-    ws.config.testing.denomselAllowLate,
+    wex.ws.config.testing.denomselAllowLate,
   );
 
   const transactionId = constructTransactionIdentifier({
@@ -424,7 +424,7 @@ async function provideRefreshSession(
         availableAmount,
       )} too small`,
     );
-    const transitionInfo = await ws.db.runReadWriteTx(
+    const transitionInfo = await wex.db.runReadWriteTx(
       ["refreshGroups", "coins", "coinAvailability"],
       async (tx) => {
         const rg = await tx.refreshGroups.get(refreshGroupId);
@@ -435,25 +435,25 @@ async function provideRefreshSession(
         rg.statusPerCoin[coinIndex] = RefreshCoinStatus.Finished;
         const updateRes = updateGroupStatus(rg);
         if (updateRes.final) {
-          await makeCoinsVisible(ws, tx, transactionId);
+          await makeCoinsVisible(wex, tx, transactionId);
         }
         await tx.refreshGroups.put(rg);
         const newTxState = computeRefreshTransactionState(rg);
         return { oldTxState, newTxState };
       },
     );
-    ws.notify({
+    wex.ws.notify({
       type: NotificationType.BalanceChange,
       hintTransactionId: transactionId,
     });
-    notifyTransition(ws, transactionId, transitionInfo);
+    notifyTransition(wex, transactionId, transitionInfo);
     return;
   }
 
   const sessionSecretSeed = encodeCrock(getRandomBytes(64));
 
   // Store refresh session for this coin in the database.
-  const mySession = await ws.db.runReadWriteTx(
+  const mySession = await wex.db.runReadWriteTx(
     ["refreshGroups", "refreshSessions"],
     async (tx) => {
       const rg = await tx.refreshGroups.get(refreshGroupId);
@@ -495,12 +495,11 @@ function getRefreshRequestTimeout(rg: 
RefreshGroupRecord): Duration {
 }
 
 async function refreshMelt(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   refreshGroupId: string,
   coinIndex: number,
-  cancellationToken: CancellationToken,
 ): Promise<void> {
-  const d = await ws.db.runReadWriteTx(
+  const d = await wex.db.runReadWriteTx(
     ["refreshGroups", "refreshSessions", "coins", "denominations"],
     async (tx) => {
       const refreshGroup = await tx.refreshGroups.get(refreshGroupId);
@@ -521,7 +520,7 @@ async function refreshMelt(
       const oldCoin = await tx.coins.get(refreshGroup.oldCoinPubs[coinIndex]);
       checkDbInvariant(!!oldCoin, "melt coin doesn't exist");
       const oldDenom = await getDenomInfo(
-        ws,
+        wex,
         tx,
         oldCoin.exchangeBaseUrl,
         oldCoin.denomPubHash,
@@ -535,7 +534,7 @@ async function refreshMelt(
 
       for (const dh of refreshSession.newDenoms) {
         const newDenom = await getDenomInfo(
-          ws,
+          wex,
           tx,
           oldCoin.exchangeBaseUrl,
           dh.denomPubHash,
@@ -572,7 +571,7 @@ async function refreshMelt(
       throw Error("unsupported key type");
   }
 
-  const derived = await ws.cryptoApi.deriveRefreshSession({
+  const derived = await wex.cryptoApi.deriveRefreshSession({
     exchangeProtocolVersion,
     kappa: 3,
     meltCoinDenomPubHash: oldCoin.denomPubHash,
@@ -607,14 +606,17 @@ async function refreshMelt(
     age_commitment_hash: maybeAch,
   };
 
-  const resp = await ws.runSequentialized([EXCHANGE_COINS_LOCK], async () => {
-    return await ws.http.fetch(reqUrl.href, {
-      method: "POST",
-      body: meltReqBody,
-      timeout: getRefreshRequestTimeout(refreshGroup),
-      cancellationToken,
-    });
-  });
+  const resp = await wex.ws.runSequentialized(
+    [EXCHANGE_COINS_LOCK],
+    async () => {
+      return await wex.http.fetch(reqUrl.href, {
+        method: "POST",
+        body: meltReqBody,
+        timeout: getRefreshRequestTimeout(refreshGroup),
+        cancellationToken: wex.cancellationToken,
+      });
+    },
+  );
 
   const transactionId = constructTransactionIdentifier({
     tag: TransactionType.Refresh,
@@ -623,7 +625,7 @@ async function refreshMelt(
 
   if (resp.status === HttpStatusCode.NotFound) {
     const errDetails = await readUnexpectedResponseDetails(resp);
-    const transitionInfo = await ws.db.runReadWriteTx(
+    const transitionInfo = await wex.db.runReadWriteTx(
       ["refreshGroups", "refreshSessions", "coins", "coinAvailability"],
       async (tx) => {
         const rg = await tx.refreshGroups.get(refreshGroupId);
@@ -650,7 +652,7 @@ async function refreshMelt(
         refreshSession.lastError = errDetails;
         const updateRes = updateGroupStatus(rg);
         if (updateRes.final) {
-          await makeCoinsVisible(ws, tx, transactionId);
+          await makeCoinsVisible(wex, tx, transactionId);
         }
         await tx.refreshGroups.put(rg);
         await tx.refreshSessions.put(refreshSession);
@@ -661,11 +663,11 @@ async function refreshMelt(
         };
       },
     );
-    ws.notify({
+    wex.ws.notify({
       type: NotificationType.BalanceChange,
       hintTransactionId: transactionId,
     });
-    notifyTransition(ws, transactionId, transitionInfo);
+    notifyTransition(wex, transactionId, transitionInfo);
     return;
   }
 
@@ -678,7 +680,7 @@ async function refreshMelt(
       )} failed in refresh group ${refreshGroupId} due to conflict`,
     );
 
-    const historySig = await ws.cryptoApi.signCoinHistoryRequest({
+    const historySig = await wex.cryptoApi.signCoinHistoryRequest({
       coinPriv: oldCoin.coinPriv,
       coinPub: oldCoin.coinPub,
       startOffset: 0,
@@ -689,12 +691,12 @@ async function refreshMelt(
       oldCoin.exchangeBaseUrl,
     );
 
-    const historyResp = await ws.http.fetch(historyUrl.href, {
+    const historyResp = await wex.http.fetch(historyUrl.href, {
       method: "GET",
       headers: {
         "Taler-Coin-History-Signature": historySig.sig,
       },
-      cancellationToken,
+      cancellationToken: wex.cancellationToken,
     });
 
     const historyJson = await historyResp.json();
@@ -712,7 +714,7 @@ async function refreshMelt(
 
   refreshSession.norevealIndex = norevealIndex;
 
-  await ws.db.runReadWriteTx(
+  await wex.db.runReadWriteTx(
     ["refreshGroups", "refreshSessions"],
     async (tx) => {
       const rg = await tx.refreshGroups.get(refreshGroupId);
@@ -794,15 +796,14 @@ export async function assembleRefreshRevealRequest(args: {
 }
 
 async function refreshReveal(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   refreshGroupId: string,
   coinIndex: number,
-  cancellationToken: CancellationToken,
 ): Promise<void> {
   logger.trace(
     `doing refresh reveal for ${refreshGroupId} (old coin ${coinIndex})`,
   );
-  const d = await ws.db.runReadOnlyTx(
+  const d = await wex.db.runReadOnlyTx(
     ["refreshGroups", "refreshSessions", "coins", "denominations"],
     async (tx) => {
       const refreshGroup = await tx.refreshGroups.get(refreshGroupId);
@@ -824,7 +825,7 @@ async function refreshReveal(
       const oldCoin = await tx.coins.get(refreshGroup.oldCoinPubs[coinIndex]);
       checkDbInvariant(!!oldCoin, "melt coin doesn't exist");
       const oldDenom = await getDenomInfo(
-        ws,
+        wex,
         tx,
         oldCoin.exchangeBaseUrl,
         oldCoin.denomPubHash,
@@ -838,7 +839,7 @@ async function refreshReveal(
 
       for (const dh of refreshSession.newDenoms) {
         const newDenom = await getDenomInfo(
-          ws,
+          wex,
           tx,
           oldCoin.exchangeBaseUrl,
           dh.denomPubHash,
@@ -889,7 +890,7 @@ async function refreshReveal(
       throw Error("unsupported key type");
   }
 
-  const derived = await ws.cryptoApi.deriveRefreshSession({
+  const derived = await wex.cryptoApi.deriveRefreshSession({
     exchangeProtocolVersion,
     kappa: 3,
     meltCoinDenomPubHash: oldCoin.denomPubHash,
@@ -908,7 +909,7 @@ async function refreshReveal(
   );
 
   const req = await assembleRefreshRevealRequest({
-    cryptoApi: ws.cryptoApi,
+    cryptoApi: wex.cryptoApi,
     derived,
     newDenoms: newCoinDenoms,
     norevealIndex: norevealIndex,
@@ -917,14 +918,17 @@ async function refreshReveal(
     oldAgeCommitment: oldCoin.ageCommitmentProof?.commitment,
   });
 
-  const resp = await ws.runSequentialized([EXCHANGE_COINS_LOCK], async () => {
-    return await ws.http.fetch(reqUrl.href, {
-      body: req,
-      method: "POST",
-      timeout: getRefreshRequestTimeout(refreshGroup),
-      cancellationToken,
-    });
-  });
+  const resp = await wex.ws.runSequentialized(
+    [EXCHANGE_COINS_LOCK],
+    async () => {
+      return await wex.http.fetch(reqUrl.href, {
+        body: req,
+        method: "POST",
+        timeout: getRefreshRequestTimeout(refreshGroup),
+        cancellationToken: wex.cancellationToken,
+      });
+    },
+  );
 
   const reveal = await readSuccessResponseJsonOrThrow(
     resp,
@@ -947,7 +951,7 @@ async function refreshReveal(
         throw Error("cipher unsupported");
       }
       const evSig = reveal.ev_sigs[newCoinIndex].ev_sig;
-      const denomSig = await ws.cryptoApi.unblindDenominationSignature({
+      const denomSig = await wex.cryptoApi.unblindDenominationSignature({
         planchet: {
           blindingKey: pc.blindingKey,
           denomPub: ncd.denomPub,
@@ -978,7 +982,7 @@ async function refreshReveal(
     }
   }
 
-  const transitionInfo = await ws.db.runReadWriteTx(
+  const transitionInfo = await wex.db.runReadWriteTx(
     [
       "coins",
       "denominations",
@@ -1000,26 +1004,25 @@ async function refreshReveal(
       rg.statusPerCoin[coinIndex] = RefreshCoinStatus.Finished;
       updateGroupStatus(rg);
       for (const coin of coins) {
-        await makeCoinAvailable(ws, tx, coin);
+        await makeCoinAvailable(wex, tx, coin);
       }
-      await makeCoinsVisible(ws, tx, transactionId);
+      await makeCoinsVisible(wex, tx, transactionId);
       await tx.refreshGroups.put(rg);
       const newTxState = computeRefreshTransactionState(rg);
       return { oldTxState, newTxState };
     },
   );
-  notifyTransition(ws, transactionId, transitionInfo);
+  notifyTransition(wex, transactionId, transitionInfo);
   logger.trace("refresh finished (end of reveal)");
 }
 
 export async function processRefreshGroup(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   refreshGroupId: string,
-  cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
   logger.trace(`processing refresh group ${refreshGroupId}`);
 
-  const refreshGroup = await ws.db.runReadOnlyTx(
+  const refreshGroup = await wex.db.runReadOnlyTx(
     ["refreshGroups"],
     async (tx) => tx.refreshGroups.get(refreshGroupId),
   );
@@ -1036,28 +1039,26 @@ export async function processRefreshGroup(
   let errors: TalerErrorDetail[] = [];
   let inShutdown = false;
   const ps = refreshGroup.oldCoinPubs.map((x, i) =>
-    processRefreshSession(ws, refreshGroupId, i, cancellationToken).catch(
-      (x) => {
-        if (x instanceof CryptoApiStoppedError) {
-          inShutdown = true;
-          logger.info(
-            "crypto API stopped while processing refresh group, probably the 
wallet is currently shutting down.",
-          );
-          return;
-        }
-        if (x instanceof TalerError) {
-          logger.warn("process refresh session got exception (TalerError)");
-          logger.warn(`exc ${x}`);
-          logger.warn(`exc stack ${x.stack}`);
-          logger.warn(`error detail: ${j2s(x.errorDetail)}`);
-        } else {
-          logger.warn("process refresh session got exception");
-          logger.warn(`exc ${x}`);
-          logger.warn(`exc stack ${x.stack}`);
-        }
-        errors.push(getErrorDetailFromException(x));
-      },
-    ),
+    processRefreshSession(wex, refreshGroupId, i).catch((x) => {
+      if (x instanceof CryptoApiStoppedError) {
+        inShutdown = true;
+        logger.info(
+          "crypto API stopped while processing refresh group, probably the 
wallet is currently shutting down.",
+        );
+        return;
+      }
+      if (x instanceof TalerError) {
+        logger.warn("process refresh session got exception (TalerError)");
+        logger.warn(`exc ${x}`);
+        logger.warn(`exc stack ${x.stack}`);
+        logger.warn(`error detail: ${j2s(x.errorDetail)}`);
+      } else {
+        logger.warn("process refresh session got exception");
+        logger.warn(`exc ${x}`);
+        logger.warn(`exc stack ${x.stack}`);
+      }
+      errors.push(getErrorDetailFromException(x));
+    }),
   );
   try {
     logger.info("waiting for refreshes");
@@ -1087,15 +1088,14 @@ export async function processRefreshGroup(
 }
 
 async function processRefreshSession(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   refreshGroupId: string,
   coinIndex: number,
-  cancellationToken: CancellationToken,
 ): Promise<void> {
   logger.trace(
     `processing refresh session for coin ${coinIndex} of group 
${refreshGroupId}`,
   );
-  let { refreshGroup, refreshSession } = await ws.db.runReadOnlyTx(
+  let { refreshGroup, refreshSession } = await wex.db.runReadOnlyTx(
     ["refreshGroups", "refreshSessions"],
     async (tx) => {
       const rg = await tx.refreshGroups.get(refreshGroupId);
@@ -1113,7 +1113,11 @@ async function processRefreshSession(
     return;
   }
   if (!refreshSession) {
-    refreshSession = await provideRefreshSession(ws, refreshGroupId, 
coinIndex);
+    refreshSession = await provideRefreshSession(
+      wex,
+      refreshGroupId,
+      coinIndex,
+    );
   }
   if (!refreshSession) {
     // We tried to create the refresh session, but didn't get a result back.
@@ -1122,9 +1126,9 @@ async function processRefreshSession(
     return;
   }
   if (refreshSession.norevealIndex === undefined) {
-    await refreshMelt(ws, refreshGroupId, coinIndex, cancellationToken);
+    await refreshMelt(wex, refreshGroupId, coinIndex);
   }
-  await refreshReveal(ws, refreshGroupId, coinIndex, cancellationToken);
+  await refreshReveal(wex, refreshGroupId, coinIndex);
 }
 
 export interface RefreshOutputInfo {
@@ -1133,7 +1137,7 @@ export interface RefreshOutputInfo {
 }
 
 export async function calculateRefreshOutput(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   tx: WalletDbReadOnlyTransaction<
     ["denominations", "coins", "refreshGroups", "coinAvailability"]
   >,
@@ -1154,7 +1158,7 @@ export async function calculateRefreshOutput(
       return denomsPerExchange[exchangeBaseUrl];
     }
     const allDenoms = await getCandidateWithdrawalDenomsTx(
-      ws,
+      wex,
       tx,
       exchangeBaseUrl,
       currency,
@@ -1167,7 +1171,7 @@ export async function calculateRefreshOutput(
     const coin = await tx.coins.get(ocp.coinPub);
     checkDbInvariant(!!coin, "coin must be in database");
     const denom = await getDenomInfo(
-      ws,
+      wex,
       tx,
       coin.exchangeBaseUrl,
       coin.denomPubHash,
@@ -1182,7 +1186,7 @@ export async function calculateRefreshOutput(
       denoms,
       denom,
       Amounts.parseOrThrow(refreshAmount),
-      ws.config.testing.denomselAllowLate,
+      wex.ws.config.testing.denomselAllowLate,
     );
     const output = Amounts.sub(refreshAmount, cost).amount;
     let exchInfo = infoPerExchange[coin.exchangeBaseUrl];
@@ -1204,7 +1208,7 @@ export async function calculateRefreshOutput(
 }
 
 async function applyRefresh(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   tx: WalletDbReadWriteTransaction<
     ["denominations", "coins", "refreshGroups", "coinAvailability"]
   >,
@@ -1215,7 +1219,7 @@ async function applyRefresh(
     const coin = await tx.coins.get(ocp.coinPub);
     checkDbInvariant(!!coin, "coin must be in database");
     const denom = await getDenomInfo(
-      ws,
+      wex,
       tx,
       coin.exchangeBaseUrl,
       coin.denomPubHash,
@@ -1278,7 +1282,7 @@ export interface CreateRefreshGroupResult {
  * in the current database transaction.
  */
 export async function createRefreshGroup(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   tx: WalletDbReadWriteTransaction<
     ["denominations", "coins", "refreshGroups", "coinAvailability"]
   >,
@@ -1289,11 +1293,11 @@ export async function createRefreshGroup(
 ): Promise<CreateRefreshGroupResult> {
   const refreshGroupId = encodeCrock(getRandomBytes(32));
 
-  const outInfo = await calculateRefreshOutput(ws, tx, currency, oldCoinPubs);
+  const outInfo = await calculateRefreshOutput(wex, tx, currency, oldCoinPubs);
 
   const estimatedOutputPerCoin = outInfo.outputPerCoin;
 
-  await applyRefresh(ws, tx, oldCoinPubs, refreshGroupId);
+  await applyRefresh(wex, tx, oldCoinPubs, refreshGroupId);
 
   const refreshGroup: RefreshGroupRecord = {
     operationStatus: RefreshOperationStatus.Pending,
@@ -1326,12 +1330,12 @@ export async function createRefreshGroup(
 
   logger.trace(`created refresh group ${refreshGroupId}`);
 
-  const ctx = new RefreshTransactionContext(ws, refreshGroupId);
+  const ctx = new RefreshTransactionContext(wex, refreshGroupId);
 
   // Shepherd the task.
   // If the current transaction fails to commit the refresh
   // group to the DB, the shepherd will give up.
-  ws.taskScheduler.startShepherdTask(ctx.taskId);
+  wex.taskScheduler.startShepherdTask(ctx.taskId);
 
   return {
     refreshGroupId,
@@ -1391,10 +1395,10 @@ export function computeRefreshTransactionActions(
 }
 
 export function getRefreshesForTransaction(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   transactionId: string,
 ): Promise<string[]> {
-  return ws.db.runReadOnlyTx(["refreshGroups"], async (tx) => {
+  return wex.db.runReadOnlyTx(["refreshGroups"], async (tx) => {
     const groups =
       await tx.refreshGroups.indexes.byOriginatingTransactionId.getAll(
         transactionId,
@@ -1409,13 +1413,13 @@ export function getRefreshesForTransaction(
 }
 
 export async function forceRefresh(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: ForceRefreshRequest,
 ): Promise<{ refreshGroupId: RefreshGroupId }> {
   if (req.coinPubList.length == 0) {
     throw Error("refusing to create empty refresh group");
   }
-  const refreshGroupId = await ws.db.runReadWriteTx(
+  const refreshGroupId = await wex.db.runReadWriteTx(
     ["refreshGroups", "coinAvailability", "denominations", "coins"],
     async (tx) => {
       let coinPubs: CoinRefreshRequest[] = [];
@@ -1425,7 +1429,7 @@ export async function forceRefresh(
           throw Error(`coin (pubkey ${c}) not found`);
         }
         const denom = await getDenomInfo(
-          ws,
+          wex,
           tx,
           coin.exchangeBaseUrl,
           coin.denomPubHash,
@@ -1437,7 +1441,7 @@ export async function forceRefresh(
         });
       }
       return await createRefreshGroup(
-        ws,
+        wex,
         tx,
         Amounts.currencyOf(coinPubs[0].amount),
         coinPubs,
diff --git a/packages/taler-wallet-core/src/reward.ts 
b/packages/taler-wallet-core/src/reward.ts
index 51eb0f5bd..b8cf41326 100644
--- a/packages/taler-wallet-core/src/reward.ts
+++ b/packages/taler-wallet-core/src/reward.ts
@@ -42,7 +42,7 @@ import {
   constructTransactionIdentifier,
   notifyTransition,
 } from "./transactions.js";
-import { InternalWalletState } from "./wallet.js";
+import { InternalWalletState, WalletExecutionContext } from "./wallet.js";
 
 const logger = new Logger("operations/tip.ts");
 
@@ -51,7 +51,7 @@ export class RewardTransactionContext implements 
TransactionContext {
   public taskId: TaskIdStr;
 
   constructor(
-    public ws: InternalWalletState,
+    public wex: WalletExecutionContext,
     public walletRewardId: string,
   ) {
     this.transactionId = constructTransactionIdentifier({
@@ -65,8 +65,8 @@ export class RewardTransactionContext implements 
TransactionContext {
   }
 
   async deleteTransaction(): Promise<void> {
-    const { ws, walletRewardId } = this;
-    await ws.db.runReadWriteTx(["rewards", "tombstones"], async (tx) => {
+    const { wex, walletRewardId } = this;
+    await wex.db.runReadWriteTx(["rewards", "tombstones"], async (tx) => {
       const tipRecord = await tx.rewards.get(walletRewardId);
       if (tipRecord) {
         await tx.rewards.delete(walletRewardId);
@@ -78,8 +78,8 @@ export class RewardTransactionContext implements 
TransactionContext {
   }
 
   async suspendTransaction(): Promise<void> {
-    const { ws, walletRewardId, transactionId, taskId: retryTag } = this;
-    const transitionInfo = await ws.db.runReadWriteTx(
+    const { wex, walletRewardId, transactionId, taskId } = this;
+    const transitionInfo = await wex.db.runReadWriteTx(
       ["rewards"],
       async (tx) => {
         const tipRec = await tx.rewards.get(walletRewardId);
@@ -115,12 +115,12 @@ export class RewardTransactionContext implements 
TransactionContext {
         return undefined;
       },
     );
-    notifyTransition(ws, transactionId, transitionInfo);
+    notifyTransition(wex, transactionId, transitionInfo);
   }
 
   async abortTransaction(): Promise<void> {
-    const { ws, walletRewardId, transactionId, taskId: retryTag } = this;
-    const transitionInfo = await ws.db.runReadWriteTx(
+    const { wex, walletRewardId, transactionId } = this;
+    const transitionInfo = await wex.db.runReadWriteTx(
       ["rewards"],
       async (tx) => {
         const tipRec = await tx.rewards.get(walletRewardId);
@@ -155,11 +155,11 @@ export class RewardTransactionContext implements 
TransactionContext {
         return undefined;
       },
     );
-    notifyTransition(ws, transactionId, transitionInfo);
+    notifyTransition(wex, transactionId, transitionInfo);
   }
 
   async resumeTransaction(): Promise<void> {
-    const { ws, walletRewardId, transactionId, taskId: retryTag } = this;
+    const { wex: ws, walletRewardId, transactionId, taskId: retryTag } = this;
     const transitionInfo = await ws.db.runReadWriteTx(
       ["rewards"],
       async (tx) => {
@@ -199,7 +199,7 @@ export class RewardTransactionContext implements 
TransactionContext {
   }
 
   async failTransaction(): Promise<void> {
-    const { ws, walletRewardId, transactionId, taskId: retryTag } = this;
+    const { wex: ws, walletRewardId, transactionId, taskId: retryTag } = this;
     const transitionInfo = await ws.db.runReadWriteTx(
       ["rewards"],
       async (tx) => {
diff --git a/packages/taler-wallet-core/src/shepherd.ts 
b/packages/taler-wallet-core/src/shepherd.ts
index ec0f6a76e..e3a0bd609 100644
--- a/packages/taler-wallet-core/src/shepherd.ts
+++ b/packages/taler-wallet-core/src/shepherd.ts
@@ -93,7 +93,7 @@ import {
   constructTransactionIdentifier,
   parseTransactionIdentifier,
 } from "./transactions.js";
-import { InternalWalletState } from "./wallet.js";
+import { InternalWalletState, WalletExecutionContext } from "./wallet.js";
 import {
   computeWithdrawalTransactionStatus,
   processWithdrawalGroup,
@@ -563,66 +563,41 @@ async function callOperationHandlerForTaskId(
   taskId: TaskIdStr,
   cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
+  const wex: WalletExecutionContext = {
+    ws,
+    cancellationToken,
+    cryptoApi: ws.cryptoApi,
+    db: ws.db,
+    http: ws.http,
+    taskScheduler: ws.taskScheduler,
+    oc: {
+      observe(event) {},
+    },
+  };
   const pending = parseTaskIdentifier(taskId);
   switch (pending.tag) {
     case PendingTaskType.ExchangeUpdate:
-      return await updateExchangeFromUrlHandler(
-        ws,
-        pending.exchangeBaseUrl,
-        cancellationToken,
-      );
+      return await updateExchangeFromUrlHandler(wex, pending.exchangeBaseUrl);
     case PendingTaskType.Refresh:
-      return await processRefreshGroup(
-        ws,
-        pending.refreshGroupId,
-        cancellationToken,
-      );
+      return await processRefreshGroup(wex, pending.refreshGroupId);
     case PendingTaskType.Withdraw:
-      return await processWithdrawalGroup(
-        ws,
-        pending.withdrawalGroupId,
-        cancellationToken,
-      );
+      return await processWithdrawalGroup(wex, pending.withdrawalGroupId);
     case PendingTaskType.Purchase:
-      return await processPurchase(ws, pending.proposalId, cancellationToken);
+      return await processPurchase(wex, pending.proposalId);
     case PendingTaskType.Recoup:
-      return await processRecoupGroup(
-        ws,
-        pending.recoupGroupId,
-        cancellationToken,
-      );
+      return await processRecoupGroup(wex, pending.recoupGroupId);
     case PendingTaskType.Deposit:
-      return await processDepositGroup(
-        ws,
-        pending.depositGroupId,
-        cancellationToken,
-      );
+      return await processDepositGroup(wex, pending.depositGroupId);
     case PendingTaskType.Backup:
-      return await processBackupForProvider(ws, pending.backupProviderBaseUrl);
+      return await processBackupForProvider(wex, 
pending.backupProviderBaseUrl);
     case PendingTaskType.PeerPushDebit:
-      return await processPeerPushDebit(
-        ws,
-        pending.pursePub,
-        cancellationToken,
-      );
+      return await processPeerPushDebit(wex, pending.pursePub);
     case PendingTaskType.PeerPullCredit:
-      return await processPeerPullCredit(
-        ws,
-        pending.pursePub,
-        cancellationToken,
-      );
+      return await processPeerPullCredit(wex, pending.pursePub);
     case PendingTaskType.PeerPullDebit:
-      return await processPeerPullDebit(
-        ws,
-        pending.peerPullDebitId,
-        cancellationToken,
-      );
+      return await processPeerPullDebit(wex, pending.peerPullDebitId);
     case PendingTaskType.PeerPushCredit:
-      return await processPeerPushCredit(
-        ws,
-        pending.peerPushCreditId,
-        cancellationToken,
-      );
+      return await processPeerPushCredit(wex, pending.peerPushCreditId);
     case PendingTaskType.RewardPickup:
       throw Error("not supported anymore");
     default:
diff --git a/packages/taler-wallet-core/src/testing.ts 
b/packages/taler-wallet-core/src/testing.ts
index 22af816e5..45a29a6e3 100644
--- a/packages/taler-wallet-core/src/testing.ts
+++ b/packages/taler-wallet-core/src/testing.ts
@@ -77,7 +77,7 @@ import {
 import { initiatePeerPushDebit } from "./pay-peer-push-debit.js";
 import { getRefreshesForTransaction } from "./refresh.js";
 import { getTransactionById, getTransactions } from "./transactions.js";
-import type { InternalWalletState } from "./wallet.js";
+import type { InternalWalletState, WalletExecutionContext } from "./wallet.js";
 import { acceptWithdrawalFromUri } from "./withdraw.js";
 
 const logger = new Logger("operations/testing.ts");
@@ -100,7 +100,7 @@ export interface WithdrawTestBalanceResult {
 }
 
 export async function withdrawTestBalance(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: WithdrawTestBalanceRequest,
 ): Promise<WithdrawTestBalanceResult> {
   const amount = req.amount;
@@ -123,7 +123,7 @@ export async function withdrawTestBalance(
     amount,
   );
 
-  const acceptResp = await acceptWithdrawalFromUri(ws, {
+  const acceptResp = await acceptWithdrawalFromUri(wex, {
     talerWithdrawUri: wresp.taler_withdraw_uri,
     selectedExchange: exchangeBaseUrl,
     forcedDenomSel: req.forcedDenomSel,
@@ -239,13 +239,13 @@ interface MakePaymentResult {
 }
 
 async function makePayment(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   merchant: MerchantBackendInfo,
   amount: string,
   summary: string,
 ): Promise<MakePaymentResult> {
   const orderResp = await createOrder(
-    ws.http,
+    wex.http,
     merchant,
     amount,
     summary,
@@ -254,7 +254,7 @@ async function makePayment(
 
   logger.trace("created order with orderId", orderResp.orderId);
 
-  let paymentStatus = await checkPayment(ws.http, merchant, orderResp.orderId);
+  let paymentStatus = await checkPayment(wex.http, merchant, 
orderResp.orderId);
 
   logger.trace("payment status", paymentStatus);
 
@@ -263,7 +263,7 @@ async function makePayment(
     throw Error("no taler://pay/ URI in payment response");
   }
 
-  const preparePayResult = await preparePayForUri(ws, talerPayUri);
+  const preparePayResult = await preparePayForUri(wex, talerPayUri);
 
   logger.trace("prepare pay result", preparePayResult);
 
@@ -272,14 +272,14 @@ async function makePayment(
   }
 
   const confirmPayResult = await confirmPay(
-    ws,
+    wex,
     preparePayResult.transactionId,
     undefined,
   );
 
   logger.trace("confirmPayResult", confirmPayResult);
 
-  paymentStatus = await checkPayment(ws.http, merchant, orderResp.orderId);
+  paymentStatus = await checkPayment(wex.http, merchant, orderResp.orderId);
 
   logger.trace("payment status after wallet payment:", paymentStatus);
 
@@ -294,7 +294,7 @@ async function makePayment(
 }
 
 export async function runIntegrationTest(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   args: IntegrationTestArgs,
 ): Promise<void> {
   logger.info("running test with arguments", args);
@@ -303,15 +303,15 @@ export async function runIntegrationTest(
   const currency = parsedSpendAmount.currency;
 
   logger.info("withdrawing test balance");
-  const withdrawRes1 = await withdrawTestBalance(ws, {
+  const withdrawRes1 = await withdrawTestBalance(wex, {
     amount: args.amountToWithdraw,
     corebankApiBaseUrl: args.corebankApiBaseUrl,
     exchangeBaseUrl: args.exchangeBaseUrl,
   });
-  await waitUntilGivenTransactionsFinal(ws, [withdrawRes1.transactionId]);
+  await waitUntilGivenTransactionsFinal(wex, [withdrawRes1.transactionId]);
   logger.info("done withdrawing test balance");
 
-  const balance = await getBalances(ws);
+  const balance = await getBalances(wex);
 
   logger.trace(JSON.stringify(balance, null, 2));
 
@@ -321,14 +321,14 @@ export async function runIntegrationTest(
   };
 
   const makePaymentRes = await makePayment(
-    ws,
+    wex,
     myMerchant,
     args.amountToSpend,
     "hello world",
   );
 
   await waitUntilTransactionWithAssociatedRefreshesFinal(
-    ws,
+    wex,
     makePaymentRes.paymentTransactionId,
   );
 
@@ -338,23 +338,23 @@ export async function runIntegrationTest(
   const refundAmount = Amounts.parseOrThrow(`${currency}:6`);
   const spendAmountThree = Amounts.parseOrThrow(`${currency}:3`);
 
-  const withdrawRes2 = await withdrawTestBalance(ws, {
+  const withdrawRes2 = await withdrawTestBalance(wex, {
     amount: Amounts.stringify(withdrawAmountTwo),
     corebankApiBaseUrl: args.corebankApiBaseUrl,
     exchangeBaseUrl: args.exchangeBaseUrl,
   });
 
-  await waitUntilGivenTransactionsFinal(ws, [withdrawRes2.transactionId]);
+  await waitUntilGivenTransactionsFinal(wex, [withdrawRes2.transactionId]);
 
   const { orderId: refundOrderId } = await makePayment(
-    ws,
+    wex,
     myMerchant,
     Amounts.stringify(spendAmountTwo),
     "order that will be refunded",
   );
 
   const refundUri = await refund(
-    ws.http,
+    wex.http,
     myMerchant,
     refundOrderId,
     "test refund",
@@ -363,20 +363,20 @@ export async function runIntegrationTest(
 
   logger.trace("refund URI", refundUri);
 
-  const refundResp = await startRefundQueryForUri(ws, refundUri);
+  const refundResp = await startRefundQueryForUri(wex, refundUri);
 
   logger.trace("integration test: applied refund");
 
   // Wait until the refund is done
   await waitUntilTransactionWithAssociatedRefreshesFinal(
-    ws,
+    wex,
     refundResp.transactionId,
   );
 
   logger.trace("integration test: making payment after refund");
 
   const paymentResp2 = await makePayment(
-    ws,
+    wex,
     myMerchant,
     Amounts.stringify(spendAmountThree),
     "payment after refund",
@@ -384,12 +384,12 @@ export async function runIntegrationTest(
 
   logger.trace("integration test: make payment done");
 
-  await waitUntilGivenTransactionsFinal(ws, [
+  await waitUntilGivenTransactionsFinal(wex, [
     paymentResp2.paymentTransactionId,
   ]);
   await waitUntilGivenTransactionsFinal(
-    ws,
-    await getRefreshesForTransaction(ws, paymentResp2.paymentTransactionId),
+    wex,
+    await getRefreshesForTransaction(wex, paymentResp2.paymentTransactionId),
   );
 
   logger.trace("integration test: all done!");
@@ -399,12 +399,12 @@ export async function runIntegrationTest(
  * Wait until all transactions are in a final state.
  */
 export async function waitUntilAllTransactionsFinal(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
 ): Promise<void> {
   logger.info("waiting until all transactions are in a final state");
-  ws.taskScheduler.ensureRunning();
+  wex.taskScheduler.ensureRunning();
   let p: OpenedPromise<void> | undefined = undefined;
-  const cancelNotifs = ws.addNotificationListener((notif) => {
+  const cancelNotifs = wex.ws.addNotificationListener((notif) => {
     if (!p) {
       return;
     }
@@ -420,7 +420,7 @@ export async function waitUntilAllTransactionsFinal(
   });
   while (1) {
     p = openPromise();
-    const txs = await getTransactions(ws, {
+    const txs = await getTransactions(wex, {
       includeRefreshes: true,
       filterByState: "nonfinal",
     });
@@ -452,7 +452,7 @@ export async function waitUntilAllTransactionsFinal(
  * Wait until all chosen transactions are in a final state.
  */
 export async function waitUntilGivenTransactionsFinal(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   transactionIds: string[],
 ): Promise<void> {
   logger.info(
@@ -462,10 +462,10 @@ export async function waitUntilGivenTransactionsFinal(
   if (transactionIds.length === 0) {
     return;
   }
-  ws.taskScheduler.ensureRunning();
+  wex.taskScheduler.ensureRunning();
   const txIdSet = new Set(transactionIds);
   let p: OpenedPromise<void> | undefined = undefined;
-  const cancelNotifs = ws.addNotificationListener((notif) => {
+  const cancelNotifs = wex.ws.addNotificationListener((notif) => {
     if (!p) {
       return;
     }
@@ -486,7 +486,7 @@ export async function waitUntilGivenTransactionsFinal(
   });
   while (1) {
     p = openPromise();
-    const txs = await getTransactions(ws, {
+    const txs = await getTransactions(wex, {
       includeRefreshes: true,
       filterByState: "nonfinal",
     });
@@ -519,12 +519,12 @@ export async function waitUntilGivenTransactionsFinal(
 }
 
 export async function waitUntilRefreshesDone(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
 ): Promise<void> {
   logger.info("waiting until all refresh transactions are in a final state");
-  ws.taskScheduler.ensureRunning();
+  wex.taskScheduler.ensureRunning();
   let p: OpenedPromise<void> | undefined = undefined;
-  const cancelNotifs = ws.addNotificationListener((notif) => {
+  const cancelNotifs = wex.ws.addNotificationListener((notif) => {
     if (!p) {
       return;
     }
@@ -540,7 +540,7 @@ export async function waitUntilRefreshesDone(
   });
   while (1) {
     p = openPromise();
-    const txs = await getTransactions(ws, {
+    const txs = await getTransactions(wex, {
       includeRefreshes: true,
       filterByState: "nonfinal",
     });
@@ -572,13 +572,13 @@ export async function waitUntilRefreshesDone(
 }
 
 async function waitUntilTransactionPendingReady(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   transactionId: string,
 ): Promise<void> {
   logger.info(`starting waiting for ${transactionId} to be in pending(ready)`);
-  ws.taskScheduler.ensureRunning();
+  wex.taskScheduler.ensureRunning();
   let p: OpenedPromise<void> | undefined = undefined;
-  const cancelNotifs = ws.addNotificationListener((notif) => {
+  const cancelNotifs = wex.ws.addNotificationListener((notif) => {
     if (!p) {
       return;
     }
@@ -588,7 +588,7 @@ async function waitUntilTransactionPendingReady(
   });
   while (1) {
     p = openPromise();
-    const tx = await getTransactionById(ws, {
+    const tx = await getTransactionById(wex, {
       transactionId,
     });
     if (
@@ -608,7 +608,7 @@ async function waitUntilTransactionPendingReady(
  * Wait until a transaction is in a particular state.
  */
 export async function waitTransactionState(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   transactionId: string,
   txState: TransactionState,
 ): Promise<void> {
@@ -617,9 +617,9 @@ export async function waitTransactionState(
       txState,
     )})`,
   );
-  ws.taskScheduler.ensureRunning();
+  wex.taskScheduler.ensureRunning();
   let p: OpenedPromise<void> | undefined = undefined;
-  const cancelNotifs = ws.addNotificationListener((notif) => {
+  const cancelNotifs = wex.ws.addNotificationListener((notif) => {
     if (!p) {
       return;
     }
@@ -629,7 +629,7 @@ export async function waitTransactionState(
   });
   while (1) {
     p = openPromise();
-    const tx = await getTransactionById(ws, {
+    const tx = await getTransactionById(wex, {
       transactionId,
     });
     if (
@@ -648,31 +648,31 @@ export async function waitTransactionState(
 }
 
 export async function waitUntilTransactionWithAssociatedRefreshesFinal(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   transactionId: string,
 ): Promise<void> {
-  await waitUntilGivenTransactionsFinal(ws, [transactionId]);
+  await waitUntilGivenTransactionsFinal(wex, [transactionId]);
   await waitUntilGivenTransactionsFinal(
-    ws,
-    await getRefreshesForTransaction(ws, transactionId),
+    wex,
+    await getRefreshesForTransaction(wex, transactionId),
   );
 }
 
 export async function waitUntilTransactionFinal(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   transactionId: string,
 ): Promise<void> {
-  await waitUntilGivenTransactionsFinal(ws, [transactionId]);
+  await waitUntilGivenTransactionsFinal(wex, [transactionId]);
 }
 
 export async function runIntegrationTest2(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   args: IntegrationTestV2Args,
 ): Promise<void> {
-  ws.taskScheduler.ensureRunning();
+  wex.taskScheduler.ensureRunning();
   logger.info("running test with arguments", args);
 
-  const exchangeInfo = await fetchFreshExchange(ws, args.exchangeBaseUrl);
+  const exchangeInfo = await fetchFreshExchange(wex, args.exchangeBaseUrl);
 
   const currency = exchangeInfo.currency;
 
@@ -680,15 +680,15 @@ export async function runIntegrationTest2(
   const amountToSpend = Amounts.parseOrThrow(`${currency}:2`);
 
   logger.info("withdrawing test balance");
-  const withdrawalRes = await withdrawTestBalance(ws, {
+  const withdrawalRes = await withdrawTestBalance(wex, {
     amount: Amounts.stringify(amountToWithdraw),
     corebankApiBaseUrl: args.corebankApiBaseUrl,
     exchangeBaseUrl: args.exchangeBaseUrl,
   });
-  await waitUntilTransactionFinal(ws, withdrawalRes.transactionId);
+  await waitUntilTransactionFinal(wex, withdrawalRes.transactionId);
   logger.info("done withdrawing test balance");
 
-  const balance = await getBalances(ws);
+  const balance = await getBalances(wex);
 
   logger.trace(JSON.stringify(balance, null, 2));
 
@@ -698,14 +698,14 @@ export async function runIntegrationTest2(
   };
 
   const makePaymentRes = await makePayment(
-    ws,
+    wex,
     myMerchant,
     Amounts.stringify(amountToSpend),
     "hello world",
   );
 
   await waitUntilTransactionWithAssociatedRefreshesFinal(
-    ws,
+    wex,
     makePaymentRes.paymentTransactionId,
   );
 
@@ -715,24 +715,24 @@ export async function runIntegrationTest2(
   const refundAmount = Amounts.parseOrThrow(`${currency}:6`);
   const spendAmountThree = Amounts.parseOrThrow(`${currency}:3`);
 
-  const withdrawalRes2 = await withdrawTestBalance(ws, {
+  const withdrawalRes2 = await withdrawTestBalance(wex, {
     amount: Amounts.stringify(withdrawAmountTwo),
     corebankApiBaseUrl: args.corebankApiBaseUrl,
     exchangeBaseUrl: args.exchangeBaseUrl,
   });
 
   // Wait until the withdraw is done
-  await waitUntilTransactionFinal(ws, withdrawalRes2.transactionId);
+  await waitUntilTransactionFinal(wex, withdrawalRes2.transactionId);
 
   const { orderId: refundOrderId } = await makePayment(
-    ws,
+    wex,
     myMerchant,
     Amounts.stringify(spendAmountTwo),
     "order that will be refunded",
   );
 
   const refundUri = await refund(
-    ws.http,
+    wex.http,
     myMerchant,
     refundOrderId,
     "test refund",
@@ -741,33 +741,33 @@ export async function runIntegrationTest2(
 
   logger.trace("refund URI", refundUri);
 
-  const refundResp = await startRefundQueryForUri(ws, refundUri);
+  const refundResp = await startRefundQueryForUri(wex, refundUri);
 
   logger.trace("integration test: applied refund");
 
   // Wait until the refund is done
   await waitUntilTransactionWithAssociatedRefreshesFinal(
-    ws,
+    wex,
     refundResp.transactionId,
   );
 
   logger.trace("integration test: making payment after refund");
 
   const makePaymentRes2 = await makePayment(
-    ws,
+    wex,
     myMerchant,
     Amounts.stringify(spendAmountThree),
     "payment after refund",
   );
 
   await waitUntilTransactionWithAssociatedRefreshesFinal(
-    ws,
+    wex,
     makePaymentRes2.paymentTransactionId,
   );
 
   logger.trace("integration test: make payment done");
 
-  const peerPushInit = await initiatePeerPushDebit(ws, {
+  const peerPushInit = await initiatePeerPushDebit(wex, {
     partialContractTerms: {
       amount: `${currency}:1` as AmountString,
       summary: "Payment Peer Push Test",
@@ -780,8 +780,8 @@ export async function runIntegrationTest2(
     },
   });
 
-  await waitUntilTransactionPendingReady(ws, peerPushInit.transactionId);
-  const txDetails = await getTransactionById(ws, {
+  await waitUntilTransactionPendingReady(wex, peerPushInit.transactionId);
+  const txDetails = await getTransactionById(wex, {
     transactionId: peerPushInit.transactionId,
   });
 
@@ -793,15 +793,15 @@ export async function runIntegrationTest2(
     throw Error("internal invariant failed");
   }
 
-  const peerPushCredit = await preparePeerPushCredit(ws, {
+  const peerPushCredit = await preparePeerPushCredit(wex, {
     talerUri: txDetails.talerUri,
   });
 
-  await confirmPeerPushCredit(ws, {
+  await confirmPeerPushCredit(wex, {
     transactionId: peerPushCredit.transactionId,
   });
 
-  const peerPullInit = await initiatePeerPullPayment(ws, {
+  const peerPullInit = await initiatePeerPullPayment(wex, {
     partialContractTerms: {
       amount: `${currency}:1` as AmountString,
       summary: "Payment Peer Pull Test",
@@ -814,33 +814,33 @@ export async function runIntegrationTest2(
     },
   });
 
-  await waitUntilTransactionPendingReady(ws, peerPullInit.transactionId);
+  await waitUntilTransactionPendingReady(wex, peerPullInit.transactionId);
 
-  const peerPullInc = await preparePeerPullDebit(ws, {
+  const peerPullInc = await preparePeerPullDebit(wex, {
     talerUri: peerPullInit.talerUri,
   });
 
-  await confirmPeerPullDebit(ws, {
+  await confirmPeerPullDebit(wex, {
     peerPullDebitId: peerPullInc.peerPullDebitId,
   });
 
   await waitUntilTransactionWithAssociatedRefreshesFinal(
-    ws,
+    wex,
     peerPullInc.transactionId,
   );
 
   await waitUntilTransactionWithAssociatedRefreshesFinal(
-    ws,
+    wex,
     peerPullInit.transactionId,
   );
 
   await waitUntilTransactionWithAssociatedRefreshesFinal(
-    ws,
+    wex,
     peerPushCredit.transactionId,
   );
 
   await waitUntilTransactionWithAssociatedRefreshesFinal(
-    ws,
+    wex,
     peerPushInit.transactionId,
   );
 
@@ -858,7 +858,7 @@ export async function runIntegrationTest2(
     });
   }
 
-  await createDepositGroup(ws, {
+  await createDepositGroup(wex, {
     amount: `${currency}:5` as AmountString,
     depositPaytoUri: depositPayto,
   });
@@ -867,7 +867,7 @@ export async function runIntegrationTest2(
 }
 
 export async function testPay(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   args: TestPayArgs,
 ): Promise<TestPayResult> {
   logger.trace("creating order");
@@ -876,26 +876,26 @@ export async function testPay(
     baseUrl: args.merchantBaseUrl,
   };
   const orderResp = await createOrder(
-    ws.http,
+    wex.http,
     merchant,
     args.amount,
     args.summary,
     "taler://fulfillment-success/thank+you",
   );
   logger.trace("created new order with order ID", orderResp.orderId);
-  const checkPayResp = await checkPayment(ws.http, merchant, 
orderResp.orderId);
+  const checkPayResp = await checkPayment(wex.http, merchant, 
orderResp.orderId);
   const talerPayUri = checkPayResp.taler_pay_uri;
   if (!talerPayUri) {
     console.error("fatal: no taler pay URI received from backend");
     process.exit(1);
   }
   logger.trace("taler pay URI:", talerPayUri);
-  const result = await preparePayForUri(ws, talerPayUri);
+  const result = await preparePayForUri(wex, talerPayUri);
   if (result.status !== PreparePayResultType.PaymentPossible) {
     throw Error(`unexpected prepare pay status: ${result.status}`);
   }
   const r = await confirmPay(
-    ws,
+    wex,
     result.transactionId,
     undefined,
     args.forcedCoinSel,
@@ -903,7 +903,7 @@ export async function testPay(
   if (r.type != ConfirmPayResultType.Done) {
     throw Error("payment not done");
   }
-  const purchase = await ws.db.runReadOnlyTx(["purchases"], async (tx) => {
+  const purchase = await wex.db.runReadOnlyTx(["purchases"], async (tx) => {
     return tx.purchases.get(result.proposalId);
   });
   checkLogicInvariant(!!purchase);
diff --git a/packages/taler-wallet-core/src/transactions.ts 
b/packages/taler-wallet-core/src/transactions.ts
index d7f0c0d18..3beb42187 100644
--- a/packages/taler-wallet-core/src/transactions.ts
+++ b/packages/taler-wallet-core/src/transactions.ts
@@ -132,7 +132,7 @@ import {
   computeTipTransactionActions,
   RewardTransactionContext,
 } from "./reward.js";
-import type { InternalWalletState } from "./wallet.js";
+import type { InternalWalletState, WalletExecutionContext } from "./wallet.js";
 import {
   augmentPaytoUrisForWithdrawal,
   computeWithdrawalTransactionActions,
@@ -215,7 +215,7 @@ const txOrder: { [t in TransactionType]: number } = {
 };
 
 export async function getTransactionById(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: TransactionByIdRequest,
 ): Promise<Transaction> {
   const parsedTx = parseTransactionIdentifier(req.transactionId);
@@ -228,7 +228,7 @@ export async function getTransactionById(
     case TransactionType.InternalWithdrawal:
     case TransactionType.Withdrawal: {
       const withdrawalGroupId = parsedTx.withdrawalGroupId;
-      return await ws.db.runReadWriteTx(
+      return await wex.db.runReadWriteTx(
         [
           "withdrawalGroups",
           "exchangeDetails",
@@ -273,7 +273,7 @@ export async function getTransactionById(
 
     case TransactionType.Payment: {
       const proposalId = parsedTx.proposalId;
-      return await ws.db.runReadWriteTx(
+      return await wex.db.runReadWriteTx(
         [
           "purchases",
           "tombstones",
@@ -284,7 +284,7 @@ export async function getTransactionById(
         async (tx) => {
           const purchase = await tx.purchases.get(proposalId);
           if (!purchase) throw Error("not found");
-          const download = await expectProposalDownload(ws, purchase, tx);
+          const download = await expectProposalDownload(wex, purchase, tx);
           const contractData = download.contractData;
           const payOpId = TaskIdentifiers.forPay(purchase);
           const payRetryRecord = await tx.operationRetries.get(payOpId);
@@ -306,7 +306,7 @@ export async function getTransactionById(
     case TransactionType.Refresh: {
       // FIXME: We should return info about the refresh here!;
       const refreshGroupId = parsedTx.refreshGroupId;
-      return await ws.db.runReadOnlyTx(
+      return await wex.db.runReadOnlyTx(
         ["refreshGroups", "operationRetries"],
         async (tx) => {
           const refreshGroupRec = await tx.refreshGroups.get(refreshGroupId);
@@ -323,7 +323,7 @@ export async function getTransactionById(
 
     case TransactionType.Reward: {
       const tipId = parsedTx.walletRewardId;
-      return await ws.db.runReadWriteTx(
+      return await wex.db.runReadWriteTx(
         ["rewards", "operationRetries"],
         async (tx) => {
           const tipRecord = await tx.rewards.get(tipId);
@@ -339,7 +339,7 @@ export async function getTransactionById(
 
     case TransactionType.Deposit: {
       const depositGroupId = parsedTx.depositGroupId;
-      return await ws.db.runReadWriteTx(
+      return await wex.db.runReadWriteTx(
         ["depositGroups", "operationRetries"],
         async (tx) => {
           const depositRecord = await tx.depositGroups.get(depositGroupId);
@@ -354,7 +354,7 @@ export async function getTransactionById(
     }
 
     case TransactionType.Refund: {
-      return await ws.db.runReadOnlyTx(
+      return await wex.db.runReadOnlyTx(
         ["refundGroups", "purchases", "operationRetries", "contractTerms"],
         async (tx) => {
           const refundRecord = await tx.refundGroups.get(
@@ -372,7 +372,7 @@ export async function getTransactionById(
       );
     }
     case TransactionType.PeerPullDebit: {
-      return await ws.db.runReadWriteTx(
+      return await wex.db.runReadWriteTx(
         ["peerPullDebit", "contractTerms"],
         async (tx) => {
           const debit = await tx.peerPullDebit.get(parsedTx.peerPullDebitId);
@@ -391,7 +391,7 @@ export async function getTransactionById(
     }
 
     case TransactionType.PeerPushDebit: {
-      return await ws.db.runReadWriteTx(
+      return await wex.db.runReadWriteTx(
         ["peerPushDebit", "contractTerms"],
         async (tx) => {
           const debit = await tx.peerPushDebit.get(parsedTx.pursePub);
@@ -408,7 +408,7 @@ export async function getTransactionById(
 
     case TransactionType.PeerPushCredit: {
       const peerPushCreditId = parsedTx.peerPushCreditId;
-      return await ws.db.runReadWriteTx(
+      return await wex.db.runReadWriteTx(
         [
           "peerPushCredit",
           "contractTerms",
@@ -446,7 +446,7 @@ export async function getTransactionById(
 
     case TransactionType.PeerPullCredit: {
       const pursePub = parsedTx.pursePub;
-      return await ws.db.runReadWriteTx(
+      return await wex.db.runReadWriteTx(
         [
           "peerPullCredit",
           "contractTerms",
@@ -1039,10 +1039,10 @@ async function buildTransactionForPurchase(
 }
 
 export async function getWithdrawalTransactionByUri(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   request: WithdrawalTransactionByURIRequest,
 ): Promise<TransactionWithdrawal | undefined> {
-  return await ws.db.runReadWriteTx(
+  return await wex.db.runReadWriteTx(
     ["withdrawalGroups", "exchangeDetails", "exchanges", "operationRetries"],
     async (tx) => {
       const withdrawalGroupRecord =
@@ -1085,7 +1085,7 @@ export async function getWithdrawalTransactionByUri(
  * Retrieve the full event history for this wallet.
  */
 export async function getTransactions(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   transactionsRequest?: TransactionsRequest,
 ): Promise<TransactionsResponse> {
   const transactions: Transaction[] = [];
@@ -1095,7 +1095,7 @@ export async function getTransactions(
     filter.onlyState = transactionsRequest.filterByState;
   }
 
-  await ws.db.runReadOnlyTx(
+  await wex.db.runReadOnlyTx(
     [
       "coins",
       "denominations",
@@ -1698,18 +1698,18 @@ function maybeTaskFromTransaction(
  * of a transaction.
  */
 export async function retryTransaction(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   transactionId: string,
 ): Promise<void> {
   logger.info(`resetting retry timeout for ${transactionId}`);
   const taskId = maybeTaskFromTransaction(transactionId);
   if (taskId) {
-    ws.taskScheduler.resetTaskRetries(taskId);
+    wex.taskScheduler.resetTaskRetries(taskId);
   }
 }
 
 async function getContextForTransaction(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   transactionId: string,
 ): Promise<TransactionContext> {
   const tx = parseTransactionIdentifier(transactionId);
@@ -1718,26 +1718,26 @@ async function getContextForTransaction(
   }
   switch (tx.tag) {
     case TransactionType.Deposit:
-      return new DepositTransactionContext(ws, tx.depositGroupId);
+      return new DepositTransactionContext(wex, tx.depositGroupId);
     case TransactionType.Refresh:
-      return new RefreshTransactionContext(ws, tx.refreshGroupId);
+      return new RefreshTransactionContext(wex, tx.refreshGroupId);
     case TransactionType.InternalWithdrawal:
     case TransactionType.Withdrawal:
-      return new WithdrawTransactionContext(ws, tx.withdrawalGroupId);
+      return new WithdrawTransactionContext(wex, tx.withdrawalGroupId);
     case TransactionType.Payment:
-      return new PayMerchantTransactionContext(ws, tx.proposalId);
+      return new PayMerchantTransactionContext(wex, tx.proposalId);
     case TransactionType.PeerPullCredit:
-      return new PeerPullCreditTransactionContext(ws, tx.pursePub);
+      return new PeerPullCreditTransactionContext(wex, tx.pursePub);
     case TransactionType.PeerPushDebit:
-      return new PeerPushDebitTransactionContext(ws, tx.pursePub);
+      return new PeerPushDebitTransactionContext(wex, tx.pursePub);
     case TransactionType.PeerPullDebit:
-      return new PeerPullDebitTransactionContext(ws, tx.peerPullDebitId);
+      return new PeerPullDebitTransactionContext(wex, tx.peerPullDebitId);
     case TransactionType.PeerPushCredit:
-      return new PeerPushCreditTransactionContext(ws, tx.peerPushCreditId);
+      return new PeerPushCreditTransactionContext(wex, tx.peerPushCreditId);
     case TransactionType.Refund:
-      return new RefundTransactionContext(ws, tx.refundGroupId);
+      return new RefundTransactionContext(wex, tx.refundGroupId);
     case TransactionType.Reward:
-      return new RewardTransactionContext(ws, tx.walletRewardId);
+      return new RewardTransactionContext(wex, tx.walletRewardId);
     case TransactionType.Recoup:
       throw new Error("not yet supported");
     //return new RecoupTransactionContext(ws, tx.recoupGroupId);
@@ -1753,18 +1753,18 @@ async function getContextForTransaction(
  * to take longer (such as a backup, recovery or very large withdrawal 
operation).
  */
 export async function suspendTransaction(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   transactionId: string,
 ): Promise<void> {
-  const ctx = await getContextForTransaction(ws, transactionId);
+  const ctx = await getContextForTransaction(wex, transactionId);
   await ctx.suspendTransaction();
 }
 
 export async function failTransaction(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   transactionId: string,
 ): Promise<void> {
-  const ctx = await getContextForTransaction(ws, transactionId);
+  const ctx = await getContextForTransaction(wex, transactionId);
   await ctx.failTransaction();
 }
 
@@ -1772,10 +1772,10 @@ export async function failTransaction(
  * Resume a suspended transaction.
  */
 export async function resumeTransaction(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   transactionId: string,
 ): Promise<void> {
-  const ctx = await getContextForTransaction(ws, transactionId);
+  const ctx = await getContextForTransaction(wex, transactionId);
   await ctx.resumeTransaction();
 }
 
@@ -1783,21 +1783,21 @@ export async function resumeTransaction(
  * Permanently delete a transaction based on the transaction ID.
  */
 export async function deleteTransaction(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   transactionId: string,
 ): Promise<void> {
-  const ctx = await getContextForTransaction(ws, transactionId);
+  const ctx = await getContextForTransaction(wex, transactionId);
   await ctx.deleteTransaction();
   if (ctx.taskId) {
-    ws.taskScheduler.stopShepherdTask(ctx.taskId);
+    wex.taskScheduler.stopShepherdTask(ctx.taskId);
   }
 }
 
 export async function abortTransaction(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   transactionId: string,
 ): Promise<void> {
-  const ctx = await getContextForTransaction(ws, transactionId);
+  const ctx = await getContextForTransaction(wex, transactionId);
   await ctx.abortTransaction();
 }
 
@@ -1810,7 +1810,7 @@ export interface TransitionInfo {
  * Notify of a state transition if necessary.
  */
 export function notifyTransition(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   transactionId: string,
   transitionInfo: TransitionInfo | undefined,
   experimentalUserData: any = undefined,
@@ -1822,7 +1822,7 @@ export function notifyTransition(
       transitionInfo.oldTxState.minor === transitionInfo.newTxState.minor
     )
   ) {
-    ws.notify({
+    wex.ws.notify({
       type: NotificationType.TransactionStateTransition,
       oldTxState: transitionInfo.oldTxState,
       newTxState: transitionInfo.newTxState,
diff --git a/packages/taler-wallet-core/src/wallet.ts 
b/packages/taler-wallet-core/src/wallet.ts
index f21ba6ec1..236b27575 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -285,6 +285,7 @@ export interface WalletExecutionContext {
   readonly http: HttpRequestLibrary;
   readonly db: DbAccess<typeof WalletStoresV1>;
   readonly oc: ObservabilityContext;
+  readonly taskScheduler: TaskScheduler;
 }
 
 export const EXCHANGE_COINS_LOCK = "exchange-coins-lock";
@@ -299,16 +300,16 @@ type CancelFn = () => void;
  * auditors into the database, unless these defaults have
  * already been applied.
  */
-async function fillDefaults(ws: InternalWalletState): Promise<void> {
+async function fillDefaults(wex: WalletExecutionContext): Promise<void> {
   const notifications: WalletNotification[] = [];
-  await ws.db.runReadWriteTx(["config", "exchanges"], async (tx) => {
+  await wex.db.runReadWriteTx(["config", "exchanges"], async (tx) => {
     const appliedRec = await tx.config.get("currencyDefaultsApplied");
     let alreadyApplied = appliedRec ? !!appliedRec.value : false;
     if (alreadyApplied) {
       logger.trace("defaults already applied");
       return;
     }
-    for (const exch of ws.config.builtin.exchanges) {
+    for (const exch of wex.ws.config.builtin.exchanges) {
       const resp = await addPresetExchangeEntry(
         tx,
         exch.exchangeBaseUrl,
@@ -324,18 +325,18 @@ async function fillDefaults(ws: InternalWalletState): 
Promise<void> {
     });
   });
   for (const notif of notifications) {
-    ws.notify(notif);
+    wex.ws.notify(notif);
   }
 }
 
 export async function getDenomInfo(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   tx: WalletDbReadOnlyTransaction<["denominations"]>,
   exchangeBaseUrl: string,
   denomPubHash: string,
 ): Promise<DenominationInfo | undefined> {
   const key = `${exchangeBaseUrl}:${denomPubHash}`;
-  const cached = ws.denomCache[key];
+  const cached = wex.ws.denomCache[key];
   if (cached) {
     return cached;
   }
@@ -351,11 +352,11 @@ export async function getDenomInfo(
  * previous withdrawals.
  */
 async function listKnownBankAccounts(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   currency?: string,
 ): Promise<KnownBankAccounts> {
   const accounts: KnownBankAccountsInfo[] = [];
-  await ws.db.runReadOnlyTx(["bankAccounts"], async (tx) => {
+  await wex.db.runReadOnlyTx(["bankAccounts"], async (tx) => {
     const knownAccounts = await tx.bankAccounts.iter().toArray();
     for (const r of knownAccounts) {
       if (currency && currency !== r.currency) {
@@ -378,12 +379,12 @@ async function listKnownBankAccounts(
 /**
  */
 async function addKnownBankAccounts(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   payto: string,
   alias: string,
   currency: string,
 ): Promise<void> {
-  await ws.db.runReadWriteTx(["bankAccounts"], async (tx) => {
+  await wex.db.runReadWriteTx(["bankAccounts"], async (tx) => {
     tx.bankAccounts.put({
       uri: payto,
       alias: alias,
@@ -397,10 +398,10 @@ async function addKnownBankAccounts(
 /**
  */
 async function forgetKnownBankAccounts(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   payto: string,
 ): Promise<void> {
-  await ws.db.runReadWriteTx(["bankAccounts"], async (tx) => {
+  await wex.db.runReadWriteTx(["bankAccounts"], async (tx) => {
     const account = await tx.bankAccounts.get(payto);
     if (!account) {
       throw Error(`account not found: ${payto}`);
@@ -411,11 +412,11 @@ async function forgetKnownBankAccounts(
 }
 
 async function setCoinSuspended(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   coinPub: string,
   suspended: boolean,
 ): Promise<void> {
-  await ws.db.runReadWriteTx(["coins", "coinAvailability"], async (tx) => {
+  await wex.db.runReadWriteTx(["coins", "coinAvailability"], async (tx) => {
     const c = await tx.coins.get(coinPub);
     if (!c) {
       logger.warn(`coin ${coinPub} not found, won't suspend`);
@@ -453,10 +454,10 @@ async function setCoinSuspended(
 /**
  * Dump the public information of coins we have in an easy-to-process format.
  */
-async function dumpCoins(ws: InternalWalletState): Promise<CoinDumpJson> {
+async function dumpCoins(wex: WalletExecutionContext): Promise<CoinDumpJson> {
   const coinsJson: CoinDumpJson = { coins: [] };
   logger.info("dumping coins");
-  await ws.db.runReadOnlyTx(["coins", "denominations"], async (tx) => {
+  await wex.db.runReadOnlyTx(["coins", "denominations"], async (tx) => {
     const coins = await tx.coins.iter().toArray();
     for (const c of coins) {
       const denom = await tx.denominations.get([
@@ -477,7 +478,7 @@ async function dumpCoins(ws: InternalWalletState): 
Promise<CoinDumpJson> {
         withdrawalReservePub = cs.reservePub;
       }
       const denomInfo = await getDenomInfo(
-        ws,
+        wex,
         tx,
         c.exchangeBaseUrl,
         c.denomPubHash,
@@ -530,10 +531,10 @@ async function getClientFromWalletState(
 }
 
 async function createStoredBackup(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
 ): Promise<CreateStoredBackupResponse> {
-  const backup = await exportDb(ws.idb);
-  const backupsDb = await openStoredBackupsDatabase(ws.idb);
+  const backup = await exportDb(wex.ws.idb);
+  const backupsDb = await openStoredBackupsDatabase(wex.ws.idb);
   const name = `backup-${new Date().getTime()}`;
   await backupsDb.runAllStoresReadWriteTx(async (tx) => {
     await tx.backupMeta.add({
@@ -547,12 +548,12 @@ async function createStoredBackup(
 }
 
 async function listStoredBackups(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
 ): Promise<StoredBackupList> {
   const storedBackups: StoredBackupList = {
     storedBackups: [],
   };
-  const backupsDb = await openStoredBackupsDatabase(ws.idb);
+  const backupsDb = await openStoredBackupsDatabase(wex.ws.idb);
   await backupsDb.runAllStoresReadWriteTx(async (tx) => {
     await tx.backupMeta.iter().forEach((x) => {
       storedBackups.storedBackups.push({
@@ -564,10 +565,10 @@ async function listStoredBackups(
 }
 
 async function deleteStoredBackup(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: DeleteStoredBackupRequest,
 ): Promise<void> {
-  const backupsDb = await openStoredBackupsDatabase(ws.idb);
+  const backupsDb = await openStoredBackupsDatabase(wex.ws.idb);
   await backupsDb.runAllStoresReadWriteTx(async (tx) => {
     await tx.backupData.delete(req.name);
     await tx.backupMeta.delete(req.name);
@@ -575,12 +576,12 @@ async function deleteStoredBackup(
 }
 
 async function recoverStoredBackup(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: RecoverStoredBackupRequest,
 ): Promise<void> {
   logger.info(`Recovering stored backup ${req.name}`);
   const { name } = req;
-  const backupsDb = await openStoredBackupsDatabase(ws.idb);
+  const backupsDb = await openStoredBackupsDatabase(wex.ws.idb);
   const bd = await backupsDb.runAllStoresReadWriteTx(async (tx) => {
     const backupMeta = tx.backupMeta.get(name);
     if (!backupMeta) {
@@ -593,12 +594,12 @@ async function recoverStoredBackup(
     return backupData;
   });
   logger.info(`backup found, now importing`);
-  await importDb(ws.db.idbHandle(), bd);
+  await importDb(wex.db.idbHandle(), bd);
   logger.info(`import done`);
 }
 
 async function handlePrepareWithdrawExchange(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: PrepareWithdrawExchangeRequest,
 ): Promise<PrepareWithdrawExchangeResponse> {
   const parsedUri = parseTalerUri(req.talerUri);
@@ -606,7 +607,7 @@ async function handlePrepareWithdrawExchange(
     throw Error("expected a taler://withdraw-exchange URI");
   }
   const exchangeBaseUrl = parsedUri.exchangeBaseUrl;
-  const exchange = await fetchFreshExchange(ws, exchangeBaseUrl);
+  const exchange = await fetchFreshExchange(wex, exchangeBaseUrl);
   if (exchange.masterPub != parsedUri.exchangePub) {
     throw Error("mismatch of exchange master public key (URI vs actual)");
   }
@@ -638,11 +639,11 @@ export interface PendingOperationsResponse {
  * Implementation of the "wallet-core" API.
  */
 async function dispatchRequestInternal<Op extends WalletApiOperation>(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   operation: WalletApiOperation,
   payload: unknown,
 ): Promise<WalletCoreResponseType<typeof operation>> {
-  if (!ws.initCalled && operation !== WalletApiOperation.InitWallet) {
+  if (!wex.ws.initCalled && operation !== WalletApiOperation.InitWallet) {
     throw Error(
       `wallet must be initialized before running operation ${operation}`,
     );
@@ -651,17 +652,17 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
   // definitions we already have?
   switch (operation) {
     case WalletApiOperation.CreateStoredBackup:
-      return createStoredBackup(ws);
+      return createStoredBackup(wex);
     case WalletApiOperation.DeleteStoredBackup: {
       const req = codecForDeleteStoredBackupRequest().decode(payload);
-      await deleteStoredBackup(ws, req);
+      await deleteStoredBackup(wex, req);
       return {};
     }
     case WalletApiOperation.ListStoredBackups:
-      return listStoredBackups(ws);
+      return listStoredBackups(wex);
     case WalletApiOperation.RecoverStoredBackup: {
       const req = codecForRecoverStoredBackupRequest().decode(payload);
-      await recoverStoredBackup(ws, req);
+      await recoverStoredBackup(wex, req);
       return {};
     }
     case WalletApiOperation.InitWallet: {
@@ -669,7 +670,7 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
       // Write to the DB to make sure that we're failing early in
       // case the DB is not writeable.
       try {
-        await ws.db.runReadWriteTx(["config"], async (tx) => {
+        await wex.db.runReadWriteTx(["config"], async (tx) => {
           tx.config.put({
             key: ConfigRecordKey.LastInitInfo,
             value: timestampProtocolToDb(TalerProtocolTimestamp.now()),
@@ -681,41 +682,41 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
           innerError: getErrorDetailFromException(e),
         });
       }
-      ws.initCalled = true;
-      if (ws.config.testing.skipDefaults) {
+      wex.ws.initCalled = true;
+      if (wex.ws.config.testing.skipDefaults) {
         logger.trace("skipping defaults");
       } else {
         logger.trace("filling defaults");
-        await fillDefaults(ws);
+        await fillDefaults(wex);
       }
       const resp: InitResponse = {
-        versionInfo: getVersion(ws),
+        versionInfo: getVersion(wex),
       };
       return resp;
     }
     case WalletApiOperation.WithdrawTestkudos: {
-      await withdrawTestBalance(ws, {
+      await withdrawTestBalance(wex, {
         amount: "TESTKUDOS:10" as AmountString,
         corebankApiBaseUrl: "https://bank.test.taler.net/";,
         exchangeBaseUrl: "https://exchange.test.taler.net/";,
       });
       return {
-        versionInfo: getVersion(ws),
+        versionInfo: getVersion(wex),
       };
     }
     case WalletApiOperation.WithdrawTestBalance: {
       const req = codecForWithdrawTestBalance().decode(payload);
-      await withdrawTestBalance(ws, req);
+      await withdrawTestBalance(wex, req);
       return {};
     }
     case WalletApiOperation.RunIntegrationTest: {
       const req = codecForIntegrationTestArgs().decode(payload);
-      await runIntegrationTest(ws, req);
+      await runIntegrationTest(wex, req);
       return {};
     }
     case WalletApiOperation.RunIntegrationTestV2: {
       const req = codecForIntegrationTestV2Args().decode(payload);
-      await runIntegrationTest2(ws, req);
+      await runIntegrationTest2(wex, req);
       return {};
     }
     case WalletApiOperation.ValidateIban: {
@@ -728,45 +729,45 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
     }
     case WalletApiOperation.TestPay: {
       const req = codecForTestPayArgs().decode(payload);
-      return await testPay(ws, req);
+      return await testPay(wex, req);
     }
     case WalletApiOperation.GetTransactions: {
       const req = codecForTransactionsRequest().decode(payload);
-      return await getTransactions(ws, req);
+      return await getTransactions(wex, req);
     }
     case WalletApiOperation.GetTransactionById: {
       const req = codecForTransactionByIdRequest().decode(payload);
-      return await getTransactionById(ws, req);
+      return await getTransactionById(wex, req);
     }
     case WalletApiOperation.GetWithdrawalTransactionByUri: {
       const req = codecForGetWithdrawalDetailsForUri().decode(payload);
-      return await getWithdrawalTransactionByUri(ws, req);
+      return await getWithdrawalTransactionByUri(wex, req);
     }
     case WalletApiOperation.AddExchange: {
       const req = codecForAddExchangeRequest().decode(payload);
-      await fetchFreshExchange(ws, req.exchangeBaseUrl, {
+      await fetchFreshExchange(wex, req.exchangeBaseUrl, {
         expectedMasterPub: req.masterPub,
       });
       return {};
     }
     case WalletApiOperation.UpdateExchangeEntry: {
       const req = codecForUpdateExchangeEntryRequest().decode(payload);
-      await fetchFreshExchange(ws, req.exchangeBaseUrl, {
+      await fetchFreshExchange(wex, req.exchangeBaseUrl, {
         forceUpdate: !!req.force,
       });
       return {};
     }
     case WalletApiOperation.ListExchanges: {
-      return await listExchanges(ws);
+      return await listExchanges(wex);
     }
     case WalletApiOperation.GetExchangeEntryByUrl: {
       const req = codecForGetExchangeEntryByUrlRequest().decode(payload);
-      return lookupExchangeByUri(ws, req);
+      return lookupExchangeByUri(wex, req);
     }
     case WalletApiOperation.ListExchangesForScopedCurrency: {
       const req =
         codecForListExchangesForScopedCurrencyRequest().decode(payload);
-      const exchangesResp = await listExchanges(ws);
+      const exchangesResp = await listExchanges(wex);
       const result: ExchangesShortListResponse = {
         exchanges: [],
       };
@@ -783,32 +784,32 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
     }
     case WalletApiOperation.GetExchangeDetailedInfo: {
       const req = codecForAddExchangeRequest().decode(payload);
-      return await getExchangeDetailedInfo(ws, req.exchangeBaseUrl);
+      return await getExchangeDetailedInfo(wex, req.exchangeBaseUrl);
     }
     case WalletApiOperation.ListKnownBankAccounts: {
       const req = codecForListKnownBankAccounts().decode(payload);
-      return await listKnownBankAccounts(ws, req.currency);
+      return await listKnownBankAccounts(wex, req.currency);
     }
     case WalletApiOperation.AddKnownBankAccounts: {
       const req = codecForAddKnownBankAccounts().decode(payload);
-      await addKnownBankAccounts(ws, req.payto, req.alias, req.currency);
+      await addKnownBankAccounts(wex, req.payto, req.alias, req.currency);
       return {};
     }
     case WalletApiOperation.ForgetKnownBankAccounts: {
       const req = codecForForgetKnownBankAccounts().decode(payload);
-      await forgetKnownBankAccounts(ws, req.payto);
+      await forgetKnownBankAccounts(wex, req.payto);
       return {};
     }
     case WalletApiOperation.GetWithdrawalDetailsForUri: {
       const req = codecForGetWithdrawalDetailsForUri().decode(payload);
-      return await getWithdrawalDetailsForUri(ws, req.talerWithdrawUri, {
+      return await getWithdrawalDetailsForUri(wex, req.talerWithdrawUri, {
         notifyChangeFromPendingTimeoutMs: req.notifyChangeFromPendingTimeoutMs,
         restrictAge: req.restrictAge,
       });
     }
     case WalletApiOperation.AcceptManualWithdrawal: {
       const req = codecForAcceptManualWithdrawalRequet().decode(payload);
-      const res = await createManualWithdrawal(ws, {
+      const res = await createManualWithdrawal(wex, {
         amount: Amounts.parseOrThrow(req.amount),
         exchangeBaseUrl: req.exchangeBaseUrl,
         restrictAge: req.restrictAge,
@@ -819,7 +820,7 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
       const req =
         codecForGetWithdrawalDetailsForAmountRequest().decode(payload);
       const wi = await getExchangeWithdrawalInfo(
-        ws,
+        wex,
         req.exchangeBaseUrl,
         Amounts.parseOrThrow(req.amount),
         req.restrictAge,
@@ -843,23 +844,23 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
       return resp;
     }
     case WalletApiOperation.GetBalances: {
-      return await getBalances(ws);
+      return await getBalances(wex);
     }
     case WalletApiOperation.GetBalanceDetail: {
       const req = codecForGetBalanceDetailRequest().decode(payload);
-      return await getBalanceDetail(ws, req);
+      return await getBalanceDetail(wex, req);
     }
     case WalletApiOperation.GetUserAttentionRequests: {
       const req = codecForUserAttentionsRequest().decode(payload);
-      return await getUserAttentions(ws, req);
+      return await getUserAttentions(wex, req);
     }
     case WalletApiOperation.MarkAttentionRequestAsRead: {
       const req = codecForUserAttentionByIdRequest().decode(payload);
-      return await markAttentionRequestAsRead(ws, req);
+      return await markAttentionRequestAsRead(wex, req);
     }
     case WalletApiOperation.GetUserAttentionUnreadCount: {
       const req = codecForUserAttentionsRequest().decode(payload);
-      return await getUserAttentionsUnreadCount(ws, req);
+      return await getUserAttentionsUnreadCount(wex, req);
     }
     case WalletApiOperation.GetPendingOperations: {
       // FIXME: Eventually remove the handler after deprecation period.
@@ -869,7 +870,7 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
     }
     case WalletApiOperation.SetExchangeTosAccepted: {
       const req = codecForAcceptExchangeTosRequest().decode(payload);
-      await acceptExchangeTermsOfService(ws, req.exchangeBaseUrl);
+      await acceptExchangeTermsOfService(wex, req.exchangeBaseUrl);
       return {};
     }
     case WalletApiOperation.SetExchangeTosForgotten: {
@@ -880,7 +881,7 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
     case WalletApiOperation.AcceptBankIntegratedWithdrawal: {
       const req =
         codecForAcceptBankIntegratedWithdrawalRequest().decode(payload);
-      return await acceptWithdrawalFromUri(ws, {
+      return await acceptWithdrawalFromUri(wex, {
         selectedExchange: req.exchangeBaseUrl,
         talerWithdrawUri: req.talerWithdrawUri,
         forcedDenomSel: req.forcedDenomSel,
@@ -890,7 +891,7 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
     case WalletApiOperation.GetExchangeTos: {
       const req = codecForGetExchangeTosRequest().decode(payload);
       return getExchangeTos(
-        ws,
+        wex,
         req.exchangeBaseUrl,
         req.acceptedFormat,
         req.acceptLanguage,
@@ -898,7 +899,7 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
     }
     case WalletApiOperation.GetContractTermsDetails: {
       const req = codecForGetContractTermsDetails().decode(payload);
-      return getContractTermsDetails(ws, req.proposalId);
+      return getContractTermsDetails(wex, req.proposalId);
     }
     case WalletApiOperation.RetryPendingNow: {
       logger.error("retryPendingNow currently not implemented");
@@ -906,19 +907,19 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
     }
     case WalletApiOperation.SharePayment: {
       const req = codecForSharePaymentRequest().decode(payload);
-      return await sharePayment(ws, req.merchantBaseUrl, req.orderId);
+      return await sharePayment(wex, req.merchantBaseUrl, req.orderId);
     }
     case WalletApiOperation.PrepareWithdrawExchange: {
       const req = codecForPrepareWithdrawExchangeRequest().decode(payload);
-      return handlePrepareWithdrawExchange(ws, req);
+      return handlePrepareWithdrawExchange(wex, req);
     }
     case WalletApiOperation.PreparePayForUri: {
       const req = codecForPreparePayRequest().decode(payload);
-      return await preparePayForUri(ws, req.talerPayUri);
+      return await preparePayForUri(wex, req.talerPayUri);
     }
     case WalletApiOperation.PreparePayForTemplate: {
       const req = codecForPreparePayTemplateRequest().decode(payload);
-      return preparePayForTemplate(ws, req);
+      return preparePayForTemplate(wex, req);
     }
     case WalletApiOperation.ConfirmPay: {
       const req = codecForConfirmPayRequest().decode(payload);
@@ -934,45 +935,45 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
       } else {
         throw Error("transactionId or (deprecated) proposalId required");
       }
-      return await confirmPay(ws, transactionId, req.sessionId);
+      return await confirmPay(wex, transactionId, req.sessionId);
     }
     case WalletApiOperation.AbortTransaction: {
       const req = codecForAbortTransaction().decode(payload);
-      await abortTransaction(ws, req.transactionId);
+      await abortTransaction(wex, req.transactionId);
       return {};
     }
     case WalletApiOperation.SuspendTransaction: {
       const req = codecForSuspendTransaction().decode(payload);
-      await suspendTransaction(ws, req.transactionId);
+      await suspendTransaction(wex, req.transactionId);
       return {};
     }
     case WalletApiOperation.FailTransaction: {
       const req = codecForFailTransactionRequest().decode(payload);
-      await failTransaction(ws, req.transactionId);
+      await failTransaction(wex, req.transactionId);
       return {};
     }
     case WalletApiOperation.ResumeTransaction: {
       const req = codecForResumeTransaction().decode(payload);
-      await resumeTransaction(ws, req.transactionId);
+      await resumeTransaction(wex, req.transactionId);
       return {};
     }
     case WalletApiOperation.DumpCoins: {
-      return await dumpCoins(ws);
+      return await dumpCoins(wex);
     }
     case WalletApiOperation.SetCoinSuspended: {
       const req = codecForSetCoinSuspendedRequest().decode(payload);
-      await setCoinSuspended(ws, req.coinPub, req.suspended);
+      await setCoinSuspended(wex, req.coinPub, req.suspended);
       return {};
     }
     case WalletApiOperation.TestingGetSampleTransactions:
       return { transactions: sampleWalletCoreTransactions };
     case WalletApiOperation.ForceRefresh: {
       const req = codecForForceRefreshRequest().decode(payload);
-      return await forceRefresh(ws, req);
+      return await forceRefresh(wex, req);
     }
     case WalletApiOperation.StartRefundQueryForUri: {
       const req = codecForPrepareRefundRequest().decode(payload);
-      return await startRefundQueryForUri(ws, req.talerRefundUri);
+      return await startRefundQueryForUri(wex, req.talerRefundUri);
     }
     case WalletApiOperation.StartRefundQuery: {
       const req = codecForStartRefundQueryRequest().decode(payload);
@@ -983,30 +984,30 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
       if (txIdParsed.tag !== TransactionType.Payment) {
         throw Error("expected payment transaction ID");
       }
-      await startQueryRefund(ws, txIdParsed.proposalId);
+      await startQueryRefund(wex, txIdParsed.proposalId);
       return {};
     }
     case WalletApiOperation.AddBackupProvider: {
       const req = codecForAddBackupProviderRequest().decode(payload);
-      return await addBackupProvider(ws, req);
+      return await addBackupProvider(wex, req);
     }
     case WalletApiOperation.RunBackupCycle: {
       const req = codecForRunBackupCycle().decode(payload);
-      await runBackupCycle(ws, req);
+      await runBackupCycle(wex, req);
       return {};
     }
     case WalletApiOperation.RemoveBackupProvider: {
       const req = codecForRemoveBackupProvider().decode(payload);
-      await removeBackupProvider(ws, req);
+      await removeBackupProvider(wex, req);
       return {};
     }
     case WalletApiOperation.ExportBackupRecovery: {
-      const resp = await getBackupRecovery(ws);
+      const resp = await getBackupRecovery(wex);
       return resp;
     }
     case WalletApiOperation.TestingWaitTransactionState: {
       const req = payload as TestingWaitTransactionRequest;
-      await waitTransactionState(ws, req.transactionId, req.txState);
+      await waitTransactionState(wex, req.transactionId, req.txState);
       return {};
     }
     case WalletApiOperation.GetCurrencySpecification: {
@@ -1055,7 +1056,7 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
     }
     case WalletApiOperation.ImportBackupRecovery: {
       const req = codecForAny().decode(payload);
-      await loadBackupRecovery(ws, req);
+      await loadBackupRecovery(wex, req);
       return {};
     }
     // case WalletApiOperation.GetPlanForOperation: {
@@ -1064,31 +1065,31 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
     // }
     case WalletApiOperation.ConvertDepositAmount: {
       const req = codecForConvertAmountRequest.decode(payload);
-      return await convertDepositAmount(ws, req);
+      return await convertDepositAmount(wex, req);
     }
     case WalletApiOperation.GetMaxDepositAmount: {
       const req = codecForGetAmountRequest.decode(payload);
-      return await getMaxDepositAmount(ws, req);
+      return await getMaxDepositAmount(wex, req);
     }
     case WalletApiOperation.ConvertPeerPushAmount: {
       const req = codecForConvertAmountRequest.decode(payload);
-      return await convertPeerPushAmount(ws, req);
+      return await convertPeerPushAmount(wex, req);
     }
     case WalletApiOperation.GetMaxPeerPushAmount: {
       const req = codecForGetAmountRequest.decode(payload);
-      return await getMaxPeerPushAmount(ws, req);
+      return await getMaxPeerPushAmount(wex, req);
     }
     case WalletApiOperation.ConvertWithdrawalAmount: {
       const req = codecForConvertAmountRequest.decode(payload);
-      return await convertWithdrawalAmount(ws, req);
+      return await convertWithdrawalAmount(wex, req);
     }
     case WalletApiOperation.GetBackupInfo: {
-      const resp = await getBackupInfo(ws);
+      const resp = await getBackupInfo(wex);
       return resp;
     }
     case WalletApiOperation.PrepareDeposit: {
       const req = codecForPrepareDepositRequest().decode(payload);
-      return await prepareDepositGroup(ws, req);
+      return await prepareDepositGroup(wex, req);
     }
     case WalletApiOperation.GenerateDepositGroupTxId:
       return {
@@ -1096,42 +1097,42 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
       };
     case WalletApiOperation.CreateDepositGroup: {
       const req = codecForCreateDepositGroupRequest().decode(payload);
-      return await createDepositGroup(ws, req);
+      return await createDepositGroup(wex, req);
     }
     case WalletApiOperation.DeleteTransaction: {
       const req = codecForDeleteTransactionRequest().decode(payload);
-      await deleteTransaction(ws, req.transactionId);
+      await deleteTransaction(wex, req.transactionId);
       return {};
     }
     case WalletApiOperation.RetryTransaction: {
       const req = codecForRetryTransactionRequest().decode(payload);
-      await retryTransaction(ws, req.transactionId);
+      await retryTransaction(wex, req.transactionId);
       return {};
     }
     case WalletApiOperation.SetWalletDeviceId: {
       const req = codecForSetWalletDeviceIdRequest().decode(payload);
-      await setWalletDeviceId(ws, req.walletDeviceId);
+      await setWalletDeviceId(wex, req.walletDeviceId);
       return {};
     }
     case WalletApiOperation.TestCrypto: {
-      return await ws.cryptoApi.hashString({ str: "hello world" });
+      return await wex.cryptoApi.hashString({ str: "hello world" });
     }
     case WalletApiOperation.ClearDb:
-      await clearDatabase(ws.db.idbHandle());
+      await clearDatabase(wex.db.idbHandle());
       return {};
     case WalletApiOperation.Recycle: {
       throw Error("not implemented");
       return {};
     }
     case WalletApiOperation.ExportDb: {
-      const dbDump = await exportDb(ws.idb);
+      const dbDump = await exportDb(wex.ws.idb);
       return dbDump;
     }
     case WalletApiOperation.ListGlobalCurrencyExchanges: {
       const resp: ListGlobalCurrencyExchangesResponse = {
         exchanges: [],
       };
-      await ws.db.runReadOnlyTx(["globalCurrencyExchanges"], async (tx) => {
+      await wex.db.runReadOnlyTx(["globalCurrencyExchanges"], async (tx) => {
         const gceList = await tx.globalCurrencyExchanges.iter().toArray();
         for (const gce of gceList) {
           resp.exchanges.push({
@@ -1147,7 +1148,7 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
       const resp: ListGlobalCurrencyAuditorsResponse = {
         auditors: [],
       };
-      await ws.db.runReadOnlyTx(["globalCurrencyAuditors"], async (tx) => {
+      await wex.db.runReadOnlyTx(["globalCurrencyAuditors"], async (tx) => {
         const gcaList = await tx.globalCurrencyAuditors.iter().toArray();
         for (const gca of gcaList) {
           resp.auditors.push({
@@ -1161,7 +1162,7 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
     }
     case WalletApiOperation.AddGlobalCurrencyExchange: {
       const req = codecForAddGlobalCurrencyExchangeRequest().decode(payload);
-      await ws.db.runReadWriteTx(["globalCurrencyExchanges"], async (tx) => {
+      await wex.db.runReadWriteTx(["globalCurrencyExchanges"], async (tx) => {
         const key = [req.currency, req.exchangeBaseUrl, req.exchangeMasterPub];
         const existingRec =
           await tx.globalCurrencyExchanges.indexes.byCurrencyAndUrlAndPub.get(
@@ -1180,7 +1181,7 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
     }
     case WalletApiOperation.RemoveGlobalCurrencyExchange: {
       const req = 
codecForRemoveGlobalCurrencyExchangeRequest().decode(payload);
-      await ws.db.runReadWriteTx(["globalCurrencyExchanges"], async (tx) => {
+      await wex.db.runReadWriteTx(["globalCurrencyExchanges"], async (tx) => {
         const key = [req.currency, req.exchangeBaseUrl, req.exchangeMasterPub];
         const existingRec =
           await tx.globalCurrencyExchanges.indexes.byCurrencyAndUrlAndPub.get(
@@ -1196,7 +1197,7 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
     }
     case WalletApiOperation.AddGlobalCurrencyAuditor: {
       const req = codecForAddGlobalCurrencyAuditorRequest().decode(payload);
-      await ws.db.runReadWriteTx(["globalCurrencyAuditors"], async (tx) => {
+      await wex.db.runReadWriteTx(["globalCurrencyAuditors"], async (tx) => {
         const key = [req.currency, req.auditorBaseUrl, req.auditorPub];
         const existingRec =
           await tx.globalCurrencyAuditors.indexes.byCurrencyAndUrlAndPub.get(
@@ -1215,7 +1216,7 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
     }
     case WalletApiOperation.RemoveGlobalCurrencyAuditor: {
       const req = codecForRemoveGlobalCurrencyAuditorRequest().decode(payload);
-      await ws.db.runReadWriteTx(["globalCurrencyAuditors"], async (tx) => {
+      await wex.db.runReadWriteTx(["globalCurrencyAuditors"], async (tx) => {
         const key = [req.currency, req.auditorBaseUrl, req.auditorPub];
         const existingRec =
           await tx.globalCurrencyAuditors.indexes.byCurrencyAndUrlAndPub.get(
@@ -1231,67 +1232,67 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
     }
     case WalletApiOperation.ImportDb: {
       const req = codecForImportDbRequest().decode(payload);
-      await importDb(ws.db.idbHandle(), req.dump);
+      await importDb(wex.db.idbHandle(), req.dump);
       return [];
     }
     case WalletApiOperation.CheckPeerPushDebit: {
       const req = codecForCheckPeerPushDebitRequest().decode(payload);
-      return await checkPeerPushDebit(ws, req);
+      return await checkPeerPushDebit(wex, req);
     }
     case WalletApiOperation.InitiatePeerPushDebit: {
       const req = codecForInitiatePeerPushDebitRequest().decode(payload);
-      return await initiatePeerPushDebit(ws, req);
+      return await initiatePeerPushDebit(wex, req);
     }
     case WalletApiOperation.PreparePeerPushCredit: {
       const req = codecForPreparePeerPushCreditRequest().decode(payload);
-      return await preparePeerPushCredit(ws, req);
+      return await preparePeerPushCredit(wex, req);
     }
     case WalletApiOperation.ConfirmPeerPushCredit: {
       const req = codecForConfirmPeerPushPaymentRequest().decode(payload);
-      return await confirmPeerPushCredit(ws, req);
+      return await confirmPeerPushCredit(wex, req);
     }
     case WalletApiOperation.CheckPeerPullCredit: {
       const req = codecForPreparePeerPullPaymentRequest().decode(payload);
-      return await checkPeerPullPaymentInitiation(ws, req);
+      return await checkPeerPullPaymentInitiation(wex, req);
     }
     case WalletApiOperation.InitiatePeerPullCredit: {
       const req = codecForInitiatePeerPullPaymentRequest().decode(payload);
-      return await initiatePeerPullPayment(ws, req);
+      return await initiatePeerPullPayment(wex, req);
     }
     case WalletApiOperation.PreparePeerPullDebit: {
       const req = codecForCheckPeerPullPaymentRequest().decode(payload);
-      return await preparePeerPullDebit(ws, req);
+      return await preparePeerPullDebit(wex, req);
     }
     case WalletApiOperation.ConfirmPeerPullDebit: {
       const req = codecForAcceptPeerPullPaymentRequest().decode(payload);
-      return await confirmPeerPullDebit(ws, req);
+      return await confirmPeerPullDebit(wex, req);
     }
     case WalletApiOperation.ApplyDevExperiment: {
       const req = codecForApplyDevExperiment().decode(payload);
-      await applyDevExperiment(ws, req.devExperimentUri);
+      await applyDevExperiment(wex, req.devExperimentUri);
       return {};
     }
     case WalletApiOperation.GetVersion: {
-      return getVersion(ws);
+      return getVersion(wex);
     }
     case WalletApiOperation.TestingWaitTransactionsFinal:
-      return await waitUntilAllTransactionsFinal(ws);
+      return await waitUntilAllTransactionsFinal(wex);
     case WalletApiOperation.TestingWaitRefreshesFinal:
-      return await waitUntilRefreshesDone(ws);
+      return await waitUntilRefreshesDone(wex);
     case WalletApiOperation.TestingSetTimetravel: {
       const req = codecForTestingSetTimetravelRequest().decode(payload);
       setDangerousTimetravel(req.offsetMs);
-      ws.taskScheduler.reload();
+      wex.taskScheduler.reload();
       return {};
     }
     case WalletApiOperation.DeleteExchange: {
       const req = codecForDeleteExchangeRequest().decode(payload);
-      await deleteExchange(ws, req);
+      await deleteExchange(wex, req);
       return {};
     }
     case WalletApiOperation.GetExchangeResources: {
       const req = codecForGetExchangeResourcesRequest().decode(payload);
-      return await getExchangeResources(ws, req.exchangeBaseUrl);
+      return await getExchangeResources(wex, req.exchangeBaseUrl);
     }
     case WalletApiOperation.TestingInfiniteTransactionLoop: {
       const myDelayMs = (payload as any).delayMs ?? 5;
@@ -1301,7 +1302,7 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
           const url =
             
"https://exchange.demo.taler.net/reserves/01PMMB9PJN0QBWAFBXV6R0KNJJMAKXCV4D6FDG0GJFDJQXGYP32G?timeout_ms=30000";;
           logger.info(`fetching ${url}`);
-          const res = await ws.http.fetch(url);
+          const res = await wex.http.fetch(url);
           logger.info(`fetch result ${res.status}`);
         }
       };
@@ -1312,7 +1313,7 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
       let loopCount = 0;
       while (true) {
         logger.info(`looping test write tx, iteration ${loopCount}`);
-        await ws.db.runReadWriteTx(["config"], async (tx) => {
+        await wex.db.runReadWriteTx(["config"], async (tx) => {
           await tx.config.put({
             key: ConfigRecordKey.TestLoopTx,
             value: loopCount,
@@ -1338,7 +1339,7 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
   );
 }
 
-export function getVersion(ws: InternalWalletState): WalletCoreVersion {
+export function getVersion(wex: WalletExecutionContext): WalletCoreVersion {
   const result: WalletCoreVersion = {
     implementationSemver: walletCoreBuildInfo.implementationSemver,
     implementationGitHash: walletCoreBuildInfo.implementationGitHash,
@@ -1364,9 +1365,21 @@ async function handleCoreApiRequest(
   id: string,
   payload: unknown,
 ): Promise<CoreApiResponse> {
+  const wex: WalletExecutionContext = {
+    ws,
+    cancellationToken: CancellationToken.CONTINUE,
+    cryptoApi: ws.cryptoApi,
+    db: ws.db,
+    http: ws.http,
+    taskScheduler: ws.taskScheduler,
+    oc: {
+      observe(event) {},
+    },
+  };
+
   try {
     await ws.ensureWalletDbOpen();
-    const result = await dispatchRequestInternal(ws, operation as any, 
payload);
+    const result = await dispatchRequestInternal(wex, operation as any, 
payload);
     return {
       type: "response",
       operation,
diff --git a/packages/taler-wallet-core/src/withdraw.ts 
b/packages/taler-wallet-core/src/withdraw.ts
index 541525c6b..44f1ee4f9 100644
--- a/packages/taler-wallet-core/src/withdraw.ts
+++ b/packages/taler-wallet-core/src/withdraw.ts
@@ -146,7 +146,11 @@ import {
   WALLET_BANK_INTEGRATION_PROTOCOL_VERSION,
   WALLET_EXCHANGE_PROTOCOL_VERSION,
 } from "./versions.js";
-import { getDenomInfo, type InternalWalletState } from "./wallet.js";
+import {
+  WalletExecutionContext,
+  getDenomInfo,
+  type InternalWalletState,
+} from "./wallet.js";
 
 /**
  * Logger for this file.
@@ -158,7 +162,7 @@ export class WithdrawTransactionContext implements 
TransactionContext {
   readonly taskId: TaskIdStr;
 
   constructor(
-    public ws: InternalWalletState,
+    public wex: WalletExecutionContext,
     public withdrawalGroupId: string,
   ) {
     this.transactionId = constructTransactionIdentifier({
@@ -172,7 +176,7 @@ export class WithdrawTransactionContext implements 
TransactionContext {
   }
 
   async deleteTransaction(): Promise<void> {
-    const { ws, withdrawalGroupId } = this;
+    const { wex: ws, withdrawalGroupId } = this;
     await ws.db.runReadWriteTx(
       ["withdrawalGroups", "tombstones"],
       async (tx) => {
@@ -190,8 +194,8 @@ export class WithdrawTransactionContext implements 
TransactionContext {
   }
 
   async suspendTransaction(): Promise<void> {
-    const { ws, withdrawalGroupId, transactionId, taskId } = this;
-    const transitionInfo = await ws.db.runReadWriteTx(
+    const { wex, withdrawalGroupId, transactionId, taskId } = this;
+    const transitionInfo = await wex.db.runReadWriteTx(
       ["withdrawalGroups"],
       async (tx) => {
         const wg = await tx.withdrawalGroups.get(withdrawalGroupId);
@@ -240,12 +244,12 @@ export class WithdrawTransactionContext implements 
TransactionContext {
         return undefined;
       },
     );
-    ws.taskScheduler.stopShepherdTask(taskId);
-    notifyTransition(ws, transactionId, transitionInfo);
+    wex.taskScheduler.stopShepherdTask(taskId);
+    notifyTransition(wex, transactionId, transitionInfo);
   }
 
   async abortTransaction(): Promise<void> {
-    const { ws, withdrawalGroupId, transactionId, taskId } = this;
+    const { wex: ws, withdrawalGroupId, transactionId, taskId } = this;
     const transitionInfo = await ws.db.runReadWriteTx(
       ["withdrawalGroups"],
       async (tx) => {
@@ -307,7 +311,12 @@ export class WithdrawTransactionContext implements 
TransactionContext {
   }
 
   async resumeTransaction(): Promise<void> {
-    const { ws, withdrawalGroupId, transactionId, taskId: retryTag } = this;
+    const {
+      wex: ws,
+      withdrawalGroupId,
+      transactionId,
+      taskId: retryTag,
+    } = this;
     const transitionInfo = await ws.db.runReadWriteTx(
       ["withdrawalGroups"],
       async (tx) => {
@@ -362,7 +371,12 @@ export class WithdrawTransactionContext implements 
TransactionContext {
   }
 
   async failTransaction(): Promise<void> {
-    const { ws, withdrawalGroupId, transactionId, taskId: retryTag } = this;
+    const {
+      wex: ws,
+      withdrawalGroupId,
+      transactionId,
+      taskId: retryTag,
+    } = this;
     const stateUpdate = await ws.db.runReadWriteTx(
       ["withdrawalGroups"],
       async (tx) => {
@@ -618,17 +632,17 @@ export async function getBankWithdrawalInfo(
  * Return denominations that can potentially used for a withdrawal.
  */
 async function getCandidateWithdrawalDenoms(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   exchangeBaseUrl: string,
   currency: string,
 ): Promise<DenominationRecord[]> {
-  return await ws.db.runReadOnlyTx(["denominations"], async (tx) => {
-    return getCandidateWithdrawalDenomsTx(ws, tx, exchangeBaseUrl, currency);
+  return await wex.db.runReadOnlyTx(["denominations"], async (tx) => {
+    return getCandidateWithdrawalDenomsTx(wex, tx, exchangeBaseUrl, currency);
   });
 }
 
 export async function getCandidateWithdrawalDenomsTx(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   tx: WalletDbReadOnlyTransaction<["denominations"]>,
   exchangeBaseUrl: string,
   currency: string,
@@ -638,7 +652,9 @@ export async function getCandidateWithdrawalDenomsTx(
     await tx.denominations.indexes.byExchangeBaseUrl.getAll(exchangeBaseUrl);
   return allDenoms
     .filter((d) => d.currency === currency)
-    .filter((d) => isWithdrawableDenom(d, 
ws.config.testing.denomselAllowLate));
+    .filter((d) =>
+      isWithdrawableDenom(d, wex.ws.config.testing.denomselAllowLate),
+    );
 }
 
 /**
@@ -649,11 +665,11 @@ export async function getCandidateWithdrawalDenomsTx(
  * the exchange requests per reserve.
  */
 async function processPlanchetGenerate(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   withdrawalGroup: WithdrawalGroupRecord,
   coinIdx: number,
 ): Promise<void> {
-  let planchet = await ws.db.runReadOnlyTx(["planchets"], async (tx) => {
+  let planchet = await wex.db.runReadOnlyTx(["planchets"], async (tx) => {
     return tx.planchets.indexes.byGroupAndIndex.get([
       withdrawalGroup.withdrawalGroupId,
       coinIdx,
@@ -677,11 +693,11 @@ async function processPlanchetGenerate(
   }
   const denomPubHash = maybeDenomPubHash;
 
-  const denom = await ws.db.runReadOnlyTx(["denominations"], async (tx) => {
-    return getDenomInfo(ws, tx, withdrawalGroup.exchangeBaseUrl, denomPubHash);
+  const denom = await wex.db.runReadOnlyTx(["denominations"], async (tx) => {
+    return getDenomInfo(wex, tx, withdrawalGroup.exchangeBaseUrl, 
denomPubHash);
   });
   checkDbInvariant(!!denom);
-  const r = await ws.cryptoApi.createPlanchet({
+  const r = await wex.cryptoApi.createPlanchet({
     denomPub: denom.denomPub,
     feeWithdraw: Amounts.parseOrThrow(denom.feeWithdraw),
     reservePriv: withdrawalGroup.reservePriv,
@@ -705,7 +721,7 @@ async function processPlanchetGenerate(
     ageCommitmentProof: r.ageCommitmentProof,
     lastError: undefined,
   };
-  await ws.db.runReadWriteTx(["planchets"], async (tx) => {
+  await wex.db.runReadWriteTx(["planchets"], async (tx) => {
     const p = await tx.planchets.indexes.byGroupAndIndex.get([
       withdrawalGroup.withdrawalGroupId,
       coinIdx,
@@ -743,15 +759,15 @@ enum ExchangeAmlStatus {
  * Emit a notification for the (self-)transition.
  */
 async function transitionKycUrlUpdate(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   withdrawalGroupId: string,
   kycUrl: string,
 ): Promise<void> {
   let notificationKycUrl: string | undefined = undefined;
-  const ctx = new WithdrawTransactionContext(ws, withdrawalGroupId);
+  const ctx = new WithdrawTransactionContext(wex, withdrawalGroupId);
   const transactionId = ctx.transactionId;
 
-  const transitionInfo = await ws.db.runReadWriteTx(
+  const transitionInfo = await wex.db.runReadWriteTx(
     ["withdrawalGroups"],
     async (tx) => {
       const wg2 = await tx.withdrawalGroups.get(withdrawalGroupId);
@@ -777,7 +793,7 @@ async function transitionKycUrlUpdate(
   );
   if (transitionInfo) {
     // Always notify, even on self-transition, as the KYC URL might have 
changed.
-    ws.notify({
+    wex.ws.notify({
       type: NotificationType.TransactionStateTransition,
       oldTxState: transitionInfo.oldTxState,
       newTxState: transitionInfo.newTxState,
@@ -785,16 +801,15 @@ async function transitionKycUrlUpdate(
       experimentalUserData: notificationKycUrl,
     });
   }
-  ws.taskScheduler.startShepherdTask(ctx.taskId);
+  wex.taskScheduler.startShepherdTask(ctx.taskId);
 }
 
 async function handleKycRequired(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   withdrawalGroup: WithdrawalGroupRecord,
   resp: HttpResponse,
   startIdx: number,
   requestCoinIdxs: number[],
-  cancellationToken: CancellationToken,
 ): Promise<void> {
   logger.info("withdrawal requires KYC");
   const respJson = await resp.json();
@@ -816,9 +831,9 @@ async function handleKycRequired(
     exchangeUrl,
   );
   logger.info(`kyc url ${url.href}`);
-  const kycStatusRes = await ws.http.fetch(url.href, {
+  const kycStatusRes = await wex.http.fetch(url.href, {
     method: "GET",
-    cancellationToken,
+    cancellationToken: wex.cancellationToken,
   });
   let kycUrl: string;
   let amlStatus: ExchangeAmlStatus | undefined;
@@ -846,7 +861,7 @@ async function handleKycRequired(
 
   let notificationKycUrl: string | undefined = undefined;
 
-  const transitionInfo = await ws.db.runReadWriteTx(
+  const transitionInfo = await wex.db.runReadWriteTx(
     ["planchets", "withdrawalGroups"],
     async (tx) => {
       for (let i = startIdx; i < requestCoinIdxs.length; i++) {
@@ -897,7 +912,7 @@ async function handleKycRequired(
       }
     },
   );
-  notifyTransition(ws, transactionId, transitionInfo, notificationKycUrl);
+  notifyTransition(wex, transactionId, transitionInfo, notificationKycUrl);
 }
 
 /**
@@ -906,10 +921,9 @@ async function handleKycRequired(
  * The verification of the response is done asynchronously to enable 
parallelism.
  */
 async function processPlanchetExchangeBatchRequest(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   wgContext: WithdrawalGroupContext,
   args: WithdrawalRequestBatchArgs,
-  cancellationToken: CancellationToken,
 ): Promise<WithdrawalBatchResult> {
   const withdrawalGroup: WithdrawalGroupRecord = wgContext.wgRecord;
   logger.info(
@@ -920,7 +934,7 @@ async function processPlanchetExchangeBatchRequest(
   // Indices of coins that are included in the batch request
   const requestCoinIdxs: number[] = [];
 
-  await ws.db.runReadOnlyTx(["planchets", "denominations"], async (tx) => {
+  await wex.db.runReadOnlyTx(["planchets", "denominations"], async (tx) => {
     for (
       let coinIdx = args.coinStartIndex;
       coinIdx < args.coinStartIndex + args.batchSize &&
@@ -939,7 +953,7 @@ async function processPlanchetExchangeBatchRequest(
         continue;
       }
       const denom = await getDenomInfo(
-        ws,
+        wex,
         tx,
         withdrawalGroup.exchangeBaseUrl,
         planchet.denomPubHash,
@@ -972,7 +986,7 @@ async function processPlanchetExchangeBatchRequest(
     const errDetail = getErrorDetailFromException(e);
     logger.trace("withdrawal request failed", e);
     logger.trace(String(e));
-    await ws.db.runReadWriteTx(["planchets"], async (tx) => {
+    await wex.db.runReadWriteTx(["planchets"], async (tx) => {
       let planchet = await tx.planchets.indexes.byGroupAndIndex.get([
         withdrawalGroup.withdrawalGroupId,
         coinIdx,
@@ -993,21 +1007,14 @@ async function processPlanchetExchangeBatchRequest(
   ).href;
 
   try {
-    const resp = await ws.http.fetch(reqUrl, {
+    const resp = await wex.http.fetch(reqUrl, {
       method: "POST",
       body: batchReq,
-      cancellationToken,
+      cancellationToken: wex.cancellationToken,
       timeout: Duration.fromSpec({ seconds: 40 }),
     });
     if (resp.status === HttpStatusCode.UnavailableForLegalReasons) {
-      await handleKycRequired(
-        ws,
-        withdrawalGroup,
-        resp,
-        0,
-        requestCoinIdxs,
-        cancellationToken,
-      );
+      await handleKycRequired(wex, withdrawalGroup, resp, 0, requestCoinIdxs);
       return {
         batchResp: { ev_sigs: [] },
         coinIdxs: [],
@@ -1031,14 +1038,14 @@ async function processPlanchetExchangeBatchRequest(
 }
 
 async function processPlanchetVerifyAndStoreCoin(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   wgContext: WithdrawalGroupContext,
   coinIdx: number,
   resp: ExchangeWithdrawResponse,
 ): Promise<void> {
   const withdrawalGroup = wgContext.wgRecord;
   logger.trace(`checking and storing planchet idx=${coinIdx}`);
-  const d = await ws.db.runReadOnlyTx(
+  const d = await wex.db.runReadOnlyTx(
     ["planchets", "denominations"],
     async (tx) => {
       let planchet = await tx.planchets.indexes.byGroupAndIndex.get([
@@ -1053,7 +1060,7 @@ async function processPlanchetVerifyAndStoreCoin(
         return;
       }
       const denomInfo = await getDenomInfo(
-        ws,
+        wex,
         tx,
         withdrawalGroup.exchangeBaseUrl,
         planchet.denomPubHash,
@@ -1090,20 +1097,20 @@ async function processPlanchetVerifyAndStoreCoin(
     throw Error("unsupported cipher");
   }
 
-  const denomSigRsa = await ws.cryptoApi.rsaUnblind({
+  const denomSigRsa = await wex.cryptoApi.rsaUnblind({
     bk: planchet.blindingKey,
     blindedSig: evSig.blinded_rsa_signature,
     pk: planchetDenomPub.rsa_public_key,
   });
 
-  const isValid = await ws.cryptoApi.rsaVerify({
+  const isValid = await wex.cryptoApi.rsaVerify({
     hm: planchet.coinPub,
     pk: planchetDenomPub.rsa_public_key,
     sig: denomSigRsa.sig,
   });
 
   if (!isValid) {
-    await ws.db.runReadWriteTx(["planchets"], async (tx) => {
+    await wex.db.runReadWriteTx(["planchets"], async (tx) => {
       let planchet = await tx.planchets.indexes.byGroupAndIndex.get([
         withdrawalGroup.withdrawalGroupId,
         coinIdx,
@@ -1156,7 +1163,7 @@ async function processPlanchetVerifyAndStoreCoin(
 
   wgContext.planchetsFinished.add(planchet.coinPub);
 
-  await ws.db.runReadWriteTx(
+  await wex.db.runReadWriteTx(
     ["planchets", "coins", "coinAvailability", "denominations"],
     async (tx) => {
       const p = await tx.planchets.get(planchetCoinPub);
@@ -1166,7 +1173,7 @@ async function processPlanchetVerifyAndStoreCoin(
       p.planchetStatus = PlanchetStatus.WithdrawalDone;
       p.lastError = undefined;
       await tx.planchets.put(p);
-      await makeCoinAvailable(ws, tx, coin);
+      await makeCoinAvailable(wex, tx, coin);
     },
   );
 }
@@ -1176,13 +1183,13 @@ async function processPlanchetVerifyAndStoreCoin(
  * are validated, and the result of validation is stored in the database.
  */
 async function updateWithdrawalDenoms(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   exchangeBaseUrl: string,
 ): Promise<void> {
   logger.trace(
     `updating denominations used for withdrawal for ${exchangeBaseUrl}`,
   );
-  const exchangeDetails = await ws.db.runReadOnlyTx(
+  const exchangeDetails = await wex.db.runReadOnlyTx(
     ["exchanges", "exchangeDetails"],
     async (tx) => {
       return getExchangeWireDetailsInTx(tx, exchangeBaseUrl);
@@ -1196,7 +1203,7 @@ async function updateWithdrawalDenoms(
   // is checked and the result is stored in the database.
   logger.trace("getting candidate denominations");
   const denominations = await getCandidateWithdrawalDenoms(
-    ws,
+    wex,
     exchangeBaseUrl,
     exchangeDetails.currency,
   );
@@ -1222,10 +1229,10 @@ async function updateWithdrawalDenoms(
           }) signature of ${denom.denomPubHash}`,
         );
         let valid = false;
-        if (ws.config.testing.insecureTrustExchange) {
+        if (wex.ws.config.testing.insecureTrustExchange) {
           valid = true;
         } else {
-          const res = await ws.cryptoApi.isValidDenom({
+          const res = await wex.cryptoApi.isValidDenom({
             denom,
             masterPub: exchangeDetails.masterPublicKey,
           });
@@ -1246,7 +1253,7 @@ async function updateWithdrawalDenoms(
     }
     if (updatedDenominations.length > 0) {
       logger.trace("writing denomination batch to db");
-      await ws.db.runReadWriteTx(["denominations"], async (tx) => {
+      await wex.db.runReadWriteTx(["denominations"], async (tx) => {
         for (let i = 0; i < updatedDenominations.length; i++) {
           const denom = updatedDenominations[i];
           await tx.denominations.put(denom);
@@ -1266,15 +1273,14 @@ async function updateWithdrawalDenoms(
  * create a new withdrawal group for the remaining amount.
  */
 async function processQueryReserve(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   withdrawalGroupId: string,
-  cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
   const transactionId = constructTransactionIdentifier({
     tag: TransactionType.Withdrawal,
     withdrawalGroupId,
   });
-  const withdrawalGroup = await getWithdrawalGroupRecordTx(ws.db, {
+  const withdrawalGroup = await getWithdrawalGroupRecordTx(wex.db, {
     withdrawalGroupId,
   });
   checkDbInvariant(!!withdrawalGroup);
@@ -1291,9 +1297,9 @@ async function processQueryReserve(
 
   logger.trace(`querying reserve status via ${reserveUrl.href}`);
 
-  const resp = await ws.http.fetch(reserveUrl.href, {
+  const resp = await wex.http.fetch(reserveUrl.href, {
     timeout: getReserveRequestTimeout(withdrawalGroup),
-    cancellationToken,
+    cancellationToken: wex.cancellationToken,
   });
 
   logger.trace(`reserve status code: HTTP ${resp.status}`);
@@ -1316,7 +1322,7 @@ async function processQueryReserve(
 
   logger.trace(`got reserve status ${j2s(result.response)}`);
 
-  const transitionResult = await ws.db.runReadWriteTx(
+  const transitionResult = await wex.db.runReadWriteTx(
     ["withdrawalGroups"],
     async (tx) => {
       const wg = await tx.withdrawalGroups.get(withdrawalGroupId);
@@ -1336,7 +1342,7 @@ async function processQueryReserve(
     },
   );
 
-  notifyTransition(ws, transactionId, transitionResult);
+  notifyTransition(wex, transactionId, transitionResult);
 
   if (transitionResult) {
     return TaskRunResult.progress();
@@ -1361,9 +1367,8 @@ interface WithdrawalGroupContext {
 }
 
 async function processWithdrawalGroupAbortingBank(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   withdrawalGroup: WithdrawalGroupRecord,
-  cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
   const { withdrawalGroupId } = withdrawalGroup;
   const transactionId = constructTransactionIdentifier({
@@ -1377,14 +1382,14 @@ async function processWithdrawalGroupAbortingBank(
   }
   const abortUrl = getBankAbortUrl(wgInfo.bankInfo.talerWithdrawUri);
   logger.info(`aborting withdrawal at ${abortUrl}`);
-  const abortResp = await ws.http.fetch(abortUrl, {
+  const abortResp = await wex.http.fetch(abortUrl, {
     method: "POST",
     body: {},
-    cancellationToken,
+    cancellationToken: wex.cancellationToken,
   });
   logger.info(`abort response status: ${abortResp.status}`);
 
-  const transitionInfo = await ws.db.runReadWriteTx(
+  const transitionInfo = await wex.db.runReadWriteTx(
     ["withdrawalGroups"],
     async (tx) => {
       const wg = await tx.withdrawalGroups.get(withdrawalGroupId);
@@ -1402,7 +1407,7 @@ async function processWithdrawalGroupAbortingBank(
       };
     },
   );
-  notifyTransition(ws, transactionId, transitionInfo);
+  notifyTransition(wex, transactionId, transitionInfo);
   return TaskRunResult.finished();
 }
 
@@ -1411,14 +1416,14 @@ async function processWithdrawalGroupAbortingBank(
  * satisfied.
  */
 async function transitionKycSatisfied(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   withdrawalGroup: WithdrawalGroupRecord,
 ): Promise<void> {
   const transactionId = constructTransactionIdentifier({
     tag: TransactionType.Withdrawal,
     withdrawalGroupId: withdrawalGroup.withdrawalGroupId,
   });
-  const transitionInfo = await ws.db.runReadWriteTx(
+  const transitionInfo = await wex.db.runReadWriteTx(
     ["withdrawalGroups"],
     async (tx) => {
       const wg2 = await tx.withdrawalGroups.get(
@@ -1445,13 +1450,12 @@ async function transitionKycSatisfied(
       }
     },
   );
-  notifyTransition(ws, transactionId, transitionInfo);
+  notifyTransition(wex, transactionId, transitionInfo);
 }
 
 async function processWithdrawalGroupPendingKyc(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   withdrawalGroup: WithdrawalGroupRecord,
-  cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
   const userType = "individual";
   const kycInfo = withdrawalGroup.kycPending;
@@ -1468,9 +1472,9 @@ async function processWithdrawalGroupPendingKyc(
   const withdrawalGroupId = withdrawalGroup.withdrawalGroupId;
 
   logger.info(`long-polling for withdrawal KYC status via ${url.href}`);
-  const kycStatusRes = await ws.http.fetch(url.href, {
+  const kycStatusRes = await wex.http.fetch(url.href, {
     method: "GET",
-    cancellationToken,
+    cancellationToken: wex.cancellationToken,
   });
   logger.info(`kyc long-polling response status: HTTP ${kycStatusRes.status}`);
   if (
@@ -1479,13 +1483,13 @@ async function processWithdrawalGroupPendingKyc(
     // remove after the exchange is fixed or clarified
     kycStatusRes.status === HttpStatusCode.NoContent
   ) {
-    await transitionKycSatisfied(ws, withdrawalGroup);
+    await transitionKycSatisfied(wex, withdrawalGroup);
   } else if (kycStatusRes.status === HttpStatusCode.Accepted) {
     const kycStatus = await kycStatusRes.json();
     logger.info(`kyc status: ${j2s(kycStatus)}`);
     const kycUrl = kycStatus.kyc_url;
     if (typeof kycUrl === "string") {
-      await transitionKycUrlUpdate(ws, withdrawalGroupId, kycUrl);
+      await transitionKycUrlUpdate(wex, withdrawalGroupId, kycUrl);
     }
   } else if (
     kycStatusRes.status === HttpStatusCode.UnavailableForLegalReasons
@@ -1499,9 +1503,8 @@ async function processWithdrawalGroupPendingKyc(
 }
 
 async function processWithdrawalGroupPendingReady(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   withdrawalGroup: WithdrawalGroupRecord,
-  cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
   const { withdrawalGroupId } = withdrawalGroup;
   const transactionId = constructTransactionIdentifier({
@@ -1509,11 +1512,11 @@ async function processWithdrawalGroupPendingReady(
     withdrawalGroupId,
   });
 
-  await fetchFreshExchange(ws, withdrawalGroup.exchangeBaseUrl);
+  await fetchFreshExchange(wex, withdrawalGroup.exchangeBaseUrl);
 
   if (withdrawalGroup.denomsSel.selectedDenoms.length === 0) {
     logger.warn("Finishing empty withdrawal group (no denoms)");
-    const transitionInfo = await ws.db.runReadWriteTx(
+    const transitionInfo = await wex.db.runReadWriteTx(
       ["withdrawalGroups"],
       async (tx) => {
         const wg = await tx.withdrawalGroups.get(withdrawalGroupId);
@@ -1531,7 +1534,7 @@ async function processWithdrawalGroupPendingReady(
         };
       },
     );
-    notifyTransition(ws, transactionId, transitionInfo);
+    notifyTransition(wex, transactionId, transitionInfo);
     return TaskRunResult.finished();
   }
 
@@ -1545,7 +1548,7 @@ async function processWithdrawalGroupPendingReady(
     wgRecord: withdrawalGroup,
   };
 
-  await ws.db.runReadOnlyTx(["planchets"], async (tx) => {
+  await wex.db.runReadOnlyTx(["planchets"], async (tx) => {
     const planchets =
       await tx.planchets.indexes.byGroup.getAll(withdrawalGroupId);
     for (const p of planchets) {
@@ -1558,21 +1561,16 @@ async function processWithdrawalGroupPendingReady(
   // We sequentially generate planchets, so that
   // large withdrawal groups don't make the wallet unresponsive.
   for (let i = 0; i < numTotalCoins; i++) {
-    await processPlanchetGenerate(ws, withdrawalGroup, i);
+    await processPlanchetGenerate(wex, withdrawalGroup, i);
   }
 
   const maxBatchSize = 100;
 
   for (let i = 0; i < numTotalCoins; i += maxBatchSize) {
-    const resp = await processPlanchetExchangeBatchRequest(
-      ws,
-      wgContext,
-      {
-        batchSize: maxBatchSize,
-        coinStartIndex: i,
-      },
-      cancellationToken,
-    );
+    const resp = await processPlanchetExchangeBatchRequest(wex, wgContext, {
+      batchSize: maxBatchSize,
+      coinStartIndex: i,
+    });
     let work: Promise<void>[] = [];
     work = [];
     for (let j = 0; j < resp.coinIdxs.length; j++) {
@@ -1582,7 +1580,7 @@ async function processWithdrawalGroupPendingReady(
       }
       work.push(
         processPlanchetVerifyAndStoreCoin(
-          ws,
+          wex,
           wgContext,
           resp.coinIdxs[j],
           resp.batchResp.ev_sigs[j],
@@ -1597,7 +1595,7 @@ async function processWithdrawalGroupPendingReady(
   let numPlanchetErrors = 0;
   const maxReportedErrors = 5;
 
-  const res = await ws.db.runReadWriteTx(
+  const res = await wex.db.runReadWriteTx(
     ["coins", "coinAvailability", "withdrawalGroups", "planchets"],
     async (tx) => {
       const wg = await tx.withdrawalGroups.get(withdrawalGroupId);
@@ -1623,7 +1621,7 @@ async function processWithdrawalGroupPendingReady(
       if (wg.timestampFinish === undefined && numFinished === numTotalCoins) {
         wg.timestampFinish = timestampPreciseToDb(TalerPreciseTimestamp.now());
         wg.status = WithdrawalGroupStatus.Done;
-        await makeCoinsVisible(ws, tx, transactionId);
+        await makeCoinsVisible(wex, tx, transactionId);
       }
 
       const newTxState = computeWithdrawalTransactionStatus(wg);
@@ -1643,8 +1641,8 @@ async function processWithdrawalGroupPendingReady(
     throw Error("withdrawal group does not exist anymore");
   }
 
-  notifyTransition(ws, transactionId, res.transitionInfo);
-  ws.notify({
+  notifyTransition(wex, transactionId, res.transitionInfo);
+  wex.ws.notify({
     type: NotificationType.BalanceChange,
     hintTransactionId: transactionId,
   });
@@ -1666,12 +1664,11 @@ async function processWithdrawalGroupPendingReady(
 }
 
 export async function processWithdrawalGroup(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   withdrawalGroupId: string,
-  cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
   logger.trace("processing withdrawal group", withdrawalGroupId);
-  const withdrawalGroup = await ws.db.runReadOnlyTx(
+  const withdrawalGroup = await wex.db.runReadOnlyTx(
     ["withdrawalGroups"],
     async (tx) => {
       return tx.withdrawalGroups.get(withdrawalGroupId);
@@ -1684,41 +1681,21 @@ export async function processWithdrawalGroup(
 
   switch (withdrawalGroup.status) {
     case WithdrawalGroupStatus.PendingRegisteringBank:
-      return await processBankRegisterReserve(
-        ws,
-        withdrawalGroupId,
-        cancellationToken,
-      );
+      return await processBankRegisterReserve(wex, withdrawalGroupId);
     case WithdrawalGroupStatus.PendingQueryingStatus:
-      return processQueryReserve(ws, withdrawalGroupId, cancellationToken);
+      return processQueryReserve(wex, withdrawalGroupId);
     case WithdrawalGroupStatus.PendingWaitConfirmBank:
-      return await processReserveBankStatus(
-        ws,
-        withdrawalGroupId,
-        cancellationToken,
-      );
+      return await processReserveBankStatus(wex, withdrawalGroupId);
     case WithdrawalGroupStatus.PendingAml:
       // FIXME: Handle this case, withdrawal doesn't support AML yet.
       return TaskRunResult.backoff();
     case WithdrawalGroupStatus.PendingKyc:
-      return processWithdrawalGroupPendingKyc(
-        ws,
-        withdrawalGroup,
-        cancellationToken,
-      );
+      return processWithdrawalGroupPendingKyc(wex, withdrawalGroup);
     case WithdrawalGroupStatus.PendingReady:
       // Continue with the actual withdrawal!
-      return await processWithdrawalGroupPendingReady(
-        ws,
-        withdrawalGroup,
-        cancellationToken,
-      );
+      return await processWithdrawalGroupPendingReady(wex, withdrawalGroup);
     case WithdrawalGroupStatus.AbortingBank:
-      return await processWithdrawalGroupAbortingBank(
-        ws,
-        withdrawalGroup,
-        cancellationToken,
-      );
+      return await processWithdrawalGroupAbortingBank(wex, withdrawalGroup);
     case WithdrawalGroupStatus.AbortedBank:
     case WithdrawalGroupStatus.AbortedExchange:
     case WithdrawalGroupStatus.FailedAbortingBank:
@@ -1743,13 +1720,13 @@ const AGE_MASK_GROUPS = "8:10:12:14:16:18"
   .map((n) => parseInt(n, 10));
 
 export async function getExchangeWithdrawalInfo(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   exchangeBaseUrl: string,
   instructedAmount: AmountJson,
   ageRestricted: number | undefined,
 ): Promise<ExchangeWithdrawalDetails> {
   logger.trace("updating exchange");
-  const exchange = await fetchFreshExchange(ws, exchangeBaseUrl);
+  const exchange = await fetchFreshExchange(wex, exchangeBaseUrl);
 
   if (exchange.currency != instructedAmount.currency) {
     // Specifying the amount in the conversion input currency is not yet 
supported.
@@ -1760,7 +1737,7 @@ export async function getExchangeWithdrawalInfo(
   }
 
   const withdrawalAccountsList = await fetchWithdrawalAccountInfo(
-    ws,
+    wex,
     {
       exchange,
       instructedAmount,
@@ -1769,11 +1746,11 @@ export async function getExchangeWithdrawalInfo(
   );
 
   logger.trace("updating withdrawal denoms");
-  await updateWithdrawalDenoms(ws, exchangeBaseUrl);
+  await updateWithdrawalDenoms(wex, exchangeBaseUrl);
 
   logger.trace("getting candidate denoms");
   const denoms = await getCandidateWithdrawalDenoms(
-    ws,
+    wex,
     exchangeBaseUrl,
     instructedAmount.currency,
   );
@@ -1781,7 +1758,7 @@ export async function getExchangeWithdrawalInfo(
   const selectedDenoms = selectWithdrawalDenominations(
     instructedAmount,
     denoms,
-    ws.config.testing.denomselAllowLate,
+    wex.ws.config.testing.denomselAllowLate,
   );
 
   logger.trace("selection done");
@@ -1806,11 +1783,11 @@ export async function getExchangeWithdrawalInfo(
 
   let earliestDepositExpiration: TalerProtocolTimestamp | undefined;
 
-  await ws.db.runReadOnlyTx(["denominations"], async (tx) => {
+  await wex.db.runReadOnlyTx(["denominations"], async (tx) => {
     for (let i = 0; i < selectedDenoms.selectedDenoms.length; i++) {
       const ds = selectedDenoms.selectedDenoms[i];
       const denom = await getDenomInfo(
-        ws,
+        wex,
         tx,
         exchangeBaseUrl,
         ds.denomPubHash,
@@ -1837,7 +1814,7 @@ export async function getExchangeWithdrawalInfo(
   checkLogicInvariant(!!earliestDepositExpiration);
 
   const possibleDenoms = await getCandidateWithdrawalDenoms(
-    ws,
+    wex,
     exchangeBaseUrl,
     instructedAmount.currency,
   );
@@ -1915,18 +1892,18 @@ const ongoingChecks: WithdrawalOperationMemoryMap = {};
  * to the wallet's list of known exchanges.
  */
 export async function getWithdrawalDetailsForUri(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   talerWithdrawUri: string,
   opts: GetWithdrawalDetailsForUriOpts = {},
 ): Promise<WithdrawUriInfoResponse> {
   logger.trace(`getting withdrawal details for URI ${talerWithdrawUri}`);
-  const info = await getBankWithdrawalInfo(ws.http, talerWithdrawUri);
+  const info = await getBankWithdrawalInfo(wex.http, talerWithdrawUri);
   logger.trace(`got bank info`);
   if (info.suggestedExchange) {
     try {
       // If the exchange entry doesn't exist yet,
       // it'll be created as an ephemeral entry.
-      await fetchFreshExchange(ws, info.suggestedExchange);
+      await fetchFreshExchange(wex, info.suggestedExchange);
     } catch (e) {
       // We still continued if it failed, as other exchanges might be 
available.
       // We don't want to fail if the bank-suggested exchange is 
broken/offline.
@@ -1938,7 +1915,7 @@ export async function getWithdrawalDetailsForUri(
 
   const currency = Amounts.currencyOf(info.amount);
 
-  const listExchangesResp = await listExchanges(ws);
+  const listExchangesResp = await listExchanges(wex);
   const possibleExchanges = listExchangesResp.exchanges.filter((x) => {
     return (
       x.currency === currency &&
@@ -1957,7 +1934,7 @@ export async function getWithdrawalDetailsForUri(
     ongoingChecks[talerWithdrawUri] = true;
     const bankApi = new TalerBankIntegrationHttpClient(
       info.apiBaseUrl,
-      ws.http,
+      wex.http,
     );
     console.log(
       `waiting operation (${info.operationId}) to change from pending`,
@@ -2076,11 +2053,10 @@ export function getBankAbortUrl(talerWithdrawUri: 
string): string {
 }
 
 async function registerReserveWithBank(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   withdrawalGroupId: string,
-  cancellationToken: CancellationToken,
 ): Promise<void> {
-  const withdrawalGroup = await ws.db.runReadOnlyTx(
+  const withdrawalGroup = await wex.db.runReadOnlyTx(
     ["withdrawalGroups"],
     async (tx) => {
       return await tx.withdrawalGroups.get(withdrawalGroupId);
@@ -2112,17 +2088,17 @@ async function registerReserveWithBank(
     selected_exchange: bankInfo.exchangePaytoUri,
   };
   logger.info(`registering reserve with bank: ${j2s(reqBody)}`);
-  const httpResp = await ws.http.fetch(bankStatusUrl, {
+  const httpResp = await wex.http.fetch(bankStatusUrl, {
     method: "POST",
     body: reqBody,
     timeout: getReserveRequestTimeout(withdrawalGroup),
-    cancellationToken,
+    cancellationToken: wex.cancellationToken,
   });
   const status = await readSuccessResponseJsonOrThrow(
     httpResp,
     codeForBankWithdrawalOperationPostResponse(),
   );
-  const transitionInfo = await ws.db.runReadWriteTx(
+  const transitionInfo = await wex.db.runReadWriteTx(
     ["withdrawalGroups"],
     async (tx) => {
       const r = await tx.withdrawalGroups.get(withdrawalGroupId);
@@ -2154,14 +2130,14 @@ async function registerReserveWithBank(
     },
   );
 
-  notifyTransition(ws, transactionId, transitionInfo);
+  notifyTransition(wex, transactionId, transitionInfo);
 }
 
 async function transitionBankAborted(
   ctx: WithdrawTransactionContext,
 ): Promise<TaskRunResult> {
   logger.info("bank aborted the withdrawal");
-  const transitionInfo = await ctx.ws.db.runReadWriteTx(
+  const transitionInfo = await ctx.wex.db.runReadWriteTx(
     ["withdrawalGroups"],
     async (tx) => {
       const r = await tx.withdrawalGroups.get(ctx.withdrawalGroupId);
@@ -2190,17 +2166,16 @@ async function transitionBankAborted(
       };
     },
   );
-  notifyTransition(ctx.ws, ctx.transactionId, transitionInfo);
+  notifyTransition(ctx.wex, ctx.transactionId, transitionInfo);
   return TaskRunResult.finished();
 }
 
 async function processBankRegisterReserve(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   withdrawalGroupId: string,
-  cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
-  const ctx = new WithdrawTransactionContext(ws, withdrawalGroupId);
-  const withdrawalGroup = await getWithdrawalGroupRecordTx(ws.db, {
+  const ctx = new WithdrawTransactionContext(wex, withdrawalGroupId);
+  const withdrawalGroup = await getWithdrawalGroupRecordTx(wex.db, {
     withdrawalGroupId,
   });
   if (!withdrawalGroup) {
@@ -2226,9 +2201,9 @@ async function processBankRegisterReserve(
     uriResult.bankIntegrationApiBaseUrl,
   );
 
-  const statusResp = await ws.http.fetch(url.href, {
+  const statusResp = await wex.http.fetch(url.href, {
     timeout: getReserveRequestTimeout(withdrawalGroup),
-    cancellationToken,
+    cancellationToken: wex.cancellationToken,
   });
 
   const status = await readSuccessResponseJsonOrThrow(
@@ -2242,16 +2217,15 @@ async function processBankRegisterReserve(
 
   // FIXME: Put confirm transfer URL in the DB!
 
-  await registerReserveWithBank(ws, withdrawalGroupId, cancellationToken);
+  await registerReserveWithBank(wex, withdrawalGroupId);
   return TaskRunResult.progress();
 }
 
 async function processReserveBankStatus(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   withdrawalGroupId: string,
-  cancellationToken: CancellationToken,
 ): Promise<TaskRunResult> {
-  const withdrawalGroup = await getWithdrawalGroupRecordTx(ws.db, {
+  const withdrawalGroup = await getWithdrawalGroupRecordTx(wex.db, {
     withdrawalGroupId,
   });
 
@@ -2259,7 +2233,7 @@ async function processReserveBankStatus(
     return TaskRunResult.finished();
   }
 
-  const ctx = new WithdrawTransactionContext(ws, withdrawalGroupId);
+  const ctx = new WithdrawTransactionContext(wex, withdrawalGroupId);
 
   if (
     withdrawalGroup.wgInfo.withdrawalType != 
WithdrawalRecordType.BankIntegrated
@@ -2282,9 +2256,9 @@ async function processReserveBankStatus(
   bankStatusUrl.searchParams.set("long_poll_ms", "30000");
 
   logger.info(`long-polling for withdrawal operation at 
${bankStatusUrl.href}`);
-  const statusResp = await ws.http.fetch(bankStatusUrl.href, {
+  const statusResp = await wex.http.fetch(bankStatusUrl.href, {
     timeout: getReserveRequestTimeout(withdrawalGroup),
-    cancellationToken,
+    cancellationToken: wex.cancellationToken,
   });
   logger.info(
     `long-polling for withdrawal operation returned status 
${statusResp.status}`,
@@ -2307,7 +2281,7 @@ async function processReserveBankStatus(
     return TaskRunResult.longpollReturnedPending();
   }
 
-  const transitionInfo = await ws.db.runReadWriteTx(
+  const transitionInfo = await wex.db.runReadWriteTx(
     ["withdrawalGroups"],
     async (tx) => {
       const r = await tx.withdrawalGroups.get(withdrawalGroupId);
@@ -2341,7 +2315,7 @@ async function processReserveBankStatus(
     },
   );
 
-  notifyTransition(ws, ctx.transactionId, transitionInfo);
+  notifyTransition(wex, ctx.transactionId, transitionInfo);
 
   if (transitionInfo) {
     return TaskRunResult.progress();
@@ -2360,7 +2334,7 @@ export interface PrepareCreateWithdrawalGroupResult {
 }
 
 export async function internalPrepareCreateWithdrawalGroup(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   args: {
     reserveStatus: WithdrawalGroupStatus;
     amount: AmountJson;
@@ -2373,7 +2347,7 @@ export async function 
internalPrepareCreateWithdrawalGroup(
   },
 ): Promise<PrepareCreateWithdrawalGroupResult> {
   const reserveKeyPair =
-    args.reserveKeyPair ?? (await ws.cryptoApi.createEddsaKeypair({}));
+    args.reserveKeyPair ?? (await wex.cryptoApi.createEddsaKeypair({}));
   const now = AbsoluteTime.toPreciseTimestamp(AbsoluteTime.now());
   const secretSeed = encodeCrock(getRandomBytes(32));
   const canonExchange = canonicalizeBaseUrl(args.exchangeBaseUrl);
@@ -2385,7 +2359,7 @@ export async function 
internalPrepareCreateWithdrawalGroup(
   if (args.forcedWithdrawalGroupId) {
     withdrawalGroupId = args.forcedWithdrawalGroupId;
     const wgId = withdrawalGroupId;
-    const existingWg = await ws.db.runReadOnlyTx(
+    const existingWg = await wex.db.runReadOnlyTx(
       ["withdrawalGroups"],
       async (tx) => {
         return tx.withdrawalGroups.get(wgId);
@@ -2403,9 +2377,9 @@ export async function 
internalPrepareCreateWithdrawalGroup(
     withdrawalGroupId = encodeCrock(getRandomBytes(32));
   }
 
-  await updateWithdrawalDenoms(ws, canonExchange);
+  await updateWithdrawalDenoms(wex, canonExchange);
   const denoms = await getCandidateWithdrawalDenoms(
-    ws,
+    wex,
     canonExchange,
     currency,
   );
@@ -2418,13 +2392,13 @@ export async function 
internalPrepareCreateWithdrawalGroup(
       amount,
       denoms,
       args.forcedDenomSel,
-      ws.config.testing.denomselAllowLate,
+      wex.ws.config.testing.denomselAllowLate,
     );
   } else {
     initialDenomSel = selectWithdrawalDenominations(
       amount,
       denoms,
-      ws.config.testing.denomselAllowLate,
+      wex.ws.config.testing.denomselAllowLate,
     );
   }
 
@@ -2447,7 +2421,7 @@ export async function 
internalPrepareCreateWithdrawalGroup(
     wgInfo: args.wgInfo,
   };
 
-  await fetchFreshExchange(ws, canonExchange);
+  await fetchFreshExchange(wex, canonExchange);
   const transactionId = constructTransactionIdentifier({
     tag: TransactionType.Withdrawal,
     withdrawalGroupId: withdrawalGroup.withdrawalGroupId,
@@ -2476,7 +2450,7 @@ export interface PerformCreateWithdrawalGroupResult {
 }
 
 export async function internalPerformCreateWithdrawalGroup(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   tx: WalletDbReadWriteTransaction<
     ["withdrawalGroups", "reserves", "exchanges"]
   >,
@@ -2523,17 +2497,17 @@ export async function 
internalPerformCreateWithdrawalGroup(
   };
 
   const exchangeUsedRes = await markExchangeUsed(
-    ws,
+    wex,
     tx,
     prep.withdrawalGroup.exchangeBaseUrl,
   );
 
   const ctx = new WithdrawTransactionContext(
-    ws,
+    wex,
     withdrawalGroup.withdrawalGroupId,
   );
 
-  ws.taskScheduler.startShepherdTask(ctx.taskId);
+  wex.taskScheduler.startShepherdTask(ctx.taskId);
 
   return {
     withdrawalGroup,
@@ -2551,7 +2525,7 @@ export async function 
internalPerformCreateWithdrawalGroup(
  * of the other arguments is done in that case.
  */
 export async function internalCreateWithdrawalGroup(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   args: {
     reserveStatus: WithdrawalGroupStatus;
     amount: AmountJson;
@@ -2563,21 +2537,21 @@ export async function internalCreateWithdrawalGroup(
     wgInfo: WgInfo;
   },
 ): Promise<WithdrawalGroupRecord> {
-  const prep = await internalPrepareCreateWithdrawalGroup(ws, args);
+  const prep = await internalPrepareCreateWithdrawalGroup(wex, args);
   const transactionId = constructTransactionIdentifier({
     tag: TransactionType.Withdrawal,
     withdrawalGroupId: prep.withdrawalGroup.withdrawalGroupId,
   });
-  const res = await ws.db.runReadWriteTx(
+  const res = await wex.db.runReadWriteTx(
     ["withdrawalGroups", "reserves", "exchanges", "exchangeDetails"],
     async (tx) => {
-      return await internalPerformCreateWithdrawalGroup(ws, tx, prep);
+      return await internalPerformCreateWithdrawalGroup(wex, tx, prep);
     },
   );
   if (res.exchangeNotif) {
-    ws.notify(res.exchangeNotif);
+    wex.ws.notify(res.exchangeNotif);
   }
-  notifyTransition(ws, transactionId, res.transitionInfo);
+  notifyTransition(wex, transactionId, res.transitionInfo);
   return res.withdrawalGroup;
 }
 
@@ -2590,7 +2564,7 @@ export async function internalCreateWithdrawalGroup(
  * with the bank.
  */
 export async function acceptWithdrawalFromUri(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: {
     talerWithdrawUri: string;
     selectedExchange: string;
@@ -2602,7 +2576,7 @@ export async function acceptWithdrawalFromUri(
   logger.info(
     `accepting withdrawal via ${req.talerWithdrawUri}, canonicalized selected 
exchange ${selectedExchange}`,
   );
-  const existingWithdrawalGroup = await ws.db.runReadOnlyTx(
+  const existingWithdrawalGroup = await wex.db.runReadOnlyTx(
     ["withdrawalGroups"],
     async (tx) => {
       return await tx.withdrawalGroups.indexes.byTalerWithdrawUri.get(
@@ -2629,21 +2603,21 @@ export async function acceptWithdrawalFromUri(
     };
   }
 
-  await fetchFreshExchange(ws, selectedExchange);
+  await fetchFreshExchange(wex, selectedExchange);
   const withdrawInfo = await getBankWithdrawalInfo(
-    ws.http,
+    wex.http,
     req.talerWithdrawUri,
   );
   const exchangePaytoUri = await getExchangePaytoUri(
-    ws,
+    wex,
     selectedExchange,
     withdrawInfo.wireTypes,
   );
 
-  const exchange = await fetchFreshExchange(ws, selectedExchange);
+  const exchange = await fetchFreshExchange(wex, selectedExchange);
 
   const withdrawalAccountList = await fetchWithdrawalAccountInfo(
-    ws,
+    wex,
     {
       exchange,
       instructedAmount: withdrawInfo.amount,
@@ -2651,7 +2625,7 @@ export async function acceptWithdrawalFromUri(
     CancellationToken.CONTINUE,
   );
 
-  const withdrawalGroup = await internalCreateWithdrawalGroup(ws, {
+  const withdrawalGroup = await internalCreateWithdrawalGroup(wex, {
     amount: withdrawInfo.amount,
     exchangeBaseUrl: req.selectedExchange,
     wgInfo: {
@@ -2672,16 +2646,16 @@ export async function acceptWithdrawalFromUri(
 
   const withdrawalGroupId = withdrawalGroup.withdrawalGroupId;
 
-  const ctx = new WithdrawTransactionContext(ws, withdrawalGroupId);
+  const ctx = new WithdrawTransactionContext(wex, withdrawalGroupId);
 
-  ws.notify({
+  wex.ws.notify({
     type: NotificationType.BalanceChange,
     hintTransactionId: ctx.transactionId,
   });
 
-  await waitWithdrawalRegistered(ws, ctx);
+  await waitWithdrawalRegistered(wex, ctx);
 
-  ws.taskScheduler.startShepherdTask(ctx.taskId);
+  wex.taskScheduler.startShepherdTask(ctx.taskId);
 
   return {
     reservePub: withdrawalGroup.reservePub,
@@ -2691,12 +2665,12 @@ export async function acceptWithdrawalFromUri(
 }
 
 async function internalWaitWithdrawalRegistered(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   ctx: WithdrawTransactionContext,
   withdrawalNotifFlag: AsyncFlag,
 ): Promise<void> {
   while (true) {
-    const { withdrawalRec, retryRec } = await ws.db.runReadOnlyTx(
+    const { withdrawalRec, retryRec } = await wex.db.runReadOnlyTx(
       ["withdrawalGroups", "operationRetries"],
       async (tx) => {
         return {
@@ -2742,7 +2716,7 @@ async function internalWaitWithdrawalRegistered(
 }
 
 async function waitWithdrawalRegistered(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   ctx: WithdrawTransactionContext,
 ): Promise<void> {
   // FIXME: We should use Symbol.dispose magic here for cleanup!
@@ -2750,7 +2724,7 @@ async function waitWithdrawalRegistered(
   const withdrawalNotifFlag = new AsyncFlag();
   // Raise exchangeNotifFlag whenever we get a notification
   // about our exchange.
-  const cancelNotif = ws.addNotificationListener((notif) => {
+  const cancelNotif = wex.ws.addNotificationListener((notif) => {
     if (
       notif.type === NotificationType.TransactionStateTransition &&
       notif.transactionId === ctx.transactionId
@@ -2762,7 +2736,7 @@ async function waitWithdrawalRegistered(
 
   try {
     const res = await internalWaitWithdrawalRegistered(
-      ws,
+      wex,
       ctx,
       withdrawalNotifFlag,
     );
@@ -2774,7 +2748,7 @@ async function waitWithdrawalRegistered(
 }
 
 async function fetchAccount(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   instructedAmount: AmountJson,
   acct: ExchangeWireAccount,
   reservePub: string | undefined,
@@ -2789,7 +2763,7 @@ async function fetchAccount(
       "amount_credit",
       Amounts.stringify(instructedAmount),
     );
-    const httpResp = await ws.http.fetch(reqUrl.href, {
+    const httpResp = await wex.http.fetch(reqUrl.href, {
       cancellationToken,
     });
     const respOrErr = await readSuccessResponseJsonOrErrorCode(
@@ -2807,7 +2781,7 @@ async function fetchAccount(
     paytoUri = acct.payto_uri;
     transferAmount = resp.amount_debit;
     const configUrl = new URL("config", acct.conversion_url);
-    const configResp = await ws.http.fetch(configUrl.href, {
+    const configResp = await wex.http.fetch(configUrl.href, {
       cancellationToken,
     });
     const configRespOrError = await readSuccessResponseJsonOrErrorCode(
@@ -2854,7 +2828,7 @@ async function fetchAccount(
  * currency and require conversion.
  */
 async function fetchWithdrawalAccountInfo(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: {
     exchange: ReadyExchangeSummary;
     instructedAmount: AmountJson;
@@ -2866,7 +2840,7 @@ async function fetchWithdrawalAccountInfo(
   const withdrawalAccounts: WithdrawalExchangeAccountDetails[] = [];
   for (let acct of exchange.wireInfo.accounts) {
     const acctInfo = await fetchAccount(
-      ws,
+      wex,
       req.instructedAmount,
       acct,
       req.reservePub,
@@ -2886,7 +2860,7 @@ async function fetchWithdrawalAccountInfo(
  * Asynchronously starts the withdrawal.
  */
 export async function createManualWithdrawal(
-  ws: InternalWalletState,
+  wex: WalletExecutionContext,
   req: {
     exchangeBaseUrl: string;
     amount: AmountLike;
@@ -2896,19 +2870,19 @@ export async function createManualWithdrawal(
 ): Promise<AcceptManualWithdrawalResult> {
   const { exchangeBaseUrl } = req;
   const amount = Amounts.parseOrThrow(req.amount);
-  const exchange = await fetchFreshExchange(ws, exchangeBaseUrl);
+  const exchange = await fetchFreshExchange(wex, exchangeBaseUrl);
 
   if (exchange.currency != amount.currency) {
     throw Error(
       "manual withdrawal with conversion from foreign currency is not yet 
supported",
     );
   }
-  const reserveKeyPair: EddsaKeypair = await ws.cryptoApi.createEddsaKeypair(
+  const reserveKeyPair: EddsaKeypair = await wex.cryptoApi.createEddsaKeypair(
     {},
   );
 
   const withdrawalAccountsList = await fetchWithdrawalAccountInfo(
-    ws,
+    wex,
     {
       exchange,
       instructedAmount: amount,
@@ -2917,7 +2891,7 @@ export async function createManualWithdrawal(
     CancellationToken.CONTINUE,
   );
 
-  const withdrawalGroup = await internalCreateWithdrawalGroup(ws, {
+  const withdrawalGroup = await internalCreateWithdrawalGroup(wex, {
     amount: Amounts.jsonifyAmount(req.amount),
     wgInfo: {
       withdrawalType: WithdrawalRecordType.BankManual,
@@ -2931,23 +2905,23 @@ export async function createManualWithdrawal(
   });
 
   const ctx = new WithdrawTransactionContext(
-    ws,
+    wex,
     withdrawalGroup.withdrawalGroupId,
   );
 
-  const exchangePaytoUris = await ws.db.runReadOnlyTx(
+  const exchangePaytoUris = await wex.db.runReadOnlyTx(
     ["withdrawalGroups", "exchanges", "exchangeDetails"],
     async (tx) => {
       return await getFundingPaytoUris(tx, withdrawalGroup.withdrawalGroupId);
     },
   );
 
-  ws.notify({
+  wex.ws.notify({
     type: NotificationType.BalanceChange,
     hintTransactionId: ctx.transactionId,
   });
 
-  ws.taskScheduler.startShepherdTask(ctx.taskId);
+  wex.taskScheduler.startShepherdTask(ctx.taskId);
 
   return {
     reservePub: withdrawalGroup.reservePub,

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