gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated (68dddc84 -> 3d2b7b2a)


From: gnunet
Subject: [taler-wallet-core] branch master updated (68dddc84 -> 3d2b7b2a)
Date: Wed, 09 Jun 2021 15:26:21 +0200

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

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

    from 68dddc84 bump version @gnu-taler/taler-util
     new 5c264612 database access refactor
     new 3d2b7b2a formatting: re-run prettier

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 packages/idb-bridge/src/backend-interface.ts       |   2 +-
 packages/idb-bridge/src/idb-wpt-ported/README      |  11 +-
 .../src/idb-wpt-ported/cursor-overloads.test.ts    |  65 +-
 .../idb-bridge/src/idb-wpt-ported/value.test.ts    |   4 +-
 packages/idb-bridge/src/index.ts                   |   2 +-
 packages/taler-util/src/ReserveTransaction.ts      |  16 +-
 packages/taler-util/src/helpers.test.js            |  32 +-
 packages/taler-util/src/helpers.ts                 |   6 +-
 packages/taler-util/src/i18n.ts                    |  21 +-
 packages/taler-util/src/index.ts                   |   2 +-
 packages/taler-util/src/logging.ts                 |   4 +-
 packages/taler-util/src/taler-error-codes.ts       |   3 -
 packages/taler-util/src/talerTypes.ts              |  61 +-
 packages/taler-util/src/talerconfig.ts             |  12 +-
 packages/taler-wallet-cli/src/assets.ts            |   2 +-
 packages/taler-wallet-cli/src/index.ts             |   2 +-
 .../src/integrationtests/libeufin.ts               | 129 ++--
 .../integrationtests/test-exchange-management.ts   |   4 +-
 .../test-libeufin-api-bankaccount.ts               | 110 ++-
 .../test-libeufin-api-bankconnection.ts            |  37 +-
 .../integrationtests/test-libeufin-api-facade.ts   |  10 +-
 .../test-libeufin-api-permissions.ts               |   4 +-
 .../test-libeufin-api-scheduling.ts                |  39 +-
 .../integrationtests/test-libeufin-api-users.ts    |  28 +-
 .../test-libeufin-refund-multiple-users.ts         |   6 +-
 .../src/integrationtests/test-libeufin-refund.ts   |   6 +-
 .../test-merchant-exchange-confusion.ts            |   1 -
 .../integrationtests/test-merchant-refund-api.ts   |   5 +-
 .../src/integrationtests/test-pay-abort.ts         |   6 +-
 .../src/integrationtests/test-paywall-flow.ts      |   2 +-
 .../src/integrationtests/test-refund-auto.ts       |   2 +-
 .../src/integrationtests/test-refund-gone.ts       |   2 +-
 .../integrationtests/test-refund-incremental.ts    |   2 +-
 .../src/integrationtests/testrunner.ts             |   2 +-
 .../src/crypto/workers/cryptoImplementation.ts     |   2 +-
 .../src/crypto/workers/nodeThreadWorker.ts         |   8 +-
 packages/taler-wallet-core/src/db.ts               | 541 +++++--------
 packages/taler-wallet-core/src/headless/helpers.ts |  24 +-
 .../src/operations/backup/export.ts                |  65 +-
 .../src/operations/backup/import.ts                | 134 ++--
 .../src/operations/backup/index.ts                 | 169 +++--
 .../src/operations/backup/state.ts                 |  63 +-
 .../taler-wallet-core/src/operations/balance.ts    |  55 +-
 .../taler-wallet-core/src/operations/currencies.ts |  71 +-
 .../taler-wallet-core/src/operations/deposits.ts   | 173 +++--
 .../taler-wallet-core/src/operations/exchanges.ts  | 141 ++--
 packages/taler-wallet-core/src/operations/pay.ts   | 836 ++++++++++++---------
 .../taler-wallet-core/src/operations/pending.ts    |  86 ++-
 .../taler-wallet-core/src/operations/recoup.ts     | 247 +++---
 .../taler-wallet-core/src/operations/refresh.ts    | 514 ++++++++-----
 .../taler-wallet-core/src/operations/refund.ts     | 257 ++++---
 .../taler-wallet-core/src/operations/reserves.ts   | 334 ++++----
 packages/taler-wallet-core/src/operations/state.ts |  22 +-
 .../taler-wallet-core/src/operations/testing.ts    |  15 +-
 packages/taler-wallet-core/src/operations/tip.ts   | 169 +++--
 .../src/operations/transactions.ts                 | 656 ++++++++--------
 .../taler-wallet-core/src/operations/withdraw.ts   | 459 ++++++-----
 packages/taler-wallet-core/src/pending-types.ts    |   7 +-
 packages/taler-wallet-core/src/util/query.ts       | 679 ++++++++---------
 packages/taler-wallet-core/src/wallet.ts           | 393 +++++-----
 .../taler-wallet-webextension/src/wxBackend.ts     |   6 +-
 61 files changed, 3537 insertions(+), 3229 deletions(-)

diff --git a/packages/idb-bridge/src/backend-interface.ts 
b/packages/idb-bridge/src/backend-interface.ts
index 5ca70c8a..ae43c9df 100644
--- a/packages/idb-bridge/src/backend-interface.ts
+++ b/packages/idb-bridge/src/backend-interface.ts
@@ -220,5 +220,5 @@ export interface Backend {
   clearObjectStore(
     btx: DatabaseTransaction,
     objectStoreName: string,
-  ): Promise<void>
+  ): Promise<void>;
 }
diff --git a/packages/idb-bridge/src/idb-wpt-ported/README 
b/packages/idb-bridge/src/idb-wpt-ported/README
index 1947074d..8efc8569 100644
--- a/packages/idb-bridge/src/idb-wpt-ported/README
+++ b/packages/idb-bridge/src/idb-wpt-ported/README
@@ -3,11 +3,12 @@ This directory contains test cases from the W3C Web Platform 
Tests suite for Ind
 The original code for these tests can be found here: 
https://github.com/web-platform-tests/wpt/tree/master/IndexedDB
 
 The following tests are intentionally not included:
-* error-attributes.html (assumes we have a DOM)
-* file_support.sub.html (assumes we have a DOM)
-* fire-error-event-exception.html (ava can't test unhandled rejections)
-* fire-success-event-exception.html (ava can't test unhandled rejections)
-* fire-upgradeneeded-event-exception.html (ava can't test unhandled rejections)
+
+- error-attributes.html (assumes we have a DOM)
+- file_support.sub.html (assumes we have a DOM)
+- fire-error-event-exception.html (ava can't test unhandled rejections)
+- fire-success-event-exception.html (ava can't test unhandled rejections)
+- fire-upgradeneeded-event-exception.html (ava can't test unhandled rejections)
 
 Test todo:
 
diff --git a/packages/idb-bridge/src/idb-wpt-ported/cursor-overloads.test.ts 
b/packages/idb-bridge/src/idb-wpt-ported/cursor-overloads.test.ts
index 0f0713d1..c4bce874 100644
--- a/packages/idb-bridge/src/idb-wpt-ported/cursor-overloads.test.ts
+++ b/packages/idb-bridge/src/idb-wpt-ported/cursor-overloads.test.ts
@@ -28,76 +28,85 @@ test.cb("WPT test cursor-overloads.htm", (t) => {
 
     await checkCursorDirection(store.openCursor(), "next");
     await checkCursorDirection(store.openCursor(0), "next");
-    await checkCursorDirection(store.openCursor(0, 'next'), "next");
-    await checkCursorDirection(store.openCursor(0, 'nextunique'), 
"nextunique");
-    await checkCursorDirection(store.openCursor(0, 'prev'), "prev");
-    await checkCursorDirection(store.openCursor(0, 'prevunique'), 
"prevunique");
+    await checkCursorDirection(store.openCursor(0, "next"), "next");
+    await checkCursorDirection(store.openCursor(0, "nextunique"), 
"nextunique");
+    await checkCursorDirection(store.openCursor(0, "prev"), "prev");
+    await checkCursorDirection(store.openCursor(0, "prevunique"), 
"prevunique");
 
     await checkCursorDirection(store.openCursor(IDBKeyRange.only(0)), "next");
     await checkCursorDirection(
-      store.openCursor(BridgeIDBKeyRange.only(0), 'next'),
+      store.openCursor(BridgeIDBKeyRange.only(0), "next"),
       "next",
     );
     await checkCursorDirection(
-      store.openCursor(IDBKeyRange.only(0), 'nextunique'),
+      store.openCursor(IDBKeyRange.only(0), "nextunique"),
       "nextunique",
     );
     await checkCursorDirection(
-      store.openCursor(IDBKeyRange.only(0), 'prev'),
+      store.openCursor(IDBKeyRange.only(0), "prev"),
       "prev",
     );
     await checkCursorDirection(
-      store.openCursor(IDBKeyRange.only(0), 'prevunique'),
+      store.openCursor(IDBKeyRange.only(0), "prevunique"),
       "prevunique",
     );
 
     await checkCursorDirection(index.openCursor(), "next");
     await checkCursorDirection(index.openCursor(0), "next");
-    await checkCursorDirection(index.openCursor(0, 'next'), "next");
-    await checkCursorDirection(index.openCursor(0, 'nextunique'), 
"nextunique");
-    await checkCursorDirection(index.openCursor(0, 'prev'), "prev");
-    await checkCursorDirection(index.openCursor(0, 'prevunique'), 
"prevunique");
+    await checkCursorDirection(index.openCursor(0, "next"), "next");
+    await checkCursorDirection(index.openCursor(0, "nextunique"), 
"nextunique");
+    await checkCursorDirection(index.openCursor(0, "prev"), "prev");
+    await checkCursorDirection(index.openCursor(0, "prevunique"), 
"prevunique");
 
     await checkCursorDirection(index.openCursor(IDBKeyRange.only(0)), "next");
     await checkCursorDirection(
-      index.openCursor(IDBKeyRange.only(0), 'next'),
+      index.openCursor(IDBKeyRange.only(0), "next"),
       "next",
     );
     await checkCursorDirection(
-      index.openCursor(IDBKeyRange.only(0), 'nextunique'),
+      index.openCursor(IDBKeyRange.only(0), "nextunique"),
       "nextunique",
     );
     await checkCursorDirection(
-      index.openCursor(IDBKeyRange.only(0), 'prev'),
+      index.openCursor(IDBKeyRange.only(0), "prev"),
       "prev",
     );
     await checkCursorDirection(
-      index.openCursor(IDBKeyRange.only(0), 'prevunique'),
+      index.openCursor(IDBKeyRange.only(0), "prevunique"),
       "prevunique",
     );
 
     await checkCursorDirection(index.openKeyCursor(), "next");
     await checkCursorDirection(index.openKeyCursor(0), "next");
-    await checkCursorDirection(index.openKeyCursor(0, 'next'), "next");
-    await checkCursorDirection(index.openKeyCursor(0, 'nextunique'), 
"nextunique");
-    await checkCursorDirection(index.openKeyCursor(0, 'prev'), "prev");
-    await checkCursorDirection(index.openKeyCursor(0, 'prevunique'), 
"prevunique");
+    await checkCursorDirection(index.openKeyCursor(0, "next"), "next");
+    await checkCursorDirection(
+      index.openKeyCursor(0, "nextunique"),
+      "nextunique",
+    );
+    await checkCursorDirection(index.openKeyCursor(0, "prev"), "prev");
+    await checkCursorDirection(
+      index.openKeyCursor(0, "prevunique"),
+      "prevunique",
+    );
 
-    await checkCursorDirection(index.openKeyCursor(IDBKeyRange.only(0)), 
"next");
     await checkCursorDirection(
-      index.openKeyCursor(IDBKeyRange.only(0), 'next'),
+      index.openKeyCursor(IDBKeyRange.only(0)),
       "next",
     );
     await checkCursorDirection(
-      index.openKeyCursor(IDBKeyRange.only(0), 'nextunique'),
+      index.openKeyCursor(IDBKeyRange.only(0), "next"),
+      "next",
+    );
+    await checkCursorDirection(
+      index.openKeyCursor(IDBKeyRange.only(0), "nextunique"),
       "nextunique",
     );
     await checkCursorDirection(
-      index.openKeyCursor(IDBKeyRange.only(0), 'prev'),
+      index.openKeyCursor(IDBKeyRange.only(0), "prev"),
       "prev",
     );
     await checkCursorDirection(
-      index.openKeyCursor(IDBKeyRange.only(0), 'prevunique'),
+      index.openKeyCursor(IDBKeyRange.only(0), "prevunique"),
       "prevunique",
     );
 
@@ -110,7 +119,11 @@ test.cb("WPT test cursor-overloads.htm", (t) => {
   ): Promise<void> {
     return new Promise<void>((resolve, reject) => {
       request.onsuccess = function (event: any) {
-        t.notDeepEqual(event.target.result, null, "Check the result is not 
null");
+        t.notDeepEqual(
+          event.target.result,
+          null,
+          "Check the result is not null",
+        );
         t.deepEqual(
           event.target.result.direction,
           direction,
diff --git a/packages/idb-bridge/src/idb-wpt-ported/value.test.ts 
b/packages/idb-bridge/src/idb-wpt-ported/value.test.ts
index b1c2b3be..acae2fe6 100644
--- a/packages/idb-bridge/src/idb-wpt-ported/value.test.ts
+++ b/packages/idb-bridge/src/idb-wpt-ported/value.test.ts
@@ -37,8 +37,8 @@ test.cb("WPT test value.htm, date", (t) => {
         .transaction("store")
         .objectStore("store")
         .get(1).onsuccess = (e: any) => {
-          console.log("target", e.target);
-          console.log("result", e.target.result);
+        console.log("target", e.target);
+        console.log("result", e.target.result);
         t.assert(e.target.result instanceof _instanceof, "instanceof");
         t.end();
       };
diff --git a/packages/idb-bridge/src/index.ts b/packages/idb-bridge/src/index.ts
index a0bd8b86..fa9edaea 100644
--- a/packages/idb-bridge/src/index.ts
+++ b/packages/idb-bridge/src/index.ts
@@ -116,4 +116,4 @@ export function shimIndexedDB(factory: BridgeIDBFactory): 
void {
 
 export * from "./idbtypes";
 
-export * from "./util/structuredClone";
\ No newline at end of file
+export * from "./util/structuredClone";
diff --git a/packages/taler-util/src/ReserveTransaction.ts 
b/packages/taler-util/src/ReserveTransaction.ts
index f477106b..b282ef18 100644
--- a/packages/taler-util/src/ReserveTransaction.ts
+++ b/packages/taler-util/src/ReserveTransaction.ts
@@ -182,9 +182,7 @@ export type ReserveTransaction =
   | ReserveClosingTransaction
   | ReserveRecoupTransaction;
 
-export const codecForReserveWithdrawTransaction = (): Codec<
-  ReserveWithdrawTransaction
-> =>
+export const codecForReserveWithdrawTransaction = (): 
Codec<ReserveWithdrawTransaction> =>
   buildCodecForObject<ReserveWithdrawTransaction>()
     .property("amount", codecForString())
     .property("h_coin_envelope", codecForString())
@@ -194,9 +192,7 @@ export const codecForReserveWithdrawTransaction = (): Codec<
     .property("withdraw_fee", codecForString())
     .build("ReserveWithdrawTransaction");
 
-export const codecForReserveCreditTransaction = (): Codec<
-  ReserveCreditTransaction
-> =>
+export const codecForReserveCreditTransaction = (): 
Codec<ReserveCreditTransaction> =>
   buildCodecForObject<ReserveCreditTransaction>()
     .property("amount", codecForString())
     .property("sender_account_url", codecForString())
@@ -205,9 +201,7 @@ export const codecForReserveCreditTransaction = (): Codec<
     .property("type", codecForConstString(ReserveTransactionType.Credit))
     .build("ReserveCreditTransaction");
 
-export const codecForReserveClosingTransaction = (): Codec<
-  ReserveClosingTransaction
-> =>
+export const codecForReserveClosingTransaction = (): 
Codec<ReserveClosingTransaction> =>
   buildCodecForObject<ReserveClosingTransaction>()
     .property("amount", codecForString())
     .property("closing_fee", codecForString())
@@ -219,9 +213,7 @@ export const codecForReserveClosingTransaction = (): Codec<
     .property("wtid", codecForString())
     .build("ReserveClosingTransaction");
 
-export const codecForReserveRecoupTransaction = (): Codec<
-  ReserveRecoupTransaction
-> =>
+export const codecForReserveRecoupTransaction = (): 
Codec<ReserveRecoupTransaction> =>
   buildCodecForObject<ReserveRecoupTransaction>()
     .property("amount", codecForString())
     .property("coin_pub", codecForString())
diff --git a/packages/taler-util/src/helpers.test.js 
b/packages/taler-util/src/helpers.test.js
index ed3198c8..b311f8a1 100644
--- a/packages/taler-util/src/helpers.test.js
+++ b/packages/taler-util/src/helpers.test.js
@@ -16,14 +16,26 @@
 import test from "ava";
 import * as helpers from "./helpers";
 test("URL canonicalization", (t) => {
-    // converts to relative, adds https
-    t.is("https://alice.example.com/exchange/";, 
helpers.canonicalizeBaseUrl("alice.example.com/exchange"));
-    // keeps http, adds trailing slash
-    t.is("http://alice.example.com/exchange/";, 
helpers.canonicalizeBaseUrl("http://alice.example.com/exchange";));
-    // keeps http, adds trailing slash
-    t.is("http://alice.example.com/exchange/";, 
helpers.canonicalizeBaseUrl("http://alice.example.com/exchange#foobar";));
-    // Remove search component
-    t.is("http://alice.example.com/exchange/";, 
helpers.canonicalizeBaseUrl("http://alice.example.com/exchange?foo=bar";));
-    t.pass();
+  // converts to relative, adds https
+  t.is(
+    "https://alice.example.com/exchange/";,
+    helpers.canonicalizeBaseUrl("alice.example.com/exchange"),
+  );
+  // keeps http, adds trailing slash
+  t.is(
+    "http://alice.example.com/exchange/";,
+    helpers.canonicalizeBaseUrl("http://alice.example.com/exchange";),
+  );
+  // keeps http, adds trailing slash
+  t.is(
+    "http://alice.example.com/exchange/";,
+    helpers.canonicalizeBaseUrl("http://alice.example.com/exchange#foobar";),
+  );
+  // Remove search component
+  t.is(
+    "http://alice.example.com/exchange/";,
+    helpers.canonicalizeBaseUrl("http://alice.example.com/exchange?foo=bar";),
+  );
+  t.pass();
 });
-//# sourceMappingURL=helpers.test.js.map
\ No newline at end of file
+//# sourceMappingURL=helpers.test.js.map
diff --git a/packages/taler-util/src/helpers.ts 
b/packages/taler-util/src/helpers.ts
index fb0cd8a2..53a9a43d 100644
--- a/packages/taler-util/src/helpers.ts
+++ b/packages/taler-util/src/helpers.ts
@@ -68,11 +68,7 @@ export function canonicalJson(obj: any): string {
       return "\\u" + ("0000" + chr.charCodeAt(0).toString(16)).substr(-4);
     });
   }
-  if (
-    typeof obj === "number" ||
-    typeof obj === "boolean" ||
-    obj === null
-  ) {
+  if (typeof obj === "number" || typeof obj === "boolean" || obj === null) {
     return JSON.stringify(obj);
   }
   if (Array.isArray(obj)) {
diff --git a/packages/taler-util/src/i18n.ts b/packages/taler-util/src/i18n.ts
index 4253eb22..a4b0bf81 100644
--- a/packages/taler-util/src/i18n.ts
+++ b/packages/taler-util/src/i18n.ts
@@ -17,7 +17,7 @@ export function setupI18n(lang: string, strings: { [s: 
string]: any }): any {
     lang = "en-US";
     logger.warn(`language ${lang} not found, defaulting to english`);
   }
-  debugger
+  debugger;
   jed = new jedLib.Jed(strings[lang]);
 }
 
@@ -58,11 +58,14 @@ export function str(stringSeq: TemplateStringsArray, 
...values: any[]): string {
 /**
  * Internationalize a string template without serializing
  */
-export function translate(stringSeq: TemplateStringsArray, ...values: any[]): 
any[] {
+export function translate(
+  stringSeq: TemplateStringsArray,
+  ...values: any[]
+): any[] {
   const s = toI18nString(stringSeq);
-  if (!s) return []
+  if (!s) return [];
   const translation: string = jed.ngettext(s, s, 1);
-  return replacePlaceholderWithValues(translation, values)
+  return replacePlaceholderWithValues(translation, values);
 }
 
 /**
@@ -71,9 +74,9 @@ export function translate(stringSeq: TemplateStringsArray, 
...values: any[]): an
 export function Translate({ children, ...rest }: { children: any }): any {
   const c = [].concat(children);
   const s = stringifyArray(c);
-  if (!s) return []
+  if (!s) return [];
   const translation: string = jed.ngettext(s, s, 1);
-  return replacePlaceholderWithValues(translation, c)
+  return replacePlaceholderWithValues(translation, c);
 }
 
 /**
@@ -94,7 +97,6 @@ export function getTranslatedArray(array: Array<any>) {
   return replacePlaceholderWithValues(translation, array);
 }
 
-
 function replacePlaceholderWithValues(
   translation: string,
   childArray: Array<any>,
@@ -142,6 +144,5 @@ function stringifyArray(children: Array<any>): string {
 export const i18n = {
   str,
   Translate,
-  translate
-}
-
+  translate,
+};
diff --git a/packages/taler-util/src/index.ts b/packages/taler-util/src/index.ts
index 25a24fa1..2e1c6876 100644
--- a/packages/taler-util/src/index.ts
+++ b/packages/taler-util/src/index.ts
@@ -18,4 +18,4 @@ export * from "./time.js";
 export * from "./transactionsTypes.js";
 export * from "./walletTypes.js";
 export * from "./i18n.js";
-export * from "./logging.js";
\ No newline at end of file
+export * from "./logging.js";
diff --git a/packages/taler-util/src/logging.ts 
b/packages/taler-util/src/logging.ts
index 4f48e24d..532174fd 100644
--- a/packages/taler-util/src/logging.ts
+++ b/packages/taler-util/src/logging.ts
@@ -19,7 +19,9 @@
  */
 
 const isNode =
-  typeof process !== "undefined" && typeof process.release !== "undefined" && 
process.release.name === "node";
+  typeof process !== "undefined" &&
+  typeof process.release !== "undefined" &&
+  process.release.name === "node";
 
 function writeNodeLog(
   message: any,
diff --git a/packages/taler-util/src/taler-error-codes.ts 
b/packages/taler-util/src/taler-error-codes.ts
index 2d8e31b2..fec2cf0f 100644
--- a/packages/taler-util/src/taler-error-codes.ts
+++ b/packages/taler-util/src/taler-error-codes.ts
@@ -22,8 +22,6 @@
  */
 
 export enum TalerErrorCode {
-
-
   /**
    * Special code to indicate success (no error).
    * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
@@ -2319,5 +2317,4 @@ export enum TalerErrorCode {
    * (A value of 0 indicates that the error is generated client-side).
    */
   END = 9999,
-
 }
diff --git a/packages/taler-util/src/talerTypes.ts 
b/packages/taler-util/src/talerTypes.ts
index db20234c..4b9ad96e 100644
--- a/packages/taler-util/src/talerTypes.ts
+++ b/packages/taler-util/src/talerTypes.ts
@@ -989,9 +989,7 @@ export interface BankWithdrawalOperationPostResponse {
   transfer_done: boolean;
 }
 
-export const codecForBankWithdrawalOperationPostResponse = (): Codec<
-  BankWithdrawalOperationPostResponse
-> =>
+export const codecForBankWithdrawalOperationPostResponse = (): 
Codec<BankWithdrawalOperationPostResponse> =>
   buildCodecForObject<BankWithdrawalOperationPostResponse>()
     .property("transfer_done", codecForBoolean())
     .build("BankWithdrawalOperationPostResponse");
@@ -1070,9 +1068,8 @@ export const codecForTax = (): Codec<Tax> =>
     .property("tax", codecForString())
     .build("Tax");
 
-export const codecForInternationalizedString = (): Codec<
-  InternationalizedString
-> => codecForMap(codecForString());
+export const codecForInternationalizedString = (): 
Codec<InternationalizedString> =>
+  codecForMap(codecForString());
 
 export const codecForProduct = (): Codec<Product> =>
   buildCodecForObject<Product>()
@@ -1120,9 +1117,7 @@ export const codecForContractTerms = (): 
Codec<ContractTerms> =>
     .property("extra", codecForAny())
     .build("ContractTerms");
 
-export const codecForMerchantRefundPermission = (): Codec<
-  MerchantAbortPayRefundDetails
-> =>
+export const codecForMerchantRefundPermission = (): 
Codec<MerchantAbortPayRefundDetails> =>
   buildCodecForObject<MerchantAbortPayRefundDetails>()
     .property("refund_amount", codecForAmountString())
     .property("refund_fee", codecForAmountString())
@@ -1135,9 +1130,7 @@ export const codecForMerchantRefundPermission = (): Codec<
     .property("exchange_pub", codecOptional(codecForString()))
     .build("MerchantRefundPermission");
 
-export const codecForMerchantRefundResponse = (): Codec<
-  MerchantRefundResponse
-> =>
+export const codecForMerchantRefundResponse = (): 
Codec<MerchantRefundResponse> =>
   buildCodecForObject<MerchantRefundResponse>()
     .property("merchant_pub", codecForString())
     .property("h_contract_terms", codecForString())
@@ -1217,9 +1210,7 @@ export const codecForCheckPaymentResponse = (): 
Codec<CheckPaymentResponse> =>
     .property("contract_url", codecOptional(codecForString()))
     .build("CheckPaymentResponse");
 
-export const codecForWithdrawOperationStatusResponse = (): Codec<
-  WithdrawOperationStatusResponse
-> =>
+export const codecForWithdrawOperationStatusResponse = (): 
Codec<WithdrawOperationStatusResponse> =>
   buildCodecForObject<WithdrawOperationStatusResponse>()
     .property("selection_done", codecForBoolean())
     .property("transfer_done", codecForBoolean())
@@ -1267,16 +1258,12 @@ export const codecForExchangeRevealItem = (): 
Codec<ExchangeRevealItem> =>
     .property("ev_sig", codecForString())
     .build("ExchangeRevealItem");
 
-export const codecForExchangeRevealResponse = (): Codec<
-  ExchangeRevealResponse
-> =>
+export const codecForExchangeRevealResponse = (): 
Codec<ExchangeRevealResponse> =>
   buildCodecForObject<ExchangeRevealResponse>()
     .property("ev_sigs", codecForList(codecForExchangeRevealItem()))
     .build("ExchangeRevealResponse");
 
-export const codecForMerchantCoinRefundSuccessStatus = (): Codec<
-  MerchantCoinRefundSuccessStatus
-> =>
+export const codecForMerchantCoinRefundSuccessStatus = (): 
Codec<MerchantCoinRefundSuccessStatus> =>
   buildCodecForObject<MerchantCoinRefundSuccessStatus>()
     .property("type", codecForConstString("success"))
     .property("coin_pub", codecForString())
@@ -1288,9 +1275,7 @@ export const codecForMerchantCoinRefundSuccessStatus = 
(): Codec<
     .property("execution_time", codecForTimestamp)
     .build("MerchantCoinRefundSuccessStatus");
 
-export const codecForMerchantCoinRefundFailureStatus = (): Codec<
-  MerchantCoinRefundFailureStatus
-> =>
+export const codecForMerchantCoinRefundFailureStatus = (): 
Codec<MerchantCoinRefundFailureStatus> =>
   buildCodecForObject<MerchantCoinRefundFailureStatus>()
     .property("type", codecForConstString("failure"))
     .property("coin_pub", codecForString())
@@ -1302,35 +1287,27 @@ export const codecForMerchantCoinRefundFailureStatus = 
(): Codec<
     .property("execution_time", codecForTimestamp)
     .build("MerchantCoinRefundFailureStatus");
 
-export const codecForMerchantCoinRefundStatus = (): Codec<
-  MerchantCoinRefundStatus
-> =>
+export const codecForMerchantCoinRefundStatus = (): 
Codec<MerchantCoinRefundStatus> =>
   buildCodecForUnion<MerchantCoinRefundStatus>()
     .discriminateOn("type")
     .alternative("success", codecForMerchantCoinRefundSuccessStatus())
     .alternative("failure", codecForMerchantCoinRefundFailureStatus())
     .build("MerchantCoinRefundStatus");
 
-export const codecForMerchantOrderStatusPaid = (): Codec<
-  MerchantOrderStatusPaid
-> =>
+export const codecForMerchantOrderStatusPaid = (): 
Codec<MerchantOrderStatusPaid> =>
   buildCodecForObject<MerchantOrderStatusPaid>()
     .property("refund_amount", codecForString())
     .property("refunded", codecForBoolean())
     .build("MerchantOrderStatusPaid");
 
-export const codecForMerchantOrderRefundPickupResponse = (): Codec<
-  MerchantOrderRefundResponse
-> =>
+export const codecForMerchantOrderRefundPickupResponse = (): 
Codec<MerchantOrderRefundResponse> =>
   buildCodecForObject<MerchantOrderRefundResponse>()
     .property("merchant_pub", codecForString())
     .property("refund_amount", codecForString())
     .property("refunds", codecForList(codecForMerchantCoinRefundStatus()))
     .build("MerchantOrderRefundPickupResponse");
 
-export const codecForMerchantOrderStatusUnpaid = (): Codec<
-  MerchantOrderStatusUnpaid
-> =>
+export const codecForMerchantOrderStatusUnpaid = (): 
Codec<MerchantOrderStatusUnpaid> =>
   buildCodecForObject<MerchantOrderStatusUnpaid>()
     .property("taler_pay_uri", codecForString())
     .property("already_paid_order_id", codecOptional(codecForString()))
@@ -1412,9 +1389,7 @@ export interface MerchantAbortPayRefundSuccessStatus {
   exchange_pub: string;
 }
 
-export const codecForMerchantAbortPayRefundSuccessStatus = (): Codec<
-  MerchantAbortPayRefundSuccessStatus
-> =>
+export const codecForMerchantAbortPayRefundSuccessStatus = (): 
Codec<MerchantAbortPayRefundSuccessStatus> =>
   buildCodecForObject<MerchantAbortPayRefundSuccessStatus>()
     .property("exchange_pub", codecForString())
     .property("exchange_sig", codecForString())
@@ -1422,9 +1397,7 @@ export const codecForMerchantAbortPayRefundSuccessStatus 
= (): Codec<
     .property("type", codecForConstString("success"))
     .build("MerchantAbortPayRefundSuccessStatus");
 
-export const codecForMerchantAbortPayRefundFailureStatus = (): Codec<
-  MerchantAbortPayRefundFailureStatus
-> =>
+export const codecForMerchantAbortPayRefundFailureStatus = (): 
Codec<MerchantAbortPayRefundFailureStatus> =>
   buildCodecForObject<MerchantAbortPayRefundFailureStatus>()
     .property("exchange_code", codecForNumber())
     .property("exchange_reply", codecForAny())
@@ -1432,9 +1405,7 @@ export const codecForMerchantAbortPayRefundFailureStatus 
= (): Codec<
     .property("type", codecForConstString("failure"))
     .build("MerchantAbortPayRefundFailureStatus");
 
-export const codecForMerchantAbortPayRefundStatus = (): Codec<
-  MerchantAbortPayRefundStatus
-> =>
+export const codecForMerchantAbortPayRefundStatus = (): 
Codec<MerchantAbortPayRefundStatus> =>
   buildCodecForUnion<MerchantAbortPayRefundStatus>()
     .discriminateOn("type")
     .alternative("success", codecForMerchantAbortPayRefundSuccessStatus())
diff --git a/packages/taler-util/src/talerconfig.ts 
b/packages/taler-util/src/talerconfig.ts
index f3c1220e..7704e261 100644
--- a/packages/taler-util/src/talerconfig.ts
+++ b/packages/taler-util/src/talerconfig.ts
@@ -28,18 +28,18 @@ import { Amounts } from "./amounts.js";
 
 const nodejs_fs = (function () {
   let fs: typeof import("fs");
-  return function() {
+  return function () {
     if (!fs) {
       /**
        * need to use an expression when doing a require if we want
        * webpack not to find out about the requirement
        */
-      const _r = "require"
-      fs = module[_r]("fs") 
+      const _r = "require";
+      fs = module[_r]("fs");
     }
-    return fs
-  }
-})()
+    return fs;
+  };
+})();
 
 export class ConfigError extends Error {
   constructor(message: string) {
diff --git a/packages/taler-wallet-cli/src/assets.ts 
b/packages/taler-wallet-cli/src/assets.ts
index 7d6b8f33..72fc9fe0 100644
--- a/packages/taler-wallet-cli/src/assets.ts
+++ b/packages/taler-wallet-cli/src/assets.ts
@@ -22,7 +22,7 @@ import fs from "fs";
 
 /**
  * Resolve an asset name into an absolute filename.
- * 
+ *
  * The asset file should be placed in the "assets" directory
  * at the top level of the package (i.e. next to package.json).
  */
diff --git a/packages/taler-wallet-cli/src/index.ts 
b/packages/taler-wallet-cli/src/index.ts
index 93ef0cf5..1e22b4ca 100644
--- a/packages/taler-wallet-cli/src/index.ts
+++ b/packages/taler-wallet-cli/src/index.ts
@@ -642,7 +642,7 @@ reservesCli
   })
   .action(async (args) => {
     await withWallet(args, async (wallet) => {
-      const reserves = await wallet.getReserves();
+      const reserves = await wallet.getReservesForExchange();
       console.log(JSON.stringify(reserves, undefined, 2));
     });
   });
diff --git a/packages/taler-wallet-cli/src/integrationtests/libeufin.ts 
b/packages/taler-wallet-cli/src/integrationtests/libeufin.ts
index ddc19b6e..4b14cc02 100644
--- a/packages/taler-wallet-cli/src/integrationtests/libeufin.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/libeufin.ts
@@ -82,7 +82,7 @@ interface LibeufinNexusMoneyMovement {
     };
     endToEndId: string;
     unstructuredRemittanceInformation: string;
-  }
+  };
 }
 
 interface LibeufinNexusBatches {
@@ -668,7 +668,10 @@ export class LibeufinCli {
     console.log(stdout);
   }
 
-  async submitPayment(details: LibeufinPreparedPaymentDetails, paymentUuid: 
string): Promise<void> {
+  async submitPayment(
+    details: LibeufinPreparedPaymentDetails,
+    paymentUuid: string,
+  ): Promise<void> {
     const stdout = await sh(
       this.globalTestState,
       "libeufin-cli-submitpayment",
@@ -820,7 +823,7 @@ export interface NexusAuth {
   auth: {
     username: string;
     password: string;
-  }
+  };
 }
 
 export interface CreateNexusUserRequest {
@@ -832,10 +835,12 @@ export interface PostNexusTaskRequest {
   name: string;
   cronspec: string;
   type: string; // fetch | submit
-  params: {
-    level: string; // report | statement | all
-    rangeType: string; // all | since-last | previous-days | latest
-  } | {}
+  params:
+    | {
+        level: string; // report | statement | all
+        rangeType: string; // all | since-last | previous-days | latest
+      }
+    | {};
 }
 
 export interface PostNexusPermissionRequest {
@@ -850,20 +855,16 @@ export interface PostNexusPermissionRequest {
 }
 
 export namespace LibeufinNexusApi {
-
   export async function getAllConnections(
     nexus: LibeufinNexusServiceInterface,
   ): Promise<any> {
     let url = new URL("bank-connections", nexus.baseUrl);
-    const res = await axios.get(
-      url.href,
-      {
-        auth: {
-          username: "admin",
-          password: "test",
-        },
+    const res = await axios.get(url.href, {
+      auth: {
+        username: "admin",
+        password: "test",
       },
-    );
+    });
     return res;
   }
 
@@ -873,16 +874,12 @@ export namespace LibeufinNexusApi {
   ): Promise<any> {
     const baseUrl = libeufinNexusService.baseUrl;
     let url = new URL("bank-connections/delete-connection", baseUrl);
-    return await axios.post(
-      url.href,
-      req,
-      {
-        auth: {
-          username: "admin",
-          password: "test",
-        }
-      }
-    );
+    return await axios.post(url.href, req, {
+      auth: {
+        username: "admin",
+        password: "test",
+      },
+    });
   }
 
   export async function createEbicsBankConnection(
@@ -1012,31 +1009,26 @@ export namespace LibeufinNexusApi {
       `/bank-accounts/${accountName}/payment-initiations`,
       baseUrl,
     );
-    let response = await axios.get(
-      url.href,
-      {
-        auth: {
-          username: username,
-          password: password,
-        },
+    let response = await axios.get(url.href, {
+      auth: {
+        username: username,
+        password: password,
       },
+    });
+    console.log(
+      `Payment initiations of: ${accountName}`,
+      JSON.stringify(response.data, null, 2),
     );
-    console.log(`Payment initiations of: ${accountName}`,
-                JSON.stringify(response.data, null, 2));
   }
 
   export async function getConfig(
     libeufinNexusService: LibeufinNexusService,
   ): Promise<void> {
     const baseUrl = libeufinNexusService.baseUrl;
-    let url = new URL(
-      `/config`,
-      baseUrl,
-    );
+    let url = new URL(`/config`, baseUrl);
     let response = await axios.get(url.href);
   }
 
-
   // FIXME: this function should return some structured
   // object that represents a history.
   export async function getAccountTransactions(
@@ -1046,19 +1038,13 @@ export namespace LibeufinNexusApi {
     password: string = "test",
   ): Promise<any> {
     const baseUrl = libeufinNexusService.baseUrl;
-    let url = new URL(
-      `/bank-accounts/${accountName}/transactions`,
-      baseUrl,
-    );
-    let response = await axios.get(
-      url.href,
-      {
-        auth: {
-          username: username,
-          password: password,
-        },
+    let url = new URL(`/bank-accounts/${accountName}/transactions`, baseUrl);
+    let response = await axios.get(url.href, {
+      auth: {
+        username: username,
+        password: password,
       },
-    );
+    });
     return response;
   }
 
@@ -1174,7 +1160,10 @@ export namespace LibeufinNexusApi {
     taskName: string,
   ) {
     const baseUrl = libeufinNexusService.baseUrl;
-    let url = new 
URL(`/bank-accounts/${bankAccountName}/schedule/${taskName}`, baseUrl);
+    let url = new URL(
+      `/bank-accounts/${bankAccountName}/schedule/${taskName}`,
+      baseUrl,
+    );
     await axios.delete(url.href, {
       auth: {
         username: "admin",
@@ -1204,15 +1193,12 @@ export namespace LibeufinNexusApi {
   ): Promise<any> {
     const baseUrl = libeufinNexusService.baseUrl;
     let url = new URL(`facades/${facadeName}`, baseUrl);
-    return await axios.delete(
-      url.href,
-      {
-        auth: {
-          username: "admin",
-          password: "test",
-        },
-      }
-    );
+    return await axios.delete(url.href, {
+      auth: {
+        username: "admin",
+        password: "test",
+      },
+    });
   }
 
   export async function getAllFacades(
@@ -1220,15 +1206,12 @@ export namespace LibeufinNexusApi {
   ): Promise<any> {
     const baseUrl = libeufinNexusService.baseUrl;
     let url = new URL("facades", baseUrl);
-    return await axios.get(
-      url.href,
-      {
-        auth: {
-          username: "admin",
-          password: "test",
-        },
-      }
-    );
+    return await axios.get(url.href, {
+      auth: {
+        username: "admin",
+        password: "test",
+      },
+    });
   }
 
   export async function createTwgFacade(
@@ -1368,16 +1351,12 @@ export function findNexusPayment(
   key: string,
   payments: LibeufinNexusTransactions,
 ): LibeufinNexusMoneyMovement | void {
-  
   let transactions = payments["transactions"];
   for (let i = 0; i < transactions.length; i++) {
-
     let batches = transactions[i]["batches"];
     for (let y = 0; y < batches.length; y++) {
-
       let movements = batches[y]["batchTransactions"];
       for (let z = 0; z < movements.length; z++) {
-
         let movement = movements[z];
         if (movement["details"]["unstructuredRemittanceInformation"] == key)
           return movement;
diff --git 
a/packages/taler-wallet-cli/src/integrationtests/test-exchange-management.ts 
b/packages/taler-wallet-cli/src/integrationtests/test-exchange-management.ts
index 0d0253d7..c2494be9 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-exchange-management.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-exchange-management.ts
@@ -27,9 +27,7 @@ import {
   BankApi,
   BankAccessApi,
 } from "./harness";
-import {
-  URL,
-} from "@gnu-taler/taler-wallet-core";
+import { URL } from "@gnu-taler/taler-wallet-core";
 import { ExchangesListRespose, TalerErrorCode } from "@gnu-taler/taler-util";
 import {
   FaultInjectedExchangeService,
diff --git 
a/packages/taler-wallet-cli/src/integrationtests/test-libeufin-api-bankaccount.ts
 
b/packages/taler-wallet-cli/src/integrationtests/test-libeufin-api-bankaccount.ts
index dd9a99a4..3c391225 100644
--- 
a/packages/taler-wallet-cli/src/integrationtests/test-libeufin-api-bankaccount.ts
+++ 
b/packages/taler-wallet-cli/src/integrationtests/test-libeufin-api-bankaccount.ts
@@ -38,13 +38,10 @@ export async function runLibeufinApiBankaccountTest(t: 
GlobalTestState) {
   await nexus.start();
   await nexus.pingUntilAvailable();
 
-  await LibeufinNexusApi.createUser(
-    nexus,
-    {
-      username: "one",
-      password: "testing-the-bankaccount-api",
-    }
-  );
+  await LibeufinNexusApi.createUser(nexus, {
+    username: "one",
+    password: "testing-the-bankaccount-api",
+  });
   const sandbox = await LibeufinSandboxService.create(t, {
     httpPort: 5012,
     databaseJdbcUri: `jdbc:sqlite:${t.testDir}/libeufin-sandbox.sqlite3`,
@@ -52,43 +49,38 @@ export async function runLibeufinApiBankaccountTest(t: 
GlobalTestState) {
   await sandbox.start();
   await sandbox.pingUntilAvailable();
   await LibeufinSandboxApi.createEbicsHost(sandbox, "mock");
-  await LibeufinSandboxApi.createEbicsSubscriber(
-    sandbox,
-    {
-      hostID: "mock", 
-      userID: "mock", 
-      partnerID: "mock", 
-    }
-  );
-  await LibeufinSandboxApi.createEbicsBankAccount(
-    sandbox,
-    {
-      subscriber: {
-        hostID: "mock",
-        partnerID: "mock",
-        userID: "mock",
-      },
-      iban: "DE71500105179674997361",
-      bic: "BELADEBEXXX",
-      name: "mock",
-      currency: "mock",
-      label: "mock",
-    },
-  );
-  await LibeufinNexusApi.createEbicsBankConnection(
-    nexus,
-    {
-      name: "bankaccount-api-test-connection",
-      ebicsURL: "http://localhost:5012/ebicsweb";,
+  await LibeufinSandboxApi.createEbicsSubscriber(sandbox, {
+    hostID: "mock",
+    userID: "mock",
+    partnerID: "mock",
+  });
+  await LibeufinSandboxApi.createEbicsBankAccount(sandbox, {
+    subscriber: {
       hostID: "mock",
-      userID: "mock",
       partnerID: "mock",
-    }
-  );
+      userID: "mock",
+    },
+    iban: "DE71500105179674997361",
+    bic: "BELADEBEXXX",
+    name: "mock",
+    currency: "mock",
+    label: "mock",
+  });
+  await LibeufinNexusApi.createEbicsBankConnection(nexus, {
+    name: "bankaccount-api-test-connection",
+    ebicsURL: "http://localhost:5012/ebicsweb";,
+    hostID: "mock",
+    userID: "mock",
+    partnerID: "mock",
+  });
   await LibeufinNexusApi.connectBankConnection(
-    nexus, "bankaccount-api-test-connection"
+    nexus,
+    "bankaccount-api-test-connection",
+  );
+  await LibeufinNexusApi.fetchAccounts(
+    nexus,
+    "bankaccount-api-test-connection",
   );
-  await LibeufinNexusApi.fetchAccounts(nexus, 
"bankaccount-api-test-connection");
 
   await LibeufinNexusApi.importConnectionAccount(
     nexus,
@@ -96,30 +88,24 @@ export async function runLibeufinApiBankaccountTest(t: 
GlobalTestState) {
     "mock",
     "local-mock",
   );
-  
-  await LibeufinSandboxApi.bookPayment2(
-    sandbox,
-    {
-      creditorIban: "DE71500105179674997361",
-      creditorBic: "BELADEBEXXX",
-      creditorName: "mock",
-      debitorIban: "DE84500105176881385584",
-      debitorBic: "BELADEBEXXX",
-      debitorName: "mock2",
-      subject: "mock subject",
-      currency: "EUR",
-      amount: "1",
-      uid: "mock",
-      direction: "CRDT",
-    }
-  );
-  await LibeufinNexusApi.fetchAllTransactions(
-    nexus,
-    "local-mock"
-  );
+
+  await LibeufinSandboxApi.bookPayment2(sandbox, {
+    creditorIban: "DE71500105179674997361",
+    creditorBic: "BELADEBEXXX",
+    creditorName: "mock",
+    debitorIban: "DE84500105176881385584",
+    debitorBic: "BELADEBEXXX",
+    debitorName: "mock2",
+    subject: "mock subject",
+    currency: "EUR",
+    amount: "1",
+    uid: "mock",
+    direction: "CRDT",
+  });
+  await LibeufinNexusApi.fetchAllTransactions(nexus, "local-mock");
   let transactions = await LibeufinNexusApi.getAccountTransactions(
     nexus,
-    "local-mock"
+    "local-mock",
   );
   let el = findNexusPayment("mock subject", transactions.data);
   t.assertTrue(el instanceof Object);
diff --git 
a/packages/taler-wallet-cli/src/integrationtests/test-libeufin-api-bankconnection.ts
 
b/packages/taler-wallet-cli/src/integrationtests/test-libeufin-api-bankconnection.ts
index 9e6e1166..f8bee7f1 100644
--- 
a/packages/taler-wallet-cli/src/integrationtests/test-libeufin-api-bankconnection.ts
+++ 
b/packages/taler-wallet-cli/src/integrationtests/test-libeufin-api-bankconnection.ts
@@ -38,34 +38,25 @@ export async function runLibeufinApiBankconnectionTest(t: 
GlobalTestState) {
   await nexus.start();
   await nexus.pingUntilAvailable();
 
-  await LibeufinNexusApi.createUser(
-    nexus,
-    {
-      username: "one",
-      password: "testing-the-bankconnection-api",
-    }
-  );
+  await LibeufinNexusApi.createUser(nexus, {
+    username: "one",
+    password: "testing-the-bankconnection-api",
+  });
 
-  await LibeufinNexusApi.createEbicsBankConnection(
-    nexus,
-    {
-      name: "bankconnection-api-test-connection",
-      ebicsURL: "http://localhost:5012/ebicsweb";,
-      hostID: "mock",
-      userID: "mock",
-      partnerID: "mock",
-    }
-  );
+  await LibeufinNexusApi.createEbicsBankConnection(nexus, {
+    name: "bankconnection-api-test-connection",
+    ebicsURL: "http://localhost:5012/ebicsweb";,
+    hostID: "mock",
+    userID: "mock",
+    partnerID: "mock",
+  });
 
   let connections = await LibeufinNexusApi.getAllConnections(nexus);
   t.assertTrue(connections.data["bankConnections"].length == 1);
 
-  await LibeufinNexusApi.deleteBankConnection(
-    nexus,
-    {
-      bankConnectionId: "bankconnection-api-test-connection",
-    }
-  );
+  await LibeufinNexusApi.deleteBankConnection(nexus, {
+    bankConnectionId: "bankconnection-api-test-connection",
+  });
   connections = await LibeufinNexusApi.getAllConnections(nexus);
   t.assertTrue(connections.data["bankConnections"].length == 0);
 }
diff --git 
a/packages/taler-wallet-cli/src/integrationtests/test-libeufin-api-facade.ts 
b/packages/taler-wallet-cli/src/integrationtests/test-libeufin-api-facade.ts
index 85a6b7bd..edbbbca0 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-libeufin-api-facade.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-libeufin-api-facade.ts
@@ -43,15 +43,19 @@ export async function runLibeufinApiFacadeTest(t: 
GlobalTestState) {
    * Launch Sandbox and Nexus.
    */
   const libeufinServices = await launchLibeufinServices(
-    t, [user01nexus], [user01sandbox],
+    t,
+    [user01nexus],
+    [user01sandbox],
+  );
+  let resp = await LibeufinNexusApi.getAllFacades(
+    libeufinServices.libeufinNexus,
   );
-  let resp = await 
LibeufinNexusApi.getAllFacades(libeufinServices.libeufinNexus);
   // check that original facade shows up.
   t.assertTrue(resp.data["facades"][0]["name"] == user01nexus.twgReq["name"]);
   // delete it.
   resp = await LibeufinNexusApi.deleteFacade(
     libeufinServices.libeufinNexus,
-    user01nexus.twgReq["name"]
+    user01nexus.twgReq["name"],
   );
   // check that no facades show up.
   t.assertTrue(!resp.data.hasOwnProperty("facades"));
diff --git 
a/packages/taler-wallet-cli/src/integrationtests/test-libeufin-api-permissions.ts
 
b/packages/taler-wallet-cli/src/integrationtests/test-libeufin-api-permissions.ts
index 85385b75..0090819e 100644
--- 
a/packages/taler-wallet-cli/src/integrationtests/test-libeufin-api-permissions.ts
+++ 
b/packages/taler-wallet-cli/src/integrationtests/test-libeufin-api-permissions.ts
@@ -51,8 +51,8 @@ export async function runLibeufinApiPermissionsTest(t: 
GlobalTestState) {
   let transferPermission = await LibeufinNexusApi.getAllPermissions(nexus);
   let element = transferPermission.data["permissions"].pop();
   t.assertTrue(
-    element["permissionName"] == "facade.talerWireGateway.transfer"
-      && element["subjectId"] == "username-01"
+    element["permissionName"] == "facade.talerWireGateway.transfer" &&
+      element["subjectId"] == "username-01",
   );
   let denyTransfer = user01nexus.twgTransferPermission;
 
diff --git 
a/packages/taler-wallet-cli/src/integrationtests/test-libeufin-api-scheduling.ts
 
b/packages/taler-wallet-cli/src/integrationtests/test-libeufin-api-scheduling.ts
index 13c9e92a..d543bc4a 100644
--- 
a/packages/taler-wallet-cli/src/integrationtests/test-libeufin-api-scheduling.ts
+++ 
b/packages/taler-wallet-cli/src/integrationtests/test-libeufin-api-scheduling.ts
@@ -40,7 +40,6 @@ export async function runLibeufinApiSchedulingTest(t: 
GlobalTestState) {
   await nexus.start();
   await nexus.pingUntilAvailable();
 
-
   const user01nexus = new NexusUserBundle(
     "01",
     "http://localhost:5010/ebicsweb";,
@@ -54,13 +53,25 @@ export async function runLibeufinApiSchedulingTest(t: 
GlobalTestState) {
     params: {
       level: "all",
       rangeType: "all",
-    }
+    },
   });
-  let resp = await LibeufinNexusApi.getTasks(nexus, 
user01nexus.localAccountName, "test-task");
+  let resp = await LibeufinNexusApi.getTasks(
+    nexus,
+    user01nexus.localAccountName,
+    "test-task",
+  );
   t.assertTrue(resp.data["taskName"] == "test-task");
-  await LibeufinNexusApi.deleteTask(nexus, user01nexus.localAccountName, 
"test-task");
+  await LibeufinNexusApi.deleteTask(
+    nexus,
+    user01nexus.localAccountName,
+    "test-task",
+  );
   try {
-    await LibeufinNexusApi.getTasks(nexus, user01nexus.localAccountName, 
"test-task");
+    await LibeufinNexusApi.getTasks(
+      nexus,
+      user01nexus.localAccountName,
+      "test-task",
+    );
   } catch (err) {
     t.assertTrue(err.response.status == 404);
   }
@@ -72,11 +83,23 @@ export async function runLibeufinApiSchedulingTest(t: 
GlobalTestState) {
     type: "submit",
     params: {},
   });
-  resp = await LibeufinNexusApi.getTasks(nexus, user01nexus.localAccountName, 
"test-task");
+  resp = await LibeufinNexusApi.getTasks(
+    nexus,
+    user01nexus.localAccountName,
+    "test-task",
+  );
   t.assertTrue(resp.data["taskName"] == "test-task");
-  await LibeufinNexusApi.deleteTask(nexus, user01nexus.localAccountName, 
"test-task");
+  await LibeufinNexusApi.deleteTask(
+    nexus,
+    user01nexus.localAccountName,
+    "test-task",
+  );
   try {
-    await LibeufinNexusApi.getTasks(nexus, user01nexus.localAccountName, 
"test-task");
+    await LibeufinNexusApi.getTasks(
+      nexus,
+      user01nexus.localAccountName,
+      "test-task",
+    );
   } catch (err) {
     t.assertTrue(err.response.status == 404);
   }
diff --git 
a/packages/taler-wallet-cli/src/integrationtests/test-libeufin-api-users.ts 
b/packages/taler-wallet-cli/src/integrationtests/test-libeufin-api-users.ts
index eb41bfe7..d4d451ed 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-libeufin-api-users.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-libeufin-api-users.ts
@@ -35,13 +35,10 @@ export async function runLibeufinApiUsersTest(t: 
GlobalTestState) {
   await nexus.start();
   await nexus.pingUntilAvailable();
 
-  await LibeufinNexusApi.createUser(
-    nexus,
-    {
-      username: "one",
-      password: "will-be-changed",
-    }
-  );
+  await LibeufinNexusApi.createUser(nexus, {
+    username: "one",
+    password: "will-be-changed",
+  });
 
   await LibeufinNexusApi.changePassword(
     nexus,
@@ -52,19 +49,16 @@ export async function runLibeufinApiUsersTest(t: 
GlobalTestState) {
       auth: {
         username: "one",
         password: "will-be-changed",
-      }
+      },
     },
   );
 
-  let resp = await LibeufinNexusApi.getUser(
-    nexus,
-    {
-      auth: {
-        username: "one",
-        password: "got-changed",
-      }
-    }
-  );
+  let resp = await LibeufinNexusApi.getUser(nexus, {
+    auth: {
+      username: "one",
+      password: "got-changed",
+    },
+  });
   console.log(resp.data);
   t.assertTrue(resp.data["username"] == "one" && !resp.data["superuser"]);
 }
diff --git 
a/packages/taler-wallet-cli/src/integrationtests/test-libeufin-refund-multiple-users.ts
 
b/packages/taler-wallet-cli/src/integrationtests/test-libeufin-refund-multiple-users.ts
index 9946e02d..3a228866 100644
--- 
a/packages/taler-wallet-cli/src/integrationtests/test-libeufin-refund-multiple-users.ts
+++ 
b/packages/taler-wallet-cli/src/integrationtests/test-libeufin-refund-multiple-users.ts
@@ -90,15 +90,15 @@ export async function runLibeufinRefundMultipleUsersTest(t: 
GlobalTestState) {
     libeufinServices.libeufinNexus,
     user02nexus.localAccountName,
     "1", // so far the only one that can exist.
-  ); 
+  );
 
   // Counterpart checks whether the reimbursement shows up.
   let history = await LibeufinSandboxApi.getAccountTransactions(
     libeufinServices.libeufinSandbox,
-    user01sandbox.ebicsBankAccount["label"]
+    user01sandbox.ebicsBankAccount["label"],
   );
 
-  t.assertTrue(history["payments"].length == 1)
+  t.assertTrue(history["payments"].length == 1);
 }
 
 runLibeufinRefundMultipleUsersTest.suites = ["libeufin"];
diff --git 
a/packages/taler-wallet-cli/src/integrationtests/test-libeufin-refund.ts 
b/packages/taler-wallet-cli/src/integrationtests/test-libeufin-refund.ts
index f4288cb5..4cd943f5 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-libeufin-refund.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-libeufin-refund.ts
@@ -80,14 +80,14 @@ export async function runLibeufinRefundTest(t: 
GlobalTestState) {
     libeufinServices.libeufinNexus,
     user02nexus.localAccountName,
     "1", // so far the only one that can exist.
-  ); 
+  );
 
   // Counterpart checks whether the reimbursement shows up.
   let history = await LibeufinSandboxApi.getAccountTransactions(
     libeufinServices.libeufinSandbox,
-    user01sandbox.ebicsBankAccount["label"]
+    user01sandbox.ebicsBankAccount["label"],
   );
 
-  t.assertTrue(history["payments"].length == 1)
+  t.assertTrue(history["payments"].length == 1);
 }
 runLibeufinRefundTest.suites = ["libeufin"];
diff --git 
a/packages/taler-wallet-cli/src/integrationtests/test-merchant-exchange-confusion.ts
 
b/packages/taler-wallet-cli/src/integrationtests/test-merchant-exchange-confusion.ts
index deb0026f..3336f0c5 100644
--- 
a/packages/taler-wallet-cli/src/integrationtests/test-merchant-exchange-confusion.ts
+++ 
b/packages/taler-wallet-cli/src/integrationtests/test-merchant-exchange-confusion.ts
@@ -109,7 +109,6 @@ export async function 
createConfusedMerchantTestkudosEnvironment(
     paytoUris: [`payto://x-taler-bank/merchant-default`],
   });
 
-
   await merchant.addInstance({
     id: "minst1",
     name: "minst1",
diff --git 
a/packages/taler-wallet-cli/src/integrationtests/test-merchant-refund-api.ts 
b/packages/taler-wallet-cli/src/integrationtests/test-merchant-refund-api.ts
index 25c2ea36..32bc310d 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-merchant-refund-api.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-merchant-refund-api.ts
@@ -30,10 +30,7 @@ import {
   withdrawViaBank,
   SimpleTestEnvironment,
 } from "./helpers";
-import {
-  durationFromSpec,
-  PreparePayResultType,
-} from "@gnu-taler/taler-util";
+import { durationFromSpec, PreparePayResultType } from "@gnu-taler/taler-util";
 import axios from "axios";
 import { URL } from "@gnu-taler/taler-wallet-core";
 
diff --git a/packages/taler-wallet-cli/src/integrationtests/test-pay-abort.ts 
b/packages/taler-wallet-cli/src/integrationtests/test-pay-abort.ts
index 4d0ea489..10f9904f 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-pay-abort.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-pay-abort.ts
@@ -23,9 +23,7 @@
  * Imports.
  */
 import { PreparePayResultType, TalerErrorCode } from "@gnu-taler/taler-util";
-import {
-  URL,
-} from "@gnu-taler/taler-wallet-core";
+import { URL } from "@gnu-taler/taler-wallet-core";
 import {
   FaultInjectionRequestContext,
   FaultInjectionResponseContext,
@@ -152,4 +150,4 @@ export async function runPayAbortTest(t: GlobalTestState) {
   t.assertDeepEqual(txTypes, ["withdrawal", "payment", "refund"]);
 }
 
-runPayAbortTest.suites = ["wallet"];
\ No newline at end of file
+runPayAbortTest.suites = ["wallet"];
diff --git 
a/packages/taler-wallet-cli/src/integrationtests/test-paywall-flow.ts 
b/packages/taler-wallet-cli/src/integrationtests/test-paywall-flow.ts
index ba4ef291..865fd77d 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-paywall-flow.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-paywall-flow.ts
@@ -232,4 +232,4 @@ export async function runPaywallFlowTest(t: 
GlobalTestState) {
   t.assertTrue(pubUnpaidStatus.already_paid_order_id === firstOrderId);
 }
 
-runPaywallFlowTest.suites = ["wallet"];
\ No newline at end of file
+runPaywallFlowTest.suites = ["wallet"];
diff --git a/packages/taler-wallet-cli/src/integrationtests/test-refund-auto.ts 
b/packages/taler-wallet-cli/src/integrationtests/test-refund-auto.ts
index 36bdfac2..a78b0ecc 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-refund-auto.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-refund-auto.ts
@@ -99,4 +99,4 @@ export async function runRefundAutoTest(t: GlobalTestState) {
   await t.shutdown();
 }
 
-runRefundAutoTest.suites = ["wallet"];
\ No newline at end of file
+runRefundAutoTest.suites = ["wallet"];
diff --git a/packages/taler-wallet-cli/src/integrationtests/test-refund-gone.ts 
b/packages/taler-wallet-cli/src/integrationtests/test-refund-gone.ts
index abaa10f7..c2640bb2 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-refund-gone.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-refund-gone.ts
@@ -126,4 +126,4 @@ export async function runRefundGoneTest(t: GlobalTestState) 
{
   await t.shutdown();
 }
 
-runRefundGoneTest.suites = ["wallet"];
\ No newline at end of file
+runRefundGoneTest.suites = ["wallet"];
diff --git 
a/packages/taler-wallet-cli/src/integrationtests/test-refund-incremental.ts 
b/packages/taler-wallet-cli/src/integrationtests/test-refund-incremental.ts
index 4c046cbe..55f8ad6c 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-refund-incremental.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-refund-incremental.ts
@@ -189,4 +189,4 @@ export async function runRefundIncrementalTest(t: 
GlobalTestState) {
   await t.shutdown();
 }
 
-runRefundIncrementalTest.suites = ["wallet"];
\ No newline at end of file
+runRefundIncrementalTest.suites = ["wallet"];
diff --git a/packages/taler-wallet-cli/src/integrationtests/testrunner.ts 
b/packages/taler-wallet-cli/src/integrationtests/testrunner.ts
index 4898a9ef..c50838f0 100644
--- a/packages/taler-wallet-cli/src/integrationtests/testrunner.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/testrunner.ts
@@ -135,7 +135,7 @@ const allTests: TestMainFunction[] = [
 export interface TestRunSpec {
   includePattern?: string;
   suiteSpec?: string;
-  dryRun?: boolean,
+  dryRun?: boolean;
 }
 
 export interface TestInfo {
diff --git 
a/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts 
b/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts
index d8c02cb1..1969dee9 100644
--- a/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts
+++ b/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts
@@ -222,7 +222,7 @@ export class CryptoImplementation {
       .put(decodeCrock(req.merchantPub))
       .put(decodeCrock(req.coinPub))
       .build();
-      return encodeCrock(eddsaSign(p, decodeCrock(req.merchantPriv)));
+    return encodeCrock(eddsaSign(p, decodeCrock(req.merchantPriv)));
   }
 
   /**
diff --git a/packages/taler-wallet-core/src/crypto/workers/nodeThreadWorker.ts 
b/packages/taler-wallet-core/src/crypto/workers/nodeThreadWorker.ts
index 37b93abe..453a4694 100644
--- a/packages/taler-wallet-core/src/crypto/workers/nodeThreadWorker.ts
+++ b/packages/taler-wallet-core/src/crypto/workers/nodeThreadWorker.ts
@@ -92,8 +92,10 @@ export function handleWorkerMessage(msg: any): void {
     try {
       const result = (impl as any)[operation](...args);
       // eslint-disable-next-line @typescript-eslint/no-var-requires
-      const _r = "require"
-      const worker_threads: typeof import("worker_threads") = 
module[_r]("worker_threads");
+      const _r = "require";
+      const worker_threads: typeof import("worker_threads") = module[_r](
+        "worker_threads",
+      );
       // const worker_threads = require("worker_threads");
 
       const p = worker_threads.parentPort;
@@ -149,7 +151,7 @@ class NodeThreadCryptoWorker implements CryptoWorker {
 
   constructor() {
     // eslint-disable-next-line @typescript-eslint/no-var-requires
-    const _r = "require"
+    const _r = "require";
     const worker_threads = module[_r]("worker_threads");
 
     logger.trace("starting node crypto worker");
diff --git a/packages/taler-wallet-core/src/db.ts 
b/packages/taler-wallet-core/src/db.ts
index 6de23a79..2754fd34 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -1,16 +1,19 @@
 import {
   openDatabase,
-  Database,
-  Store,
-  Index,
-  AnyStoreMap,
+  describeStore,
+  describeContents,
+  describeIndex,
+  DbAccess,
+  StoreDescriptor,
+  StoreWithIndexes,
+  IndexDescriptor,
 } from "./util/query";
 import {
   IDBFactory,
   IDBDatabase,
   IDBObjectStore,
   IDBTransaction,
-  IDBKeyPath,
+  IDBObjectStoreParameters,
 } from "@gnu-taler/idb-bridge";
 import { Logger } from "@gnu-taler/taler-util";
 import {
@@ -55,7 +58,7 @@ export const WALLET_DB_MINOR_VERSION = 1;
 const logger = new Logger("db.ts");
 
 function upgradeFromStoreMap(
-  storeMap: AnyStoreMap,
+  storeMap: any,
   db: IDBDatabase,
   oldVersion: number,
   newVersion: number,
@@ -63,15 +66,17 @@ function upgradeFromStoreMap(
 ): void {
   if (oldVersion === 0) {
     for (const n in storeMap) {
-      if ((storeMap as any)[n] instanceof Store) {
-        const si: Store<string, any> = (storeMap as any)[n];
-        const s = db.createObjectStore(si.name, si.storeParams);
-        for (const indexName in si as any) {
-          if ((si as any)[indexName] instanceof Index) {
-            const ii: Index<string, string, any, any> = (si as any)[indexName];
-            s.createIndex(ii.indexName, ii.keyPath, ii.options);
-          }
-        }
+      const swi: StoreWithIndexes<StoreDescriptor<unknown>, any> = storeMap[n];
+      const storeDesc: StoreDescriptor<unknown> = swi.store;
+      const s = db.createObjectStore(storeDesc.name, {
+        autoIncrement: storeDesc.autoIncrement,
+        keyPath: storeDesc.keyPath,
+      });
+      for (const indexName in swi.indexMap as any) {
+        const indexDesc: IndexDescriptor = swi.indexMap[indexName];
+        s.createIndex(indexDesc.name, indexDesc.keyPath, {
+          multiEntry: indexDesc.multiEntry,
+        });
       }
     }
     return;
@@ -80,30 +85,7 @@ function upgradeFromStoreMap(
     return;
   }
   logger.info(`upgrading database from ${oldVersion} to ${newVersion}`);
-  for (const n in Stores) {
-    if ((Stores as any)[n] instanceof Store) {
-      const si: Store<string, any> = (Stores as any)[n];
-      let s: IDBObjectStore;
-      const storeVersionAdded = si.storeParams?.versionAdded ?? 1;
-      if (storeVersionAdded > oldVersion) {
-        s = db.createObjectStore(si.name, si.storeParams);
-      } else {
-        s = upgradeTransaction.objectStore(si.name);
-      }
-      for (const indexName in si as any) {
-        if ((si as any)[indexName] instanceof Index) {
-          const ii: Index<string, string, any, any> = (si as any)[indexName];
-          const indexVersionAdded = ii.options?.versionAdded ?? 0;
-          if (
-            indexVersionAdded > oldVersion ||
-            storeVersionAdded > oldVersion
-          ) {
-            s.createIndex(ii.indexName, ii.keyPath, ii.options);
-          }
-        }
-      }
-    }
-  }
+  throw Error("upgrade not supported");
 }
 
 function onTalerDbUpgradeNeeded(
@@ -112,7 +94,13 @@ function onTalerDbUpgradeNeeded(
   newVersion: number,
   upgradeTransaction: IDBTransaction,
 ) {
-  upgradeFromStoreMap(Stores, db, oldVersion, newVersion, upgradeTransaction);
+  upgradeFromStoreMap(
+    WalletStoresV1,
+    db,
+    oldVersion,
+    newVersion,
+    upgradeTransaction,
+  );
 }
 
 function onMetaDbUpgradeNeeded(
@@ -122,7 +110,7 @@ function onMetaDbUpgradeNeeded(
   upgradeTransaction: IDBTransaction,
 ) {
   upgradeFromStoreMap(
-    MetaStores,
+    walletMetadataStore,
     db,
     oldVersion,
     newVersion,
@@ -137,7 +125,7 @@ function onMetaDbUpgradeNeeded(
 export async function openTalerDatabase(
   idbFactory: IDBFactory,
   onVersionChange: () => void,
-): Promise<Database<typeof Stores>> {
+): Promise<DbAccess<typeof WalletStoresV1>> {
   const metaDbHandle = await openDatabase(
     idbFactory,
     TALER_META_DB_NAME,
@@ -146,23 +134,24 @@ export async function openTalerDatabase(
     onMetaDbUpgradeNeeded,
   );
 
-  const metaDb = new Database(metaDbHandle, MetaStores);
+  const metaDb = new DbAccess(metaDbHandle, walletMetadataStore);
   let currentMainVersion: string | undefined;
-  await metaDb.runWithWriteTransaction([MetaStores.metaConfig], async (tx) => {
-    const dbVersionRecord = await tx.get(
-      MetaStores.metaConfig,
-      CURRENT_DB_CONFIG_KEY,
-    );
-    if (!dbVersionRecord) {
-      currentMainVersion = TALER_DB_NAME;
-      await tx.put(MetaStores.metaConfig, {
-        key: CURRENT_DB_CONFIG_KEY,
-        value: TALER_DB_NAME,
-      });
-    } else {
-      currentMainVersion = dbVersionRecord.value;
-    }
-  });
+  await metaDb
+    .mktx((x) => ({
+      metaConfig: x.metaConfig,
+    }))
+    .runReadWrite(async (tx) => {
+      const dbVersionRecord = await tx.metaConfig.get(CURRENT_DB_CONFIG_KEY);
+      if (!dbVersionRecord) {
+        currentMainVersion = TALER_DB_NAME;
+        await tx.metaConfig.put({
+          key: CURRENT_DB_CONFIG_KEY,
+          value: TALER_DB_NAME,
+        });
+      } else {
+        currentMainVersion = dbVersionRecord.value;
+      }
+    });
 
   if (currentMainVersion !== TALER_DB_NAME) {
     // In the future, the migration logic will be implemented here.
@@ -177,11 +166,11 @@ export async function openTalerDatabase(
     onTalerDbUpgradeNeeded,
   );
 
-  return new Database(mainDbHandle, Stores);
+  return new DbAccess(mainDbHandle, WalletStoresV1);
 }
 
-export function deleteTalerDatabase(idbFactory: IDBFactory): Promise<void> {
-  return Database.deleteDatabase(idbFactory, TALER_DB_NAME);
+export function deleteTalerDatabase(idbFactory: IDBFactory): void {
+  idbFactory.deleteDatabase(TALER_DB_NAME);
 }
 
 export enum ReserveRecordStatus {
@@ -634,7 +623,7 @@ export interface ExchangeRecord {
 
   /**
    * Status of updating the info about the exchange.
-   * 
+   *
    * FIXME:  Adapt this to recent changes regarding how
    * updating exchange details works.
    */
@@ -1683,289 +1672,167 @@ export interface TombstoneRecord {
   id: string;
 }
 
-class ExchangesStore extends Store<"exchanges", ExchangeRecord> {
-  constructor() {
-    super("exchanges", { keyPath: "baseUrl" });
-  }
-}
-
-class ExchangeDetailsStore extends Store<
-  "exchangeDetails",
-  ExchangeDetailsRecord
-> {
-  constructor() {
-    super("exchangeDetails", {
+export const WalletStoresV1 = {
+  coins: describeStore(
+    describeContents<CoinRecord>("coins", {
+      keyPath: "coinPub",
+    }),
+    {
+      byBaseUrl: describeIndex("byBaseUrl", "exchangeBaseUrl"),
+      byDenomPubHash: describeIndex("byDenomPubHash", "denomPubHash"),
+      byCoinEvHash: describeIndex("byCoinEvHash", "coinEvHash"),
+    },
+  ),
+  config: describeStore(
+    describeContents<ConfigRecord<any>>("config", { keyPath: "key" }),
+    {},
+  ),
+  auditorTrust: describeStore(
+    describeContents<AuditorTrustRecord>("auditorTrust", {
+      keyPath: ["currency", "auditorBaseUrl"],
+    }),
+    {
+      byAuditorPub: describeIndex("byAuditorPub", "auditorPub"),
+      byUid: describeIndex("byUid", "uids", {
+        multiEntry: true,
+      }),
+    },
+  ),
+  exchangeTrust: describeStore(
+    describeContents<ExchangeTrustRecord>("exchangeTrust", {
+      keyPath: ["currency", "exchangeBaseUrl"],
+    }),
+    {
+      byExchangeMasterPub: describeIndex(
+        "byExchangeMasterPub",
+        "exchangeMasterPub",
+      ),
+    },
+  ),
+  denominations: describeStore(
+    describeContents<DenominationRecord>("denominations", {
+      keyPath: ["exchangeBaseUrl", "denomPubHash"],
+    }),
+    {
+      byExchangeBaseUrl: describeIndex("byExchangeBaseUrl", "exchangeBaseUrl"),
+    },
+  ),
+  exchanges: describeStore(
+    describeContents<ExchangeRecord>("exchanges", {
+      keyPath: "baseUrl",
+    }),
+    {},
+  ),
+  exchangeDetails: describeStore(
+    describeContents<ExchangeDetailsRecord>("exchangeDetails", {
       keyPath: ["exchangeBaseUrl", "currency", "masterPublicKey"],
-    });
-  }
-}
-
-class CoinsStore extends Store<"coins", CoinRecord> {
-  constructor() {
-    super("coins", { keyPath: "coinPub" });
-  }
-
-  exchangeBaseUrlIndex = new Index<
-    "coins",
-    "exchangeBaseUrl",
-    string,
-    CoinRecord
-  >(this, "exchangeBaseUrl", "exchangeBaseUrl");
-
-  denomPubHashIndex = new Index<
-    "coins",
-    "denomPubHashIndex",
-    string,
-    CoinRecord
-  >(this, "denomPubHashIndex", "denomPubHash");
-
-  coinEvHashIndex = new Index<"coins", "coinEvHashIndex", string, CoinRecord>(
-    this,
-    "coinEvHashIndex",
-    "coinEvHash",
-  );
-}
-
-class ProposalsStore extends Store<"proposals", ProposalRecord> {
-  constructor() {
-    super("proposals", { keyPath: "proposalId" });
-  }
-  urlAndOrderIdIndex = new Index<
-    "proposals",
-    "urlIndex",
-    string,
-    ProposalRecord
-  >(this, "urlIndex", ["merchantBaseUrl", "orderId"]);
-}
-
-class PurchasesStore extends Store<"purchases", PurchaseRecord> {
-  constructor() {
-    super("purchases", { keyPath: "proposalId" });
-  }
-
-  fulfillmentUrlIndex = new Index<
-    "purchases",
-    "fulfillmentUrlIndex",
-    string,
-    PurchaseRecord
-  >(this, "fulfillmentUrlIndex", "download.contractData.fulfillmentUrl");
-
-  orderIdIndex = new Index<"purchases", "orderIdIndex", string, 
PurchaseRecord>(
-    this,
-    "orderIdIndex",
-    ["download.contractData.merchantBaseUrl", "download.contractData.orderId"],
-  );
-}
-
-class DenominationsStore extends Store<"denominations", DenominationRecord> {
-  constructor() {
-    // cast needed because of bug in type annotations
-    super("denominations", {
-      keyPath: (["exchangeBaseUrl", "denomPubHash"] as any) as IDBKeyPath,
-    });
-  }
-  exchangeBaseUrlIndex = new Index<
-    "denominations",
-    "exchangeBaseUrlIndex",
-    string,
-    DenominationRecord
-  >(this, "exchangeBaseUrlIndex", "exchangeBaseUrl");
-}
-
-class AuditorTrustStore extends Store<"auditorTrust", AuditorTrustRecord> {
-  constructor() {
-    super("auditorTrust", {
-      keyPath: ["currency", "auditorBaseUrl", "auditorPub"],
-    });
-  }
-  auditorPubIndex = new Index<
-    "auditorTrust",
-    "auditorPubIndex",
-    string,
-    AuditorTrustRecord
-  >(this, "auditorPubIndex", "auditorPub");
-  uidIndex = new Index<"auditorTrust", "uidIndex", string, AuditorTrustRecord>(
-    this,
-    "uidIndex",
-    "uids",
-    { multiEntry: true },
-  );
-}
-
-class ExchangeTrustStore extends Store<"exchangeTrust", ExchangeTrustRecord> {
-  constructor() {
-    super("exchangeTrust", {
-      keyPath: ["currency", "exchangeBaseUrl", "exchangeMasterPub"],
-    });
-  }
-  exchangeMasterPubIndex = new Index<
-    "exchangeTrust",
-    "exchangeMasterPubIndex",
-    string,
-    ExchangeTrustRecord
-  >(this, "exchangeMasterPubIndex", "exchangeMasterPub");
-  uidIndex = new Index<
-    "exchangeTrust",
-    "uidIndex",
-    string,
-    ExchangeTrustRecord
-  >(this, "uidIndex", "uids", { multiEntry: true });
-}
-
-class ConfigStore extends Store<"config", ConfigRecord<any>> {
-  constructor() {
-    super("config", { keyPath: "key" });
-  }
-}
-
-class ReservesStore extends Store<"reserves", ReserveRecord> {
-  constructor() {
-    super("reserves", { keyPath: "reservePub" });
-  }
-  byInitialWithdrawalGroupId = new Index<
-    "reserves",
-    "initialWithdrawalGroupIdIndex",
-    string,
-    ReserveRecord
-  >(this, "initialWithdrawalGroupIdIndex", "initialWithdrawalGroupId");
-}
-
-class TipsStore extends Store<"tips", TipRecord> {
-  constructor() {
-    super("tips", { keyPath: "walletTipId" });
-  }
-  // Added in version 2
-  byMerchantTipIdAndBaseUrl = new Index<
-    "tips",
-    "tipsByMerchantTipIdAndOriginIndex",
-    [string, string],
-    TipRecord
-  >(this, "tipsByMerchantTipIdAndOriginIndex", [
-    "merchantTipId",
-    "merchantBaseUrl",
-  ]);
-}
-
-class WithdrawalGroupsStore extends Store<
-  "withdrawals",
-  WithdrawalGroupRecord
-> {
-  constructor() {
-    super("withdrawals", { keyPath: "withdrawalGroupId" });
-  }
-  byReservePub = new Index<
-    "withdrawals",
-    "withdrawalsByReserveIndex",
-    string,
-    WithdrawalGroupRecord
-  >(this, "withdrawalsByReserveIndex", "reservePub");
-}
-
-class PlanchetsStore extends Store<"planchets", PlanchetRecord> {
-  constructor() {
-    super("planchets", { keyPath: "coinPub" });
-  }
-  byGroupAndIndex = new Index<
-    "planchets",
-    "withdrawalGroupAndCoinIdxIndex",
-    string,
-    PlanchetRecord
-  >(this, "withdrawalGroupAndCoinIdxIndex", ["withdrawalGroupId", "coinIdx"]);
-  byGroup = new Index<
-    "planchets",
-    "withdrawalGroupIndex",
-    string,
-    PlanchetRecord
-  >(this, "withdrawalGroupIndex", "withdrawalGroupId");
-
-  coinEvHashIndex = new Index<
-    "planchets",
-    "coinEvHashIndex",
-    string,
-    PlanchetRecord
-  >(this, "coinEvHashIndex", "coinEvHash");
-}
-
-/**
- * This store is effectively a materialized index for
- * reserve records that are for a bank-integrated withdrawal.
- */
-class BankWithdrawUrisStore extends Store<
-  "bankWithdrawUris",
-  BankWithdrawUriRecord
-> {
-  constructor() {
-    super("bankWithdrawUris", { keyPath: "talerWithdrawUri" });
-  }
-}
-
-/**
- */
-class BackupProvidersStore extends Store<
-  "backupProviders",
-  BackupProviderRecord
-> {
-  constructor() {
-    super("backupProviders", { keyPath: "baseUrl" });
-  }
-}
-
-class DepositGroupsStore extends Store<"depositGroups", DepositGroupRecord> {
-  constructor() {
-    super("depositGroups", { keyPath: "depositGroupId" });
-  }
-}
-
-class TombstonesStore extends Store<"tombstones", TombstoneRecord> {
-  constructor() {
-    super("tombstones", { keyPath: "id" });
-  }
-}
-
-/**
- * The stores and indices for the wallet database.
- */
-export const Stores = {
-  coins: new CoinsStore(),
-  config: new ConfigStore(),
-  auditorTrustStore: new AuditorTrustStore(),
-  exchangeTrustStore: new ExchangeTrustStore(),
-  denominations: new DenominationsStore(),
-  exchanges: new ExchangesStore(),
-  exchangeDetails: new ExchangeDetailsStore(),
-  proposals: new ProposalsStore(),
-  refreshGroups: new Store<"refreshGroups", RefreshGroupRecord>(
-    "refreshGroups",
+    }),
+    {},
+  ),
+  proposals: describeStore(
+    describeContents<ProposalRecord>("proposals", { keyPath: "proposalId" }),
     {
+      byUrlAndOrderId: describeIndex("byUrlAndOrderId", [
+        "merchantBaseUrl",
+        "orderId",
+      ]),
+    },
+  ),
+  refreshGroups: describeStore(
+    describeContents<RefreshGroupRecord>("refreshGroups", {
       keyPath: "refreshGroupId",
+    }),
+    {},
+  ),
+  recoupGroups: describeStore(
+    describeContents<RecoupGroupRecord>("recoupGroups", {
+      keyPath: "recoupGroupId",
+    }),
+    {},
+  ),
+  reserves: describeStore(
+    describeContents<ReserveRecord>("reserves", { keyPath: "reservePub" }),
+    {
+      byInitialWithdrawalGroupId: describeIndex(
+        "byInitialWithdrawalGroupId",
+        "initialWithdrawalGroupId",
+      ),
     },
   ),
-  recoupGroups: new Store<"recoupGroups", RecoupGroupRecord>("recoupGroups", {
-    keyPath: "recoupGroupId",
-  }),
-  reserves: new ReservesStore(),
-  purchases: new PurchasesStore(),
-  tips: new TipsStore(),
-  withdrawalGroups: new WithdrawalGroupsStore(),
-  planchets: new PlanchetsStore(),
-  bankWithdrawUris: new BankWithdrawUrisStore(),
-  backupProviders: new BackupProvidersStore(),
-  depositGroups: new DepositGroupsStore(),
-  tombstones: new TombstonesStore(),
-  ghostDepositGroups: new Store<"ghostDepositGroups", GhostDepositGroupRecord>(
-    "ghostDepositGroups",
+  purchases: describeStore(
+    describeContents<PurchaseRecord>("purchases", { keyPath: "proposalId" }),
     {
-      keyPath: "contractTermsHash",
+      byFulfillmentUrl: describeIndex(
+        "byFulfillmentUrl",
+        "download.contractData.fulfillmentUrl",
+      ),
+      byMerchantUrlAndOrderId: describeIndex("byOrderId", [
+        "download.contractData.merchantBaseUrl",
+        "download.contractData.orderId",
+      ]),
     },
   ),
+  tips: describeStore(
+    describeContents<TipRecord>("tips", { keyPath: "walletTipId" }),
+    {
+      byMerchantTipIdAndBaseUrl: describeIndex("byMerchantTipIdAndBaseUrl", [
+        "merchantTipId",
+        "merchantBaseUrl",
+      ]),
+    },
+  ),
+  withdrawalGroups: describeStore(
+    describeContents<WithdrawalGroupRecord>("withdrawalGroups", {
+      keyPath: "withdrawalGroupId",
+    }),
+    {
+      byReservePub: describeIndex("byReservePub", "reservePub"),
+    },
+  ),
+  planchets: describeStore(
+    describeContents<PlanchetRecord>("planchets", { keyPath: "coinPub" }),
+    {
+      byGroupAndIndex: describeIndex("byGroupAndIndex", [
+        "withdrawalGroupId",
+        "coinIdx",
+      ]),
+      byGroup: describeIndex("byGroup", "withdrawalGroupId"),
+      byCoinEvHash: describeIndex("byCoinEv", "coinEvHash"),
+    },
+  ),
+  bankWithdrawUris: describeStore(
+    describeContents<BankWithdrawUriRecord>("bankWithdrawUris", {
+      keyPath: "talerWithdrawUri",
+    }),
+    {},
+  ),
+  backupProviders: describeStore(
+    describeContents<BackupProviderRecord>("backupProviders", {
+      keyPath: "baseUrl",
+    }),
+    {},
+  ),
+  depositGroups: describeStore(
+    describeContents<DepositGroupRecord>("depositGroups", {
+      keyPath: "depositGroupId",
+    }),
+    {},
+  ),
+  tombstones: describeStore(
+    describeContents<TombstoneRecord>("tombstones", { keyPath: "id" }),
+    {},
+  ),
+  ghostDepositGroups: describeStore(
+    describeContents<GhostDepositGroupRecord>("ghostDepositGroups", {
+      keyPath: "contractTermsHash",
+    }),
+    {},
+  ),
 };
 
-export class MetaConfigStore extends Store<"metaConfig", ConfigRecord<any>> {
-  constructor() {
-    super("metaConfig", { keyPath: "key" });
-  }
-}
-
-export const MetaStores = {
-  metaConfig: new MetaConfigStore(),
+export const walletMetadataStore = {
+  metaConfig: describeStore(
+    describeContents<ConfigRecord<any>>("metaConfig", { keyPath: "key" }),
+    {},
+  ),
 };
diff --git a/packages/taler-wallet-core/src/headless/helpers.ts 
b/packages/taler-wallet-core/src/headless/helpers.ts
index 73196492..7b918d5d 100644
--- a/packages/taler-wallet-core/src/headless/helpers.ts
+++ b/packages/taler-wallet-core/src/headless/helpers.ts
@@ -41,18 +41,18 @@ const logger = new Logger("headless/helpers.ts");
 
 const nodejs_fs = (function () {
   let fs: typeof import("fs");
-  return function() {
+  return function () {
     if (!fs) {
       /**
        * need to use an expression when doing a require if we want
        * webpack not to find out about the requirement
        */
-      const _r = "require"
-      fs = module[_r]("fs") 
+      const _r = "require";
+      fs = module[_r]("fs");
     }
-    return fs
-  }
-})()
+    return fs;
+  };
+})();
 
 export interface DefaultNodeWalletArgs {
   /**
@@ -123,9 +123,13 @@ export async function getDefaultNodeWallet(
       }
       const tmpPath = `${args.persistentStoragePath}-${makeId(5)}.tmp`;
       const dbContent = myBackend.exportDump();
-      nodejs_fs().writeFileSync(tmpPath, JSON.stringify(dbContent, undefined, 
2), {
-        encoding: "utf-8",
-      });
+      nodejs_fs().writeFileSync(
+        tmpPath,
+        JSON.stringify(dbContent, undefined, 2),
+        {
+          encoding: "utf-8",
+        },
+      );
       // Atomically move the temporary file onto the DB path.
       nodejs_fs().renameSync(tmpPath, args.persistentStoragePath);
     };
@@ -157,7 +161,7 @@ export async function getDefaultNodeWallet(
   let workerFactory;
   try {
     // Try if we have worker threads available, fails in older node versions.
-    const _r = "require"
+    const _r = "require";
     const worker_threads = module[_r]("worker_threads");
     // require("worker_threads");
     workerFactory = new NodeThreadCryptoWorkerFactory();
diff --git a/packages/taler-wallet-core/src/operations/backup/export.ts 
b/packages/taler-wallet-core/src/operations/backup/export.ts
index fa0af1b0..a6b2ff2a 100644
--- a/packages/taler-wallet-core/src/operations/backup/export.ts
+++ b/packages/taler-wallet-core/src/operations/backup/export.ts
@@ -57,7 +57,6 @@ import {
 } from "./state";
 import { Amounts, getTimestampNow } from "@gnu-taler/taler-util";
 import {
-  Stores,
   CoinSourceType,
   CoinStatus,
   RefundState,
@@ -66,29 +65,28 @@ import {
 } from "../../db.js";
 import { encodeCrock, stringToBytes, getRandomBytes } from "../../index.js";
 import { canonicalizeBaseUrl, canonicalJson } from "@gnu-taler/taler-util";
-import { getExchangeDetails } from "../exchanges.js";
 
 export async function exportBackup(
   ws: InternalWalletState,
 ): Promise<WalletBackupContentV1> {
   await provideBackupState(ws);
-  return ws.db.runWithWriteTransaction(
-    [
-      Stores.config,
-      Stores.exchanges,
-      Stores.exchangeDetails,
-      Stores.coins,
-      Stores.denominations,
-      Stores.purchases,
-      Stores.proposals,
-      Stores.refreshGroups,
-      Stores.backupProviders,
-      Stores.tips,
-      Stores.recoupGroups,
-      Stores.reserves,
-      Stores.withdrawalGroups,
-    ],
-    async (tx) => {
+  return ws.db
+    .mktx((x) => ({
+      config: x.config,
+      exchanges: x.exchanges,
+      exchangeDetails: x.exchangeDetails,
+      coins: x.coins,
+      denominations: x.denominations,
+      purchases: x.purchases,
+      proposals: x.proposals,
+      refreshGroups: x.refreshGroups,
+      backupProviders: x.backupProviders,
+      tips: x.tips,
+      recoupGroups: x.recoupGroups,
+      reserves: x.reserves,
+      withdrawalGroups: x.withdrawalGroups,
+    }))
+    .runReadWrite(async (tx) => {
       const bs = await getWalletBackupState(ws, tx);
 
       const backupExchangeDetails: BackupExchangeDetails[] = [];
@@ -108,7 +106,7 @@ export async function exportBackup(
         [reservePub: string]: BackupWithdrawalGroup[];
       } = {};
 
-      await tx.iter(Stores.withdrawalGroups).forEachAsync(async (wg) => {
+      await tx.withdrawalGroups.iter().forEachAsync(async (wg) => {
         const withdrawalGroups = (withdrawalGroupsByReserve[
           wg.reservePub
         ] ??= []);
@@ -126,7 +124,7 @@ export async function exportBackup(
         });
       });
 
-      await tx.iter(Stores.reserves).forEach((reserve) => {
+      await tx.reserves.iter().forEach((reserve) => {
         const backupReserve: BackupReserve = {
           initial_selected_denoms: reserve.initialDenomSel.selectedDenoms.map(
             (x) => ({
@@ -149,7 +147,7 @@ export async function exportBackup(
         backupReserves.push(backupReserve);
       });
 
-      await tx.iter(Stores.tips).forEach((tip) => {
+      await tx.tips.iter().forEach((tip) => {
         backupTips.push({
           exchange_base_url: tip.exchangeBaseUrl,
           merchant_base_url: tip.merchantBaseUrl,
@@ -169,7 +167,7 @@ export async function exportBackup(
         });
       });
 
-      await tx.iter(Stores.recoupGroups).forEach((recoupGroup) => {
+      await tx.recoupGroups.iter().forEach((recoupGroup) => {
         backupRecoupGroups.push({
           recoup_group_id: recoupGroup.recoupGroupId,
           timestamp_created: recoupGroup.timestampStarted,
@@ -182,7 +180,7 @@ export async function exportBackup(
         });
       });
 
-      await tx.iter(Stores.backupProviders).forEach((bp) => {
+      await tx.backupProviders.iter().forEach((bp) => {
         let terms: BackupBackupProviderTerms | undefined;
         if (bp.terms) {
           terms = {
@@ -199,7 +197,7 @@ export async function exportBackup(
         });
       });
 
-      await tx.iter(Stores.coins).forEach((coin) => {
+      await tx.coins.iter().forEach((coin) => {
         let bcs: BackupCoinSource;
         switch (coin.coinSource.type) {
           case CoinSourceType.Refresh:
@@ -236,7 +234,7 @@ export async function exportBackup(
         });
       });
 
-      await tx.iter(Stores.denominations).forEach((denom) => {
+      await tx.denominations.iter().forEach((denom) => {
         const backupDenoms = (backupDenominationsByExchange[
           denom.exchangeBaseUrl
         ] ??= []);
@@ -258,7 +256,7 @@ export async function exportBackup(
         });
       });
 
-      await tx.iter(Stores.exchanges).forEachAsync(async (ex) => {
+      await tx.exchanges.iter().forEachAsync(async (ex) => {
         const dp = ex.detailsPointer;
         if (!dp) {
           return;
@@ -271,7 +269,7 @@ export async function exportBackup(
         });
       });
 
-      await tx.iter(Stores.exchangeDetails).forEachAsync(async (ex) => {
+      await tx.exchangeDetails.iter().forEachAsync(async (ex) => {
         // Only back up permanently added exchanges.
 
         const wi = ex.wireInfo;
@@ -323,7 +321,7 @@ export async function exportBackup(
 
       const purchaseProposalIdSet = new Set<string>();
 
-      await tx.iter(Stores.purchases).forEach((purch) => {
+      await tx.purchases.iter().forEach((purch) => {
         const refunds: BackupRefundItem[] = [];
         purchaseProposalIdSet.add(purch.proposalId);
         for (const refundKey of Object.keys(purch.refunds)) {
@@ -376,7 +374,7 @@ export async function exportBackup(
         });
       });
 
-      await tx.iter(Stores.proposals).forEach((prop) => {
+      await tx.proposals.iter().forEach((prop) => {
         if (purchaseProposalIdSet.has(prop.proposalId)) {
           return;
         }
@@ -413,7 +411,7 @@ export async function exportBackup(
         });
       });
 
-      await tx.iter(Stores.refreshGroups).forEach((rg) => {
+      await tx.refreshGroups.iter().forEach((rg) => {
         const oldCoins: BackupRefreshOldCoin[] = [];
 
         for (let i = 0; i < rg.oldCoinPubs.length; i++) {
@@ -482,13 +480,12 @@ export async function exportBackup(
           hash(stringToBytes(canonicalJson(backupBlob))),
         );
         bs.lastBackupNonce = encodeCrock(getRandomBytes(32));
-        await tx.put(Stores.config, {
+        await tx.config.put({
           key: WALLET_BACKUP_STATE_KEY,
           value: bs,
         });
       }
 
       return backupBlob;
-    },
-  );
+    });
 }
diff --git a/packages/taler-wallet-core/src/operations/backup/import.ts 
b/packages/taler-wallet-core/src/operations/backup/import.ts
index 74b7a3b5..e024b76a 100644
--- a/packages/taler-wallet-core/src/operations/backup/import.ts
+++ b/packages/taler-wallet-core/src/operations/backup/import.ts
@@ -29,7 +29,6 @@ import {
   BackupRefreshReason,
 } from "@gnu-taler/taler-util";
 import {
-  Stores,
   WalletContractData,
   DenomSelectionState,
   ExchangeUpdateStatus,
@@ -46,8 +45,8 @@ import {
   AbortStatus,
   RefreshSessionRecord,
   WireInfo,
+  WalletStoresV1,
 } from "../../db.js";
-import { TransactionHandle } from "../../index.js";
 import { PayCoinSelection } from "../../util/coinSelection";
 import { j2s } from "@gnu-taler/taler-util";
 import { checkDbInvariant, checkLogicInvariant } from "../../util/invariants";
@@ -57,6 +56,7 @@ import { InternalWalletState } from "../state";
 import { provideBackupState } from "./state";
 import { makeEventId, TombstoneTag } from "../transactions.js";
 import { getExchangeDetails } from "../exchanges.js";
+import { GetReadOnlyAccess, GetReadWriteAccess } from "../../util/query.js";
 
 const logger = new Logger("operations/backup/import.ts");
 
@@ -74,9 +74,12 @@ function checkBackupInvariant(b: boolean, m?: string): 
asserts b {
  * Re-compute information about the coin selection for a payment.
  */
 async function recoverPayCoinSelection(
-  tx: TransactionHandle<
-    typeof Stores.exchanges | typeof Stores.coins | typeof Stores.denominations
-  >,
+  tx: GetReadWriteAccess<{
+    exchanges: typeof WalletStoresV1.exchanges;
+    exchangeDetails: typeof WalletStoresV1.exchangeDetails;
+    coins: typeof WalletStoresV1.coins;
+    denominations: typeof WalletStoresV1.denominations;
+  }>,
   contractData: WalletContractData,
   backupPurchase: BackupPurchase,
 ): Promise<PayCoinSelection> {
@@ -93,9 +96,9 @@ async function recoverPayCoinSelection(
   );
 
   for (const coinPub of coinPubs) {
-    const coinRecord = await tx.get(Stores.coins, coinPub);
+    const coinRecord = await tx.coins.get(coinPub);
     checkBackupInvariant(!!coinRecord);
-    const denom = await tx.get(Stores.denominations, [
+    const denom = await tx.denominations.get([
       coinRecord.exchangeBaseUrl,
       coinRecord.denomPubHash,
     ]);
@@ -154,11 +157,11 @@ async function recoverPayCoinSelection(
 }
 
 async function getDenomSelStateFromBackup(
-  tx: TransactionHandle<typeof Stores.denominations>,
+  tx: GetReadOnlyAccess<{ denominations: typeof WalletStoresV1.denominations 
}>,
   exchangeBaseUrl: string,
   sel: BackupDenomSel,
 ): Promise<DenomSelectionState> {
-  const d0 = await tx.get(Stores.denominations, [
+  const d0 = await tx.denominations.get([
     exchangeBaseUrl,
     sel[0].denom_pub_hash,
   ]);
@@ -170,10 +173,7 @@ async function getDenomSelStateFromBackup(
   let totalCoinValue = Amounts.getZero(d0.value.currency);
   let totalWithdrawCost = Amounts.getZero(d0.value.currency);
   for (const s of sel) {
-    const d = await tx.get(Stores.denominations, [
-      exchangeBaseUrl,
-      s.denom_pub_hash,
-    ]);
+    const d = await tx.denominations.get([exchangeBaseUrl, s.denom_pub_hash]);
     checkBackupInvariant(!!d);
     totalCoinValue = Amounts.add(totalCoinValue, d.value).amount;
     totalWithdrawCost = Amounts.add(totalWithdrawCost, d.value, d.feeWithdraw)
@@ -215,32 +215,32 @@ export async function importBackup(
 
   logger.info(`importing backup ${j2s(backupBlobArg)}`);
 
-  return ws.db.runWithWriteTransaction(
-    [
-      Stores.config,
-      Stores.exchanges,
-      Stores.exchangeDetails,
-      Stores.coins,
-      Stores.denominations,
-      Stores.purchases,
-      Stores.proposals,
-      Stores.refreshGroups,
-      Stores.backupProviders,
-      Stores.tips,
-      Stores.recoupGroups,
-      Stores.reserves,
-      Stores.withdrawalGroups,
-      Stores.tombstones,
-      Stores.depositGroups,
-    ],
-    async (tx) => {
+  return ws.db
+    .mktx((x) => ({
+      config: x.config,
+      exchanges: x.exchanges,
+      exchangeDetails: x.exchangeDetails,
+      coins: x.coins,
+      denominations: x.denominations,
+      purchases: x.purchases,
+      proposals: x.proposals,
+      refreshGroups: x.refreshGroups,
+      backupProviders: x.backupProviders,
+      tips: x.tips,
+      recoupGroups: x.recoupGroups,
+      reserves: x.reserves,
+      withdrawalGroups: x.withdrawalGroups,
+      tombstones: x.tombstones,
+      depositGroups: x.depositGroups,
+    }))
+    .runReadWrite(async (tx) => {
       // FIXME: validate schema!
       const backupBlob = backupBlobArg as WalletBackupContentV1;
 
       // FIXME: validate version
 
       for (const tombstone of backupBlob.tombstones) {
-        await tx.put(Stores.tombstones, {
+        await tx.tombstones.put({
           id: tombstone,
         });
       }
@@ -250,14 +250,13 @@ export async function importBackup(
       // FIXME:  Validate that the "details pointer" is correct
 
       for (const backupExchange of backupBlob.exchanges) {
-        const existingExchange = await tx.get(
-          Stores.exchanges,
+        const existingExchange = await tx.exchanges.get(
           backupExchange.base_url,
         );
         if (existingExchange) {
           continue;
         }
-        await tx.put(Stores.exchanges, {
+        await tx.exchanges.put({
           baseUrl: backupExchange.base_url,
           detailsPointer: {
             currency: backupExchange.currency,
@@ -272,7 +271,7 @@ export async function importBackup(
       }
 
       for (const backupExchangeDetails of backupBlob.exchange_details) {
-        const existingExchangeDetails = await tx.get(Stores.exchangeDetails, [
+        const existingExchangeDetails = await tx.exchangeDetails.get([
           backupExchangeDetails.base_url,
           backupExchangeDetails.currency,
           backupExchangeDetails.master_public_key,
@@ -296,7 +295,7 @@ export async function importBackup(
               wireFee: Amounts.parseOrThrow(fee.wire_fee),
             });
           }
-          await tx.put(Stores.exchangeDetails, {
+          await tx.exchangeDetails.put({
             exchangeBaseUrl: backupExchangeDetails.base_url,
             termsOfServiceAcceptedEtag: 
backupExchangeDetails.tos_etag_accepted,
             termsOfServiceText: undefined,
@@ -327,7 +326,7 @@ export async function importBackup(
           const denomPubHash =
             cryptoComp.denomPubToHash[backupDenomination.denom_pub];
           checkLogicInvariant(!!denomPubHash);
-          const existingDenom = await tx.get(Stores.denominations, [
+          const existingDenom = await tx.denominations.get([
             backupExchangeDetails.base_url,
             denomPubHash,
           ]);
@@ -336,7 +335,7 @@ export async function importBackup(
               `importing backup denomination: ${j2s(backupDenomination)}`,
             );
 
-            await tx.put(Stores.denominations, {
+            await tx.denominations.put({
               denomPub: backupDenomination.denom_pub,
               denomPubHash: denomPubHash,
               exchangeBaseUrl: backupExchangeDetails.base_url,
@@ -361,7 +360,7 @@ export async function importBackup(
             const compCoin =
               cryptoComp.coinPrivToCompletedCoin[backupCoin.coin_priv];
             checkLogicInvariant(!!compCoin);
-            const existingCoin = await tx.get(Stores.coins, compCoin.coinPub);
+            const existingCoin = await tx.coins.get(compCoin.coinPub);
             if (!existingCoin) {
               let coinSource: CoinSource;
               switch (backupCoin.coin_source.type) {
@@ -388,7 +387,7 @@ export async function importBackup(
                   };
                   break;
               }
-              await tx.put(Stores.coins, {
+              await tx.coins.put({
                 blindingKey: backupCoin.blinding_key,
                 coinEvHash: compCoin.coinEvHash,
                 coinPriv: backupCoin.coin_priv,
@@ -416,7 +415,7 @@ export async function importBackup(
             continue;
           }
           checkLogicInvariant(!!reservePub);
-          const existingReserve = await tx.get(Stores.reserves, reservePub);
+          const existingReserve = await tx.reserves.get(reservePub);
           const instructedAmount = Amounts.parseOrThrow(
             backupReserve.instructed_amount,
           );
@@ -429,7 +428,7 @@ export async function importBackup(
                 confirmUrl: backupReserve.bank_info.confirm_url,
               };
             }
-            await tx.put(Stores.reserves, {
+            await tx.reserves.put({
               currency: instructedAmount.currency,
               instructedAmount,
               exchangeBaseUrl: backupExchangeDetails.base_url,
@@ -467,12 +466,11 @@ export async function importBackup(
             if (tombstoneSet.has(ts)) {
               continue;
             }
-            const existingWg = await tx.get(
-              Stores.withdrawalGroups,
+            const existingWg = await tx.withdrawalGroups.get(
               backupWg.withdrawal_group_id,
             );
             if (!existingWg) {
-              await tx.put(Stores.withdrawalGroups, {
+              await tx.withdrawalGroups.put({
                 denomsSel: await getDenomSelStateFromBackup(
                   tx,
                   backupExchangeDetails.base_url,
@@ -504,8 +502,7 @@ export async function importBackup(
         if (tombstoneSet.has(ts)) {
           continue;
         }
-        const existingProposal = await tx.get(
-          Stores.proposals,
+        const existingProposal = await tx.proposals.get(
           backupProposal.proposal_id,
         );
         if (!existingProposal) {
@@ -584,7 +581,7 @@ export async function importBackup(
               contractTermsRaw: backupProposal.contract_terms_raw,
             };
           }
-          await tx.put(Stores.proposals, {
+          await tx.proposals.put({
             claimToken: backupProposal.claim_token,
             lastError: undefined,
             merchantBaseUrl: backupProposal.merchant_base_url,
@@ -610,17 +607,16 @@ export async function importBackup(
         if (tombstoneSet.has(ts)) {
           continue;
         }
-        const existingPurchase = await tx.get(
-          Stores.purchases,
+        const existingPurchase = await tx.purchases.get(
           backupPurchase.proposal_id,
         );
         if (!existingPurchase) {
           const refunds: { [refundKey: string]: WalletRefundItem } = {};
           for (const backupRefund of backupPurchase.refunds) {
             const key = 
`${backupRefund.coin_pub}-${backupRefund.rtransaction_id}`;
-            const coin = await tx.get(Stores.coins, backupRefund.coin_pub);
+            const coin = await tx.coins.get(backupRefund.coin_pub);
             checkBackupInvariant(!!coin);
-            const denom = await tx.get(Stores.denominations, [
+            const denom = await tx.denominations.get([
               coin.exchangeBaseUrl,
               coin.denomPubHash,
             ]);
@@ -724,7 +720,7 @@ export async function importBackup(
             },
             contractTermsRaw: backupPurchase.contract_terms_raw,
           };
-          await tx.put(Stores.purchases, {
+          await tx.purchases.put({
             proposalId: backupPurchase.proposal_id,
             noncePriv: backupPurchase.nonce_priv,
             noncePub:
@@ -766,8 +762,7 @@ export async function importBackup(
         if (tombstoneSet.has(ts)) {
           continue;
         }
-        const existingRg = await tx.get(
-          Stores.refreshGroups,
+        const existingRg = await tx.refreshGroups.get(
           backupRefreshGroup.refresh_group_id,
         );
         if (!existingRg) {
@@ -800,7 +795,7 @@ export async function importBackup(
             | undefined
           )[] = [];
           for (const oldCoin of backupRefreshGroup.old_coins) {
-            const c = await tx.get(Stores.coins, oldCoin.coin_pub);
+            const c = await tx.coins.get(oldCoin.coin_pub);
             checkBackupInvariant(!!c);
             if (oldCoin.refresh_session) {
               const denomSel = await getDenomSelStateFromBackup(
@@ -821,7 +816,7 @@ export async function importBackup(
               refreshSessionPerCoin.push(undefined);
             }
           }
-          await tx.put(Stores.refreshGroups, {
+          await tx.refreshGroups.put({
             timestampFinished: backupRefreshGroup.timestamp_finish,
             timestampCreated: backupRefreshGroup.timestamp_created,
             refreshGroupId: backupRefreshGroup.refresh_group_id,
@@ -849,14 +844,14 @@ export async function importBackup(
         if (tombstoneSet.has(ts)) {
           continue;
         }
-        const existingTip = await tx.get(Stores.tips, backupTip.wallet_tip_id);
+        const existingTip = await tx.tips.get(backupTip.wallet_tip_id);
         if (!existingTip) {
           const denomsSel = await getDenomSelStateFromBackup(
             tx,
             backupTip.exchange_base_url,
             backupTip.selected_denoms,
           );
-          await tx.put(Stores.tips, {
+          await tx.tips.put({
             acceptedTimestamp: backupTip.timestamp_accepted,
             createdTimestamp: backupTip.timestamp_created,
             denomsSel,
@@ -884,27 +879,26 @@ export async function importBackup(
       for (const tombstone of backupBlob.tombstones) {
         const [type, ...rest] = tombstone.split(":");
         if (type === TombstoneTag.DeleteDepositGroup) {
-          await tx.delete(Stores.depositGroups, rest[0]);
+          await tx.depositGroups.delete(rest[0]);
         } else if (type === TombstoneTag.DeletePayment) {
-          await tx.delete(Stores.purchases, rest[0]);
-          await tx.delete(Stores.proposals, rest[0]);
+          await tx.purchases.delete(rest[0]);
+          await tx.proposals.delete(rest[0]);
         } else if (type === TombstoneTag.DeleteRefreshGroup) {
-          await tx.delete(Stores.refreshGroups, rest[0]);
+          await tx.refreshGroups.delete(rest[0]);
         } else if (type === TombstoneTag.DeleteRefund) {
           // Nothing required, will just prevent display
           // in the transactions list
         } else if (type === TombstoneTag.DeleteReserve) {
           // FIXME:  Once we also have account (=kyc) reserves,
           // we need to check if the reserve is an account before deleting here
-          await tx.delete(Stores.reserves, rest[0]);
+          await tx.reserves.delete(rest[0]);
         } else if (type === TombstoneTag.DeleteTip) {
-          await tx.delete(Stores.tips, rest[0]);
+          await tx.tips.delete(rest[0]);
         } else if (type === TombstoneTag.DeleteWithdrawalGroup) {
-          await tx.delete(Stores.withdrawalGroups, rest[0]);
+          await tx.withdrawalGroups.delete(rest[0]);
         } else {
           logger.warn(`unable to process tombstone of type '${type}'`);
         }
       }
-    },
-  );
+    });
 }
diff --git a/packages/taler-wallet-core/src/operations/backup/index.ts 
b/packages/taler-wallet-core/src/operations/backup/index.ts
index 74331479..bb067dfb 100644
--- a/packages/taler-wallet-core/src/operations/backup/index.ts
+++ b/packages/taler-wallet-core/src/operations/backup/index.ts
@@ -35,7 +35,6 @@ import {
   BackupProviderRecord,
   BackupProviderTerms,
   ConfigRecord,
-  Stores,
 } from "../../db.js";
 import { checkDbInvariant, checkLogicInvariant } from "../../util/invariants";
 import {
@@ -312,18 +311,17 @@ async function runBackupCycleForProvider(
 
     // FIXME: check if the provider is overcharging us!
 
-    await ws.db.runWithWriteTransaction(
-      [Stores.backupProviders],
-      async (tx) => {
-        const provRec = await tx.get(Stores.backupProviders, provider.baseUrl);
+    await ws.db
+      .mktx((x) => ({ backupProviders: x.backupProviders }))
+      .runReadWrite(async (tx) => {
+        const provRec = await tx.backupProviders.get(provider.baseUrl);
         checkDbInvariant(!!provRec);
         const ids = new Set(provRec.paymentProposalIds);
         ids.add(proposalId);
         provRec.paymentProposalIds = Array.from(ids).sort();
         provRec.currentPaymentProposalId = proposalId;
-        await tx.put(Stores.backupProviders, provRec);
-      },
-    );
+        await tx.backupProviders.put(provRec);
+      });
 
     if (doPay) {
       const confirmRes = await confirmPay(ws, proposalId);
@@ -344,19 +342,18 @@ async function runBackupCycleForProvider(
   }
 
   if (resp.status === HttpResponseStatus.NoContent) {
-    await ws.db.runWithWriteTransaction(
-      [Stores.backupProviders],
-      async (tx) => {
-        const prov = await tx.get(Stores.backupProviders, provider.baseUrl);
+    await ws.db
+      .mktx((x) => ({ backupProviders: x.backupProviders }))
+      .runReadWrite(async (tx) => {
+        const prov = await tx.backupProviders.get(provider.baseUrl);
         if (!prov) {
           return;
         }
         prov.lastBackupHash = encodeCrock(currentBackupHash);
         prov.lastBackupTimestamp = getTimestampNow();
         prov.lastError = undefined;
-        await tx.put(Stores.backupProviders, prov);
-      },
-    );
+        await tx.backupProviders.put(prov);
+      });
     return;
   }
 
@@ -367,19 +364,18 @@ async function runBackupCycleForProvider(
     const blob = await decryptBackup(backupConfig, backupEnc);
     const cryptoData = await computeBackupCryptoData(ws.cryptoApi, blob);
     await importBackup(ws, blob, cryptoData);
-    await ws.db.runWithWriteTransaction(
-      [Stores.backupProviders],
-      async (tx) => {
-        const prov = await tx.get(Stores.backupProviders, provider.baseUrl);
+    await ws.db
+      .mktx((x) => ({ backupProvider: x.backupProviders }))
+      .runReadWrite(async (tx) => {
+        const prov = await tx.backupProvider.get(provider.baseUrl);
         if (!prov) {
           return;
         }
         prov.lastBackupHash = encodeCrock(hash(backupEnc));
         prov.lastBackupTimestamp = getTimestampNow();
         prov.lastError = undefined;
-        await tx.put(Stores.backupProviders, prov);
-      },
-    );
+        await tx.backupProvider.put(prov);
+      });
     logger.info("processed existing backup");
     return;
   }
@@ -390,14 +386,16 @@ async function runBackupCycleForProvider(
 
   const err = await readTalerErrorResponse(resp);
   logger.error(`got error response from backup provider: ${j2s(err)}`);
-  await ws.db.runWithWriteTransaction([Stores.backupProviders], async (tx) => {
-    const prov = await tx.get(Stores.backupProviders, provider.baseUrl);
-    if (!prov) {
-      return;
-    }
-    prov.lastError = err;
-    await tx.put(Stores.backupProviders, prov);
-  });
+  await ws.db
+    .mktx((x) => ({ backupProvider: x.backupProviders }))
+    .runReadWrite(async (tx) => {
+      const prov = await tx.backupProvider.get(provider.baseUrl);
+      if (!prov) {
+        return;
+      }
+      prov.lastError = err;
+      await tx.backupProvider.put(prov);
+    });
 }
 
 /**
@@ -408,7 +406,11 @@ async function runBackupCycleForProvider(
  * 3. Upload the updated backup blob.
  */
 export async function runBackupCycle(ws: InternalWalletState): Promise<void> {
-  const providers = await ws.db.iter(Stores.backupProviders).toArray();
+  const providers = await ws.db
+    .mktx((x) => ({ backupProviders: x.backupProviders }))
+    .runReadOnly(async (tx) => {
+      return await tx.backupProviders.iter().toArray();
+    });
   logger.trace("got backup providers", providers);
   const backupJson = await exportBackup(ws);
 
@@ -472,35 +474,43 @@ export async function addBackupProvider(
   logger.info(`adding backup provider ${j2s(req)}`);
   await provideBackupState(ws);
   const canonUrl = canonicalizeBaseUrl(req.backupProviderBaseUrl);
-  const oldProv = await ws.db.get(Stores.backupProviders, canonUrl);
-  if (oldProv) {
-    logger.info("old backup provider found");
-    if (req.activate) {
-      oldProv.active = true;
-      logger.info("setting existing backup provider to active");
-      await ws.db.put(Stores.backupProviders, oldProv);
-    }
-    return;
-  }
+  await ws.db
+    .mktx((x) => ({ backupProviders: x.backupProviders }))
+    .runReadWrite(async (tx) => {
+      const oldProv = await tx.backupProviders.get(canonUrl);
+      if (oldProv) {
+        logger.info("old backup provider found");
+        if (req.activate) {
+          oldProv.active = true;
+          logger.info("setting existing backup provider to active");
+          await tx.backupProviders.put(oldProv);
+        }
+        return;
+      }
+    });
   const termsUrl = new URL("terms", canonUrl);
   const resp = await ws.http.get(termsUrl.href);
   const terms = await readSuccessResponseJsonOrThrow(
     resp,
     codecForSyncTermsOfServiceResponse(),
   );
-  await ws.db.put(Stores.backupProviders, {
-    active: !!req.activate,
-    terms: {
-      annualFee: terms.annual_fee,
-      storageLimitInMegabytes: terms.storage_limit_in_megabytes,
-      supportedProtocolVersion: terms.version,
-    },
-    paymentProposalIds: [],
-    baseUrl: canonUrl,
-    lastError: undefined,
-    retryInfo: initRetryInfo(false),
-    uids: [encodeCrock(getRandomBytes(32))],
-  });
+  await ws.db
+    .mktx((x) => ({ backupProviders: x.backupProviders }))
+    .runReadWrite(async (tx) => {
+      await tx.backupProviders.put({
+        active: !!req.activate,
+        terms: {
+          annualFee: terms.annual_fee,
+          storageLimitInMegabytes: terms.storage_limit_in_megabytes,
+          supportedProtocolVersion: terms.version,
+        },
+        paymentProposalIds: [],
+        baseUrl: canonUrl,
+        lastError: undefined,
+        retryInfo: initRetryInfo(false),
+        uids: [encodeCrock(getRandomBytes(32))],
+      });
+    });
 }
 
 export async function removeBackupProvider(
@@ -654,7 +664,11 @@ export async function getBackupInfo(
   ws: InternalWalletState,
 ): Promise<BackupInfo> {
   const backupConfig = await provideBackupState(ws);
-  const providerRecords = await ws.db.iter(Stores.backupProviders).toArray();
+  const providerRecords = await ws.db
+    .mktx((x) => ({ backupProviders: x.backupProviders }))
+    .runReadOnly(async (tx) => {
+      return await tx.backupProviders.iter().toArray();
+    });
   const providers: ProviderInfo[] = [];
   for (const x of providerRecords) {
     providers.push({
@@ -675,13 +689,18 @@ export async function getBackupInfo(
 }
 
 /**
- * Get information about the current state of wallet backups.
+ * Get backup recovery information, including the wallet's
+ * private key.
  */
 export async function getBackupRecovery(
   ws: InternalWalletState,
 ): Promise<BackupRecovery> {
   const bs = await provideBackupState(ws);
-  const providers = await ws.db.iter(Stores.backupProviders).toArray();
+  const providers = await ws.db
+    .mktx((x) => ({ backupProviders: x.backupProviders }))
+    .runReadOnly(async (tx) => {
+      return await tx.backupProviders.iter().toArray();
+    });
   return {
     providers: providers
       .filter((x) => x.active)
@@ -698,12 +717,12 @@ async function backupRecoveryTheirs(
   ws: InternalWalletState,
   br: BackupRecovery,
 ) {
-  await ws.db.runWithWriteTransaction(
-    [Stores.config, Stores.backupProviders],
-    async (tx) => {
+  await ws.db
+    .mktx((x) => ({ config: x.config, backupProviders: x.backupProviders }))
+    .runReadWrite(async (tx) => {
       let backupStateEntry:
         | ConfigRecord<WalletBackupConfState>
-        | undefined = await tx.get(Stores.config, WALLET_BACKUP_STATE_KEY);
+        | undefined = await tx.config.get(WALLET_BACKUP_STATE_KEY);
       checkDbInvariant(!!backupStateEntry);
       backupStateEntry.value.lastBackupNonce = undefined;
       backupStateEntry.value.lastBackupTimestamp = undefined;
@@ -713,11 +732,11 @@ async function backupRecoveryTheirs(
       backupStateEntry.value.walletRootPub = encodeCrock(
         eddsaGetPublic(decodeCrock(br.walletRootPriv)),
       );
-      await tx.put(Stores.config, backupStateEntry);
+      await tx.config.put(backupStateEntry);
       for (const prov of br.providers) {
-        const existingProv = await tx.get(Stores.backupProviders, prov.url);
+        const existingProv = await tx.backupProviders.get(prov.url);
         if (!existingProv) {
-          await tx.put(Stores.backupProviders, {
+          await tx.backupProviders.put({
             active: true,
             baseUrl: prov.url,
             paymentProposalIds: [],
@@ -727,14 +746,13 @@ async function backupRecoveryTheirs(
           });
         }
       }
-      const providers = await tx.iter(Stores.backupProviders).toArray();
+      const providers = await tx.backupProviders.iter().toArray();
       for (const prov of providers) {
         prov.lastBackupTimestamp = undefined;
         prov.lastBackupHash = undefined;
-        await tx.put(Stores.backupProviders, prov);
+        await tx.backupProviders.put(prov);
       }
-    },
-  );
+    });
 }
 
 async function backupRecoveryOurs(ws: InternalWalletState, br: BackupRecovery) 
{
@@ -746,7 +764,11 @@ export async function loadBackupRecovery(
   br: RecoveryLoadRequest,
 ): Promise<void> {
   const bs = await provideBackupState(ws);
-  const providers = await ws.db.iter(Stores.backupProviders).toArray();
+  const providers = await ws.db
+    .mktx((x) => ({ backupProviders: x.backupProviders }))
+    .runReadOnly(async (tx) => {
+      return await tx.backupProviders.iter().toArray();
+    });
   let strategy = br.strategy;
   if (
     br.recovery.walletRootPriv != bs.walletRootPriv &&
@@ -772,12 +794,11 @@ export async function exportBackupEncrypted(
 ): Promise<Uint8Array> {
   await provideBackupState(ws);
   const blob = await exportBackup(ws);
-  const bs = await ws.db.runWithWriteTransaction(
-    [Stores.config],
-    async (tx) => {
+  const bs = await ws.db
+    .mktx((x) => ({ config: x.config }))
+    .runReadOnly(async (tx) => {
       return await getWalletBackupState(ws, tx);
-    },
-  );
+    });
   return encryptBackup(bs, blob);
 }
 
diff --git a/packages/taler-wallet-core/src/operations/backup/state.ts 
b/packages/taler-wallet-core/src/operations/backup/state.ts
index e2a0f4cf..22688043 100644
--- a/packages/taler-wallet-core/src/operations/backup/state.ts
+++ b/packages/taler-wallet-core/src/operations/backup/state.ts
@@ -15,9 +15,11 @@
  */
 
 import { Timestamp } from "@gnu-taler/taler-util";
-import { ConfigRecord, Stores } from "../../db.js";
-import { getRandomBytes, encodeCrock, TransactionHandle } from 
"../../index.js";
+import { ConfigRecord, WalletStoresV1 } from "../../db.js";
+import { getRandomBytes, encodeCrock } from "../../index.js";
 import { checkDbInvariant } from "../../util/invariants";
+import { GetReadOnlyAccess } from "../../util/query.js";
+import { Wallet } from "../../wallet.js";
 import { InternalWalletState } from "../state";
 
 export interface WalletBackupConfState {
@@ -48,10 +50,13 @@ export const WALLET_BACKUP_STATE_KEY = "walletBackupState";
 export async function provideBackupState(
   ws: InternalWalletState,
 ): Promise<WalletBackupConfState> {
-  const bs: ConfigRecord<WalletBackupConfState> | undefined = await ws.db.get(
-    Stores.config,
-    WALLET_BACKUP_STATE_KEY,
-  );
+  const bs: ConfigRecord<WalletBackupConfState> | undefined = await ws.db
+    .mktx((x) => ({
+      config: x.config,
+    }))
+    .runReadOnly(async (tx) => {
+      return tx.config.get(WALLET_BACKUP_STATE_KEY);
+    });
   if (bs) {
     return bs.value;
   }
@@ -62,32 +67,36 @@ export async function provideBackupState(
   // 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.runWithWriteTransaction([Stores.config], async (tx) => {
-    let backupStateEntry:
-      | ConfigRecord<WalletBackupConfState>
-      | undefined = await tx.get(Stores.config, WALLET_BACKUP_STATE_KEY);
-    if (!backupStateEntry) {
-      backupStateEntry = {
-        key: WALLET_BACKUP_STATE_KEY,
-        value: {
-          deviceId,
-          clocks: { [deviceId]: 1 },
-          walletRootPub: k.pub,
-          walletRootPriv: k.priv,
-          lastBackupPlainHash: undefined,
-        },
-      };
-      await tx.put(Stores.config, backupStateEntry);
-    }
-    return backupStateEntry.value;
-  });
+  return await ws.db
+    .mktx((x) => ({
+      config: x.config,
+    }))
+    .runReadWrite(async (tx) => {
+      let backupStateEntry:
+        | ConfigRecord<WalletBackupConfState>
+        | undefined = await tx.config.get(WALLET_BACKUP_STATE_KEY);
+      if (!backupStateEntry) {
+        backupStateEntry = {
+          key: WALLET_BACKUP_STATE_KEY,
+          value: {
+            deviceId,
+            clocks: { [deviceId]: 1 },
+            walletRootPub: k.pub,
+            walletRootPriv: k.priv,
+            lastBackupPlainHash: undefined,
+          },
+        };
+        await tx.config.put(backupStateEntry);
+      }
+      return backupStateEntry.value;
+    });
 }
 
 export async function getWalletBackupState(
   ws: InternalWalletState,
-  tx: TransactionHandle<typeof Stores.config>,
+  tx: GetReadOnlyAccess<{ config: typeof WalletStoresV1.config }>,
 ): Promise<WalletBackupConfState> {
-  let bs = await tx.get(Stores.config, WALLET_BACKUP_STATE_KEY);
+  const bs = await tx.config.get(WALLET_BACKUP_STATE_KEY);
   checkDbInvariant(!!bs, "wallet backup state should be in DB");
   return bs.value;
 }
diff --git a/packages/taler-wallet-core/src/operations/balance.ts 
b/packages/taler-wallet-core/src/operations/balance.ts
index afa561bf..4dba6beb 100644
--- a/packages/taler-wallet-core/src/operations/balance.ts
+++ b/packages/taler-wallet-core/src/operations/balance.ts
@@ -17,10 +17,15 @@
 /**
  * Imports.
  */
-import { AmountJson, BalancesResponse, Amounts } from "@gnu-taler/taler-util";
-import { Stores, CoinStatus } from "../db.js";
-import { TransactionHandle } from "../index.js";
-import { Logger } from "@gnu-taler/taler-util";
+import {
+  AmountJson,
+  BalancesResponse,
+  Amounts,
+  Logger,
+} from "@gnu-taler/taler-util";
+
+import { CoinStatus, WalletStoresV1 } from "../db.js";
+import { GetReadOnlyAccess } from "../util/query.js";
 import { InternalWalletState } from "./state.js";
 
 const logger = new Logger("withdraw.ts");
@@ -36,13 +41,12 @@ interface WalletBalance {
  */
 export async function getBalancesInsideTransaction(
   ws: InternalWalletState,
-  tx: TransactionHandle<
-    | typeof Stores.reserves
-    | typeof Stores.coins
-    | typeof Stores.reserves
-    | typeof Stores.refreshGroups
-    | typeof Stores.withdrawalGroups
-  >,
+  tx: GetReadOnlyAccess<{
+    reserves: typeof WalletStoresV1.reserves;
+    coins: typeof WalletStoresV1.coins;
+    refreshGroups: typeof WalletStoresV1.refreshGroups;
+    withdrawalGroups: typeof WalletStoresV1.withdrawalGroups;
+  }>,
 ): Promise<BalancesResponse> {
   const balanceStore: Record<string, WalletBalance> = {};
 
@@ -63,7 +67,7 @@ export async function getBalancesInsideTransaction(
   };
 
   // Initialize balance to zero, even if we didn't start withdrawing yet.
-  await tx.iter(Stores.reserves).forEach((r) => {
+  await tx.reserves.iter().forEach((r) => {
     const b = initBalance(r.currency);
     if (!r.initialWithdrawalStarted) {
       b.pendingIncoming = Amounts.add(
@@ -73,7 +77,7 @@ export async function getBalancesInsideTransaction(
     }
   });
 
-  await tx.iter(Stores.coins).forEach((c) => {
+  await tx.coins.iter().forEach((c) => {
     // Only count fresh coins, as dormant coins will
     // already be in a refresh session.
     if (c.status === CoinStatus.Fresh) {
@@ -82,7 +86,7 @@ export async function getBalancesInsideTransaction(
     }
   });
 
-  await tx.iter(Stores.refreshGroups).forEach((r) => {
+  await tx.refreshGroups.iter().forEach((r) => {
     // Don't count finished refreshes, since the refresh already resulted
     // in coins being added to the wallet.
     if (r.timestampFinished) {
@@ -108,7 +112,7 @@ export async function getBalancesInsideTransaction(
     }
   });
 
-  await tx.iter(Stores.withdrawalGroups).forEach((wds) => {
+  await tx.withdrawalGroups.iter().forEach((wds) => {
     if (wds.timestampFinish) {
       return;
     }
@@ -147,18 +151,17 @@ export async function getBalances(
 ): Promise<BalancesResponse> {
   logger.trace("starting to compute balance");
 
-  const wbal = await ws.db.runWithReadTransaction(
-    [
-      Stores.coins,
-      Stores.refreshGroups,
-      Stores.reserves,
-      Stores.purchases,
-      Stores.withdrawalGroups,
-    ],
-    async (tx) => {
+  const wbal = await ws.db
+    .mktx((x) => ({
+      coins: x.coins,
+      refreshGroups: x.refreshGroups,
+      reserves: x.reserves,
+      purchases: x.purchases,
+      withdrawalGroups: x.withdrawalGroups,
+    }))
+    .runReadOnly(async (tx) => {
       return getBalancesInsideTransaction(ws, tx);
-    },
-  );
+    });
 
   logger.trace("finished computing wallet balance");
 
diff --git a/packages/taler-wallet-core/src/operations/currencies.ts 
b/packages/taler-wallet-core/src/operations/currencies.ts
index cead07a6..e591b50c 100644
--- a/packages/taler-wallet-core/src/operations/currencies.ts
+++ b/packages/taler-wallet-core/src/operations/currencies.ts
@@ -17,7 +17,7 @@
 /**
  * Imports.
  */
-import { ExchangeRecord, Stores } from "../db.js";
+import { ExchangeRecord } from "../db.js";
 import { Logger } from "@gnu-taler/taler-util";
 import { getExchangeDetails } from "./exchanges.js";
 import { InternalWalletState } from "./state.js";
@@ -38,37 +38,44 @@ export async function getExchangeTrust(
 ): Promise<TrustInfo> {
   let isTrusted = false;
   let isAudited = false;
-  const exchangeDetails = await ws.db.runWithReadTransaction(
-    [Stores.exchangeDetails, Stores.exchanges],
-    async (tx) => {
-      return getExchangeDetails(tx, exchangeInfo.baseUrl);
-    },
-  );
-  if (!exchangeDetails) {
-    throw Error(`exchange ${exchangeInfo.baseUrl} details not available`);
-  }
-  const exchangeTrustRecord = await ws.db.getIndexed(
-    Stores.exchangeTrustStore.exchangeMasterPubIndex,
-    exchangeDetails.masterPublicKey,
-  );
-  if (
-    exchangeTrustRecord &&
-    exchangeTrustRecord.uids.length > 0 &&
-    exchangeTrustRecord.currency === exchangeDetails.currency
-  ) {
-    isTrusted = true;
-  }
 
-  for (const auditor of exchangeDetails.auditors) {
-    const auditorTrustRecord = await ws.db.getIndexed(
-      Stores.auditorTrustStore.auditorPubIndex,
-      auditor.auditor_pub,
-    );
-    if (auditorTrustRecord && auditorTrustRecord.uids.length > 0) {
-      isAudited = true;
-      break;
-    }
-  }
+  return await ws.db
+    .mktx((x) => ({
+      exchanges: x.exchanges,
+      exchangeDetails: x.exchangeDetails,
+      exchangesTrustStore: x.exchangeTrust,
+      auditorTrust: x.auditorTrust,
+    }))
+    .runReadOnly(async (tx) => {
+      const exchangeDetails = await getExchangeDetails(
+        tx,
+        exchangeInfo.baseUrl,
+      );
 
-  return { isTrusted, isAudited };
+      if (!exchangeDetails) {
+        throw Error(`exchange ${exchangeInfo.baseUrl} details not available`);
+      }
+      const exchangeTrustRecord = await 
tx.exchangesTrustStore.indexes.byExchangeMasterPub.get(
+        exchangeDetails.masterPublicKey,
+      );
+      if (
+        exchangeTrustRecord &&
+        exchangeTrustRecord.uids.length > 0 &&
+        exchangeTrustRecord.currency === exchangeDetails.currency
+      ) {
+        isTrusted = true;
+      }
+
+      for (const auditor of exchangeDetails.auditors) {
+        const auditorTrustRecord = await 
tx.auditorTrust.indexes.byAuditorPub.get(
+          auditor.auditor_pub,
+        );
+        if (auditorTrustRecord && auditorTrustRecord.uids.length > 0) {
+          isAudited = true;
+          break;
+        }
+      }
+
+      return { isTrusted, isAudited };
+    });
 }
diff --git a/packages/taler-wallet-core/src/operations/deposits.ts 
b/packages/taler-wallet-core/src/operations/deposits.ts
index 408ad392..996e8cf3 100644
--- a/packages/taler-wallet-core/src/operations/deposits.ts
+++ b/packages/taler-wallet-core/src/operations/deposits.ts
@@ -56,7 +56,8 @@ import {
 } from "./pay";
 import { InternalWalletState } from "./state";
 import { Logger } from "@gnu-taler/taler-util";
-import { DepositGroupRecord, Stores } from "../db.js";
+import { DepositGroupRecord } from "../db.js";
+
 import { guardOperationException } from "./errors.js";
 import { getExchangeDetails } from "./exchanges.js";
 
@@ -116,12 +117,17 @@ async function resetDepositGroupRetry(
   ws: InternalWalletState,
   depositGroupId: string,
 ): Promise<void> {
-  await ws.db.mutate(Stores.depositGroups, depositGroupId, (x) => {
-    if (x.retryInfo.active) {
-      x.retryInfo = initRetryInfo();
-    }
-    return x;
-  });
+  await ws.db
+    .mktx((x) => ({
+      depositGroups: x.depositGroups,
+    }))
+    .runReadWrite(async (tx) => {
+      const x = await tx.depositGroups.get(depositGroupId);
+      if (x && x.retryInfo.active) {
+        x.retryInfo = initRetryInfo();
+        await tx.depositGroups.put(x);
+      }
+    });
 }
 
 async function incrementDepositRetry(
@@ -129,19 +135,21 @@ async function incrementDepositRetry(
   depositGroupId: string,
   err: TalerErrorDetails | undefined,
 ): Promise<void> {
-  await ws.db.runWithWriteTransaction([Stores.depositGroups], async (tx) => {
-    const r = await tx.get(Stores.depositGroups, depositGroupId);
-    if (!r) {
-      return;
-    }
-    if (!r.retryInfo) {
-      return;
-    }
-    r.retryInfo.retryCounter++;
-    updateRetryInfoTimeout(r.retryInfo);
-    r.lastError = err;
-    await tx.put(Stores.depositGroups, r);
-  });
+  await ws.db
+    .mktx((x) => ({ depositGroups: x.depositGroups }))
+    .runReadWrite(async (tx) => {
+      const r = await tx.depositGroups.get(depositGroupId);
+      if (!r) {
+        return;
+      }
+      if (!r.retryInfo) {
+        return;
+      }
+      r.retryInfo.retryCounter++;
+      updateRetryInfoTimeout(r.retryInfo);
+      r.lastError = err;
+      await tx.depositGroups.put(r);
+    });
   if (err) {
     ws.notify({ type: NotificationType.DepositOperationError, error: err });
   }
@@ -170,7 +178,13 @@ async function processDepositGroupImpl(
   if (forceNow) {
     await resetDepositGroupRetry(ws, depositGroupId);
   }
-  const depositGroup = await ws.db.get(Stores.depositGroups, depositGroupId);
+  const depositGroup = await ws.db
+    .mktx((x) => ({
+      depositGroups: x.depositGroups,
+    }))
+    .runReadOnly(async (tx) => {
+      return tx.depositGroups.get(depositGroupId);
+    });
   if (!depositGroup) {
     logger.warn(`deposit group ${depositGroupId} not found`);
     return;
@@ -213,32 +227,38 @@ async function processDepositGroupImpl(
       merchant_pub: depositGroup.merchantPub,
     });
     await readSuccessResponseJsonOrThrow(httpResp, codecForDepositSuccess());
-    await ws.db.runWithWriteTransaction([Stores.depositGroups], async (tx) => {
-      const dg = await tx.get(Stores.depositGroups, depositGroupId);
+    await ws.db
+      .mktx((x) => ({ depositGroups: x.depositGroups }))
+      .runReadWrite(async (tx) => {
+        const dg = await tx.depositGroups.get(depositGroupId);
+        if (!dg) {
+          return;
+        }
+        dg.depositedPerCoin[i] = true;
+        await tx.depositGroups.put(dg);
+      });
+  }
+
+  await ws.db
+    .mktx((x) => ({
+      depositGroups: x.depositGroups,
+    }))
+    .runReadWrite(async (tx) => {
+      const dg = await tx.depositGroups.get(depositGroupId);
       if (!dg) {
         return;
       }
-      dg.depositedPerCoin[i] = true;
-      await tx.put(Stores.depositGroups, dg);
-    });
-  }
-
-  await ws.db.runWithWriteTransaction([Stores.depositGroups], async (tx) => {
-    const dg = await tx.get(Stores.depositGroups, depositGroupId);
-    if (!dg) {
-      return;
-    }
-    let allDeposited = true;
-    for (const d of depositGroup.depositedPerCoin) {
-      if (!d) {
-        allDeposited = false;
+      let allDeposited = true;
+      for (const d of depositGroup.depositedPerCoin) {
+        if (!d) {
+          allDeposited = false;
+        }
       }
-    }
-    if (allDeposited) {
-      dg.timestampFinished = getTimestampNow();
-      await tx.put(Stores.depositGroups, dg);
-    }
-  });
+      if (allDeposited) {
+        dg.timestampFinished = getTimestampNow();
+        await tx.depositGroups.put(dg);
+      }
+    });
 }
 
 export async function trackDepositGroup(
@@ -249,10 +269,13 @@ export async function trackDepositGroup(
     status: number;
     body: any;
   }[] = [];
-  const depositGroup = await ws.db.get(
-    Stores.depositGroups,
-    req.depositGroupId,
-  );
+  const depositGroup = await ws.db
+    .mktx((x) => ({
+      depositGroups: x.depositGroups,
+    }))
+    .runReadOnly(async (tx) => {
+      return tx.depositGroups.get(req.depositGroupId);
+    });
   if (!depositGroup) {
     throw Error("deposit group not found");
   }
@@ -306,23 +329,26 @@ export async function createDepositGroup(
 
   const amount = Amounts.parseOrThrow(req.amount);
 
-  const allExchanges = await ws.db.iter(Stores.exchanges).toArray();
   const exchangeInfos: { url: string; master_pub: string }[] = [];
-  for (const e of allExchanges) {
-    const details = await ws.db.runWithReadTransaction(
-      [Stores.exchanges, Stores.exchangeDetails],
-      async (tx) => {
-        return getExchangeDetails(tx, e.baseUrl);
-      },
-    );
-    if (!details) {
-      continue;
-    }
-    exchangeInfos.push({
-      master_pub: details.masterPublicKey,
-      url: e.baseUrl,
+
+  await ws.db
+    .mktx((x) => ({
+      exchanges: x.exchanges,
+      exchangeDetails: x.exchangeDetails,
+    }))
+    .runReadOnly(async (tx) => {
+      const allExchanges = await tx.exchanges.iter().toArray();
+      for (const e of allExchanges) {
+        const details = await getExchangeDetails(tx, e.baseUrl);
+        if (!details) {
+          continue;
+        }
+        exchangeInfos.push({
+          master_pub: details.masterPublicKey,
+          url: e.baseUrl,
+        });
+      }
     });
-  }
 
   const timestamp = getTimestampNow();
   const timestampRound = timestampTruncateToSecond(timestamp);
@@ -421,20 +447,17 @@ export async function createDepositGroup(
     lastError: undefined,
   };
 
-  await ws.db.runWithWriteTransaction(
-    [
-      Stores.depositGroups,
-      Stores.coins,
-      Stores.refreshGroups,
-      Stores.denominations,
-    ],
-    async (tx) => {
+  await ws.db
+    .mktx((x) => ({
+      depositGroups: x.depositGroups,
+      coins: x.coins,
+      refreshGroups: x.refreshGroups,
+      denominations: x.denominations,
+    }))
+    .runReadWrite(async (tx) => {
       await applyCoinSpend(ws, tx, payCoinSel);
-      await tx.put(Stores.depositGroups, depositGroup);
-    },
-  );
-
-  await ws.db.put(Stores.depositGroups, depositGroup);
+      await tx.depositGroups.put(depositGroup);
+    });
 
   return { depositGroupId };
 }
diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts 
b/packages/taler-wallet-core/src/operations/exchanges.ts
index e48d1299..789ce1da 100644
--- a/packages/taler-wallet-core/src/operations/exchanges.ts
+++ b/packages/taler-wallet-core/src/operations/exchanges.ts
@@ -41,13 +41,13 @@ import {
 import {
   DenominationRecord,
   DenominationStatus,
-  Stores,
   ExchangeRecord,
   ExchangeUpdateStatus,
   WireFee,
   ExchangeUpdateReason,
   ExchangeDetailsRecord,
   WireInfo,
+  WalletStoresV1,
 } from "../db.js";
 import {
   URL,
@@ -73,7 +73,7 @@ import {
 } from "./versions.js";
 import { HttpRequestLibrary } from "../util/http.js";
 import { CryptoApi } from "../crypto/workers/cryptoApi.js";
-import { TransactionHandle } from "../util/query.js";
+import { DbAccess, GetReadOnlyAccess } from "../util/query.js";
 
 const logger = new Logger("exchanges.ts");
 
@@ -108,15 +108,17 @@ async function handleExchangeUpdateError(
   baseUrl: string,
   err: TalerErrorDetails,
 ): Promise<void> {
-  await ws.db.runWithWriteTransaction([Stores.exchanges], async (tx) => {
-    const exchange = await tx.get(Stores.exchanges, baseUrl);
-    if (!exchange) {
-      return;
-    }
-    exchange.retryInfo.retryCounter++;
-    updateRetryInfoTimeout(exchange.retryInfo);
-    exchange.lastError = err;
-  });
+  await ws.db
+    .mktx((x) => ({ exchanges: x.exchanges }))
+    .runReadOnly(async (tx) => {
+      const exchange = await tx.exchanges.get(baseUrl);
+      if (!exchange) {
+        return;
+      }
+      exchange.retryInfo.retryCounter++;
+      updateRetryInfoTimeout(exchange.retryInfo);
+      exchange.lastError = err;
+    });
   if (err) {
     ws.notify({ type: NotificationType.ExchangeOperationError, error: err });
   }
@@ -153,12 +155,13 @@ async function downloadExchangeWithTermsOfService(
 }
 
 export async function getExchangeDetails(
-  tx: TransactionHandle<
-    typeof Stores.exchanges | typeof Stores.exchangeDetails
-  >,
+  tx: GetReadOnlyAccess<{
+    exchanges: typeof WalletStoresV1.exchanges;
+    exchangeDetails: typeof WalletStoresV1.exchangeDetails;
+  }>,
   exchangeBaseUrl: string,
 ): Promise<ExchangeDetailsRecord | undefined> {
-  const r = await tx.get(Stores.exchanges, exchangeBaseUrl);
+  const r = await tx.exchanges.get(exchangeBaseUrl);
   if (!r) {
     return;
   }
@@ -167,28 +170,32 @@ export async function getExchangeDetails(
     return;
   }
   const { currency, masterPublicKey } = dp;
-  return await tx.get(Stores.exchangeDetails, [
-    r.baseUrl,
-    currency,
-    masterPublicKey,
-  ]);
+  return await tx.exchangeDetails.get([r.baseUrl, currency, masterPublicKey]);
 }
 
+getExchangeDetails.makeContext = (db: DbAccess<typeof WalletStoresV1>) =>
+  db.mktx((x) => ({
+    exchanges: x.exchanges,
+    exchangeDetails: x.exchangeDetails,
+  }));
+
 export async function acceptExchangeTermsOfService(
   ws: InternalWalletState,
   exchangeBaseUrl: string,
   etag: string | undefined,
 ): Promise<void> {
-  await ws.db.runWithWriteTransaction(
-    [Stores.exchanges, Stores.exchangeDetails],
-    async (tx) => {
+  await ws.db
+    .mktx((x) => ({
+      exchanges: x.exchanges,
+      exchangeDetails: x.exchangeDetails,
+    }))
+    .runReadWrite(async (tx) => {
       const d = await getExchangeDetails(tx, exchangeBaseUrl);
       if (d) {
         d.termsOfServiceAcceptedEtag = etag;
-        await tx.put(Stores.exchangeDetails, d);
+        await tx.exchangeDetails.put(d);
       }
-    },
-  );
+    });
 }
 
 async function validateWireInfo(
@@ -284,21 +291,24 @@ async function provideExchangeRecord(
   baseUrl: string,
   now: Timestamp,
 ): Promise<ExchangeRecord> {
-  let r = await ws.db.get(Stores.exchanges, baseUrl);
-  if (!r) {
-    const newExchangeRecord: ExchangeRecord = {
-      permanent: true,
-      baseUrl: baseUrl,
-      updateStatus: ExchangeUpdateStatus.FetchKeys,
-      updateStarted: now,
-      updateReason: ExchangeUpdateReason.Initial,
-      retryInfo: initRetryInfo(false),
-      detailsPointer: undefined,
-    };
-    await ws.db.put(Stores.exchanges, newExchangeRecord);
-    r = newExchangeRecord;
-  }
-  return r;
+  return await ws.db
+    .mktx((x) => ({ exchanges: x.exchanges }))
+    .runReadWrite(async (tx) => {
+      let r = await tx.exchanges.get(baseUrl);
+      if (!r) {
+        r = {
+          permanent: true,
+          baseUrl: baseUrl,
+          updateStatus: ExchangeUpdateStatus.FetchKeys,
+          updateStarted: now,
+          updateReason: ExchangeUpdateReason.Initial,
+          retryInfo: initRetryInfo(false),
+          detailsPointer: undefined,
+        };
+        await tx.exchanges.put(r);
+      }
+      return r;
+    });
 }
 
 interface ExchangeKeysDownloadResult {
@@ -427,16 +437,17 @@ async function updateExchangeFromUrlImpl(
 
   let recoupGroupId: string | undefined = undefined;
 
-  const updated = await ws.db.runWithWriteTransaction(
-    [
-      Stores.exchanges,
-      Stores.exchangeDetails,
-      Stores.denominations,
-      Stores.recoupGroups,
-      Stores.coins,
-    ],
-    async (tx) => {
-      const r = await tx.get(Stores.exchanges, baseUrl);
+  const updated = await ws.db
+    .mktx((x) => ({
+      exchanges: x.exchanges,
+      exchangeDetails: x.exchangeDetails,
+      denominations: x.denominations,
+      coins: x.coins,
+      refreshGroups: x.refreshGroups,
+      recoupGroups: x.recoupGroups,
+    }))
+    .runReadWrite(async (tx) => {
+      const r = await tx.exchanges.get(baseUrl);
       if (!r) {
         logger.warn(`exchange ${baseUrl} no longer present`);
         return;
@@ -473,18 +484,18 @@ async function updateExchangeFromUrlImpl(
         // FIXME: only change if pointer really changed
         updateClock: getTimestampNow(),
       };
-      await tx.put(Stores.exchanges, r);
-      await tx.put(Stores.exchangeDetails, details);
+      await tx.exchanges.put(r);
+      await tx.exchangeDetails.put(details);
 
       for (const currentDenom of keysInfo.currentDenominations) {
-        const oldDenom = await tx.get(Stores.denominations, [
+        const oldDenom = await tx.denominations.get([
           baseUrl,
           currentDenom.denomPubHash,
         ]);
         if (oldDenom) {
           // FIXME: Do consistency check
         } else {
-          await tx.put(Stores.denominations, currentDenom);
+          await tx.denominations.put(currentDenom);
         }
       }
 
@@ -493,7 +504,7 @@ async function updateExchangeFromUrlImpl(
       const newlyRevokedCoinPubs: string[] = [];
       logger.trace("recoup list from exchange", recoupDenomList);
       for (const recoupInfo of recoupDenomList) {
-        const oldDenom = await tx.get(Stores.denominations, [
+        const oldDenom = await tx.denominations.get([
           r.baseUrl,
           recoupInfo.h_denom_pub,
         ]);
@@ -509,9 +520,9 @@ async function updateExchangeFromUrlImpl(
         }
         logger.trace("revoking denom", recoupInfo.h_denom_pub);
         oldDenom.isRevoked = true;
-        await tx.put(Stores.denominations, oldDenom);
-        const affectedCoins = await tx
-          .iterIndexed(Stores.coins.denomPubHashIndex, recoupInfo.h_denom_pub)
+        await tx.denominations.put(oldDenom);
+        const affectedCoins = await tx.coins.indexes.byDenomPubHash
+          .iter(recoupInfo.h_denom_pub)
           .toArray();
         for (const ac of affectedCoins) {
           newlyRevokedCoinPubs.push(ac.coinPub);
@@ -525,8 +536,7 @@ async function updateExchangeFromUrlImpl(
         exchange: r,
         exchangeDetails: details,
       };
-    },
-  );
+    });
 
   if (recoupGroupId) {
     // Asynchronously start recoup.  This doesn't need to finish
@@ -553,12 +563,11 @@ export async function getExchangePaytoUri(
 ): Promise<string> {
   // We do the update here, since the exchange might not even exist
   // yet in our database.
-  const details = await ws.db.runWithReadTransaction(
-    [Stores.exchangeDetails, Stores.exchanges],
-    async (tx) => {
+  const details = await getExchangeDetails
+    .makeContext(ws.db)
+    .runReadOnly(async (tx) => {
       return getExchangeDetails(tx, exchangeBaseUrl);
-    },
-  );
+    });
   const accounts = details?.wireInfo.accounts ?? [];
   for (const account of accounts) {
     const res = parsePaytoUri(account.payto_uri);
diff --git a/packages/taler-wallet-core/src/operations/pay.ts 
b/packages/taler-wallet-core/src/operations/pay.ts
index 0b1b30f6..86b66e6f 100644
--- a/packages/taler-wallet-core/src/operations/pay.ts
+++ b/packages/taler-wallet-core/src/operations/pay.ts
@@ -72,9 +72,7 @@ import {
   readSuccessResponseJsonOrErrorCode,
   readSuccessResponseJsonOrThrow,
   readTalerErrorResponse,
-  Stores,
   throwUnexpectedRequestError,
-  TransactionHandle,
   URL,
   WalletContractData,
 } from "../index.js";
@@ -85,7 +83,7 @@ import {
   selectPayCoins,
   PreviousPayCoins,
 } from "../util/coinSelection.js";
-import { canonicalJson, j2s } from "@gnu-taler/taler-util";
+import { j2s } from "@gnu-taler/taler-util";
 import {
   initRetryInfo,
   updateRetryInfoTimeout,
@@ -95,6 +93,10 @@ import { getTotalRefreshCost, createRefreshGroup } from 
"./refresh.js";
 import { InternalWalletState, EXCHANGE_COINS_LOCK } from "./state.js";
 import { ContractTermsUtil } from "../util/contractTerms.js";
 import { getExchangeDetails } from "./exchanges.js";
+import { DbAccess, GetReadWriteAccess } from "../util/query.js";
+import { WalletStoresV1 } from "../db.js";
+import { Wallet } from "../wallet.js";
+import { x25519_edwards_keyPair_fromSecretKey } from 
"../crypto/primitives/nacl-fast.js";
 
 /**
  * Logger.
@@ -112,34 +114,35 @@ export async function getTotalPaymentCost(
   ws: InternalWalletState,
   pcs: PayCoinSelection,
 ): Promise<AmountJson> {
-  const costs = [];
-  for (let i = 0; i < pcs.coinPubs.length; i++) {
-    const coin = await ws.db.get(Stores.coins, pcs.coinPubs[i]);
-    if (!coin) {
-      throw Error("can't calculate payment cost, coin not found");
-    }
-    const denom = await ws.db.get(Stores.denominations, [
-      coin.exchangeBaseUrl,
-      coin.denomPubHash,
-    ]);
-    if (!denom) {
-      throw Error(
-        "can't calculate payment cost, denomination for coin not found",
-      );
-    }
-    const allDenoms = await ws.db
-      .iterIndex(
-        Stores.denominations.exchangeBaseUrlIndex,
-        coin.exchangeBaseUrl,
-      )
-      .toArray();
-    const amountLeft = Amounts.sub(denom.value, pcs.coinContributions[i])
-      .amount;
-    const refreshCost = getTotalRefreshCost(allDenoms, denom, amountLeft);
-    costs.push(pcs.coinContributions[i]);
-    costs.push(refreshCost);
-  }
-  return Amounts.sum(costs).amount;
+  return ws.db
+    .mktx((x) => ({ coins: x.coins, denominations: x.denominations }))
+    .runReadOnly(async (tx) => {
+      const costs = [];
+      for (let i = 0; i < pcs.coinPubs.length; i++) {
+        const coin = await tx.coins.get(pcs.coinPubs[i]);
+        if (!coin) {
+          throw Error("can't calculate payment cost, coin not found");
+        }
+        const denom = await tx.denominations.get([
+          coin.exchangeBaseUrl,
+          coin.denomPubHash,
+        ]);
+        if (!denom) {
+          throw Error(
+            "can't calculate payment cost, denomination for coin not found",
+          );
+        }
+        const allDenoms = await tx.denominations.indexes.byExchangeBaseUrl
+          .iter()
+          .toArray();
+        const amountLeft = Amounts.sub(denom.value, pcs.coinContributions[i])
+          .amount;
+        const refreshCost = getTotalRefreshCost(allDenoms, denom, amountLeft);
+        costs.push(pcs.coinContributions[i]);
+        costs.push(refreshCost);
+      }
+      return Amounts.sum(costs).amount;
+    });
 }
 
 /**
@@ -154,39 +157,48 @@ export async function getEffectiveDepositAmount(
   const amt: AmountJson[] = [];
   const fees: AmountJson[] = [];
   const exchangeSet: Set<string> = new Set();
-  for (let i = 0; i < pcs.coinPubs.length; i++) {
-    const coin = await ws.db.get(Stores.coins, pcs.coinPubs[i]);
-    if (!coin) {
-      throw Error("can't calculate deposit amountt, coin not found");
-    }
-    const denom = await ws.db.get(Stores.denominations, [
-      coin.exchangeBaseUrl,
-      coin.denomPubHash,
-    ]);
-    if (!denom) {
-      throw Error("can't find denomination to calculate deposit amount");
-    }
-    amt.push(pcs.coinContributions[i]);
-    fees.push(denom.feeDeposit);
-    exchangeSet.add(coin.exchangeBaseUrl);
-  }
-  for (const exchangeUrl of exchangeSet.values()) {
-    const exchangeDetails = await ws.db.runWithReadTransaction(
-      [Stores.exchanges, Stores.exchangeDetails],
-      async (tx) => {
-        return getExchangeDetails(tx, exchangeUrl);
-      },
-    );
-    if (!exchangeDetails) {
-      continue;
-    }
-    const fee = exchangeDetails.wireInfo.feesForType[wireType].find((x) => {
-      return timestampIsBetween(getTimestampNow(), x.startStamp, x.endStamp);
-    })?.wireFee;
-    if (fee) {
-      fees.push(fee);
-    }
-  }
+
+  await ws.db
+    .mktx((x) => ({
+      coins: x.coins,
+      denominations: x.denominations,
+      exchanges: x.exchanges,
+      exchangeDetails: x.exchangeDetails,
+    }))
+    .runReadOnly(async (tx) => {
+      for (let i = 0; i < pcs.coinPubs.length; i++) {
+        const coin = await tx.coins.get(pcs.coinPubs[i]);
+        if (!coin) {
+          throw Error("can't calculate deposit amountt, coin not found");
+        }
+        const denom = await tx.denominations.get([
+          coin.exchangeBaseUrl,
+          coin.denomPubHash,
+        ]);
+        if (!denom) {
+          throw Error("can't find denomination to calculate deposit amount");
+        }
+        amt.push(pcs.coinContributions[i]);
+        fees.push(denom.feeDeposit);
+        exchangeSet.add(coin.exchangeBaseUrl);
+      }
+      for (const exchangeUrl of exchangeSet.values()) {
+        const exchangeDetails = await getExchangeDetails(tx, exchangeUrl);
+        if (!exchangeDetails) {
+          continue;
+        }
+        const fee = exchangeDetails.wireInfo.feesForType[wireType].find((x) => 
{
+          return timestampIsBetween(
+            getTimestampNow(),
+            x.startStamp,
+            x.endStamp,
+          );
+        })?.wireFee;
+        if (fee) {
+          fees.push(fee);
+        }
+      }
+    });
   return Amounts.sub(Amounts.sum(amt).amount, Amounts.sum(fees).amount).amount;
 }
 
@@ -243,105 +255,112 @@ export async function getCandidatePayCoins(
   const candidateCoins: AvailableCoinInfo[] = [];
   const wireFeesPerExchange: Record<string, AmountJson> = {};
 
-  const exchanges = await ws.db.iter(Stores.exchanges).toArray();
-  for (const exchange of exchanges) {
-    let isOkay = false;
-    const exchangeDetails = await ws.db.runWithReadTransaction(
-      [Stores.exchanges, Stores.exchangeDetails],
-      async (tx) => {
-        return getExchangeDetails(tx, exchange.baseUrl);
-      },
-    );
-    if (!exchangeDetails) {
-      continue;
-    }
-    const exchangeFees = exchangeDetails.wireInfo;
-    if (!exchangeFees) {
-      continue;
-    }
-
-    // is the exchange explicitly allowed?
-    for (const allowedExchange of req.allowedExchanges) {
-      if (allowedExchange.exchangePub === exchangeDetails.masterPublicKey) {
-        isOkay = true;
-        break;
-      }
-    }
+  await ws.db
+    .mktx((x) => ({
+      exchanges: x.exchanges,
+      exchangeDetails: x.exchangeDetails,
+      denominations: x.denominations,
+      coins: x.coins,
+    }))
+    .runReadOnly(async (tx) => {
+      const exchanges = await tx.exchanges.iter().toArray();
+      for (const exchange of exchanges) {
+        let isOkay = false;
+        const exchangeDetails = await getExchangeDetails(tx, exchange.baseUrl);
+        if (!exchangeDetails) {
+          continue;
+        }
+        const exchangeFees = exchangeDetails.wireInfo;
+        if (!exchangeFees) {
+          continue;
+        }
 
-    // is the exchange allowed because of one of its auditors?
-    if (!isOkay) {
-      for (const allowedAuditor of req.allowedAuditors) {
-        for (const auditor of exchangeDetails.auditors) {
-          if (auditor.auditor_pub === allowedAuditor.auditorPub) {
+        // is the exchange explicitly allowed?
+        for (const allowedExchange of req.allowedExchanges) {
+          if (allowedExchange.exchangePub === exchangeDetails.masterPublicKey) 
{
             isOkay = true;
             break;
           }
         }
-        if (isOkay) {
-          break;
+
+        // is the exchange allowed because of one of its auditors?
+        if (!isOkay) {
+          for (const allowedAuditor of req.allowedAuditors) {
+            for (const auditor of exchangeDetails.auditors) {
+              if (auditor.auditor_pub === allowedAuditor.auditorPub) {
+                isOkay = true;
+                break;
+              }
+            }
+            if (isOkay) {
+              break;
+            }
+          }
         }
-      }
-    }
 
-    if (!isOkay) {
-      continue;
-    }
+        if (!isOkay) {
+          continue;
+        }
 
-    const coins = await ws.db
-      .iterIndex(Stores.coins.exchangeBaseUrlIndex, exchange.baseUrl)
-      .toArray();
+        const coins = await tx.coins.indexes.byBaseUrl
+          .iter(exchange.baseUrl)
+          .toArray();
 
-    if (!coins || coins.length === 0) {
-      continue;
-    }
+        if (!coins || coins.length === 0) {
+          continue;
+        }
 
-    // Denomination of the first coin, we assume that all other
-    // coins have the same currency
-    const firstDenom = await ws.db.get(Stores.denominations, [
-      exchange.baseUrl,
-      coins[0].denomPubHash,
-    ]);
-    if (!firstDenom) {
-      throw Error("db inconsistent");
-    }
-    const currency = firstDenom.value.currency;
-    for (const coin of coins) {
-      const denom = await ws.db.get(Stores.denominations, [
-        exchange.baseUrl,
-        coin.denomPubHash,
-      ]);
-      if (!denom) {
-        throw Error("db inconsistent");
-      }
-      if (denom.value.currency !== currency) {
-        logger.warn(
-          `same pubkey for different currencies at exchange 
${exchange.baseUrl}`,
-        );
-        continue;
-      }
-      if (!isSpendableCoin(coin, denom)) {
-        continue;
-      }
-      candidateCoins.push({
-        availableAmount: coin.currentAmount,
-        coinPub: coin.coinPub,
-        denomPub: coin.denomPub,
-        feeDeposit: denom.feeDeposit,
-        exchangeBaseUrl: denom.exchangeBaseUrl,
-      });
-    }
+        // Denomination of the first coin, we assume that all other
+        // coins have the same currency
+        const firstDenom = await tx.denominations.get([
+          exchange.baseUrl,
+          coins[0].denomPubHash,
+        ]);
+        if (!firstDenom) {
+          throw Error("db inconsistent");
+        }
+        const currency = firstDenom.value.currency;
+        for (const coin of coins) {
+          const denom = await tx.denominations.get([
+            exchange.baseUrl,
+            coin.denomPubHash,
+          ]);
+          if (!denom) {
+            throw Error("db inconsistent");
+          }
+          if (denom.value.currency !== currency) {
+            logger.warn(
+              `same pubkey for different currencies at exchange 
${exchange.baseUrl}`,
+            );
+            continue;
+          }
+          if (!isSpendableCoin(coin, denom)) {
+            continue;
+          }
+          candidateCoins.push({
+            availableAmount: coin.currentAmount,
+            coinPub: coin.coinPub,
+            denomPub: coin.denomPub,
+            feeDeposit: denom.feeDeposit,
+            exchangeBaseUrl: denom.exchangeBaseUrl,
+          });
+        }
 
-    let wireFee: AmountJson | undefined;
-    for (const fee of exchangeFees.feesForType[req.wireMethod] || []) {
-      if (fee.startStamp <= req.timestamp && fee.endStamp >= req.timestamp) {
-        wireFee = fee.wireFee;
-        break;
+        let wireFee: AmountJson | undefined;
+        for (const fee of exchangeFees.feesForType[req.wireMethod] || []) {
+          if (
+            fee.startStamp <= req.timestamp &&
+            fee.endStamp >= req.timestamp
+          ) {
+            wireFee = fee.wireFee;
+            break;
+          }
+        }
+        if (wireFee) {
+          wireFeesPerExchange[exchange.baseUrl] = wireFee;
+        }
       }
-    }
-    if (wireFee) {
-      wireFeesPerExchange[exchange.baseUrl] = wireFee;
-    }
-  }
+    });
 
   return {
     candidateCoins,
@@ -351,15 +370,15 @@ export async function getCandidatePayCoins(
 
 export async function applyCoinSpend(
   ws: InternalWalletState,
-  tx: TransactionHandle<
-    | typeof Stores.coins
-    | typeof Stores.refreshGroups
-    | typeof Stores.denominations
-  >,
+  tx: GetReadWriteAccess<{
+    coins: typeof WalletStoresV1.coins;
+    refreshGroups: typeof WalletStoresV1.refreshGroups;
+    denominations: typeof WalletStoresV1.denominations;
+  }>,
   coinSelection: PayCoinSelection,
 ) {
   for (let i = 0; i < coinSelection.coinPubs.length; i++) {
-    const coin = await tx.get(Stores.coins, coinSelection.coinPubs[i]);
+    const coin = await tx.coins.get(coinSelection.coinPubs[i]);
     if (!coin) {
       throw Error("coin allocated for payment doesn't exist anymore");
     }
@@ -379,7 +398,7 @@ export async function applyCoinSpend(
       throw Error("not enough remaining balance on coin for payment");
     }
     coin.currentAmount = remaining.amount;
-    await tx.put(Stores.coins, coin);
+    await tx.coins.put(coin);
   }
   const refreshCoinPubs = coinSelection.coinPubs.map((x) => ({
     coinPub: x,
@@ -437,26 +456,25 @@ async function recordConfirmPay(
     noncePub: proposal.noncePub,
   };
 
-  await ws.db.runWithWriteTransaction(
-    [
-      Stores.coins,
-      Stores.purchases,
-      Stores.proposals,
-      Stores.refreshGroups,
-      Stores.denominations,
-    ],
-    async (tx) => {
-      const p = await tx.get(Stores.proposals, proposal.proposalId);
+  await ws.db
+    .mktx((x) => ({
+      proposals: x.proposals,
+      purchases: x.purchases,
+      coins: x.coins,
+      refreshGroups: x.refreshGroups,
+      denominations: x.denominations,
+    }))
+    .runReadWrite(async (tx) => {
+      const p = await tx.proposals.get(proposal.proposalId);
       if (p) {
         p.proposalStatus = ProposalStatus.ACCEPTED;
         p.lastError = undefined;
         p.retryInfo = initRetryInfo(false);
-        await tx.put(Stores.proposals, p);
+        await tx.proposals.put(p);
       }
-      await tx.put(Stores.purchases, t);
+      await tx.purchases.put(t);
       await applyCoinSpend(ws, tx, coinSelection);
-    },
-  );
+    });
 
   ws.notify({
     type: NotificationType.ProposalAccepted,
@@ -470,19 +488,21 @@ async function incrementProposalRetry(
   proposalId: string,
   err: TalerErrorDetails | undefined,
 ): Promise<void> {
-  await ws.db.runWithWriteTransaction([Stores.proposals], async (tx) => {
-    const pr = await tx.get(Stores.proposals, proposalId);
-    if (!pr) {
-      return;
-    }
-    if (!pr.retryInfo) {
-      return;
-    }
-    pr.retryInfo.retryCounter++;
-    updateRetryInfoTimeout(pr.retryInfo);
-    pr.lastError = err;
-    await tx.put(Stores.proposals, pr);
-  });
+  await ws.db
+    .mktx((x) => ({ proposals: x.proposals }))
+    .runReadWrite(async (tx) => {
+      const pr = await tx.proposals.get(proposalId);
+      if (!pr) {
+        return;
+      }
+      if (!pr.retryInfo) {
+        return;
+      }
+      pr.retryInfo.retryCounter++;
+      updateRetryInfoTimeout(pr.retryInfo);
+      pr.lastError = err;
+      await tx.proposals.put(pr);
+    });
   if (err) {
     ws.notify({ type: NotificationType.ProposalOperationError, error: err });
   }
@@ -494,19 +514,21 @@ async function incrementPurchasePayRetry(
   err: TalerErrorDetails | undefined,
 ): Promise<void> {
   logger.warn("incrementing purchase pay retry with error", err);
-  await ws.db.runWithWriteTransaction([Stores.purchases], async (tx) => {
-    const pr = await tx.get(Stores.purchases, proposalId);
-    if (!pr) {
-      return;
-    }
-    if (!pr.payRetryInfo) {
-      return;
-    }
-    pr.payRetryInfo.retryCounter++;
-    updateRetryInfoTimeout(pr.payRetryInfo);
-    pr.lastPayError = err;
-    await tx.put(Stores.purchases, pr);
-  });
+  await ws.db
+    .mktx((x) => ({ purchases: x.purchases }))
+    .runReadWrite(async (tx) => {
+      const pr = await tx.purchases.get(proposalId);
+      if (!pr) {
+        return;
+      }
+      if (!pr.payRetryInfo) {
+        return;
+      }
+      pr.payRetryInfo.retryCounter++;
+      updateRetryInfoTimeout(pr.payRetryInfo);
+      pr.lastPayError = err;
+      await tx.purchases.put(pr);
+    });
   if (err) {
     ws.notify({ type: NotificationType.PayOperationError, error: err });
   }
@@ -529,12 +551,15 @@ async function resetDownloadProposalRetry(
   ws: InternalWalletState,
   proposalId: string,
 ): Promise<void> {
-  await ws.db.mutate(Stores.proposals, proposalId, (x) => {
-    if (x.retryInfo.active) {
-      x.retryInfo = initRetryInfo();
-    }
-    return x;
-  });
+  await ws.db
+    .mktx((x) => ({ proposals: x.proposals }))
+    .runReadWrite(async (tx) => {
+      const p = await tx.proposals.get(proposalId);
+      if (p && p.retryInfo.active) {
+        p.retryInfo = initRetryInfo();
+        await tx.proposals.put(p);
+      }
+    });
 }
 
 async function failProposalPermanently(
@@ -542,12 +567,18 @@ async function failProposalPermanently(
   proposalId: string,
   err: TalerErrorDetails,
 ): Promise<void> {
-  await ws.db.mutate(Stores.proposals, proposalId, (x) => {
-    x.retryInfo.active = false;
-    x.lastError = err;
-    x.proposalStatus = ProposalStatus.PERMANENTLY_FAILED;
-    return x;
-  });
+  await ws.db
+    .mktx((x) => ({ proposals: x.proposals }))
+    .runReadWrite(async (tx) => {
+      const p = await tx.proposals.get(proposalId);
+      if (!p) {
+        return;
+      }
+      p.retryInfo.active = false;
+      p.lastError = err;
+      p.proposalStatus = ProposalStatus.PERMANENTLY_FAILED;
+      await tx.proposals.put(p);
+    });
 }
 
 function getProposalRequestTimeout(proposal: ProposalRecord): Duration {
@@ -616,7 +647,11 @@ async function processDownloadProposalImpl(
   if (forceNow) {
     await resetDownloadProposalRetry(ws, proposalId);
   }
-  const proposal = await ws.db.get(Stores.proposals, proposalId);
+  const proposal = await ws.db
+    .mktx((x) => ({ proposals: x.proposals }))
+    .runReadOnly(async (tx) => {
+      return tx.proposals.get(proposalId);
+    });
   if (!proposal) {
     return;
   }
@@ -750,10 +785,10 @@ async function processDownloadProposalImpl(
     proposalResp.sig,
   );
 
-  await ws.db.runWithWriteTransaction(
-    [Stores.proposals, Stores.purchases],
-    async (tx) => {
-      const p = await tx.get(Stores.proposals, proposalId);
+  await ws.db
+    .mktx((x) => ({ proposals: x.proposals, purchases: x.purchases }))
+    .runReadWrite(async (tx) => {
+      const p = await tx.proposals.get(proposalId);
       if (!p) {
         return;
       }
@@ -769,22 +804,20 @@ async function processDownloadProposalImpl(
         (fulfillmentUrl.startsWith("http://";) ||
           fulfillmentUrl.startsWith("https://";))
       ) {
-        const differentPurchase = await tx.getIndexed(
-          Stores.purchases.fulfillmentUrlIndex,
+        const differentPurchase = await 
tx.purchases.indexes.byFulfillmentUrl.get(
           fulfillmentUrl,
         );
         if (differentPurchase) {
           logger.warn("repurchase detected");
           p.proposalStatus = ProposalStatus.REPURCHASE;
           p.repurchaseProposalId = differentPurchase.proposalId;
-          await tx.put(Stores.proposals, p);
+          await tx.proposals.put(p);
           return;
         }
       }
       p.proposalStatus = ProposalStatus.PROPOSED;
-      await tx.put(Stores.proposals, p);
-    },
-  );
+      await tx.proposals.put(p);
+    });
 
   ws.notify({
     type: NotificationType.ProposalDownloaded,
@@ -806,10 +839,14 @@ async function startDownloadProposal(
   sessionId: string | undefined,
   claimToken: string | undefined,
 ): Promise<string> {
-  const oldProposal = await ws.db.getIndexed(
-    Stores.proposals.urlAndOrderIdIndex,
-    [merchantBaseUrl, orderId],
-  );
+  const oldProposal = await ws.db
+    .mktx((x) => ({ proposals: x.proposals }))
+    .runReadOnly(async (tx) => {
+      return tx.proposals.indexes.byUrlAndOrderId.get([
+        merchantBaseUrl,
+        orderId,
+      ]);
+    });
   if (oldProposal) {
     await processDownloadProposal(ws, oldProposal.proposalId);
     return oldProposal.proposalId;
@@ -834,17 +871,19 @@ async function startDownloadProposal(
     downloadSessionId: sessionId,
   };
 
-  await ws.db.runWithWriteTransaction([Stores.proposals], async (tx) => {
-    const existingRecord = await tx.getIndexed(
-      Stores.proposals.urlAndOrderIdIndex,
-      [merchantBaseUrl, orderId],
-    );
-    if (existingRecord) {
-      // Created concurrently
-      return;
-    }
-    await tx.put(Stores.proposals, proposalRecord);
-  });
+  await ws.db
+    .mktx((x) => ({ proposals: x.proposals }))
+    .runReadWrite(async (tx) => {
+      const existingRecord = tx.proposals.indexes.byUrlAndOrderId.get([
+        merchantBaseUrl,
+        orderId,
+      ]);
+      if (existingRecord) {
+        // Created concurrently
+        return;
+      }
+      await tx.proposals.put(proposalRecord);
+    });
 
   await processDownloadProposal(ws, proposalId);
   return proposalId;
@@ -857,37 +896,38 @@ async function storeFirstPaySuccess(
   paySig: string,
 ): Promise<void> {
   const now = getTimestampNow();
-  await ws.db.runWithWriteTransaction([Stores.purchases], async (tx) => {
-    const purchase = await tx.get(Stores.purchases, proposalId);
+  await ws.db
+    .mktx((x) => ({ purchases: x.purchases }))
+    .runReadWrite(async (tx) => {
+      const purchase = await tx.purchases.get(proposalId);
 
-    if (!purchase) {
-      logger.warn("purchase does not exist anymore");
-      return;
-    }
-    const isFirst = purchase.timestampFirstSuccessfulPay === undefined;
-    if (!isFirst) {
-      logger.warn("payment success already stored");
-      return;
-    }
-    purchase.timestampFirstSuccessfulPay = now;
-    purchase.paymentSubmitPending = false;
-    purchase.lastPayError = undefined;
-    purchase.lastSessionId = sessionId;
-    purchase.payRetryInfo = initRetryInfo(false);
-    purchase.merchantPaySig = paySig;
-    if (isFirst) {
-      const ar = purchase.download.contractData.autoRefund;
-      if (ar) {
-        logger.info("auto_refund present");
-        purchase.refundQueryRequested = true;
-        purchase.refundStatusRetryInfo = initRetryInfo();
-        purchase.lastRefundStatusError = undefined;
-        purchase.autoRefundDeadline = timestampAddDuration(now, ar);
+      if (!purchase) {
+        logger.warn("purchase does not exist anymore");
+        return;
       }
-    }
-
-    await tx.put(Stores.purchases, purchase);
-  });
+      const isFirst = purchase.timestampFirstSuccessfulPay === undefined;
+      if (!isFirst) {
+        logger.warn("payment success already stored");
+        return;
+      }
+      purchase.timestampFirstSuccessfulPay = now;
+      purchase.paymentSubmitPending = false;
+      purchase.lastPayError = undefined;
+      purchase.lastSessionId = sessionId;
+      purchase.payRetryInfo = initRetryInfo(false);
+      purchase.merchantPaySig = paySig;
+      if (isFirst) {
+        const ar = purchase.download.contractData.autoRefund;
+        if (ar) {
+          logger.info("auto_refund present");
+          purchase.refundQueryRequested = true;
+          purchase.refundStatusRetryInfo = initRetryInfo();
+          purchase.lastRefundStatusError = undefined;
+          purchase.autoRefundDeadline = timestampAddDuration(now, ar);
+        }
+      }
+      await tx.purchases.put(purchase);
+    });
 }
 
 async function storePayReplaySuccess(
@@ -895,23 +935,25 @@ async function storePayReplaySuccess(
   proposalId: string,
   sessionId: string | undefined,
 ): Promise<void> {
-  await ws.db.runWithWriteTransaction([Stores.purchases], async (tx) => {
-    const purchase = await tx.get(Stores.purchases, proposalId);
+  await ws.db
+    .mktx((x) => ({ purchases: x.purchases }))
+    .runReadWrite(async (tx) => {
+      const purchase = await tx.purchases.get(proposalId);
 
-    if (!purchase) {
-      logger.warn("purchase does not exist anymore");
-      return;
-    }
-    const isFirst = purchase.timestampFirstSuccessfulPay === undefined;
-    if (isFirst) {
-      throw Error("invalid payment state");
-    }
-    purchase.paymentSubmitPending = false;
-    purchase.lastPayError = undefined;
-    purchase.payRetryInfo = initRetryInfo(false);
-    purchase.lastSessionId = sessionId;
-    await tx.put(Stores.purchases, purchase);
-  });
+      if (!purchase) {
+        logger.warn("purchase does not exist anymore");
+        return;
+      }
+      const isFirst = purchase.timestampFirstSuccessfulPay === undefined;
+      if (isFirst) {
+        throw Error("invalid payment state");
+      }
+      purchase.paymentSubmitPending = false;
+      purchase.lastPayError = undefined;
+      purchase.payRetryInfo = initRetryInfo(false);
+      purchase.lastSessionId = sessionId;
+      await tx.purchases.put(purchase);
+    });
 }
 
 /**
@@ -929,7 +971,11 @@ async function handleInsufficientFunds(
 ): Promise<void> {
   logger.trace("handling insufficient funds, trying to re-select coins");
 
-  const proposal = await ws.db.get(Stores.purchases, proposalId);
+  const proposal = await ws.db
+    .mktx((x) => ({ purchaes: x.purchases }))
+    .runReadOnly(async (tx) => {
+      return tx.purchaes.get(proposalId);
+    });
   if (!proposal) {
     return;
   }
@@ -961,30 +1007,34 @@ async function handleInsufficientFunds(
 
   const prevPayCoins: PreviousPayCoins = [];
 
-  for (let i = 0; i < proposal.payCoinSelection.coinPubs.length; i++) {
-    const coinPub = proposal.payCoinSelection.coinPubs[i];
-    if (coinPub === brokenCoinPub) {
-      continue;
-    }
-    const contrib = proposal.payCoinSelection.coinContributions[i];
-    const coin = await ws.db.get(Stores.coins, coinPub);
-    if (!coin) {
-      continue;
-    }
-    const denom = await ws.db.get(Stores.denominations, [
-      coin.exchangeBaseUrl,
-      coin.denomPubHash,
-    ]);
-    if (!denom) {
-      continue;
-    }
-    prevPayCoins.push({
-      coinPub,
-      contribution: contrib,
-      exchangeBaseUrl: coin.exchangeBaseUrl,
-      feeDeposit: denom.feeDeposit,
+  await ws.db
+    .mktx((x) => ({ coins: x.coins, denominations: x.denominations }))
+    .runReadOnly(async (tx) => {
+      for (let i = 0; i < proposal.payCoinSelection.coinPubs.length; i++) {
+        const coinPub = proposal.payCoinSelection.coinPubs[i];
+        if (coinPub === brokenCoinPub) {
+          continue;
+        }
+        const contrib = proposal.payCoinSelection.coinContributions[i];
+        const coin = await tx.coins.get(coinPub);
+        if (!coin) {
+          continue;
+        }
+        const denom = await tx.denominations.get([
+          coin.exchangeBaseUrl,
+          coin.denomPubHash,
+        ]);
+        if (!denom) {
+          continue;
+        }
+        prevPayCoins.push({
+          coinPub,
+          contribution: contrib,
+          exchangeBaseUrl: coin.exchangeBaseUrl,
+          feeDeposit: denom.feeDeposit,
+        });
+      }
     });
-  }
 
   const res = selectPayCoins({
     candidates,
@@ -1002,24 +1052,23 @@ async function handleInsufficientFunds(
 
   logger.trace("re-selected coins");
 
-  await ws.db.runWithWriteTransaction(
-    [
-      Stores.purchases,
-      Stores.coins,
-      Stores.denominations,
-      Stores.refreshGroups,
-    ],
-    async (tx) => {
-      const p = await tx.get(Stores.purchases, proposalId);
+  await ws.db
+    .mktx((x) => ({
+      purchases: x.purchases,
+      coins: x.coins,
+      denominations: x.denominations,
+      refreshGroups: x.refreshGroups,
+    }))
+    .runReadWrite(async (tx) => {
+      const p = await tx.purchases.get(proposalId);
       if (!p) {
         return;
       }
       p.payCoinSelection = res;
       p.coinDepositPermissions = undefined;
-      await tx.put(Stores.purchases, p);
+      await tx.purchases.put(p);
       await applyCoinSpend(ws, tx, res);
-    },
-  );
+    });
 }
 
 /**
@@ -1032,7 +1081,11 @@ async function submitPay(
   ws: InternalWalletState,
   proposalId: string,
 ): Promise<ConfirmPayResult> {
-  const purchase = await ws.db.get(Stores.purchases, proposalId);
+  const purchase = await ws.db
+    .mktx((x) => ({ purchases: x.purchases }))
+    .runReadOnly(async (tx) => {
+      return tx.purchases.get(proposalId);
+    });
   if (!purchase) {
     throw Error("Purchase not found: " + proposalId);
   }
@@ -1202,7 +1255,11 @@ export async function checkPaymentByProposalId(
   proposalId: string,
   sessionId?: string,
 ): Promise<PreparePayResult> {
-  let proposal = await ws.db.get(Stores.proposals, proposalId);
+  let proposal = await ws.db
+    .mktx((x) => ({ proposals: x.proposals }))
+    .runReadOnly(async (tx) => {
+      return tx.proposals.get(proposalId);
+    });
   if (!proposal) {
     throw Error(`could not get proposal ${proposalId}`);
   }
@@ -1212,7 +1269,11 @@ export async function checkPaymentByProposalId(
       throw Error("invalid proposal state");
     }
     logger.trace("using existing purchase for same product");
-    proposal = await ws.db.get(Stores.proposals, existingProposalId);
+    proposal = await ws.db
+      .mktx((x) => ({ proposals: x.proposals }))
+      .runReadOnly(async (tx) => {
+        return tx.proposals.get(existingProposalId);
+      });
     if (!proposal) {
       throw Error("existing proposal is in wrong state");
     }
@@ -1231,7 +1292,11 @@ export async function checkPaymentByProposalId(
   proposalId = proposal.proposalId;
 
   // First check if we already paid for it.
-  const purchase = await ws.db.get(Stores.purchases, proposalId);
+  const purchase = await ws.db
+    .mktx((x) => ({ purchases: x.purchases }))
+    .runReadOnly(async (tx) => {
+      return tx.purchases.get(proposalId);
+    });
 
   if (!purchase) {
     // If not already paid, check if we could pay for it.
@@ -1281,14 +1346,16 @@ export async function checkPaymentByProposalId(
     logger.trace(
       "automatically re-submitting payment with different session ID",
     );
-    await ws.db.runWithWriteTransaction([Stores.purchases], async (tx) => {
-      const p = await tx.get(Stores.purchases, proposalId);
-      if (!p) {
-        return;
-      }
-      p.lastSessionId = sessionId;
-      await tx.put(Stores.purchases, p);
-    });
+    await ws.db
+      .mktx((x) => ({ purchases: x.purchases }))
+      .runReadWrite(async (tx) => {
+        const p = await tx.purchases.get(proposalId);
+        if (!p) {
+          return;
+        }
+        p.lastSessionId = sessionId;
+        await tx.purchases.put(p);
+      });
     const r = await guardOperationException(
       () => submitPay(ws, proposalId),
       (e: TalerErrorDetails): Promise<void> =>
@@ -1375,20 +1442,33 @@ export async function generateDepositPermissions(
   contractData: WalletContractData,
 ): Promise<CoinDepositPermission[]> {
   const depositPermissions: CoinDepositPermission[] = [];
+  const coinWithDenom: Array<{
+    coin: CoinRecord;
+    denom: DenominationRecord;
+  }> = [];
+  await ws.db
+    .mktx((x) => ({ coins: x.coins, denominations: x.denominations }))
+    .runReadOnly(async (tx) => {
+      for (let i = 0; i < payCoinSel.coinPubs.length; i++) {
+        const coin = await tx.coins.get(payCoinSel.coinPubs[i]);
+        if (!coin) {
+          throw Error("can't pay, allocated coin not found anymore");
+        }
+        const denom = await tx.denominations.get([
+          coin.exchangeBaseUrl,
+          coin.denomPubHash,
+        ]);
+        if (!denom) {
+          throw Error(
+            "can't pay, denomination of allocated coin not found anymore",
+          );
+        }
+        coinWithDenom.push({ coin, denom });
+      }
+    });
+
   for (let i = 0; i < payCoinSel.coinPubs.length; i++) {
-    const coin = await ws.db.get(Stores.coins, payCoinSel.coinPubs[i]);
-    if (!coin) {
-      throw Error("can't pay, allocated coin not found anymore");
-    }
-    const denom = await ws.db.get(Stores.denominations, [
-      coin.exchangeBaseUrl,
-      coin.denomPubHash,
-    ]);
-    if (!denom) {
-      throw Error(
-        "can't pay, denomination of allocated coin not found anymore",
-      );
-    }
+    const { coin, denom } = coinWithDenom[i];
     const dp = await ws.cryptoApi.signDepositPermission({
       coinPriv: coin.coinPriv,
       coinPub: coin.coinPub,
@@ -1419,7 +1499,11 @@ export async function confirmPay(
   logger.trace(
     `executing confirmPay with proposalId ${proposalId} and sessionIdOverride 
${sessionIdOverride}`,
   );
-  const proposal = await ws.db.get(Stores.proposals, proposalId);
+  const proposal = await ws.db
+    .mktx((x) => ({ proposals: x.proposals }))
+    .runReadOnly(async (tx) => {
+      return tx.proposals.get(proposalId);
+    });
 
   if (!proposal) {
     throw Error(`proposal with id ${proposalId} not found`);
@@ -1430,20 +1514,24 @@ export async function confirmPay(
     throw Error("proposal is in invalid state");
   }
 
-  let purchase = await ws.db.get(Stores.purchases, proposalId);
+  const existingPurchase = await ws.db
+    .mktx((x) => ({ purchases: x.purchases }))
+    .runReadWrite(async (tx) => {
+      const purchase = await tx.purchases.get(proposalId);
+      if (
+        purchase &&
+        sessionIdOverride !== undefined &&
+        sessionIdOverride != purchase.lastSessionId
+      ) {
+        logger.trace(`changing session ID to ${sessionIdOverride}`);
+        purchase.lastSessionId = sessionIdOverride;
+        purchase.paymentSubmitPending = true;
+        await tx.purchases.put(purchase);
+      }
+      return purchase;
+    });
 
-  if (purchase) {
-    if (
-      sessionIdOverride !== undefined &&
-      sessionIdOverride != purchase.lastSessionId
-    ) {
-      logger.trace(`changing session ID to ${sessionIdOverride}`);
-      await ws.db.mutate(Stores.purchases, purchase.proposalId, (x) => {
-        x.lastSessionId = sessionIdOverride;
-        x.paymentSubmitPending = true;
-        return x;
-      });
-    }
+  if (existingPurchase) {
     logger.trace("confirmPay: submitting payment for existing purchase");
     return await guardOperationException(
       () => submitPay(ws, proposalId),
@@ -1491,7 +1579,7 @@ export async function confirmPay(
     res,
     d.contractData,
   );
-  purchase = await recordConfirmPay(
+  await recordConfirmPay(
     ws,
     proposal,
     res,
@@ -1523,12 +1611,15 @@ async function resetPurchasePayRetry(
   ws: InternalWalletState,
   proposalId: string,
 ): Promise<void> {
-  await ws.db.mutate(Stores.purchases, proposalId, (x) => {
-    if (x.payRetryInfo.active) {
-      x.payRetryInfo = initRetryInfo();
-    }
-    return x;
-  });
+  await ws.db
+    .mktx((x) => ({ purchases: x.purchases }))
+    .runReadWrite(async (tx) => {
+      const p = await tx.purchases.get(proposalId);
+      if (p) {
+        p.payRetryInfo = initRetryInfo();
+        await tx.purchases.put(p);
+      }
+    });
 }
 
 async function processPurchasePayImpl(
@@ -1539,7 +1630,11 @@ async function processPurchasePayImpl(
   if (forceNow) {
     await resetPurchasePayRetry(ws, proposalId);
   }
-  const purchase = await ws.db.get(Stores.purchases, proposalId);
+  const purchase = await ws.db
+    .mktx((x) => ({ purchases: x.purchases }))
+    .runReadOnly(async (tx) => {
+      return tx.purchases.get(proposalId);
+    });
   if (!purchase) {
     return;
   }
@@ -1554,10 +1649,10 @@ export async function refuseProposal(
   ws: InternalWalletState,
   proposalId: string,
 ): Promise<void> {
-  const success = await ws.db.runWithWriteTransaction(
-    [Stores.proposals],
-    async (tx) => {
-      const proposal = await tx.get(Stores.proposals, proposalId);
+  const success = await ws.db
+    .mktx((x) => ({ proposals: x.proposals }))
+    .runReadWrite(async (tx) => {
+      const proposal = await tx.proposals.get(proposalId);
       if (!proposal) {
         logger.trace(`proposal ${proposalId} not found, won't refuse 
proposal`);
         return false;
@@ -1566,10 +1661,9 @@ export async function refuseProposal(
         return false;
       }
       proposal.proposalStatus = ProposalStatus.REFUSED;
-      await tx.put(Stores.proposals, proposal);
+      await tx.proposals.put(proposal);
       return true;
-    },
-  );
+    });
   if (success) {
     ws.notify({
       type: NotificationType.ProposalRefused,
diff --git a/packages/taler-wallet-core/src/operations/pending.ts 
b/packages/taler-wallet-core/src/operations/pending.ts
index 85f8faa1..4eee8527 100644
--- a/packages/taler-wallet-core/src/operations/pending.ts
+++ b/packages/taler-wallet-core/src/operations/pending.ts
@@ -21,8 +21,8 @@ import {
   ExchangeUpdateStatus,
   ProposalStatus,
   ReserveRecordStatus,
-  Stores,
   AbortStatus,
+  WalletStoresV1,
 } from "../db.js";
 import {
   PendingOperationsResponse,
@@ -37,10 +37,10 @@ import {
   getDurationRemaining,
   durationMin,
 } from "@gnu-taler/taler-util";
-import { TransactionHandle } from "../util/query";
 import { InternalWalletState } from "./state";
 import { getBalancesInsideTransaction } from "./balance";
 import { getExchangeDetails } from "./exchanges.js";
+import { GetReadOnlyAccess } from "../util/query.js";
 
 function updateRetryDelay(
   oldDelay: Duration,
@@ -53,14 +53,15 @@ function updateRetryDelay(
 }
 
 async function gatherExchangePending(
-  tx: TransactionHandle<
-    typeof Stores.exchanges | typeof Stores.exchangeDetails
-  >,
+  tx: GetReadOnlyAccess<{
+    exchanges: typeof WalletStoresV1.exchanges;
+    exchangeDetails: typeof WalletStoresV1.exchangeDetails;
+  }>,
   now: Timestamp,
   resp: PendingOperationsResponse,
   onlyDue = false,
 ): Promise<void> {
-  await tx.iter(Stores.exchanges).forEachAsync(async (e) => {
+  await tx.exchanges.iter().forEachAsync(async (e) => {
     switch (e.updateStatus) {
       case ExchangeUpdateStatus.Finished:
         if (e.lastError) {
@@ -153,13 +154,13 @@ async function gatherExchangePending(
 }
 
 async function gatherReservePending(
-  tx: TransactionHandle<typeof Stores.reserves>,
+  tx: GetReadOnlyAccess<{ reserves: typeof WalletStoresV1.reserves }>,
   now: Timestamp,
   resp: PendingOperationsResponse,
   onlyDue = false,
 ): Promise<void> {
   // FIXME: this should be optimized by using an index for "onlyDue==true".
-  await tx.iter(Stores.reserves).forEach((reserve) => {
+  await tx.reserves.iter().forEach((reserve) => {
     const reserveType = reserve.bankInfo
       ? ReserveType.TalerBankWithdraw
       : ReserveType.Manual;
@@ -207,12 +208,12 @@ async function gatherReservePending(
 }
 
 async function gatherRefreshPending(
-  tx: TransactionHandle<typeof Stores.refreshGroups>,
+  tx: GetReadOnlyAccess<{ refreshGroups: typeof WalletStoresV1.refreshGroups 
}>,
   now: Timestamp,
   resp: PendingOperationsResponse,
   onlyDue = false,
 ): Promise<void> {
-  await tx.iter(Stores.refreshGroups).forEach((r) => {
+  await tx.refreshGroups.iter().forEach((r) => {
     if (r.timestampFinished) {
       return;
     }
@@ -236,12 +237,15 @@ async function gatherRefreshPending(
 }
 
 async function gatherWithdrawalPending(
-  tx: TransactionHandle<typeof Stores.withdrawalGroups>,
+  tx: GetReadOnlyAccess<{
+    withdrawalGroups: typeof WalletStoresV1.withdrawalGroups;
+    planchets: typeof WalletStoresV1.planchets;
+  }>,
   now: Timestamp,
   resp: PendingOperationsResponse,
   onlyDue = false,
 ): Promise<void> {
-  await tx.iter(Stores.withdrawalGroups).forEachAsync(async (wsr) => {
+  await tx.withdrawalGroups.iter().forEachAsync(async (wsr) => {
     if (wsr.timestampFinish) {
       return;
     }
@@ -255,8 +259,8 @@ async function gatherWithdrawalPending(
     }
     let numCoinsWithdrawn = 0;
     let numCoinsTotal = 0;
-    await tx
-      .iterIndexed(Stores.planchets.byGroup, wsr.withdrawalGroupId)
+    await tx.planchets.indexes.byGroup
+      .iter(wsr.withdrawalGroupId)
       .forEach((x) => {
         numCoinsTotal++;
         if (x.withdrawalDone) {
@@ -276,12 +280,12 @@ async function gatherWithdrawalPending(
 }
 
 async function gatherProposalPending(
-  tx: TransactionHandle<typeof Stores.proposals>,
+  tx: GetReadOnlyAccess<{ proposals: typeof WalletStoresV1.proposals }>,
   now: Timestamp,
   resp: PendingOperationsResponse,
   onlyDue = false,
 ): Promise<void> {
-  await tx.iter(Stores.proposals).forEach((proposal) => {
+  await tx.proposals.iter().forEach((proposal) => {
     if (proposal.proposalStatus == ProposalStatus.PROPOSED) {
       if (onlyDue) {
         return;
@@ -327,12 +331,12 @@ async function gatherProposalPending(
 }
 
 async function gatherTipPending(
-  tx: TransactionHandle<typeof Stores.tips>,
+  tx: GetReadOnlyAccess<{ tips: typeof WalletStoresV1.tips }>,
   now: Timestamp,
   resp: PendingOperationsResponse,
   onlyDue = false,
 ): Promise<void> {
-  await tx.iter(Stores.tips).forEach((tip) => {
+  await tx.tips.iter().forEach((tip) => {
     if (tip.pickedUpTimestamp) {
       return;
     }
@@ -357,12 +361,12 @@ async function gatherTipPending(
 }
 
 async function gatherPurchasePending(
-  tx: TransactionHandle<typeof Stores.purchases>,
+  tx: GetReadOnlyAccess<{ purchases: typeof WalletStoresV1.purchases }>,
   now: Timestamp,
   resp: PendingOperationsResponse,
   onlyDue = false,
 ): Promise<void> {
-  await tx.iter(Stores.purchases).forEach((pr) => {
+  await tx.purchases.iter().forEach((pr) => {
     if (pr.paymentSubmitPending && pr.abortStatus === AbortStatus.None) {
       resp.nextRetryDelay = updateRetryDelay(
         resp.nextRetryDelay,
@@ -400,12 +404,12 @@ async function gatherPurchasePending(
 }
 
 async function gatherRecoupPending(
-  tx: TransactionHandle<typeof Stores.recoupGroups>,
+  tx: GetReadOnlyAccess<{ recoupGroups: typeof WalletStoresV1.recoupGroups }>,
   now: Timestamp,
   resp: PendingOperationsResponse,
   onlyDue = false,
 ): Promise<void> {
-  await tx.iter(Stores.recoupGroups).forEach((rg) => {
+  await tx.recoupGroups.iter().forEach((rg) => {
     if (rg.timestampFinished) {
       return;
     }
@@ -428,12 +432,12 @@ async function gatherRecoupPending(
 }
 
 async function gatherDepositPending(
-  tx: TransactionHandle<typeof Stores.depositGroups>,
+  tx: GetReadOnlyAccess<{ depositGroups: typeof WalletStoresV1.depositGroups 
}>,
   now: Timestamp,
   resp: PendingOperationsResponse,
   onlyDue = false,
 ): Promise<void> {
-  await tx.iter(Stores.depositGroups).forEach((dg) => {
+  await tx.depositGroups.iter().forEach((dg) => {
     if (dg.timestampFinished) {
       return;
     }
@@ -460,21 +464,22 @@ export async function getPendingOperations(
   { onlyDue = false } = {},
 ): Promise<PendingOperationsResponse> {
   const now = getTimestampNow();
-  return await ws.db.runWithReadTransaction(
-    [
-      Stores.exchanges,
-      Stores.reserves,
-      Stores.refreshGroups,
-      Stores.coins,
-      Stores.withdrawalGroups,
-      Stores.proposals,
-      Stores.tips,
-      Stores.purchases,
-      Stores.recoupGroups,
-      Stores.planchets,
-      Stores.depositGroups,
-    ],
-    async (tx) => {
+  return await ws.db
+    .mktx((x) => ({
+      exchanges: x.exchanges,
+      exchangeDetails: x.exchangeDetails,
+      reserves: x.reserves,
+      refreshGroups: x.refreshGroups,
+      coins: x.coins,
+      withdrawalGroups: x.withdrawalGroups,
+      proposals: x.proposals,
+      tips: x.tips,
+      purchases: x.purchases,
+      planchets: x.planchets,
+      depositGroups: x.depositGroups,
+      recoupGroups: x.recoupGroups,
+    }))
+    .runReadWrite(async (tx) => {
       const walletBalance = await getBalancesInsideTransaction(ws, tx);
       const resp: PendingOperationsResponse = {
         nextRetryDelay: { d_ms: Number.MAX_SAFE_INTEGER },
@@ -492,6 +497,5 @@ export async function getPendingOperations(
       await gatherRecoupPending(tx, now, resp, onlyDue);
       await gatherDepositPending(tx, now, resp, onlyDue);
       return resp;
-    },
-  );
+    });
 }
diff --git a/packages/taler-wallet-core/src/operations/recoup.ts 
b/packages/taler-wallet-core/src/operations/recoup.ts
index da01ca82..7dac7faf 100644
--- a/packages/taler-wallet-core/src/operations/recoup.ts
+++ b/packages/taler-wallet-core/src/operations/recoup.ts
@@ -40,20 +40,19 @@ import {
   RecoupGroupRecord,
   RefreshCoinSource,
   ReserveRecordStatus,
-  Stores,
   WithdrawCoinSource,
+  WalletStoresV1,
 } from "../db.js";
 
 import { readSuccessResponseJsonOrThrow } from "../util/http";
 import { Logger } from "@gnu-taler/taler-util";
-import { TransactionHandle } from "../util/query";
 import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries";
 import { URL } from "../util/url";
 import { guardOperationException } from "./errors";
-import { getExchangeDetails } from "./exchanges.js";
 import { createRefreshGroup, processRefreshGroup } from "./refresh";
 import { getReserveRequestTimeout, processReserve } from "./reserves";
 import { InternalWalletState } from "./state";
+import { GetReadWriteAccess } from "../util/query.js";
 
 const logger = new Logger("operations/recoup.ts");
 
@@ -62,19 +61,23 @@ async function incrementRecoupRetry(
   recoupGroupId: string,
   err: TalerErrorDetails | undefined,
 ): Promise<void> {
-  await ws.db.runWithWriteTransaction([Stores.recoupGroups], async (tx) => {
-    const r = await tx.get(Stores.recoupGroups, recoupGroupId);
-    if (!r) {
-      return;
-    }
-    if (!r.retryInfo) {
-      return;
-    }
-    r.retryInfo.retryCounter++;
-    updateRetryInfoTimeout(r.retryInfo);
-    r.lastError = err;
-    await tx.put(Stores.recoupGroups, r);
-  });
+  await ws.db
+    .mktx((x) => ({
+      recoupGroups: x.recoupGroups,
+    }))
+    .runReadWrite(async (tx) => {
+      const r = await tx.recoupGroups.get(recoupGroupId);
+      if (!r) {
+        return;
+      }
+      if (!r.retryInfo) {
+        return;
+      }
+      r.retryInfo.retryCounter++;
+      updateRetryInfoTimeout(r.retryInfo);
+      r.lastError = err;
+      await tx.recoupGroups.put(r);
+    });
   if (err) {
     ws.notify({ type: NotificationType.RecoupOperationError, error: err });
   }
@@ -82,7 +85,12 @@ async function incrementRecoupRetry(
 
 async function putGroupAsFinished(
   ws: InternalWalletState,
-  tx: TransactionHandle<typeof Stores.recoupGroups>,
+  tx: GetReadWriteAccess<{
+    recoupGroups: typeof WalletStoresV1.recoupGroups;
+    denominations: typeof WalletStoresV1.denominations;
+    refreshGroups: typeof WalletStoresV1.refreshGroups;
+    coins: typeof WalletStoresV1.coins;
+  }>,
   recoupGroup: RecoupGroupRecord,
   coinIdx: number,
 ): Promise<void> {
@@ -116,7 +124,7 @@ async function putGroupAsFinished(
       });
     }
   }
-  await tx.put(Stores.recoupGroups, recoupGroup);
+  await tx.recoupGroups.put(recoupGroup);
 }
 
 async function recoupTipCoin(
@@ -128,16 +136,23 @@ async function recoupTipCoin(
   // 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.runWithWriteTransaction([Stores.recoupGroups], async (tx) => {
-    const recoupGroup = await tx.get(Stores.recoupGroups, recoupGroupId);
-    if (!recoupGroup) {
-      return;
-    }
-    if (recoupGroup.recoupFinishedPerCoin[coinIdx]) {
-      return;
-    }
-    await putGroupAsFinished(ws, tx, recoupGroup, coinIdx);
-  });
+  await ws.db
+    .mktx((x) => ({
+      recoupGroups: x.recoupGroups,
+      denominations: WalletStoresV1.denominations,
+      refreshGroups: WalletStoresV1.refreshGroups,
+      coins: WalletStoresV1.coins,
+    }))
+    .runReadWrite(async (tx) => {
+      const recoupGroup = await tx.recoupGroups.get(recoupGroupId);
+      if (!recoupGroup) {
+        return;
+      }
+      if (recoupGroup.recoupFinishedPerCoin[coinIdx]) {
+        return;
+      }
+      await putGroupAsFinished(ws, tx, recoupGroup, coinIdx);
+    });
 }
 
 async function recoupWithdrawCoin(
@@ -148,7 +163,13 @@ async function recoupWithdrawCoin(
   cs: WithdrawCoinSource,
 ): Promise<void> {
   const reservePub = cs.reservePub;
-  const reserve = await ws.db.get(Stores.reserves, reservePub);
+  const reserve = await ws.db
+    .mktx((x) => ({
+      reserves: x.reserves,
+    }))
+    .runReadOnly(async (tx) => {
+      return tx.reserves.get(reservePub);
+    });
   if (!reserve) {
     // FIXME:  We should at least emit some pending operation / warning for 
this?
     return;
@@ -172,35 +193,29 @@ async function recoupWithdrawCoin(
     throw Error(`Coin's reserve doesn't match reserve on recoup`);
   }
 
-  const exchangeDetails = await ws.db.runWithReadTransaction(
-    [Stores.exchanges, Stores.exchangeDetails],
-    async (tx) => {
-      return getExchangeDetails(tx, reserve.exchangeBaseUrl);
-    },
-  );
-
-  if (!exchangeDetails) {
-    // FIXME: report inconsistency?
-    return;
-  }
-
   // FIXME: verify that our expectations about the amount match
 
-  await ws.db.runWithWriteTransaction(
-    [Stores.coins, Stores.denominations, Stores.reserves, Stores.recoupGroups],
-    async (tx) => {
-      const recoupGroup = await tx.get(Stores.recoupGroups, recoupGroupId);
+  await ws.db
+    .mktx((x) => ({
+      coins: x.coins,
+      denominations: x.denominations,
+      reserves: x.reserves,
+      recoupGroups: x.recoupGroups,
+      refreshGroups: x.refreshGroups,
+    }))
+    .runReadWrite(async (tx) => {
+      const recoupGroup = await tx.recoupGroups.get(recoupGroupId);
       if (!recoupGroup) {
         return;
       }
       if (recoupGroup.recoupFinishedPerCoin[coinIdx]) {
         return;
       }
-      const updatedCoin = await tx.get(Stores.coins, coin.coinPub);
+      const updatedCoin = await tx.coins.get(coin.coinPub);
       if (!updatedCoin) {
         return;
       }
-      const updatedReserve = await tx.get(Stores.reserves, reserve.reservePub);
+      const updatedReserve = await tx.reserves.get(reserve.reservePub);
       if (!updatedReserve) {
         return;
       }
@@ -214,11 +229,10 @@ async function recoupWithdrawCoin(
         updatedReserve.requestedQuery = true;
         updatedReserve.retryInfo = initRetryInfo();
       }
-      await tx.put(Stores.coins, updatedCoin);
-      await tx.put(Stores.reserves, updatedReserve);
+      await tx.coins.put(updatedCoin);
+      await tx.reserves.put(updatedReserve);
       await putGroupAsFinished(ws, tx, recoupGroup, coinIdx);
-    },
-  );
+    });
 
   ws.notify({
     type: NotificationType.RecoupFinished,
@@ -250,38 +264,24 @@ async function recoupRefreshCoin(
     throw Error(`Coin's oldCoinPub doesn't match reserve on recoup`);
   }
 
-  const exchangeDetails = await ws.db.runWithReadTransaction(
-    [Stores.exchanges, Stores.exchangeDetails],
-    async (tx) => {
-      // FIXME:  Get the exchange details based on the
-      // exchange master public key instead of via just the URL.
-      return getExchangeDetails(tx, coin.exchangeBaseUrl);
-    },
-  );
-  if (!exchangeDetails) {
-    // FIXME: report inconsistency?
-    logger.warn("exchange details for recoup not found");
-    return;
-  }
-
-  await ws.db.runWithWriteTransaction(
-    [
-      Stores.coins,
-      Stores.denominations,
-      Stores.reserves,
-      Stores.recoupGroups,
-      Stores.refreshGroups,
-    ],
-    async (tx) => {
-      const recoupGroup = await tx.get(Stores.recoupGroups, recoupGroupId);
+  await ws.db
+    .mktx((x) => ({
+      coins: x.coins,
+      denominations: x.denominations,
+      reserves: x.reserves,
+      recoupGroups: x.recoupGroups,
+      refreshGroups: x.refreshGroups,
+    }))
+    .runReadWrite(async (tx) => {
+      const recoupGroup = await tx.recoupGroups.get(recoupGroupId);
       if (!recoupGroup) {
         return;
       }
       if (recoupGroup.recoupFinishedPerCoin[coinIdx]) {
         return;
       }
-      const oldCoin = await tx.get(Stores.coins, cs.oldCoinPub);
-      const revokedCoin = await tx.get(Stores.coins, coin.coinPub);
+      const oldCoin = await tx.coins.get(cs.oldCoinPub);
+      const revokedCoin = await tx.coins.get(coin.coinPub);
       if (!revokedCoin) {
         logger.warn("revoked coin for recoup not found");
         return;
@@ -300,23 +300,27 @@ async function recoupRefreshCoin(
         Amounts.stringify(oldCoin.currentAmount),
       );
       recoupGroup.scheduleRefreshCoins.push(oldCoin.coinPub);
-      await tx.put(Stores.coins, revokedCoin);
-      await tx.put(Stores.coins, oldCoin);
+      await tx.coins.put(revokedCoin);
+      await tx.coins.put(oldCoin);
       await putGroupAsFinished(ws, tx, recoupGroup, coinIdx);
-    },
-  );
+    });
 }
 
 async function resetRecoupGroupRetry(
   ws: InternalWalletState,
   recoupGroupId: string,
 ): Promise<void> {
-  await ws.db.mutate(Stores.recoupGroups, recoupGroupId, (x) => {
-    if (x.retryInfo.active) {
-      x.retryInfo = initRetryInfo();
-    }
-    return x;
-  });
+  await ws.db
+    .mktx((x) => ({
+      recoupGroups: x.recoupGroups,
+    }))
+    .runReadWrite(async (tx) => {
+      const x = await tx.recoupGroups.get(recoupGroupId);
+      if (x && x.retryInfo.active) {
+        x.retryInfo = initRetryInfo();
+        await tx.recoupGroups.put(x);
+      }
+    });
 }
 
 export async function processRecoupGroup(
@@ -342,7 +346,13 @@ async function processRecoupGroupImpl(
   if (forceNow) {
     await resetRecoupGroupRetry(ws, recoupGroupId);
   }
-  const recoupGroup = await ws.db.get(Stores.recoupGroups, recoupGroupId);
+  const recoupGroup = await ws.db
+    .mktx((x) => ({
+      recoupGroups: x.recoupGroups,
+    }))
+    .runReadOnly(async (tx) => {
+      return tx.recoupGroups.get(recoupGroupId);
+    });
   if (!recoupGroup) {
     return;
   }
@@ -358,9 +368,15 @@ async function processRecoupGroupImpl(
   const reserveSet = new Set<string>();
   for (let i = 0; i < recoupGroup.coinPubs.length; i++) {
     const coinPub = recoupGroup.coinPubs[i];
-    const coin = await ws.db.get(Stores.coins, coinPub);
+    const coin = await ws.db
+      .mktx((x) => ({
+        coins: x.coins,
+      }))
+      .runReadOnly(async (tx) => {
+        return tx.coins.get(coinPub);
+      });
     if (!coin) {
-      throw Error(`Coin ${coinPub} not found, can't request payback`);
+      throw Error(`Coin ${coinPub} not found, can't request recoup`);
     }
     if (coin.coinSource.type === CoinSourceType.Withdraw) {
       reserveSet.add(coin.coinSource.reservePub);
@@ -376,7 +392,12 @@ async function processRecoupGroupImpl(
 
 export async function createRecoupGroup(
   ws: InternalWalletState,
-  tx: TransactionHandle<typeof Stores.recoupGroups | typeof Stores.coins>,
+  tx: GetReadWriteAccess<{
+    recoupGroups: typeof WalletStoresV1.recoupGroups;
+    denominations: typeof WalletStoresV1.denominations;
+    refreshGroups: typeof WalletStoresV1.refreshGroups;
+    coins: typeof WalletStoresV1.coins;
+  }>,
   coinPubs: string[],
 ): Promise<string> {
   const recoupGroupId = encodeCrock(getRandomBytes(32));
@@ -396,7 +417,7 @@ export async function createRecoupGroup(
 
   for (let coinIdx = 0; coinIdx < coinPubs.length; coinIdx++) {
     const coinPub = coinPubs[coinIdx];
-    const coin = await tx.get(Stores.coins, coinPub);
+    const coin = await tx.coins.get(coinPub);
     if (!coin) {
       await putGroupAsFinished(ws, tx, recoupGroup, coinIdx);
       continue;
@@ -407,10 +428,10 @@ export async function createRecoupGroup(
     }
     recoupGroup.oldAmountPerCoin[coinIdx] = coin.currentAmount;
     coin.currentAmount = Amounts.getZero(coin.currentAmount.currency);
-    await tx.put(Stores.coins, coin);
+    await tx.coins.put(coin);
   }
 
-  await tx.put(Stores.recoupGroups, recoupGroup);
+  await tx.recoupGroups.put(recoupGroup);
 
   return recoupGroupId;
 }
@@ -420,22 +441,34 @@ async function processRecoup(
   recoupGroupId: string,
   coinIdx: number,
 ): Promise<void> {
-  const recoupGroup = await ws.db.get(Stores.recoupGroups, recoupGroupId);
-  if (!recoupGroup) {
-    return;
-  }
-  if (recoupGroup.timestampFinished) {
-    return;
-  }
-  if (recoupGroup.recoupFinishedPerCoin[coinIdx]) {
-    return;
-  }
+  const coin = await ws.db
+    .mktx((x) => ({
+      recoupGroups: x.recoupGroups,
+      coins: x.coins,
+    }))
+    .runReadOnly(async (tx) => {
+      const recoupGroup = await tx.recoupGroups.get(recoupGroupId);
+      if (!recoupGroup) {
+        return;
+      }
+      if (recoupGroup.timestampFinished) {
+        return;
+      }
+      if (recoupGroup.recoupFinishedPerCoin[coinIdx]) {
+        return;
+      }
 
-  const coinPub = recoupGroup.coinPubs[coinIdx];
+      const coinPub = recoupGroup.coinPubs[coinIdx];
+
+      const coin = await tx.coins.get(coinPub);
+      if (!coin) {
+        throw Error(`Coin ${coinPub} not found, can't request payback`);
+      }
+      return coin;
+    });
 
-  const coin = await ws.db.get(Stores.coins, coinPub);
   if (!coin) {
-    throw Error(`Coin ${coinPub} not found, can't request payback`);
+    return;
   }
 
   const cs = coin.coinSource;
diff --git a/packages/taler-wallet-core/src/operations/refresh.ts 
b/packages/taler-wallet-core/src/operations/refresh.ts
index 6f4c9725..8d21e811 100644
--- a/packages/taler-wallet-core/src/operations/refresh.ts
+++ b/packages/taler-wallet-core/src/operations/refresh.ts
@@ -22,7 +22,7 @@ import {
   DenominationRecord,
   RefreshGroupRecord,
   RefreshPlanchet,
-  Stores,
+  WalletStoresV1,
 } from "../db.js";
 import {
   codecForExchangeMeltResponse,
@@ -38,7 +38,6 @@ import { amountToPretty } from "@gnu-taler/taler-util";
 import { readSuccessResponseJsonOrThrow } from "../util/http";
 import { checkDbInvariant } from "../util/invariants";
 import { Logger } from "@gnu-taler/taler-util";
-import { TransactionHandle } from "../util/query";
 import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries";
 import {
   Duration,
@@ -57,6 +56,8 @@ import { updateExchangeFromUrl } from "./exchanges";
 import { EXCHANGE_COINS_LOCK, InternalWalletState } from "./state";
 import { isWithdrawableDenom, selectWithdrawalDenominations } from 
"./withdraw";
 import { RefreshNewDenomInfo } from "../crypto/cryptoTypes.js";
+import { GetReadWriteAccess } from "../util/query.js";
+import { Wallet } from "../wallet.js";
 
 const logger = new Logger("refresh.ts");
 
@@ -95,7 +96,7 @@ export function getTotalRefreshCost(
 }
 
 /**
- * Create a refresh session inside a refresh group.
+ * Create a refresh session for one particular coin inside a refresh group.
  */
 async function refreshCreateSession(
   ws: InternalWalletState,
@@ -105,45 +106,68 @@ async function refreshCreateSession(
   logger.trace(
     `creating refresh session for coin ${coinIndex} in refresh group 
${refreshGroupId}`,
   );
-  const refreshGroup = await ws.db.get(Stores.refreshGroups, refreshGroupId);
-  if (!refreshGroup) {
-    return;
-  }
-  if (refreshGroup.finishedPerCoin[coinIndex]) {
-    return;
-  }
-  const existingRefreshSession = refreshGroup.refreshSessionPerCoin[coinIndex];
-  if (existingRefreshSession) {
+
+  const d = await ws.db
+    .mktx((x) => ({
+      refreshGroups: x.refreshGroups,
+      coins: x.coins,
+    }))
+    .runReadWrite(async (tx) => {
+      const refreshGroup = await tx.refreshGroups.get(refreshGroupId);
+      if (!refreshGroup) {
+        return;
+      }
+      if (refreshGroup.finishedPerCoin[coinIndex]) {
+        return;
+      }
+      const existingRefreshSession =
+        refreshGroup.refreshSessionPerCoin[coinIndex];
+      if (existingRefreshSession) {
+        return;
+      }
+      const oldCoinPub = refreshGroup.oldCoinPubs[coinIndex];
+      const coin = await tx.coins.get(oldCoinPub);
+      if (!coin) {
+        throw Error("Can't refresh, coin not found");
+      }
+      return { refreshGroup, coin };
+    });
+
+  if (!d) {
     return;
   }
-  const oldCoinPub = refreshGroup.oldCoinPubs[coinIndex];
-  const coin = await ws.db.get(Stores.coins, oldCoinPub);
-  if (!coin) {
-    throw Error("Can't refresh, coin not found");
-  }
+
+  const { refreshGroup, coin } = d;
 
   const { exchange } = await updateExchangeFromUrl(ws, coin.exchangeBaseUrl);
   if (!exchange) {
     throw Error("db inconsistent: exchange of coin not found");
   }
 
-  const oldDenom = await ws.db.get(Stores.denominations, [
-    exchange.baseUrl,
-    coin.denomPubHash,
-  ]);
+  const { availableAmount, availableDenoms } = await ws.db
+    .mktx((x) => ({
+      denominations: x.denominations,
+    }))
+    .runReadOnly(async (tx) => {
+      const oldDenom = await tx.denominations.get([
+        exchange.baseUrl,
+        coin.denomPubHash,
+      ]);
 
-  if (!oldDenom) {
-    throw Error("db inconsistent: denomination for coin not found");
-  }
+      if (!oldDenom) {
+        throw Error("db inconsistent: denomination for coin not found");
+      }
 
-  const availableDenoms: DenominationRecord[] = await ws.db
-    .iterIndex(Stores.denominations.exchangeBaseUrlIndex, exchange.baseUrl)
-    .toArray();
+      const availableDenoms: DenominationRecord[] = await 
tx.denominations.indexes.byExchangeBaseUrl
+        .iter(exchange.baseUrl)
+        .toArray();
 
-  const availableAmount = Amounts.sub(
-    refreshGroup.inputPerCoin[coinIndex],
-    oldDenom.feeRefresh,
-  ).amount;
+      const availableAmount = Amounts.sub(
+        refreshGroup.inputPerCoin[coinIndex],
+        oldDenom.feeRefresh,
+      ).amount;
+      return { availableAmount, availableDenoms };
+    });
 
   const newCoinDenoms = selectWithdrawalDenominations(
     availableAmount,
@@ -156,10 +180,13 @@ async function refreshCreateSession(
         availableAmount,
       )} too small`,
     );
-    await ws.db.runWithWriteTransaction(
-      [Stores.coins, Stores.refreshGroups],
-      async (tx) => {
-        const rg = await tx.get(Stores.refreshGroups, refreshGroupId);
+    await ws.db
+      .mktx((x) => ({
+        coins: x.coins,
+        refreshGroups: x.refreshGroups,
+      }))
+      .runReadWrite(async (tx) => {
+        const rg = await tx.refreshGroups.get(refreshGroupId);
         if (!rg) {
           return;
         }
@@ -175,9 +202,8 @@ async function refreshCreateSession(
           rg.timestampFinished = getTimestampNow();
           rg.retryInfo = initRetryInfo(false);
         }
-        await tx.put(Stores.refreshGroups, rg);
-      },
-    );
+        await tx.refreshGroups.put(rg);
+      });
     ws.notify({ type: NotificationType.RefreshUnwarranted });
     return;
   }
@@ -185,10 +211,13 @@ async function refreshCreateSession(
   const sessionSecretSeed = encodeCrock(getRandomBytes(64));
 
   // Store refresh session for this coin in the database.
-  await ws.db.runWithWriteTransaction(
-    [Stores.refreshGroups, Stores.coins],
-    async (tx) => {
-      const rg = await tx.get(Stores.refreshGroups, refreshGroupId);
+  await ws.db
+    .mktx((x) => ({
+      refreshGroups: x.refreshGroups,
+      coins: x.coins,
+    }))
+    .runReadWrite(async (tx) => {
+      const rg = await tx.refreshGroups.get(refreshGroupId);
       if (!rg) {
         return;
       }
@@ -204,9 +233,8 @@ async function refreshCreateSession(
         })),
         amountRefreshOutput: newCoinDenoms.totalCoinValue,
       };
-      await tx.put(Stores.refreshGroups, rg);
-    },
-  );
+      await tx.refreshGroups.put(rg);
+    });
   logger.info(
     `created refresh session for coin #${coinIndex} in ${refreshGroupId}`,
   );
@@ -222,48 +250,63 @@ async function refreshMelt(
   refreshGroupId: string,
   coinIndex: number,
 ): Promise<void> {
-  const refreshGroup = await ws.db.get(Stores.refreshGroups, refreshGroupId);
-  if (!refreshGroup) {
-    return;
-  }
-  const refreshSession = refreshGroup.refreshSessionPerCoin[coinIndex];
-  if (!refreshSession) {
-    return;
-  }
-  if (refreshSession.norevealIndex !== undefined) {
-    return;
-  }
+  const d = await ws.db
+    .mktx((x) => ({
+      refreshGroups: x.refreshGroups,
+      coins: x.coins,
+      denominations: x.denominations,
+    }))
+    .runReadWrite(async (tx) => {
+      const refreshGroup = await tx.refreshGroups.get(refreshGroupId);
+      if (!refreshGroup) {
+        return;
+      }
+      const refreshSession = refreshGroup.refreshSessionPerCoin[coinIndex];
+      if (!refreshSession) {
+        return;
+      }
+      if (refreshSession.norevealIndex !== undefined) {
+        return;
+      }
 
-  const oldCoin = await ws.db.get(
-    Stores.coins,
-    refreshGroup.oldCoinPubs[coinIndex],
-  );
-  checkDbInvariant(!!oldCoin, "melt coin doesn't exist");
-  const oldDenom = await ws.db.get(Stores.denominations, [
-    oldCoin.exchangeBaseUrl,
-    oldCoin.denomPubHash,
-  ]);
-  checkDbInvariant(!!oldDenom, "denomination for melted coin doesn't exist");
+      const oldCoin = await tx.coins.get(refreshGroup.oldCoinPubs[coinIndex]);
+      checkDbInvariant(!!oldCoin, "melt coin doesn't exist");
+      const oldDenom = await tx.denominations.get([
+        oldCoin.exchangeBaseUrl,
+        oldCoin.denomPubHash,
+      ]);
+      checkDbInvariant(
+        !!oldDenom,
+        "denomination for melted coin doesn't exist",
+      );
 
-  const newCoinDenoms: RefreshNewDenomInfo[] = [];
+      const newCoinDenoms: RefreshNewDenomInfo[] = [];
 
-  for (const dh of refreshSession.newDenoms) {
-    const newDenom = await ws.db.get(Stores.denominations, [
-      oldCoin.exchangeBaseUrl,
-      dh.denomPubHash,
-    ]);
-    checkDbInvariant(
-      !!newDenom,
-      "new denomination for refresh not in database",
-    );
-    newCoinDenoms.push({
-      count: dh.count,
-      denomPub: newDenom.denomPub,
-      feeWithdraw: newDenom.feeWithdraw,
-      value: newDenom.value,
+      for (const dh of refreshSession.newDenoms) {
+        const newDenom = await tx.denominations.get([
+          oldCoin.exchangeBaseUrl,
+          dh.denomPubHash,
+        ]);
+        checkDbInvariant(
+          !!newDenom,
+          "new denomination for refresh not in database",
+        );
+        newCoinDenoms.push({
+          count: dh.count,
+          denomPub: newDenom.denomPub,
+          feeWithdraw: newDenom.feeWithdraw,
+          value: newDenom.value,
+        });
+      }
+      return { newCoinDenoms, oldCoin, oldDenom, refreshGroup, refreshSession 
};
     });
+
+  if (!d) {
+    return;
   }
 
+  const { newCoinDenoms, oldCoin, oldDenom, refreshGroup, refreshSession } = d;
+
   const derived = await ws.cryptoApi.deriveRefreshSession({
     kappa: 3,
     meltCoinDenomPubHash: oldCoin.denomPubHash,
@@ -303,20 +346,28 @@ async function refreshMelt(
 
   refreshSession.norevealIndex = norevealIndex;
 
-  await ws.db.mutate(Stores.refreshGroups, refreshGroupId, (rg) => {
-    const rs = rg.refreshSessionPerCoin[coinIndex];
-    if (rg.timestampFinished) {
-      return;
-    }
-    if (!rs) {
-      return;
-    }
-    if (rs.norevealIndex !== undefined) {
-      return;
-    }
-    rs.norevealIndex = norevealIndex;
-    return rg;
-  });
+  await ws.db
+    .mktx((x) => ({
+      refreshGroups: x.refreshGroups,
+    }))
+    .runReadWrite(async (tx) => {
+      const rg = await tx.refreshGroups.get(refreshGroupId);
+      if (!rg) {
+        return;
+      }
+      if (rg.timestampFinished) {
+        return;
+      }
+      const rs = rg.refreshSessionPerCoin[coinIndex];
+      if (!rs) {
+        return;
+      }
+      if (rs.norevealIndex !== undefined) {
+        return;
+      }
+      rs.norevealIndex = norevealIndex;
+      await tx.refreshGroups.put(rg);
+    });
 
   ws.notify({
     type: NotificationType.RefreshMelted,
@@ -328,49 +379,78 @@ async function refreshReveal(
   refreshGroupId: string,
   coinIndex: number,
 ): Promise<void> {
-  const refreshGroup = await ws.db.get(Stores.refreshGroups, refreshGroupId);
-  if (!refreshGroup) {
-    return;
-  }
-  const refreshSession = refreshGroup.refreshSessionPerCoin[coinIndex];
-  if (!refreshSession) {
-    return;
-  }
-  const norevealIndex = refreshSession.norevealIndex;
-  if (norevealIndex === undefined) {
-    throw Error("can't reveal without melting first");
-  }
+  const d = await ws.db
+    .mktx((x) => ({
+      refreshGroups: x.refreshGroups,
+      coins: x.coins,
+      denominations: x.denominations,
+    }))
+    .runReadOnly(async (tx) => {
+      const refreshGroup = await tx.refreshGroups.get(refreshGroupId);
+      if (!refreshGroup) {
+        return;
+      }
+      const refreshSession = refreshGroup.refreshSessionPerCoin[coinIndex];
+      if (!refreshSession) {
+        return;
+      }
+      const norevealIndex = refreshSession.norevealIndex;
+      if (norevealIndex === undefined) {
+        throw Error("can't reveal without melting first");
+      }
 
-  const oldCoin = await ws.db.get(
-    Stores.coins,
-    refreshGroup.oldCoinPubs[coinIndex],
-  );
-  checkDbInvariant(!!oldCoin, "melt coin doesn't exist");
-  const oldDenom = await ws.db.get(Stores.denominations, [
-    oldCoin.exchangeBaseUrl,
-    oldCoin.denomPubHash,
-  ]);
-  checkDbInvariant(!!oldDenom, "denomination for melted coin doesn't exist");
+      const oldCoin = await tx.coins.get(refreshGroup.oldCoinPubs[coinIndex]);
+      checkDbInvariant(!!oldCoin, "melt coin doesn't exist");
+      const oldDenom = await tx.denominations.get([
+        oldCoin.exchangeBaseUrl,
+        oldCoin.denomPubHash,
+      ]);
+      checkDbInvariant(
+        !!oldDenom,
+        "denomination for melted coin doesn't exist",
+      );
 
-  const newCoinDenoms: RefreshNewDenomInfo[] = [];
+      const newCoinDenoms: RefreshNewDenomInfo[] = [];
 
-  for (const dh of refreshSession.newDenoms) {
-    const newDenom = await ws.db.get(Stores.denominations, [
-      oldCoin.exchangeBaseUrl,
-      dh.denomPubHash,
-    ]);
-    checkDbInvariant(
-      !!newDenom,
-      "new denomination for refresh not in database",
-    );
-    newCoinDenoms.push({
-      count: dh.count,
-      denomPub: newDenom.denomPub,
-      feeWithdraw: newDenom.feeWithdraw,
-      value: newDenom.value,
+      for (const dh of refreshSession.newDenoms) {
+        const newDenom = await tx.denominations.get([
+          oldCoin.exchangeBaseUrl,
+          dh.denomPubHash,
+        ]);
+        checkDbInvariant(
+          !!newDenom,
+          "new denomination for refresh not in database",
+        );
+        newCoinDenoms.push({
+          count: dh.count,
+          denomPub: newDenom.denomPub,
+          feeWithdraw: newDenom.feeWithdraw,
+          value: newDenom.value,
+        });
+      }
+      return {
+        oldCoin,
+        oldDenom,
+        newCoinDenoms,
+        refreshSession,
+        refreshGroup,
+        norevealIndex,
+      };
     });
+
+  if (!d) {
+    return;
   }
 
+  const {
+    oldCoin,
+    oldDenom,
+    newCoinDenoms,
+    refreshSession,
+    refreshGroup,
+    norevealIndex,
+  } = d;
+
   const derived = await ws.cryptoApi.deriveRefreshSession({
     kappa: 3,
     meltCoinDenomPubHash: oldCoin.denomPubHash,
@@ -389,14 +469,6 @@ async function refreshReveal(
     throw Error("refresh index error");
   }
 
-  const meltCoinRecord = await ws.db.get(
-    Stores.coins,
-    refreshGroup.oldCoinPubs[coinIndex],
-  );
-  if (!meltCoinRecord) {
-    throw Error("inconsistent database");
-  }
-
   const evs = planchets.map((x: RefreshPlanchet) => x.coinEv);
   const newDenomsFlat: string[] = [];
   const linkSigs: string[] = [];
@@ -406,9 +478,9 @@ async function refreshReveal(
     for (let j = 0; j < dsel.count; j++) {
       const newCoinIndex = linkSigs.length;
       const linkSig = await ws.cryptoApi.signCoinLink(
-        meltCoinRecord.coinPriv,
+        oldCoin.coinPriv,
         dsel.denomPubHash,
-        meltCoinRecord.coinPub,
+        oldCoin.coinPub,
         derived.transferPubs[norevealIndex],
         planchets[newCoinIndex].coinEv,
       );
@@ -447,10 +519,17 @@ async function refreshReveal(
   for (let i = 0; i < refreshSession.newDenoms.length; i++) {
     for (let j = 0; j < refreshSession.newDenoms[i].count; j++) {
       const newCoinIndex = coins.length;
-      const denom = await ws.db.get(Stores.denominations, [
-        oldCoin.exchangeBaseUrl,
-        refreshSession.newDenoms[i].denomPubHash,
-      ]);
+      // FIXME: Look up in earlier transaction!
+      const denom = await ws.db
+        .mktx((x) => ({
+          denominations: x.denominations,
+        }))
+        .runReadOnly(async (tx) => {
+          return tx.denominations.get([
+            oldCoin.exchangeBaseUrl,
+            refreshSession.newDenoms[i].denomPubHash,
+          ]);
+        });
       if (!denom) {
         console.error("denom not found");
         continue;
@@ -483,10 +562,13 @@ async function refreshReveal(
     }
   }
 
-  await ws.db.runWithWriteTransaction(
-    [Stores.coins, Stores.refreshGroups],
-    async (tx) => {
-      const rg = await tx.get(Stores.refreshGroups, refreshGroupId);
+  await ws.db
+    .mktx((x) => ({
+      coins: x.coins,
+      refreshGroups: x.refreshGroups,
+    }))
+    .runReadWrite(async (tx) => {
+      const rg = await tx.refreshGroups.get(refreshGroupId);
       if (!rg) {
         logger.warn("no refresh session found");
         return;
@@ -508,11 +590,10 @@ async function refreshReveal(
         rg.retryInfo = initRetryInfo(false);
       }
       for (const coin of coins) {
-        await tx.put(Stores.coins, coin);
+        await tx.coins.put(coin);
       }
-      await tx.put(Stores.refreshGroups, rg);
-    },
-  );
+      await tx.refreshGroups.put(rg);
+    });
   logger.trace("refresh finished (end of reveal)");
   ws.notify({
     type: NotificationType.RefreshRevealed,
@@ -524,19 +605,23 @@ async function incrementRefreshRetry(
   refreshGroupId: string,
   err: TalerErrorDetails | undefined,
 ): Promise<void> {
-  await ws.db.runWithWriteTransaction([Stores.refreshGroups], async (tx) => {
-    const r = await tx.get(Stores.refreshGroups, refreshGroupId);
-    if (!r) {
-      return;
-    }
-    if (!r.retryInfo) {
-      return;
-    }
-    r.retryInfo.retryCounter++;
-    updateRetryInfoTimeout(r.retryInfo);
-    r.lastError = err;
-    await tx.put(Stores.refreshGroups, r);
-  });
+  await ws.db
+    .mktx((x) => ({
+      refreshGroups: x.refreshGroups,
+    }))
+    .runReadWrite(async (tx) => {
+      const r = await tx.refreshGroups.get(refreshGroupId);
+      if (!r) {
+        return;
+      }
+      if (!r.retryInfo) {
+        return;
+      }
+      r.retryInfo.retryCounter++;
+      updateRetryInfoTimeout(r.retryInfo);
+      r.lastError = err;
+      await tx.refreshGroups.put(r);
+    });
   if (err) {
     ws.notify({ type: NotificationType.RefreshOperationError, error: err });
   }
@@ -562,14 +647,19 @@ export async function processRefreshGroup(
 
 async function resetRefreshGroupRetry(
   ws: InternalWalletState,
-  refreshSessionId: string,
+  refreshGroupId: string,
 ): Promise<void> {
-  await ws.db.mutate(Stores.refreshGroups, refreshSessionId, (x) => {
-    if (x.retryInfo.active) {
-      x.retryInfo = initRetryInfo();
-    }
-    return x;
-  });
+  await ws.db
+    .mktx((x) => ({
+      refreshGroups: x.refreshGroups,
+    }))
+    .runReadWrite(async (tx) => {
+      const x = await tx.refreshGroups.get(refreshGroupId);
+      if (x && x.retryInfo.active) {
+        x.retryInfo = initRetryInfo();
+        await tx.refreshGroups.put(x);
+      }
+    });
 }
 
 async function processRefreshGroupImpl(
@@ -580,13 +670,20 @@ async function processRefreshGroupImpl(
   if (forceNow) {
     await resetRefreshGroupRetry(ws, refreshGroupId);
   }
-  const refreshGroup = await ws.db.get(Stores.refreshGroups, refreshGroupId);
+  const refreshGroup = await ws.db
+    .mktx((x) => ({
+      refreshGroups: x.refreshGroups,
+    }))
+    .runReadOnly(async (tx) => {
+      return tx.refreshGroups.get(refreshGroupId);
+    });
   if (!refreshGroup) {
     return;
   }
   if (refreshGroup.timestampFinished) {
     return;
   }
+  // Process refresh sessions of the group in parallel.
   const ps = refreshGroup.oldCoinPubs.map((x, i) =>
     processRefreshSession(ws, refreshGroupId, i),
   );
@@ -602,7 +699,11 @@ async function processRefreshSession(
   logger.trace(
     `processing refresh session for coin ${coinIndex} of group 
${refreshGroupId}`,
   );
-  let refreshGroup = await ws.db.get(Stores.refreshGroups, refreshGroupId);
+  let refreshGroup = await ws.db
+    .mktx((x) => ({ refreshGroups: x.refreshGroups }))
+    .runReadOnly(async (tx) => {
+      return tx.refreshGroups.get(refreshGroupId);
+    });
   if (!refreshGroup) {
     return;
   }
@@ -611,7 +712,11 @@ async function processRefreshSession(
   }
   if (!refreshGroup.refreshSessionPerCoin[coinIndex]) {
     await refreshCreateSession(ws, refreshGroupId, coinIndex);
-    refreshGroup = await ws.db.get(Stores.refreshGroups, refreshGroupId);
+    refreshGroup = await ws.db
+      .mktx((x) => ({ refreshGroups: x.refreshGroups }))
+      .runReadOnly(async (tx) => {
+        return tx.refreshGroups.get(refreshGroupId);
+      });
     if (!refreshGroup) {
       return;
     }
@@ -646,11 +751,11 @@ async function processRefreshSession(
  */
 export async function createRefreshGroup(
   ws: InternalWalletState,
-  tx: TransactionHandle<
-    | typeof Stores.denominations
-    | typeof Stores.coins
-    | typeof Stores.refreshGroups
-  >,
+  tx: GetReadWriteAccess<{
+    denominations: typeof WalletStoresV1.denominations;
+    coins: typeof WalletStoresV1.coins;
+    refreshGroups: typeof WalletStoresV1.refreshGroups;
+  }>,
   oldCoinPubs: CoinPublicKey[],
   reason: RefreshReason,
 ): Promise<RefreshGroupId> {
@@ -667,8 +772,8 @@ export async function createRefreshGroup(
     if (denomsPerExchange[exchangeBaseUrl]) {
       return denomsPerExchange[exchangeBaseUrl];
     }
-    const allDenoms = await tx
-      .iterIndexed(Stores.denominations.exchangeBaseUrlIndex, exchangeBaseUrl)
+    const allDenoms = await tx.denominations.indexes.byExchangeBaseUrl
+      .iter(exchangeBaseUrl)
       .filter((x) => {
         return isWithdrawableDenom(x);
       });
@@ -677,9 +782,9 @@ export async function createRefreshGroup(
   };
 
   for (const ocp of oldCoinPubs) {
-    const coin = await tx.get(Stores.coins, ocp.coinPub);
+    const coin = await tx.coins.get(ocp.coinPub);
     checkDbInvariant(!!coin, "coin must be in database");
-    const denom = await tx.get(Stores.denominations, [
+    const denom = await tx.denominations.get([
       coin.exchangeBaseUrl,
       coin.denomPubHash,
     ]);
@@ -691,7 +796,7 @@ export async function createRefreshGroup(
     inputPerCoin.push(refreshAmount);
     coin.currentAmount = Amounts.getZero(refreshAmount.currency);
     coin.status = CoinStatus.Dormant;
-    await tx.put(Stores.coins, coin);
+    await tx.coins.put(coin);
     const denoms = await getDenoms(coin.exchangeBaseUrl);
     const cost = getTotalRefreshCost(denoms, denom, refreshAmount);
     const output = Amounts.sub(refreshAmount, cost).amount;
@@ -718,7 +823,7 @@ export async function createRefreshGroup(
     refreshGroup.timestampFinished = getTimestampNow();
   }
 
-  await tx.put(Stores.refreshGroups, refreshGroup);
+  await tx.refreshGroups.put(refreshGroup);
 
   logger.trace(`created refresh group ${refreshGroupId}`);
 
@@ -760,20 +865,20 @@ export async function autoRefresh(
   exchangeBaseUrl: string,
 ): Promise<void> {
   await updateExchangeFromUrl(ws, exchangeBaseUrl, true);
-  await ws.db.runWithWriteTransaction(
-    [
-      Stores.coins,
-      Stores.denominations,
-      Stores.refreshGroups,
-      Stores.exchanges,
-    ],
-    async (tx) => {
-      const exchange = await tx.get(Stores.exchanges, exchangeBaseUrl);
+  await ws.db
+    .mktx((x) => ({
+      coins: x.coins,
+      denominations: x.denominations,
+      refreshGroups: x.refreshGroups,
+      exchanges: x.exchanges,
+    }))
+    .runReadWrite(async (tx) => {
+      const exchange = await tx.exchanges.get(exchangeBaseUrl);
       if (!exchange) {
         return;
       }
-      const coins = await tx
-        .iterIndexed(Stores.coins.exchangeBaseUrlIndex, exchangeBaseUrl)
+      const coins = await tx.coins.indexes.byBaseUrl
+        .iter(exchangeBaseUrl)
         .toArray();
       const refreshCoins: CoinPublicKey[] = [];
       for (const coin of coins) {
@@ -783,7 +888,7 @@ export async function autoRefresh(
         if (coin.suspended) {
           continue;
         }
-        const denom = await tx.get(Stores.denominations, [
+        const denom = await tx.denominations.get([
           exchangeBaseUrl,
           coin.denomPubHash,
         ]);
@@ -800,8 +905,8 @@ export async function autoRefresh(
         await createRefreshGroup(ws, tx, refreshCoins, 
RefreshReason.Scheduled);
       }
 
-      const denoms = await tx
-        .iterIndexed(Stores.denominations.exchangeBaseUrlIndex, 
exchangeBaseUrl)
+      const denoms = await tx.denominations.indexes.byExchangeBaseUrl
+        .iter(exchangeBaseUrl)
         .toArray();
       let minCheckThreshold = timestampAddDuration(
         getTimestampNow(),
@@ -817,7 +922,6 @@ export async function autoRefresh(
         minCheckThreshold = timestampMin(minCheckThreshold, checkThreshold);
       }
       exchange.nextRefreshCheck = minCheckThreshold;
-      await tx.put(Stores.exchanges, exchange);
-    },
-  );
+      await tx.exchanges.put(exchange);
+    });
 }
diff --git a/packages/taler-wallet-core/src/operations/refund.ts 
b/packages/taler-wallet-core/src/operations/refund.ts
index 2e2ab780..ba0674f0 100644
--- a/packages/taler-wallet-core/src/operations/refund.ts
+++ b/packages/taler-wallet-core/src/operations/refund.ts
@@ -48,13 +48,21 @@ import {
 } from "@gnu-taler/taler-util";
 import { Logger } from "@gnu-taler/taler-util";
 import { readSuccessResponseJsonOrThrow } from "../util/http";
-import { TransactionHandle } from "../util/query";
 import { URL } from "../util/url";
 import { updateRetryInfoTimeout, initRetryInfo } from "../util/retries";
 import { checkDbInvariant } from "../util/invariants";
 import { TalerErrorCode } from "@gnu-taler/taler-util";
-import { Stores, PurchaseRecord, CoinStatus, RefundState, AbortStatus, 
RefundReason } from "../db.js";
+import {
+  PurchaseRecord,
+  CoinStatus,
+  RefundState,
+  AbortStatus,
+  RefundReason,
+  WalletStoresV1,
+} from "../db.js";
 import { getTotalRefreshCost, createRefreshGroup } from "./refresh.js";
+import { GetReadWriteAccess } from "../util/query.js";
+import { Wallet } from "../wallet.js";
 
 const logger = new Logger("refund.ts");
 
@@ -66,19 +74,23 @@ async function incrementPurchaseQueryRefundRetry(
   proposalId: string,
   err: TalerErrorDetails | undefined,
 ): Promise<void> {
-  await ws.db.runWithWriteTransaction([Stores.purchases], async (tx) => {
-    const pr = await tx.get(Stores.purchases, proposalId);
-    if (!pr) {
-      return;
-    }
-    if (!pr.refundStatusRetryInfo) {
-      return;
-    }
-    pr.refundStatusRetryInfo.retryCounter++;
-    updateRetryInfoTimeout(pr.refundStatusRetryInfo);
-    pr.lastRefundStatusError = err;
-    await tx.put(Stores.purchases, pr);
-  });
+  await ws.db
+    .mktx((x) => ({
+      purchases: x.purchases,
+    }))
+    .runReadWrite(async (tx) => {
+      const pr = await tx.purchases.get(proposalId);
+      if (!pr) {
+        return;
+      }
+      if (!pr.refundStatusRetryInfo) {
+        return;
+      }
+      pr.refundStatusRetryInfo.retryCounter++;
+      updateRetryInfoTimeout(pr.refundStatusRetryInfo);
+      pr.lastRefundStatusError = err;
+      await tx.purchases.put(pr);
+    });
   if (err) {
     ws.notify({
       type: NotificationType.RefundStatusOperationError,
@@ -92,7 +104,10 @@ function getRefundKey(d: MerchantCoinRefundStatus): string {
 }
 
 async function applySuccessfulRefund(
-  tx: TransactionHandle<typeof Stores.coins | typeof Stores.denominations>,
+  tx: GetReadWriteAccess<{
+    coins: typeof WalletStoresV1.coins;
+    denominations: typeof WalletStoresV1.denominations;
+  }>,
   p: PurchaseRecord,
   refreshCoinsMap: Record<string, { coinPub: string }>,
   r: MerchantCoinRefundSuccessStatus,
@@ -100,12 +115,12 @@ async function applySuccessfulRefund(
   // FIXME: check signature before storing it as valid!
 
   const refundKey = getRefundKey(r);
-  const coin = await tx.get(Stores.coins, r.coin_pub);
+  const coin = await tx.coins.get(r.coin_pub);
   if (!coin) {
     logger.warn("coin not found, can't apply refund");
     return;
   }
-  const denom = await tx.get(Stores.denominations, [
+  const denom = await tx.denominations.get([
     coin.exchangeBaseUrl,
     coin.denomPubHash,
   ]);
@@ -119,13 +134,10 @@ async function applySuccessfulRefund(
   coin.currentAmount = Amounts.add(coin.currentAmount, refundAmount).amount;
   coin.currentAmount = Amounts.sub(coin.currentAmount, refundFee).amount;
   logger.trace(`coin amount after is 
${Amounts.stringify(coin.currentAmount)}`);
-  await tx.put(Stores.coins, coin);
+  await tx.coins.put(coin);
 
-  const allDenoms = await tx
-    .iterIndexed(
-      Stores.denominations.exchangeBaseUrlIndex,
-      coin.exchangeBaseUrl,
-    )
+  const allDenoms = await tx.denominations.indexes.byExchangeBaseUrl
+    .iter(coin.exchangeBaseUrl)
     .toArray();
 
   const amountLeft = Amounts.sub(
@@ -153,18 +165,21 @@ async function applySuccessfulRefund(
 }
 
 async function storePendingRefund(
-  tx: TransactionHandle<typeof Stores.denominations | typeof Stores.coins>,
+  tx: GetReadWriteAccess<{
+    denominations: typeof WalletStoresV1.denominations;
+    coins: typeof WalletStoresV1.coins;
+  }>,
   p: PurchaseRecord,
   r: MerchantCoinRefundFailureStatus,
 ): Promise<void> {
   const refundKey = getRefundKey(r);
 
-  const coin = await tx.get(Stores.coins, r.coin_pub);
+  const coin = await tx.coins.get(r.coin_pub);
   if (!coin) {
     logger.warn("coin not found, can't apply refund");
     return;
   }
-  const denom = await tx.get(Stores.denominations, [
+  const denom = await tx.denominations.get([
     coin.exchangeBaseUrl,
     coin.denomPubHash,
   ]);
@@ -173,11 +188,8 @@ async function storePendingRefund(
     throw Error("inconsistent database");
   }
 
-  const allDenoms = await tx
-    .iterIndexed(
-      Stores.denominations.exchangeBaseUrlIndex,
-      coin.exchangeBaseUrl,
-    )
+  const allDenoms = await tx.denominations.indexes.byExchangeBaseUrl
+    .iter(coin.exchangeBaseUrl)
     .toArray();
 
   const amountLeft = Amounts.sub(
@@ -205,19 +217,22 @@ async function storePendingRefund(
 }
 
 async function storeFailedRefund(
-  tx: TransactionHandle<typeof Stores.coins | typeof Stores.denominations>,
+  tx: GetReadWriteAccess<{
+    coins: typeof WalletStoresV1.coins;
+    denominations: typeof WalletStoresV1.denominations;
+  }>,
   p: PurchaseRecord,
   refreshCoinsMap: Record<string, { coinPub: string }>,
   r: MerchantCoinRefundFailureStatus,
 ): Promise<void> {
   const refundKey = getRefundKey(r);
 
-  const coin = await tx.get(Stores.coins, r.coin_pub);
+  const coin = await tx.coins.get(r.coin_pub);
   if (!coin) {
     logger.warn("coin not found, can't apply refund");
     return;
   }
-  const denom = await tx.get(Stores.denominations, [
+  const denom = await tx.denominations.get([
     coin.exchangeBaseUrl,
     coin.denomPubHash,
   ]);
@@ -226,11 +241,8 @@ async function storeFailedRefund(
     throw Error("inconsistent database");
   }
 
-  const allDenoms = await tx
-    .iterIndexed(
-      Stores.denominations.exchangeBaseUrlIndex,
-      coin.exchangeBaseUrl,
-    )
+  const allDenoms = await tx.denominations.indexes.byExchangeBaseUrl
+    .iter(coin.exchangeBaseUrl)
     .toArray();
 
   const amountLeft = Amounts.sub(
@@ -260,12 +272,12 @@ async function storeFailedRefund(
     // Refund failed because the merchant didn't even try to deposit
     // the coin yet, so we try to refresh.
     if (r.exchange_code === TalerErrorCode.EXCHANGE_REFUND_DEPOSIT_NOT_FOUND) {
-      const coin = await tx.get(Stores.coins, r.coin_pub);
+      const coin = await tx.coins.get(r.coin_pub);
       if (!coin) {
         logger.warn("coin not found, can't apply refund");
         return;
       }
-      const denom = await tx.get(Stores.denominations, [
+      const denom = await tx.denominations.get([
         coin.exchangeBaseUrl,
         coin.denomPubHash,
       ]);
@@ -287,7 +299,7 @@ async function storeFailedRefund(
         ).amount;
       }
       refreshCoinsMap[coin.coinPub] = { coinPub: coin.coinPub };
-      await tx.put(Stores.coins, coin);
+      await tx.coins.put(coin);
     }
   }
 }
@@ -301,15 +313,15 @@ async function acceptRefunds(
   logger.trace("handling refunds", refunds);
   const now = getTimestampNow();
 
-  await ws.db.runWithWriteTransaction(
-    [
-      Stores.purchases,
-      Stores.coins,
-      Stores.denominations,
-      Stores.refreshGroups,
-    ],
-    async (tx) => {
-      const p = await tx.get(Stores.purchases, proposalId);
+  await ws.db
+    .mktx((x) => ({
+      purchases: x.purchases,
+      coins: x.coins,
+      denominations: x.denominations,
+      refreshGroups: x.refreshGroups,
+    }))
+    .runReadWrite(async (tx) => {
+      const p = await tx.purchases.get(proposalId);
       if (!p) {
         logger.error("purchase not found, not adding refunds");
         return;
@@ -409,9 +421,8 @@ async function acceptRefunds(
         logger.trace("refund query not done");
       }
 
-      await tx.put(Stores.purchases, p);
-    },
-  );
+      await tx.purchases.put(p);
+    });
 
   ws.notify({
     type: NotificationType.RefundQueried,
@@ -444,10 +455,16 @@ export async function applyRefund(
     throw Error("invalid refund URI");
   }
 
-  let purchase = await ws.db.getIndexed(Stores.purchases.orderIdIndex, [
-    parseResult.merchantBaseUrl,
-    parseResult.orderId,
-  ]);
+  let purchase = await ws.db
+    .mktx((x) => ({
+      purchases: x.purchases,
+    }))
+    .runReadOnly(async (tx) => {
+      return tx.purchases.indexes.byMerchantUrlAndOrderId.get([
+        parseResult.merchantBaseUrl,
+        parseResult.orderId,
+      ]);
+    });
 
   if (!purchase) {
     throw Error(
@@ -458,10 +475,12 @@ export async function applyRefund(
   const proposalId = purchase.proposalId;
 
   logger.info("processing purchase for refund");
-  const success = await ws.db.runWithWriteTransaction(
-    [Stores.purchases],
-    async (tx) => {
-      const p = await tx.get(Stores.purchases, proposalId);
+  const success = await ws.db
+    .mktx((x) => ({
+      purchases: x.purchases,
+    }))
+    .runReadWrite(async (tx) => {
+      const p = await tx.purchases.get(proposalId);
       if (!p) {
         logger.error("no purchase found for refund URL");
         return false;
@@ -469,10 +488,9 @@ export async function applyRefund(
       p.refundQueryRequested = true;
       p.lastRefundStatusError = undefined;
       p.refundStatusRetryInfo = initRetryInfo();
-      await tx.put(Stores.purchases, p);
+      await tx.purchases.put(p);
       return true;
-    },
-  );
+    });
 
   if (success) {
     ws.notify({
@@ -481,7 +499,13 @@ export async function applyRefund(
     await processPurchaseQueryRefund(ws, proposalId);
   }
 
-  purchase = await ws.db.get(Stores.purchases, proposalId);
+  purchase = await ws.db
+    .mktx((x) => ({
+      purchases: x.purchases,
+    }))
+    .runReadOnly(async (tx) => {
+      return tx.purchases.get(proposalId);
+    });
 
   if (!purchase) {
     throw Error("purchase no longer exists");
@@ -559,12 +583,17 @@ async function resetPurchaseQueryRefundRetry(
   ws: InternalWalletState,
   proposalId: string,
 ): Promise<void> {
-  await ws.db.mutate(Stores.purchases, proposalId, (x) => {
-    if (x.refundStatusRetryInfo.active) {
-      x.refundStatusRetryInfo = initRetryInfo();
-    }
-    return x;
-  });
+  await ws.db
+    .mktx((x) => ({
+      purchases: x.purchases,
+    }))
+    .runReadWrite(async (tx) => {
+      const x = await tx.purchases.get(proposalId);
+      if (x && x.refundStatusRetryInfo.active) {
+        x.refundStatusRetryInfo = initRetryInfo();
+        await tx.purchases.put(x);
+      }
+    });
 }
 
 async function processPurchaseQueryRefundImpl(
@@ -575,7 +604,13 @@ async function processPurchaseQueryRefundImpl(
   if (forceNow) {
     await resetPurchaseQueryRefundRetry(ws, proposalId);
   }
-  const purchase = await ws.db.get(Stores.purchases, proposalId);
+  const purchase = await ws.db
+    .mktx((x) => ({
+      purchases: x.purchases,
+    }))
+    .runReadOnly(async (tx) => {
+      return tx.purchases.get(proposalId);
+    });
   if (!purchase) {
     return;
   }
@@ -589,7 +624,6 @@ async function processPurchaseQueryRefundImpl(
       `orders/${purchase.download.contractData.orderId}/refund`,
       purchase.download.contractData.merchantBaseUrl,
     );
-    
 
     logger.trace(`making refund request to ${requestUrl.href}`);
 
@@ -620,18 +654,25 @@ async function processPurchaseQueryRefundImpl(
     );
 
     const abortingCoins: AbortingCoin[] = [];
-    for (let i = 0; i < purchase.payCoinSelection.coinPubs.length; i++) {
-      const coinPub = purchase.payCoinSelection.coinPubs[i];
-      const coin = await ws.db.get(Stores.coins, coinPub);
-      checkDbInvariant(!!coin, "expected coin to be present");
-      abortingCoins.push({
-        coin_pub: coinPub,
-        contribution: Amounts.stringify(
-          purchase.payCoinSelection.coinContributions[i],
-        ),
-        exchange_url: coin.exchangeBaseUrl,
+
+    await ws.db
+      .mktx((x) => ({
+        coins: x.coins,
+      }))
+      .runReadOnly(async (tx) => {
+        for (let i = 0; i < purchase.payCoinSelection.coinPubs.length; i++) {
+          const coinPub = purchase.payCoinSelection.coinPubs[i];
+          const coin = await tx.coins.get(coinPub);
+          checkDbInvariant(!!coin, "expected coin to be present");
+          abortingCoins.push({
+            coin_pub: coinPub,
+            contribution: Amounts.stringify(
+              purchase.payCoinSelection.coinContributions[i],
+            ),
+            exchange_url: coin.exchangeBaseUrl,
+          });
+        }
       });
-    }
 
     const abortReq: AbortRequest = {
       h_contract: purchase.download.contractData.contractTermsHash,
@@ -678,26 +719,30 @@ export async function abortFailedPayWithRefund(
   ws: InternalWalletState,
   proposalId: string,
 ): Promise<void> {
-  await ws.db.runWithWriteTransaction([Stores.purchases], async (tx) => {
-    const purchase = await tx.get(Stores.purchases, proposalId);
-    if (!purchase) {
-      throw Error("purchase not found");
-    }
-    if (purchase.timestampFirstSuccessfulPay) {
-      // No point in aborting it.  We don't even report an error.
-      logger.warn(`tried to abort successful payment`);
-      return;
-    }
-    if (purchase.abortStatus !== AbortStatus.None) {
-      return;
-    }
-    purchase.refundQueryRequested = true;
-    purchase.paymentSubmitPending = false;
-    purchase.abortStatus = AbortStatus.AbortRefund;
-    purchase.lastPayError = undefined;
-    purchase.payRetryInfo = initRetryInfo(false);
-    await tx.put(Stores.purchases, purchase);
-  });
+  await ws.db
+    .mktx((x) => ({
+      purchases: x.purchases,
+    }))
+    .runReadWrite(async (tx) => {
+      const purchase = await tx.purchases.get(proposalId);
+      if (!purchase) {
+        throw Error("purchase not found");
+      }
+      if (purchase.timestampFirstSuccessfulPay) {
+        // No point in aborting it.  We don't even report an error.
+        logger.warn(`tried to abort successful payment`);
+        return;
+      }
+      if (purchase.abortStatus !== AbortStatus.None) {
+        return;
+      }
+      purchase.refundQueryRequested = true;
+      purchase.paymentSubmitPending = false;
+      purchase.abortStatus = AbortStatus.AbortRefund;
+      purchase.lastPayError = undefined;
+      purchase.payRetryInfo = initRetryInfo(false);
+      await tx.purchases.put(purchase);
+    });
   processPurchaseQueryRefund(ws, proposalId, true).catch((e) => {
     logger.trace(`error during refund processing after abort pay: ${e}`);
   });
diff --git a/packages/taler-wallet-core/src/operations/reserves.ts 
b/packages/taler-wallet-core/src/operations/reserves.ts
index a2482db7..73975fb0 100644
--- a/packages/taler-wallet-core/src/operations/reserves.ts
+++ b/packages/taler-wallet-core/src/operations/reserves.ts
@@ -34,11 +34,11 @@ import {
 } from "@gnu-taler/taler-util";
 import { randomBytes } from "../crypto/primitives/nacl-fast.js";
 import {
-  Stores,
   ReserveRecordStatus,
   ReserveBankInfo,
   ReserveRecord,
   WithdrawalGroupRecord,
+  WalletStoresV1,
 } from "../db.js";
 import { assertUnreachable } from "../util/assertUnreachable.js";
 import { canonicalizeBaseUrl } from "@gnu-taler/taler-util";
@@ -65,9 +65,13 @@ import {
 import { getExchangeTrust } from "./currencies.js";
 import { encodeCrock, getRandomBytes } from "../crypto/talerCrypto.js";
 import { Logger } from "@gnu-taler/taler-util";
-import { readSuccessResponseJsonOrErrorCode, readSuccessResponseJsonOrThrow, 
throwUnexpectedRequestError } from "../util/http.js";
+import {
+  readSuccessResponseJsonOrErrorCode,
+  readSuccessResponseJsonOrThrow,
+  throwUnexpectedRequestError,
+} from "../util/http.js";
 import { URL } from "../util/url.js";
-import { TransactionHandle } from "../util/query.js";
+import { GetReadOnlyAccess } from "../util/query.js";
 
 const logger = new Logger("reserves.ts");
 
@@ -75,12 +79,17 @@ async function resetReserveRetry(
   ws: InternalWalletState,
   reservePub: string,
 ): Promise<void> {
-  await ws.db.mutate(Stores.reserves, reservePub, (x) => {
-    if (x.retryInfo.active) {
-      x.retryInfo = initRetryInfo();
-    }
-    return x;
-  });
+  await ws.db
+    .mktx((x) => ({
+      reserves: x.reserves,
+    }))
+    .runReadWrite(async (tx) => {
+      const x = await tx.reserves.get(reservePub);
+      if (x && x.retryInfo.active) {
+        x.retryInfo = initRetryInfo();
+        await tx.reserves.put(x);
+      }
+    });
 }
 
 /**
@@ -157,17 +166,20 @@ export async function createReserve(
     exchangeInfo.exchange,
   );
 
-  const resp = await ws.db.runWithWriteTransaction(
-    [Stores.exchangeTrustStore, Stores.reserves, Stores.bankWithdrawUris],
-    async (tx) => {
+  const resp = await ws.db
+    .mktx((x) => ({
+      exchangeTrust: x.exchangeTrust,
+      reserves: x.reserves,
+      bankWithdrawUris: x.bankWithdrawUris,
+    }))
+    .runReadWrite(async (tx) => {
       // Check if we have already created a reserve for that 
bankWithdrawStatusUrl
       if (reserveRecord.bankInfo?.statusUrl) {
-        const bwi = await tx.get(
-          Stores.bankWithdrawUris,
+        const bwi = await tx.bankWithdrawUris.get(
           reserveRecord.bankInfo.statusUrl,
         );
         if (bwi) {
-          const otherReserve = await tx.get(Stores.reserves, bwi.reservePub);
+          const otherReserve = await tx.reserves.get(bwi.reservePub);
           if (otherReserve) {
             logger.trace(
               "returning existing reserve for bankWithdrawStatusUri",
@@ -178,27 +190,26 @@ export async function createReserve(
             };
           }
         }
-        await tx.put(Stores.bankWithdrawUris, {
+        await tx.bankWithdrawUris.put({
           reservePub: reserveRecord.reservePub,
           talerWithdrawUri: reserveRecord.bankInfo.statusUrl,
         });
       }
       if (!isAudited && !isTrusted) {
-        await tx.put(Stores.exchangeTrustStore, {
+        await tx.exchangeTrust.put({
           currency: reserveRecord.currency,
           exchangeBaseUrl: reserveRecord.exchangeBaseUrl,
           exchangeMasterPub: exchangeDetails.masterPublicKey,
           uids: [encodeCrock(getRandomBytes(32))],
         });
       }
-      await tx.put(Stores.reserves, reserveRecord);
+      await tx.reserves.put(reserveRecord);
       const r: CreateReserveResponse = {
         exchange: canonExchange,
         reservePub: keypair.pub,
       };
       return r;
-    },
-  );
+    });
 
   if (reserveRecord.reservePub === resp.reservePub) {
     // Only emit notification when a new reserve was created.
@@ -224,23 +235,27 @@ export async function forceQueryReserve(
   ws: InternalWalletState,
   reservePub: string,
 ): Promise<void> {
-  await ws.db.runWithWriteTransaction([Stores.reserves], async (tx) => {
-    const reserve = await tx.get(Stores.reserves, reservePub);
-    if (!reserve) {
-      return;
-    }
-    // Only force status query where it makes sense
-    switch (reserve.reserveStatus) {
-      case ReserveRecordStatus.DORMANT:
-        reserve.reserveStatus = ReserveRecordStatus.QUERYING_STATUS;
-        break;
-      default:
-        reserve.requestedQuery = true;
-        break;
-    }
-    reserve.retryInfo = initRetryInfo();
-    await tx.put(Stores.reserves, reserve);
-  });
+  await ws.db
+    .mktx((x) => ({
+      reserves: x.reserves,
+    }))
+    .runReadWrite(async (tx) => {
+      const reserve = await tx.reserves.get(reservePub);
+      if (!reserve) {
+        return;
+      }
+      // Only force status query where it makes sense
+      switch (reserve.reserveStatus) {
+        case ReserveRecordStatus.DORMANT:
+          reserve.reserveStatus = ReserveRecordStatus.QUERYING_STATUS;
+          break;
+        default:
+          reserve.requestedQuery = true;
+          break;
+      }
+      reserve.retryInfo = initRetryInfo();
+      await tx.reserves.put(reserve);
+    });
   await processReserve(ws, reservePub, true);
 }
 
@@ -270,7 +285,13 @@ async function registerReserveWithBank(
   ws: InternalWalletState,
   reservePub: string,
 ): Promise<void> {
-  const reserve = await ws.db.get(Stores.reserves, reservePub);
+  const reserve = await ws.db
+    .mktx((x) => ({
+      reserves: x.reserves,
+    }))
+    .runReadOnly(async (tx) => {
+      return await tx.reserves.get(reservePub);
+    });
   switch (reserve?.reserveStatus) {
     case ReserveRecordStatus.WAIT_CONFIRM_BANK:
     case ReserveRecordStatus.REGISTERING_BANK:
@@ -297,22 +318,30 @@ async function registerReserveWithBank(
     httpResp,
     codecForBankWithdrawalOperationPostResponse(),
   );
-  await ws.db.mutate(Stores.reserves, reservePub, (r) => {
-    switch (r.reserveStatus) {
-      case ReserveRecordStatus.REGISTERING_BANK:
-      case ReserveRecordStatus.WAIT_CONFIRM_BANK:
-        break;
-      default:
+  await ws.db
+    .mktx((x) => ({
+      reserves: x.reserves,
+    }))
+    .runReadWrite(async (tx) => {
+      const r = await tx.reserves.get(reservePub);
+      if (!r) {
         return;
-    }
-    r.timestampReserveInfoPosted = getTimestampNow();
-    r.reserveStatus = ReserveRecordStatus.WAIT_CONFIRM_BANK;
-    if (!r.bankInfo) {
-      throw Error("invariant failed");
-    }
-    r.retryInfo = initRetryInfo();
-    return r;
-  });
+      }
+      switch (r.reserveStatus) {
+        case ReserveRecordStatus.REGISTERING_BANK:
+        case ReserveRecordStatus.WAIT_CONFIRM_BANK:
+          break;
+        default:
+          return;
+      }
+      r.timestampReserveInfoPosted = getTimestampNow();
+      r.reserveStatus = ReserveRecordStatus.WAIT_CONFIRM_BANK;
+      if (!r.bankInfo) {
+        throw Error("invariant failed");
+      }
+      r.retryInfo = initRetryInfo();
+      await tx.reserves.put(r);
+    });
   ws.notify({ type: NotificationType.ReserveRegisteredWithBank });
   return processReserveBankStatus(ws, reservePub);
 }
@@ -340,7 +369,13 @@ async function processReserveBankStatusImpl(
   ws: InternalWalletState,
   reservePub: string,
 ): Promise<void> {
-  const reserve = await ws.db.get(Stores.reserves, reservePub);
+  const reserve = await ws.db
+    .mktx((x) => ({
+      reserves: x.reserves,
+    }))
+    .runReadOnly(async (tx) => {
+      return tx.reserves.get(reservePub);
+    });
   switch (reserve?.reserveStatus) {
     case ReserveRecordStatus.WAIT_CONFIRM_BANK:
     case ReserveRecordStatus.REGISTERING_BANK:
@@ -363,20 +398,28 @@ async function processReserveBankStatusImpl(
 
   if (status.aborted) {
     logger.trace("bank aborted the withdrawal");
-    await ws.db.mutate(Stores.reserves, reservePub, (r) => {
-      switch (r.reserveStatus) {
-        case ReserveRecordStatus.REGISTERING_BANK:
-        case ReserveRecordStatus.WAIT_CONFIRM_BANK:
-          break;
-        default:
+    await ws.db
+      .mktx((x) => ({
+        reserves: x.reserves,
+      }))
+      .runReadWrite(async (tx) => {
+        const r = await tx.reserves.get(reservePub);
+        if (!r) {
           return;
-      }
-      const now = getTimestampNow();
-      r.timestampBankConfirmed = now;
-      r.reserveStatus = ReserveRecordStatus.BANK_ABORTED;
-      r.retryInfo = initRetryInfo();
-      return r;
-    });
+        }
+        switch (r.reserveStatus) {
+          case ReserveRecordStatus.REGISTERING_BANK:
+          case ReserveRecordStatus.WAIT_CONFIRM_BANK:
+            break;
+          default:
+            return;
+        }
+        const now = getTimestampNow();
+        r.timestampBankConfirmed = now;
+        r.reserveStatus = ReserveRecordStatus.BANK_ABORTED;
+        r.retryInfo = initRetryInfo();
+        await tx.reserves.put(r);
+      });
     return;
   }
 
@@ -390,37 +433,40 @@ async function processReserveBankStatusImpl(
     return await processReserveBankStatus(ws, reservePub);
   }
 
-  if (status.transfer_done) {
-    await ws.db.mutate(Stores.reserves, reservePub, (r) => {
-      switch (r.reserveStatus) {
-        case ReserveRecordStatus.REGISTERING_BANK:
-        case ReserveRecordStatus.WAIT_CONFIRM_BANK:
-          break;
-        default:
-          return;
-      }
-      const now = getTimestampNow();
-      r.timestampBankConfirmed = now;
-      r.reserveStatus = ReserveRecordStatus.QUERYING_STATUS;
-      r.retryInfo = initRetryInfo();
-      return r;
-    });
-    await processReserveImpl(ws, reservePub, true);
-  } else {
-    await ws.db.mutate(Stores.reserves, reservePub, (r) => {
-      switch (r.reserveStatus) {
-        case ReserveRecordStatus.WAIT_CONFIRM_BANK:
-          break;
-        default:
-          return;
+  await ws.db
+    .mktx((x) => ({
+      reserves: x.reserves,
+    }))
+    .runReadWrite(async (tx) => {
+      const r = await tx.reserves.get(reservePub);
+      if (!r) {
+        return;
       }
-      if (r.bankInfo) {
-        r.bankInfo.confirmUrl = status.confirm_transfer_url;
+      if (status.transfer_done) {
+        switch (r.reserveStatus) {
+          case ReserveRecordStatus.REGISTERING_BANK:
+          case ReserveRecordStatus.WAIT_CONFIRM_BANK:
+            break;
+          default:
+            return;
+        }
+        const now = getTimestampNow();
+        r.timestampBankConfirmed = now;
+        r.reserveStatus = ReserveRecordStatus.QUERYING_STATUS;
+        r.retryInfo = initRetryInfo();
+      } else {
+        switch (r.reserveStatus) {
+          case ReserveRecordStatus.WAIT_CONFIRM_BANK:
+            break;
+          default:
+            return;
+        }
+        if (r.bankInfo) {
+          r.bankInfo.confirmUrl = status.confirm_transfer_url;
+        }
       }
-      return r;
+      await tx.reserves.put(r);
     });
-    await incrementReserveRetry(ws, reservePub, undefined);
-  }
 }
 
 async function incrementReserveRetry(
@@ -428,19 +474,23 @@ async function incrementReserveRetry(
   reservePub: string,
   err: TalerErrorDetails | undefined,
 ): Promise<void> {
-  await ws.db.runWithWriteTransaction([Stores.reserves], async (tx) => {
-    const r = await tx.get(Stores.reserves, reservePub);
-    if (!r) {
-      return;
-    }
-    if (!r.retryInfo) {
-      return;
-    }
-    r.retryInfo.retryCounter++;
-    updateRetryInfoTimeout(r.retryInfo);
-    r.lastError = err;
-    await tx.put(Stores.reserves, r);
-  });
+  await ws.db
+    .mktx((x) => ({
+      reserves: x.reserves,
+    }))
+    .runReadWrite(async (tx) => {
+      const r = await tx.reserves.get(reservePub);
+      if (!r) {
+        return;
+      }
+      if (!r.retryInfo) {
+        return;
+      }
+      r.retryInfo.retryCounter++;
+      updateRetryInfoTimeout(r.retryInfo);
+      r.lastError = err;
+      await tx.reserves.put(r);
+    });
   if (err) {
     ws.notify({
       type: NotificationType.ReserveOperationError,
@@ -461,7 +511,13 @@ async function updateReserve(
   ws: InternalWalletState,
   reservePub: string,
 ): Promise<{ ready: boolean }> {
-  const reserve = await ws.db.get(Stores.reserves, reservePub);
+  const reserve = await ws.db
+    .mktx((x) => ({
+      reserves: x.reserves,
+    }))
+    .runReadOnly(async (tx) => {
+      return tx.reserves.get(reservePub);
+    });
   if (!reserve) {
     throw Error("reserve not in db");
   }
@@ -508,10 +564,15 @@ async function updateReserve(
     reserve.exchangeBaseUrl,
   );
 
-  const newWithdrawalGroup = await ws.db.runWithWriteTransaction(
-    [Stores.coins, Stores.planchets, Stores.withdrawalGroups, Stores.reserves],
-    async (tx) => {
-      const newReserve = await tx.get(Stores.reserves, reserve.reservePub);
+  const newWithdrawalGroup = await ws.db
+    .mktx((x) => ({
+      coins: x.coins,
+      planchets: x.planchets,
+      withdrawalGroups: x.withdrawalGroups,
+      reserves: x.reserves,
+    }))
+    .runReadWrite(async (tx) => {
+      const newReserve = await tx.reserves.get(reserve.reservePub);
       if (!newReserve) {
         return;
       }
@@ -519,8 +580,8 @@ async function updateReserve(
       let amountReserveMinus = Amounts.getZero(currency);
 
       // Subtract withdrawal groups for this reserve from the available amount.
-      await tx
-        .iterIndexed(Stores.withdrawalGroups.byReservePub, reservePub)
+      await tx.withdrawalGroups.indexes.byReservePub
+        .iter(reservePub)
         .forEach((wg) => {
           const cost = wg.denomsSel.totalWithdrawCost;
           amountReserveMinus = Amounts.add(amountReserveMinus, cost).amount;
@@ -549,16 +610,14 @@ async function updateReserve(
           case ReserveTransactionType.Withdraw: {
             // Now we check if the withdrawal transaction
             // is part of any withdrawal known to this wallet.
-            const planchet = await tx.getIndexed(
-              Stores.planchets.coinEvHashIndex,
+            const planchet = await tx.planchets.indexes.byCoinEvHash.get(
               entry.h_coin_envelope,
             );
             if (planchet) {
               // Amount is already accounted in some withdrawal session
               break;
             }
-            const coin = await tx.getIndexed(
-              Stores.coins.coinEvHashIndex,
+            const coin = await tx.coins.indexes.byCoinEvHash.get(
               entry.h_coin_envelope,
             );
             if (coin) {
@@ -594,7 +653,7 @@ async function updateReserve(
         newReserve.reserveStatus = ReserveRecordStatus.DORMANT;
         newReserve.lastError = undefined;
         newReserve.retryInfo = initRetryInfo(false);
-        await tx.put(Stores.reserves, newReserve);
+        await tx.reserves.put(newReserve);
         return;
       }
 
@@ -624,11 +683,10 @@ async function updateReserve(
       newReserve.retryInfo = initRetryInfo(false);
       newReserve.reserveStatus = ReserveRecordStatus.DORMANT;
 
-      await tx.put(Stores.reserves, newReserve);
-      await tx.put(Stores.withdrawalGroups, withdrawalRecord);
+      await tx.reserves.put(newReserve);
+      await tx.withdrawalGroups.put(withdrawalRecord);
       return withdrawalRecord;
-    },
-  );
+    });
 
   if (newWithdrawalGroup) {
     logger.trace("processing new withdraw group");
@@ -647,7 +705,13 @@ async function processReserveImpl(
   reservePub: string,
   forceNow = false,
 ): Promise<void> {
-  const reserve = await ws.db.get(Stores.reserves, reservePub);
+  const reserve = await ws.db
+    .mktx((x) => ({
+      reserves: x.reserves,
+    }))
+    .runReadOnly(async (tx) => {
+      return tx.reserves.get(reservePub);
+    });
   if (!reserve) {
     logger.trace("not processing reserve: reserve does not exist");
     return;
@@ -712,7 +776,13 @@ export async function createTalerWithdrawReserve(
   // We do this here, as the reserve should be registered before we return,
   // so that we can redirect the user to the bank's status page.
   await processReserveBankStatus(ws, reserve.reservePub);
-  const processedReserve = await ws.db.get(Stores.reserves, 
reserve.reservePub);
+  const processedReserve = await ws.db
+    .mktx((x) => ({
+      reserves: x.reserves,
+    }))
+    .runReadOnly(async (tx) => {
+      return tx.reserves.get(reserve.reservePub);
+    });
   if (processedReserve?.reserveStatus === ReserveRecordStatus.BANK_ABORTED) {
     throw OperationFailedError.fromCode(
       TalerErrorCode.WALLET_WITHDRAWAL_OPERATION_ABORTED_BY_BANK,
@@ -730,14 +800,14 @@ export async function createTalerWithdrawReserve(
  * Get payto URIs needed to fund a reserve.
  */
 export async function getFundingPaytoUris(
-  tx: TransactionHandle<
-    | typeof Stores.reserves
-    | typeof Stores.exchanges
-    | typeof Stores.exchangeDetails
-  >,
+  tx: GetReadOnlyAccess<{
+    reserves: typeof WalletStoresV1.reserves;
+    exchanges: typeof WalletStoresV1.exchanges;
+    exchangeDetails: typeof WalletStoresV1.exchangeDetails;
+  }>,
   reservePub: string,
 ): Promise<string[]> {
-  const r = await tx.get(Stores.reserves, reservePub);
+  const r = await tx.reserves.get(reservePub);
   if (!r) {
     logger.error(`reserve ${reservePub} not found (DB corrupted?)`);
     return [];
diff --git a/packages/taler-wallet-core/src/operations/state.ts 
b/packages/taler-wallet-core/src/operations/state.ts
index 0d07f293..9bf73142 100644
--- a/packages/taler-wallet-core/src/operations/state.ts
+++ b/packages/taler-wallet-core/src/operations/state.ts
@@ -17,12 +17,22 @@
 /**
  * Imports.
  */
-import { WalletNotification, BalancesResponse, Logger } from 
"@gnu-taler/taler-util";
-import { Stores } from "../db.js";
-import { CryptoApi, OpenedPromise, Database, CryptoWorkerFactory, openPromise 
} from "../index.js";
+import {
+  WalletNotification,
+  BalancesResponse,
+  Logger,
+} from "@gnu-taler/taler-util";
+import { WalletStoresV1 } from "../db.js";
+import {
+  CryptoApi,
+  OpenedPromise,
+  CryptoWorkerFactory,
+  openPromise,
+} from "../index.js";
 import { PendingOperationsResponse } from "../pending-types.js";
 import { AsyncOpMemoMap, AsyncOpMemoSingle } from "../util/asyncMemo.js";
 import { HttpRequestLibrary } from "../util/http";
+import { DbAccess } from "../util/query.js";
 
 type NotificationListener = (n: WalletNotification) => void;
 
@@ -34,9 +44,7 @@ export const EXCHANGE_RESERVES_LOCK = 
"exchange-reserves-lock";
 export class InternalWalletState {
   memoProcessReserve: AsyncOpMemoMap<void> = new AsyncOpMemoMap();
   memoMakePlanchet: AsyncOpMemoMap<void> = new AsyncOpMemoMap();
-  memoGetPending: AsyncOpMemoSingle<
-    PendingOperationsResponse
-  > = new AsyncOpMemoSingle();
+  memoGetPending: AsyncOpMemoSingle<PendingOperationsResponse> = new 
AsyncOpMemoSingle();
   memoGetBalance: AsyncOpMemoSingle<BalancesResponse> = new 
AsyncOpMemoSingle();
   memoProcessRefresh: AsyncOpMemoMap<void> = new AsyncOpMemoMap();
   memoProcessRecoup: AsyncOpMemoMap<void> = new AsyncOpMemoMap();
@@ -60,7 +68,7 @@ export class InternalWalletState {
     // the actual value nullable.
     // Check if we are in a DB migration / garbage collection
     // and throw an error in that case.
-    public db: Database<typeof Stores>,
+    public db: DbAccess<typeof WalletStoresV1>,
     public http: HttpRequestLibrary,
     cryptoWorkerFactory: CryptoWorkerFactory,
   ) {
diff --git a/packages/taler-wallet-core/src/operations/testing.ts 
b/packages/taler-wallet-core/src/operations/testing.ts
index 6875a308..c9d99bf0 100644
--- a/packages/taler-wallet-core/src/operations/testing.ts
+++ b/packages/taler-wallet-core/src/operations/testing.ts
@@ -23,7 +23,16 @@ import {
   readSuccessResponseJsonOrThrow,
   checkSuccessResponseOrThrow,
 } from "../util/http";
-import { AmountString, codecForAny, CheckPaymentResponse, 
codecForCheckPaymentResponse, IntegrationTestArgs, Amounts, TestPayArgs, 
PreparePayResultType } from "@gnu-taler/taler-util";
+import {
+  AmountString,
+  codecForAny,
+  CheckPaymentResponse,
+  codecForCheckPaymentResponse,
+  IntegrationTestArgs,
+  Amounts,
+  TestPayArgs,
+  PreparePayResultType,
+} from "@gnu-taler/taler-util";
 import { URL } from "../index.js";
 import { Wallet } from "../wallet.js";
 import { createTalerWithdrawReserve } from "./reserves.js";
@@ -102,8 +111,8 @@ export async function withdrawTestBalance(
 function getMerchantAuthHeader(m: MerchantBackendInfo): Record<string, string> 
{
   if (m.authToken) {
     return {
-      "Authorization": `Bearer ${m.authToken}`,
-    }
+      Authorization: `Bearer ${m.authToken}`,
+    };
   }
   return {};
 }
diff --git a/packages/taler-wallet-core/src/operations/tip.ts 
b/packages/taler-wallet-core/src/operations/tip.ts
index f9d7a024..e9659248 100644
--- a/packages/taler-wallet-core/src/operations/tip.ts
+++ b/packages/taler-wallet-core/src/operations/tip.ts
@@ -32,7 +32,6 @@ import {
 } from "@gnu-taler/taler-util";
 import { DerivedTipPlanchet } from "../crypto/cryptoTypes.js";
 import {
-  Stores,
   DenominationRecord,
   CoinRecord,
   CoinSourceType,
@@ -70,10 +69,16 @@ export async function prepareTip(
     throw Error("invalid taler://tip URI");
   }
 
-  let tipRecord = await ws.db.getIndexed(
-    Stores.tips.byMerchantTipIdAndBaseUrl,
-    [res.merchantTipId, res.merchantBaseUrl],
-  );
+  let tipRecord = await ws.db
+    .mktx((x) => ({
+      tips: x.tips,
+    }))
+    .runReadOnly(async (tx) => {
+      return tx.tips.indexes.byMerchantTipIdAndBaseUrl.get([
+        res.merchantTipId,
+        res.merchantBaseUrl,
+      ]);
+    });
 
   if (!tipRecord) {
     const tipStatusUrl = new URL(
@@ -109,7 +114,7 @@ export async function prepareTip(
     const secretSeed = encodeCrock(getRandomBytes(64));
     const denomSelUid = encodeCrock(getRandomBytes(32));
 
-    tipRecord = {
+    const newTipRecord = {
       walletTipId: walletTipId,
       acceptedTimestamp: undefined,
       tipAmountRaw: amount,
@@ -130,7 +135,14 @@ export async function prepareTip(
       secretSeed,
       denomSelUid,
     };
-    await ws.db.put(Stores.tips, tipRecord);
+    await ws.db
+      .mktx((x) => ({
+        tips: x.tips,
+      }))
+      .runReadWrite(async (tx) => {
+        await tx.tips.put(newTipRecord);
+      });
+    tipRecord = newTipRecord;
   }
 
   const tipStatus: PrepareTipResult = {
@@ -151,19 +163,23 @@ async function incrementTipRetry(
   walletTipId: string,
   err: TalerErrorDetails | undefined,
 ): Promise<void> {
-  await ws.db.runWithWriteTransaction([Stores.tips], async (tx) => {
-    const t = await tx.get(Stores.tips, walletTipId);
-    if (!t) {
-      return;
-    }
-    if (!t.retryInfo) {
-      return;
-    }
-    t.retryInfo.retryCounter++;
-    updateRetryInfoTimeout(t.retryInfo);
-    t.lastError = err;
-    await tx.put(Stores.tips, t);
-  });
+  await ws.db
+    .mktx((x) => ({
+      tips: x.tips,
+    }))
+    .runReadWrite(async (tx) => {
+      const t = await tx.tips.get(walletTipId);
+      if (!t) {
+        return;
+      }
+      if (!t.retryInfo) {
+        return;
+      }
+      t.retryInfo.retryCounter++;
+      updateRetryInfoTimeout(t.retryInfo);
+      t.lastError = err;
+      await tx.tips.put(t);
+    });
   if (err) {
     ws.notify({ type: NotificationType.TipOperationError, error: err });
   }
@@ -186,12 +202,17 @@ async function resetTipRetry(
   ws: InternalWalletState,
   tipId: string,
 ): Promise<void> {
-  await ws.db.mutate(Stores.tips, tipId, (x) => {
-    if (x.retryInfo.active) {
-      x.retryInfo = initRetryInfo();
-    }
-    return x;
-  });
+  await ws.db
+    .mktx((x) => ({
+      tips: x.tips,
+    }))
+    .runReadWrite(async (tx) => {
+      const x = await tx.tips.get(tipId);
+      if (x && x.retryInfo.active) {
+        x.retryInfo = initRetryInfo();
+        await tx.tips.put(x);
+      }
+    });
 }
 
 async function processTipImpl(
@@ -202,7 +223,13 @@ async function processTipImpl(
   if (forceNow) {
     await resetTipRetry(ws, walletTipId);
   }
-  let tipRecord = await ws.db.get(Stores.tips, walletTipId);
+  const tipRecord = await ws.db
+    .mktx((x) => ({
+      tips: x.tips,
+    }))
+    .runReadOnly(async (tx) => {
+      return tx.tips.get(walletTipId);
+    });
   if (!tipRecord) {
     return;
   }
@@ -214,19 +241,22 @@ async function processTipImpl(
 
   const denomsForWithdraw = tipRecord.denomsSel;
 
-  tipRecord = await ws.db.get(Stores.tips, walletTipId);
-  checkDbInvariant(!!tipRecord, "tip record should be in database");
-
   const planchets: DerivedTipPlanchet[] = [];
   // Planchets in the form that the merchant expects
   const planchetsDetail: TipPlanchetDetail[] = [];
   const denomForPlanchet: { [index: number]: DenominationRecord } = [];
 
   for (const dh of denomsForWithdraw.selectedDenoms) {
-    const denom = await ws.db.get(Stores.denominations, [
-      tipRecord.exchangeBaseUrl,
-      dh.denomPubHash,
-    ]);
+    const denom = await ws.db
+      .mktx((x) => ({
+        denominations: x.denominations,
+      }))
+      .runReadOnly(async (tx) => {
+        return tx.denominations.get([
+          tipRecord.exchangeBaseUrl,
+          dh.denomPubHash,
+        ]);
+      });
     checkDbInvariant(!!denom, "denomination should be in database");
     for (let i = 0; i < dh.count; i++) {
       const deriveReq = {
@@ -306,18 +336,20 @@ async function processTipImpl(
     );
 
     if (!isValid) {
-      await ws.db.runWithWriteTransaction([Stores.tips], async (tx) => {
-        const tipRecord = await tx.get(Stores.tips, walletTipId);
-        if (!tipRecord) {
-          return;
-        }
-        tipRecord.lastError = makeErrorDetails(
-          TalerErrorCode.WALLET_TIPPING_COIN_SIGNATURE_INVALID,
-          "invalid signature from the exchange (via merchant tip) after 
unblinding",
-          {},
-        );
-        await tx.put(Stores.tips, tipRecord);
-      });
+      await ws.db
+        .mktx((x) => ({ tips: x.tips }))
+        .runReadWrite(async (tx) => {
+          const tipRecord = await tx.tips.get(walletTipId);
+          if (!tipRecord) {
+            return;
+          }
+          tipRecord.lastError = makeErrorDetails(
+            TalerErrorCode.WALLET_TIPPING_COIN_SIGNATURE_INVALID,
+            "invalid signature from the exchange (via merchant tip) after 
unblinding",
+            {},
+          );
+          await tx.tips.put(tipRecord);
+        });
       return;
     }
 
@@ -341,10 +373,14 @@ async function processTipImpl(
     });
   }
 
-  await ws.db.runWithWriteTransaction(
-    [Stores.coins, Stores.tips, Stores.withdrawalGroups],
-    async (tx) => {
-      const tr = await tx.get(Stores.tips, walletTipId);
+  await ws.db
+    .mktx((x) => ({
+      coins: x.coins,
+      tips: x.tips,
+      withdrawalGroups: x.withdrawalGroups,
+    }))
+    .runReadWrite(async (tx) => {
+      const tr = await tx.tips.get(walletTipId);
       if (!tr) {
         return;
       }
@@ -354,27 +390,32 @@ async function processTipImpl(
       tr.pickedUpTimestamp = getTimestampNow();
       tr.lastError = undefined;
       tr.retryInfo = initRetryInfo(false);
-      await tx.put(Stores.tips, tr);
+      await tx.tips.put(tr);
       for (const cr of newCoinRecords) {
-        await tx.put(Stores.coins, cr);
+        await tx.coins.put(cr);
       }
-    },
-  );
+    });
 }
 
 export async function acceptTip(
   ws: InternalWalletState,
   tipId: string,
 ): Promise<void> {
-  const tipRecord = await ws.db.get(Stores.tips, tipId);
-  if (!tipRecord) {
-    logger.error("tip not found");
-    return;
+  const found = await ws.db
+    .mktx((x) => ({
+      tips: x.tips,
+    }))
+    .runReadWrite(async (tx) => {
+      const tipRecord = await tx.tips.get(tipId);
+      if (!tipRecord) {
+        logger.error("tip not found");
+        return false;
+      }
+      tipRecord.acceptedTimestamp = getTimestampNow();
+      await tx.tips.put(tipRecord);
+      return true;
+    });
+  if (found) {
+    await processTip(ws, tipId);
   }
-
-  tipRecord.acceptedTimestamp = getTimestampNow();
-  await ws.db.put(Stores.tips, tipRecord);
-
-  await processTip(ws, tipId);
-  return;
 }
diff --git a/packages/taler-wallet-core/src/operations/transactions.ts 
b/packages/taler-wallet-core/src/operations/transactions.ts
index 42ed2d2e..ecef3c2c 100644
--- a/packages/taler-wallet-core/src/operations/transactions.ts
+++ b/packages/taler-wallet-core/src/operations/transactions.ts
@@ -19,7 +19,6 @@
  */
 import { InternalWalletState } from "./state";
 import {
-  Stores,
   WalletRefundItem,
   RefundState,
   ReserveRecordStatus,
@@ -85,296 +84,300 @@ export async function getTransactions(
 ): Promise<TransactionsResponse> {
   const transactions: Transaction[] = [];
 
-  await ws.db.runWithReadTransaction(
-    [
-      Stores.coins,
-      Stores.denominations,
-      Stores.exchanges,
-      Stores.exchangeDetails,
-      Stores.proposals,
-      Stores.purchases,
-      Stores.refreshGroups,
-      Stores.reserves,
-      Stores.tips,
-      Stores.withdrawalGroups,
-      Stores.planchets,
-      Stores.recoupGroups,
-      Stores.depositGroups,
-      Stores.tombstones,
-    ],
-    // Report withdrawals that are currently in progress.
-    async (tx) => {
-      tx.iter(Stores.withdrawalGroups).forEachAsync(async (wsr) => {
-        if (
-          shouldSkipCurrency(
-            transactionsRequest,
-            wsr.rawWithdrawalAmount.currency,
-          )
-        ) {
-          return;
-        }
+  await ws.db
+    .mktx((x) => ({
+      coins: x.coins,
+      denominations: x.denominations,
+      exchanges: x.exchanges,
+      exchangeDetails: x.exchangeDetails,
+      proposals: x.proposals,
+      purchases: x.purchases,
+      refreshGroups: x.refreshGroups,
+      reserves: x.reserves,
+      tips: x.tips,
+      withdrawalGroups: x.withdrawalGroups,
+      planchets: x.planchets,
+      recoupGroups: x.recoupGroups,
+      depositGroups: x.depositGroups,
+      tombstones: x.tombstones,
+    }))
+    .runReadOnly(
+      // Report withdrawals that are currently in progress.
+      async (tx) => {
+        tx.withdrawalGroups.iter().forEachAsync(async (wsr) => {
+          if (
+            shouldSkipCurrency(
+              transactionsRequest,
+              wsr.rawWithdrawalAmount.currency,
+            )
+          ) {
+            return;
+          }
 
-        if (shouldSkipSearch(transactionsRequest, [])) {
-          return;
-        }
+          if (shouldSkipSearch(transactionsRequest, [])) {
+            return;
+          }
 
-        const r = await tx.get(Stores.reserves, wsr.reservePub);
-        if (!r) {
-          return;
-        }
-        let amountRaw: AmountJson | undefined = undefined;
-        if (wsr.withdrawalGroupId === r.initialWithdrawalGroupId) {
-          amountRaw = r.instructedAmount;
-        } else {
-          amountRaw = wsr.denomsSel.totalWithdrawCost;
-        }
-        let withdrawalDetails: WithdrawalDetails;
-        if (r.bankInfo) {
-          withdrawalDetails = {
-            type: WithdrawalType.TalerBankIntegrationApi,
-            confirmed: true,
-            bankConfirmationUrl: r.bankInfo.confirmUrl,
-          };
-        } else {
-          const exchangeDetails = await getExchangeDetails(
-            tx,
-            wsr.exchangeBaseUrl,
-          );
-          if (!exchangeDetails) {
-            // FIXME: report somehow
+          const r = await tx.reserves.get(wsr.reservePub);
+          if (!r) {
             return;
           }
-          withdrawalDetails = {
-            type: WithdrawalType.ManualTransfer,
-            exchangePaytoUris:
-              exchangeDetails.wireInfo?.accounts.map((x) => x.payto_uri) ?? [],
-          };
-        }
-        transactions.push({
-          type: TransactionType.Withdrawal,
-          amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue),
-          amountRaw: Amounts.stringify(amountRaw),
-          withdrawalDetails,
-          exchangeBaseUrl: wsr.exchangeBaseUrl,
-          pending: !wsr.timestampFinish,
-          timestamp: wsr.timestampStart,
-          transactionId: makeEventId(
-            TransactionType.Withdrawal,
-            wsr.withdrawalGroupId,
-          ),
-          ...(wsr.lastError ? { error: wsr.lastError } : {}),
+          let amountRaw: AmountJson | undefined = undefined;
+          if (wsr.withdrawalGroupId === r.initialWithdrawalGroupId) {
+            amountRaw = r.instructedAmount;
+          } else {
+            amountRaw = wsr.denomsSel.totalWithdrawCost;
+          }
+          let withdrawalDetails: WithdrawalDetails;
+          if (r.bankInfo) {
+            withdrawalDetails = {
+              type: WithdrawalType.TalerBankIntegrationApi,
+              confirmed: true,
+              bankConfirmationUrl: r.bankInfo.confirmUrl,
+            };
+          } else {
+            const exchangeDetails = await getExchangeDetails(
+              tx,
+              wsr.exchangeBaseUrl,
+            );
+            if (!exchangeDetails) {
+              // FIXME: report somehow
+              return;
+            }
+            withdrawalDetails = {
+              type: WithdrawalType.ManualTransfer,
+              exchangePaytoUris:
+                exchangeDetails.wireInfo?.accounts.map((x) => x.payto_uri) ??
+                [],
+            };
+          }
+          transactions.push({
+            type: TransactionType.Withdrawal,
+            amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue),
+            amountRaw: Amounts.stringify(amountRaw),
+            withdrawalDetails,
+            exchangeBaseUrl: wsr.exchangeBaseUrl,
+            pending: !wsr.timestampFinish,
+            timestamp: wsr.timestampStart,
+            transactionId: makeEventId(
+              TransactionType.Withdrawal,
+              wsr.withdrawalGroupId,
+            ),
+            ...(wsr.lastError ? { error: wsr.lastError } : {}),
+          });
         });
-      });
 
-      // Report pending withdrawals based on reserves that
-      // were created, but where the actual withdrawal group has
-      // not started yet.
-      tx.iter(Stores.reserves).forEachAsync(async (r) => {
-        if (shouldSkipCurrency(transactionsRequest, r.currency)) {
-          return;
-        }
-        if (shouldSkipSearch(transactionsRequest, [])) {
-          return;
-        }
-        if (r.initialWithdrawalStarted) {
-          return;
-        }
-        if (r.reserveStatus === ReserveRecordStatus.BANK_ABORTED) {
-          return;
-        }
-        let withdrawalDetails: WithdrawalDetails;
-        if (r.bankInfo) {
-          withdrawalDetails = {
-            type: WithdrawalType.TalerBankIntegrationApi,
-            confirmed: false,
-            bankConfirmationUrl: r.bankInfo.confirmUrl,
-          };
-        } else {
-          withdrawalDetails = {
-            type: WithdrawalType.ManualTransfer,
-            exchangePaytoUris: await getFundingPaytoUris(tx, r.reservePub),
-          };
-        }
-        transactions.push({
-          type: TransactionType.Withdrawal,
-          amountRaw: Amounts.stringify(r.instructedAmount),
-          amountEffective: Amounts.stringify(r.initialDenomSel.totalCoinValue),
-          exchangeBaseUrl: r.exchangeBaseUrl,
-          pending: true,
-          timestamp: r.timestampCreated,
-          withdrawalDetails: withdrawalDetails,
-          transactionId: makeEventId(
-            TransactionType.Withdrawal,
-            r.initialWithdrawalGroupId,
-          ),
-          ...(r.lastError ? { error: r.lastError } : {}),
+        // Report pending withdrawals based on reserves that
+        // were created, but where the actual withdrawal group has
+        // not started yet.
+        tx.reserves.iter().forEachAsync(async (r) => {
+          if (shouldSkipCurrency(transactionsRequest, r.currency)) {
+            return;
+          }
+          if (shouldSkipSearch(transactionsRequest, [])) {
+            return;
+          }
+          if (r.initialWithdrawalStarted) {
+            return;
+          }
+          if (r.reserveStatus === ReserveRecordStatus.BANK_ABORTED) {
+            return;
+          }
+          let withdrawalDetails: WithdrawalDetails;
+          if (r.bankInfo) {
+            withdrawalDetails = {
+              type: WithdrawalType.TalerBankIntegrationApi,
+              confirmed: false,
+              bankConfirmationUrl: r.bankInfo.confirmUrl,
+            };
+          } else {
+            withdrawalDetails = {
+              type: WithdrawalType.ManualTransfer,
+              exchangePaytoUris: await getFundingPaytoUris(tx, r.reservePub),
+            };
+          }
+          transactions.push({
+            type: TransactionType.Withdrawal,
+            amountRaw: Amounts.stringify(r.instructedAmount),
+            amountEffective: Amounts.stringify(
+              r.initialDenomSel.totalCoinValue,
+            ),
+            exchangeBaseUrl: r.exchangeBaseUrl,
+            pending: true,
+            timestamp: r.timestampCreated,
+            withdrawalDetails: withdrawalDetails,
+            transactionId: makeEventId(
+              TransactionType.Withdrawal,
+              r.initialWithdrawalGroupId,
+            ),
+            ...(r.lastError ? { error: r.lastError } : {}),
+          });
         });
-      });
 
-      tx.iter(Stores.depositGroups).forEachAsync(async (dg) => {
-        const amount = Amounts.parseOrThrow(dg.contractTermsRaw.amount);
-        if (shouldSkipCurrency(transactionsRequest, amount.currency)) {
-          return;
-        }
-
-        transactions.push({
-          type: TransactionType.Deposit,
-          amountRaw: Amounts.stringify(dg.effectiveDepositAmount),
-          amountEffective: Amounts.stringify(dg.totalPayCost),
-          pending: !dg.timestampFinished,
-          timestamp: dg.timestampCreated,
-          targetPaytoUri: dg.wire.payto_uri,
-          transactionId: makeEventId(
-            TransactionType.Deposit,
-            dg.depositGroupId,
-          ),
-          depositGroupId: dg.depositGroupId,
-          ...(dg.lastError ? { error: dg.lastError } : {}),
-        });
-      });
+        tx.depositGroups.iter().forEachAsync(async (dg) => {
+          const amount = Amounts.parseOrThrow(dg.contractTermsRaw.amount);
+          if (shouldSkipCurrency(transactionsRequest, amount.currency)) {
+            return;
+          }
 
-      tx.iter(Stores.purchases).forEachAsync(async (pr) => {
-        if (
-          shouldSkipCurrency(
-            transactionsRequest,
-            pr.download.contractData.amount.currency,
-          )
-        ) {
-          return;
-        }
-        const contractData = pr.download.contractData;
-        if (shouldSkipSearch(transactionsRequest, [contractData.summary])) {
-          return;
-        }
-        const proposal = await tx.get(Stores.proposals, pr.proposalId);
-        if (!proposal) {
-          return;
-        }
-        const info: OrderShortInfo = {
-          merchant: contractData.merchant,
-          orderId: contractData.orderId,
-          products: contractData.products,
-          summary: contractData.summary,
-          summary_i18n: contractData.summaryI18n,
-          contractTermsHash: contractData.contractTermsHash,
-        };
-        if (contractData.fulfillmentUrl !== "") {
-          info.fulfillmentUrl = contractData.fulfillmentUrl;
-        }
-        const paymentTransactionId = makeEventId(
-          TransactionType.Payment,
-          pr.proposalId,
-        );
-        const err = pr.lastPayError ?? pr.lastRefundStatusError;
-        transactions.push({
-          type: TransactionType.Payment,
-          amountRaw: Amounts.stringify(contractData.amount),
-          amountEffective: Amounts.stringify(pr.totalPayCost),
-          status: pr.timestampFirstSuccessfulPay
-            ? PaymentStatus.Paid
-            : PaymentStatus.Accepted,
-          pending:
-            !pr.timestampFirstSuccessfulPay &&
-            pr.abortStatus === AbortStatus.None,
-          timestamp: pr.timestampAccept,
-          transactionId: paymentTransactionId,
-          proposalId: pr.proposalId,
-          info: info,
-          ...(err ? { error: err } : {}),
+          transactions.push({
+            type: TransactionType.Deposit,
+            amountRaw: Amounts.stringify(dg.effectiveDepositAmount),
+            amountEffective: Amounts.stringify(dg.totalPayCost),
+            pending: !dg.timestampFinished,
+            timestamp: dg.timestampCreated,
+            targetPaytoUri: dg.wire.payto_uri,
+            transactionId: makeEventId(
+              TransactionType.Deposit,
+              dg.depositGroupId,
+            ),
+            depositGroupId: dg.depositGroupId,
+            ...(dg.lastError ? { error: dg.lastError } : {}),
+          });
         });
 
-        const refundGroupKeys = new Set<string>();
-
-        for (const rk of Object.keys(pr.refunds)) {
-          const refund = pr.refunds[rk];
-          const groupKey = `${refund.executionTime.t_ms}`;
-          refundGroupKeys.add(groupKey);
-        }
-
-        for (const groupKey of refundGroupKeys.values()) {
-          const refundTombstoneId = makeEventId(
-            TombstoneTag.DeleteRefund,
-            pr.proposalId,
-            groupKey,
-          );
-          const tombstone = await tx.get(Stores.tombstones, refundTombstoneId);
-          if (tombstone) {
-            continue;
+        tx.purchases.iter().forEachAsync(async (pr) => {
+          if (
+            shouldSkipCurrency(
+              transactionsRequest,
+              pr.download.contractData.amount.currency,
+            )
+          ) {
+            return;
           }
-          const refundTransactionId = makeEventId(
-            TransactionType.Refund,
+          const contractData = pr.download.contractData;
+          if (shouldSkipSearch(transactionsRequest, [contractData.summary])) {
+            return;
+          }
+          const proposal = await tx.proposals.get(pr.proposalId);
+          if (!proposal) {
+            return;
+          }
+          const info: OrderShortInfo = {
+            merchant: contractData.merchant,
+            orderId: contractData.orderId,
+            products: contractData.products,
+            summary: contractData.summary,
+            summary_i18n: contractData.summaryI18n,
+            contractTermsHash: contractData.contractTermsHash,
+          };
+          if (contractData.fulfillmentUrl !== "") {
+            info.fulfillmentUrl = contractData.fulfillmentUrl;
+          }
+          const paymentTransactionId = makeEventId(
+            TransactionType.Payment,
             pr.proposalId,
-            groupKey,
           );
-          let r0: WalletRefundItem | undefined;
-          let amountRaw = Amounts.getZero(contractData.amount.currency);
-          let amountEffective = Amounts.getZero(contractData.amount.currency);
+          const err = pr.lastPayError ?? pr.lastRefundStatusError;
+          transactions.push({
+            type: TransactionType.Payment,
+            amountRaw: Amounts.stringify(contractData.amount),
+            amountEffective: Amounts.stringify(pr.totalPayCost),
+            status: pr.timestampFirstSuccessfulPay
+              ? PaymentStatus.Paid
+              : PaymentStatus.Accepted,
+            pending:
+              !pr.timestampFirstSuccessfulPay &&
+              pr.abortStatus === AbortStatus.None,
+            timestamp: pr.timestampAccept,
+            transactionId: paymentTransactionId,
+            proposalId: pr.proposalId,
+            info: info,
+            ...(err ? { error: err } : {}),
+          });
+
+          const refundGroupKeys = new Set<string>();
+
           for (const rk of Object.keys(pr.refunds)) {
             const refund = pr.refunds[rk];
-            const myGroupKey = `${refund.executionTime.t_ms}`;
-            if (myGroupKey !== groupKey) {
+            const groupKey = `${refund.executionTime.t_ms}`;
+            refundGroupKeys.add(groupKey);
+          }
+
+          for (const groupKey of refundGroupKeys.values()) {
+            const refundTombstoneId = makeEventId(
+              TombstoneTag.DeleteRefund,
+              pr.proposalId,
+              groupKey,
+            );
+            const tombstone = await tx.tombstones.get(refundTombstoneId);
+            if (tombstone) {
               continue;
             }
+            const refundTransactionId = makeEventId(
+              TransactionType.Refund,
+              pr.proposalId,
+              groupKey,
+            );
+            let r0: WalletRefundItem | undefined;
+            let amountRaw = Amounts.getZero(contractData.amount.currency);
+            let amountEffective = 
Amounts.getZero(contractData.amount.currency);
+            for (const rk of Object.keys(pr.refunds)) {
+              const refund = pr.refunds[rk];
+              const myGroupKey = `${refund.executionTime.t_ms}`;
+              if (myGroupKey !== groupKey) {
+                continue;
+              }
+              if (!r0) {
+                r0 = refund;
+              }
+
+              if (refund.type === RefundState.Applied) {
+                amountRaw = Amounts.add(amountRaw, refund.refundAmount).amount;
+                amountEffective = Amounts.add(
+                  amountEffective,
+                  Amounts.sub(
+                    refund.refundAmount,
+                    refund.refundFee,
+                    refund.totalRefreshCostBound,
+                  ).amount,
+                ).amount;
+              }
+            }
             if (!r0) {
-              r0 = refund;
+              throw Error("invariant violated");
             }
+            transactions.push({
+              type: TransactionType.Refund,
+              info,
+              refundedTransactionId: paymentTransactionId,
+              transactionId: refundTransactionId,
+              timestamp: r0.obtainedTime,
+              amountEffective: Amounts.stringify(amountEffective),
+              amountRaw: Amounts.stringify(amountRaw),
+              pending: false,
+            });
+          }
+        });
 
-            if (refund.type === RefundState.Applied) {
-              amountRaw = Amounts.add(amountRaw, refund.refundAmount).amount;
-              amountEffective = Amounts.add(
-                amountEffective,
-                Amounts.sub(
-                  refund.refundAmount,
-                  refund.refundFee,
-                  refund.totalRefreshCostBound,
-                ).amount,
-              ).amount;
-            }
+        tx.tips.iter().forEachAsync(async (tipRecord) => {
+          if (
+            shouldSkipCurrency(
+              transactionsRequest,
+              tipRecord.tipAmountRaw.currency,
+            )
+          ) {
+            return;
           }
-          if (!r0) {
-            throw Error("invariant violated");
+          if (!tipRecord.acceptedTimestamp) {
+            return;
           }
           transactions.push({
-            type: TransactionType.Refund,
-            info,
-            refundedTransactionId: paymentTransactionId,
-            transactionId: refundTransactionId,
-            timestamp: r0.obtainedTime,
-            amountEffective: Amounts.stringify(amountEffective),
-            amountRaw: Amounts.stringify(amountRaw),
-            pending: false,
+            type: TransactionType.Tip,
+            amountEffective: Amounts.stringify(tipRecord.tipAmountEffective),
+            amountRaw: Amounts.stringify(tipRecord.tipAmountRaw),
+            pending: !tipRecord.pickedUpTimestamp,
+            timestamp: tipRecord.acceptedTimestamp,
+            transactionId: makeEventId(
+              TransactionType.Tip,
+              tipRecord.walletTipId,
+            ),
+            merchantBaseUrl: tipRecord.merchantBaseUrl,
+            error: tipRecord.lastError,
           });
-        }
-      });
-
-      tx.iter(Stores.tips).forEachAsync(async (tipRecord) => {
-        if (
-          shouldSkipCurrency(
-            transactionsRequest,
-            tipRecord.tipAmountRaw.currency,
-          )
-        ) {
-          return;
-        }
-        if (!tipRecord.acceptedTimestamp) {
-          return;
-        }
-        transactions.push({
-          type: TransactionType.Tip,
-          amountEffective: Amounts.stringify(tipRecord.tipAmountEffective),
-          amountRaw: Amounts.stringify(tipRecord.tipAmountRaw),
-          pending: !tipRecord.pickedUpTimestamp,
-          timestamp: tipRecord.acceptedTimestamp,
-          transactionId: makeEventId(
-            TransactionType.Tip,
-            tipRecord.walletTipId,
-          ),
-          merchantBaseUrl: tipRecord.merchantBaseUrl,
-          error: tipRecord.lastError,
         });
-      });
-    },
-  );
+      },
+    );
 
   const txPending = transactions.filter((x) => x.pending);
   const txNotPending = transactions.filter((x) => !x.pending);
@@ -406,110 +409,126 @@ export async function deleteTransaction(
 
   if (type === TransactionType.Withdrawal) {
     const withdrawalGroupId = rest[0];
-    await ws.db.runWithWriteTransaction(
-      [Stores.withdrawalGroups, Stores.reserves, Stores.tombstones],
-      async (tx) => {
-        const withdrawalGroupRecord = await tx.get(
-          Stores.withdrawalGroups,
+    await ws.db
+      .mktx((x) => ({
+        withdrawalGroups: x.withdrawalGroups,
+        reserves: x.reserves,
+        tombstones: x.tombstones,
+      }))
+      .runReadWrite(async (tx) => {
+        const withdrawalGroupRecord = await tx.withdrawalGroups.get(
           withdrawalGroupId,
         );
         if (withdrawalGroupRecord) {
-          await tx.delete(Stores.withdrawalGroups, withdrawalGroupId);
-          await tx.put(Stores.tombstones, {
+          await tx.withdrawalGroups.delete(withdrawalGroupId);
+          await tx.tombstones.put({
             id: TombstoneTag.DeleteWithdrawalGroup + ":" + withdrawalGroupId,
           });
           return;
         }
-        const reserveRecord: ReserveRecord | undefined = await tx.getIndexed(
-          Stores.reserves.byInitialWithdrawalGroupId,
+        const reserveRecord:
+          | ReserveRecord
+          | undefined = await 
tx.reserves.indexes.byInitialWithdrawalGroupId.get(
           withdrawalGroupId,
         );
         if (reserveRecord && !reserveRecord.initialWithdrawalStarted) {
           const reservePub = reserveRecord.reservePub;
-          await tx.delete(Stores.reserves, reservePub);
-          await tx.put(Stores.tombstones, {
+          await tx.reserves.delete(reservePub);
+          await tx.tombstones.put({
             id: TombstoneTag.DeleteReserve + ":" + reservePub,
           });
         }
-      },
-    );
+      });
   } else if (type === TransactionType.Payment) {
     const proposalId = rest[0];
-    await ws.db.runWithWriteTransaction(
-      [Stores.proposals, Stores.purchases, Stores.tombstones],
-      async (tx) => {
+    await ws.db
+      .mktx((x) => ({
+        proposals: x.proposals,
+        purchases: x.purchases,
+        tombstones: x.tombstones,
+      }))
+      .runReadWrite(async (tx) => {
         let found = false;
-        const proposal = await tx.get(Stores.proposals, proposalId);
+        const proposal = await tx.proposals.get(proposalId);
         if (proposal) {
           found = true;
-          await tx.delete(Stores.proposals, proposalId);
+          await tx.proposals.delete(proposalId);
         }
-        const purchase = await tx.get(Stores.purchases, proposalId);
+        const purchase = await tx.purchases.get(proposalId);
         if (purchase) {
           found = true;
-          await tx.delete(Stores.proposals, proposalId);
+          await tx.proposals.delete(proposalId);
         }
         if (found) {
-          await tx.put(Stores.tombstones, {
+          await tx.tombstones.put({
             id: TombstoneTag.DeletePayment + ":" + proposalId,
           });
         }
-      },
-    );
+      });
   } else if (type === TransactionType.Refresh) {
     const refreshGroupId = rest[0];
-    await ws.db.runWithWriteTransaction(
-      [Stores.refreshGroups, Stores.tombstones],
-      async (tx) => {
-        const rg = await tx.get(Stores.refreshGroups, refreshGroupId);
+    await ws.db
+      .mktx((x) => ({
+        refreshGroups: x.refreshGroups,
+        tombstones: x.tombstones,
+      }))
+      .runReadWrite(async (tx) => {
+        const rg = await tx.refreshGroups.get(refreshGroupId);
         if (rg) {
-          await tx.delete(Stores.refreshGroups, refreshGroupId);
-          await tx.put(Stores.tombstones, {
+          await tx.refreshGroups.delete(refreshGroupId);
+          await tx.tombstones.put({
             id: TombstoneTag.DeleteRefreshGroup + ":" + refreshGroupId,
           });
         }
-      },
-    );
+      });
   } else if (type === TransactionType.Tip) {
     const tipId = rest[0];
-    await ws.db.runWithWriteTransaction(
-      [Stores.tips, Stores.tombstones],
-      async (tx) => {
-        const tipRecord = await tx.get(Stores.tips, tipId);
+    await ws.db
+      .mktx((x) => ({
+        tips: x.tips,
+        tombstones: x.tombstones,
+      }))
+      .runReadWrite(async (tx) => {
+        const tipRecord = await tx.tips.get(tipId);
         if (tipRecord) {
-          await tx.delete(Stores.tips, tipId);
-          await tx.put(Stores.tombstones, {
+          await tx.tips.delete(tipId);
+          await tx.tombstones.put({
             id: TombstoneTag.DeleteTip + ":" + tipId,
           });
         }
-      },
-    );
+      });
   } else if (type === TransactionType.Deposit) {
     const depositGroupId = rest[0];
-    await ws.db.runWithWriteTransaction(
-      [Stores.depositGroups, Stores.tombstones],
-      async (tx) => {
-        const tipRecord = await tx.get(Stores.depositGroups, depositGroupId);
+    await ws.db
+      .mktx((x) => ({
+        depositGroups: x.depositGroups,
+        tombstones: x.tombstones,
+      }))
+      .runReadWrite(async (tx) => {
+        const tipRecord = await tx.depositGroups.get(depositGroupId);
         if (tipRecord) {
-          await tx.delete(Stores.depositGroups, depositGroupId);
-          await tx.put(Stores.tombstones, {
+          await tx.depositGroups.delete(depositGroupId);
+          await tx.tombstones.put({
             id: TombstoneTag.DeleteDepositGroup + ":" + depositGroupId,
           });
         }
-      },
-    );
+      });
   } else if (type === TransactionType.Refund) {
     const proposalId = rest[0];
     const executionTimeStr = rest[1];
 
-    await ws.db.runWithWriteTransaction(
-      [Stores.proposals, Stores.purchases, Stores.tombstones],
-      async (tx) => {
-        const purchase = await tx.get(Stores.purchases, proposalId);
+    await ws.db
+      .mktx((x) => ({
+        proposals: x.proposals,
+        purchases: x.purchases,
+        tombstones: x.tombstones,
+      }))
+      .runReadWrite(async (tx) => {
+        const purchase = await tx.purchases.get(proposalId);
         if (purchase) {
           // This should just influence the history view,
           // but won't delete any actual refund information.
-          await tx.put(Stores.tombstones, {
+          await tx.tombstones.put({
             id: makeEventId(
               TombstoneTag.DeleteRefund,
               proposalId,
@@ -517,8 +536,7 @@ export async function deleteTransaction(
             ),
           });
         }
-      },
-    );
+      });
   } else {
     throw Error(`can't delete a '${type}' transaction`);
   }
diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts 
b/packages/taler-wallet-core/src/operations/withdraw.ts
index 36be84df..1266a3b0 100644
--- a/packages/taler-wallet-core/src/operations/withdraw.ts
+++ b/packages/taler-wallet-core/src/operations/withdraw.ts
@@ -26,7 +26,6 @@ import {
 } from "@gnu-taler/taler-util";
 import {
   DenominationRecord,
-  Stores,
   DenominationStatus,
   CoinStatus,
   CoinRecord,
@@ -314,13 +313,17 @@ export async function getCandidateWithdrawalDenoms(
   exchangeBaseUrl: string,
 ): Promise<DenominationRecord[]> {
   return await ws.db
-    .iterIndex(Stores.denominations.exchangeBaseUrlIndex, exchangeBaseUrl)
-    .filter((d) => {
-      return (
-        (d.status === DenominationStatus.Unverified ||
-          d.status === DenominationStatus.VerifiedGood) &&
-        !d.isRevoked
-      );
+    .mktx((x) => ({ denominations: x.denominations }))
+    .runReadOnly(async (tx) => {
+      return tx.denominations.indexes.byExchangeBaseUrl
+        .iter(exchangeBaseUrl)
+        .filter((d) => {
+          return (
+            (d.status === DenominationStatus.Unverified ||
+              d.status === DenominationStatus.VerifiedGood) &&
+            !d.isRevoked
+          );
+        });
     });
 }
 
@@ -336,17 +339,24 @@ async function processPlanchetGenerate(
   withdrawalGroupId: string,
   coinIdx: number,
 ): Promise<void> {
-  const withdrawalGroup = await ws.db.get(
-    Stores.withdrawalGroups,
-    withdrawalGroupId,
-  );
+  const withdrawalGroup = await ws.db
+    .mktx((x) => ({ withdrawalGroups: x.withdrawalGroups }))
+    .runReadOnly(async (tx) => {
+      return await tx.withdrawalGroups.get(withdrawalGroupId);
+    });
   if (!withdrawalGroup) {
     return;
   }
-  let planchet = await ws.db.getIndexed(Stores.planchets.byGroupAndIndex, [
-    withdrawalGroupId,
-    coinIdx,
-  ]);
+  let planchet = await ws.db
+    .mktx((x) => ({
+      planchets: x.planchets,
+    }))
+    .runReadOnly(async (tx) => {
+      return tx.planchets.indexes.byGroupAndIndex.get([
+        withdrawalGroupId,
+        coinIdx,
+      ]);
+    });
   if (!planchet) {
     let ci = 0;
     let denomPubHash: string | undefined;
@@ -365,20 +375,26 @@ async function processPlanchetGenerate(
     if (!denomPubHash) {
       throw Error("invariant violated");
     }
-    const denom = await ws.db.get(Stores.denominations, [
-      withdrawalGroup.exchangeBaseUrl,
-      denomPubHash,
-    ]);
-    if (!denom) {
-      throw Error("invariant violated");
-    }
-    const reserve = await ws.db.get(
-      Stores.reserves,
-      withdrawalGroup.reservePub,
-    );
-    if (!reserve) {
-      throw Error("invariant violated");
-    }
+
+    const { denom, reserve } = await ws.db
+      .mktx((x) => ({
+        reserves: x.reserves,
+        denominations: x.denominations,
+      }))
+      .runReadOnly(async (tx) => {
+        const denom = await tx.denominations.get([
+          withdrawalGroup.exchangeBaseUrl,
+          denomPubHash!,
+        ]);
+        if (!denom) {
+          throw Error("invariant violated");
+        }
+        const reserve = await tx.reserves.get(withdrawalGroup.reservePub);
+        if (!reserve) {
+          throw Error("invariant violated");
+        }
+        return { denom, reserve };
+      });
     const r = await ws.cryptoApi.createPlanchet({
       denomPub: denom.denomPub,
       feeWithdraw: denom.feeWithdraw,
@@ -405,18 +421,20 @@ async function processPlanchetGenerate(
       withdrawalGroupId: withdrawalGroupId,
       lastError: undefined,
     };
-    await ws.db.runWithWriteTransaction([Stores.planchets], async (tx) => {
-      const p = await tx.getIndexed(Stores.planchets.byGroupAndIndex, [
-        withdrawalGroupId,
-        coinIdx,
-      ]);
-      if (p) {
-        planchet = p;
-        return;
-      }
-      await tx.put(Stores.planchets, newPlanchet);
-      planchet = newPlanchet;
-    });
+    await ws.db
+      .mktx((x) => ({ planchets: x.planchets }))
+      .runReadWrite(async (tx) => {
+        const p = await tx.planchets.indexes.byGroupAndIndex.get([
+          withdrawalGroupId,
+          coinIdx,
+        ]);
+        if (p) {
+          planchet = p;
+          return;
+        }
+        await tx.planchets.put(newPlanchet);
+        planchet = newPlanchet;
+      });
   }
 }
 
@@ -430,59 +448,70 @@ async function processPlanchetExchangeRequest(
   withdrawalGroupId: string,
   coinIdx: number,
 ): Promise<WithdrawResponse | undefined> {
-  const withdrawalGroup = await ws.db.get(
-    Stores.withdrawalGroups,
-    withdrawalGroupId,
-  );
-  if (!withdrawalGroup) {
-    return;
-  }
-  let planchet = await ws.db.getIndexed(Stores.planchets.byGroupAndIndex, [
-    withdrawalGroupId,
-    coinIdx,
-  ]);
-  if (!planchet) {
-    return;
-  }
-  if (planchet.withdrawalDone) {
-    logger.warn("processPlanchet: planchet already withdrawn");
-    return;
-  }
-  const exchange = await ws.db.get(
-    Stores.exchanges,
-    withdrawalGroup.exchangeBaseUrl,
-  );
-  if (!exchange) {
-    logger.error("db inconsistent: exchange for planchet not found");
-    return;
-  }
+  const d = await ws.db
+    .mktx((x) => ({
+      withdrawalGroups: x.withdrawalGroups,
+      planchets: x.planchets,
+      exchanges: x.exchanges,
+      denominations: x.denominations,
+    }))
+    .runReadOnly(async (tx) => {
+      const withdrawalGroup = await tx.withdrawalGroups.get(withdrawalGroupId);
+      if (!withdrawalGroup) {
+        return;
+      }
+      let planchet = await tx.planchets.indexes.byGroupAndIndex.get([
+        withdrawalGroupId,
+        coinIdx,
+      ]);
+      if (!planchet) {
+        return;
+      }
+      if (planchet.withdrawalDone) {
+        logger.warn("processPlanchet: planchet already withdrawn");
+        return;
+      }
+      const exchange = await tx.exchanges.get(withdrawalGroup.exchangeBaseUrl);
+      if (!exchange) {
+        logger.error("db inconsistent: exchange for planchet not found");
+        return;
+      }
 
-  const denom = await ws.db.get(Stores.denominations, [
-    withdrawalGroup.exchangeBaseUrl,
-    planchet.denomPubHash,
-  ]);
+      const denom = await tx.denominations.get([
+        withdrawalGroup.exchangeBaseUrl,
+        planchet.denomPubHash,
+      ]);
 
-  if (!denom) {
-    console.error("db inconsistent: denom for planchet not found");
-    return;
-  }
+      if (!denom) {
+        console.error("db inconsistent: denom for planchet not found");
+        return;
+      }
 
-  logger.trace(
-    `processing planchet #${coinIdx} in withdrawal ${withdrawalGroupId}`,
-  );
+      logger.trace(
+        `processing planchet #${coinIdx} in withdrawal ${withdrawalGroupId}`,
+      );
 
-  const wd: any = {};
-  wd.denom_pub_hash = planchet.denomPubHash;
-  wd.reserve_pub = planchet.reservePub;
-  wd.reserve_sig = planchet.withdrawSig;
-  wd.coin_ev = planchet.coinEv;
-  const reqUrl = new URL(
-    `reserves/${planchet.reservePub}/withdraw`,
-    exchange.baseUrl,
-  ).href;
+      const reqBody: any = {
+        denom_pub_hash: planchet.denomPubHash,
+        reserve_pub: planchet.reservePub,
+        reserve_sig: planchet.withdrawSig,
+        coin_ev: planchet.coinEv,
+      };
+      const reqUrl = new URL(
+        `reserves/${planchet.reservePub}/withdraw`,
+        exchange.baseUrl,
+      ).href;
+
+      return { reqUrl, reqBody };
+    });
+
+  if (!d) {
+    return;
+  }
+  const { reqUrl, reqBody } = d;
 
   try {
-    const resp = await ws.http.postJson(reqUrl, wd);
+    const resp = await ws.http.postJson(reqUrl, reqBody);
     const r = await readSuccessResponseJsonOrThrow(
       resp,
       codecForWithdrawResponse(),
@@ -495,17 +524,19 @@ async function processPlanchetExchangeRequest(
       throw e;
     }
     const errDetails = e.operationError;
-    await ws.db.runWithWriteTransaction([Stores.planchets], async (tx) => {
-      let planchet = await tx.getIndexed(Stores.planchets.byGroupAndIndex, [
-        withdrawalGroupId,
-        coinIdx,
-      ]);
-      if (!planchet) {
-        return;
-      }
-      planchet.lastError = errDetails;
-      await tx.put(Stores.planchets, planchet);
-    });
+    await ws.db
+      .mktx((x) => ({ planchets: x.planchets }))
+      .runReadWrite(async (tx) => {
+        let planchet = await tx.planchets.indexes.byGroupAndIndex.get([
+          withdrawalGroupId,
+          coinIdx,
+        ]);
+        if (!planchet) {
+          return;
+        }
+        planchet.lastError = errDetails;
+        await tx.planchets.put(planchet);
+      });
     return;
   }
 }
@@ -516,25 +547,36 @@ async function processPlanchetVerifyAndStoreCoin(
   coinIdx: number,
   resp: WithdrawResponse,
 ): Promise<void> {
-  const withdrawalGroup = await ws.db.get(
-    Stores.withdrawalGroups,
-    withdrawalGroupId,
-  );
-  if (!withdrawalGroup) {
-    return;
-  }
-  let planchet = await ws.db.getIndexed(Stores.planchets.byGroupAndIndex, [
-    withdrawalGroupId,
-    coinIdx,
-  ]);
-  if (!planchet) {
-    return;
-  }
-  if (planchet.withdrawalDone) {
-    logger.warn("processPlanchet: planchet already withdrawn");
+  const d = await ws.db
+    .mktx((x) => ({
+      withdrawalGroups: x.withdrawalGroups,
+      planchets: x.planchets,
+    }))
+    .runReadOnly(async (tx) => {
+      const withdrawalGroup = await tx.withdrawalGroups.get(withdrawalGroupId);
+      if (!withdrawalGroup) {
+        return;
+      }
+      let planchet = await tx.planchets.indexes.byGroupAndIndex.get([
+        withdrawalGroupId,
+        coinIdx,
+      ]);
+      if (!planchet) {
+        return;
+      }
+      if (planchet.withdrawalDone) {
+        logger.warn("processPlanchet: planchet already withdrawn");
+        return;
+      }
+      return { planchet, exchangeBaseUrl: withdrawalGroup.exchangeBaseUrl };
+    });
+
+  if (!d) {
     return;
   }
 
+  const { planchet, exchangeBaseUrl } = d;
+
   const denomSig = await ws.cryptoApi.rsaUnblind(
     resp.ev_sig,
     planchet.blindingKey,
@@ -548,21 +590,23 @@ async function processPlanchetVerifyAndStoreCoin(
   );
 
   if (!isValid) {
-    await ws.db.runWithWriteTransaction([Stores.planchets], async (tx) => {
-      let planchet = await tx.getIndexed(Stores.planchets.byGroupAndIndex, [
-        withdrawalGroupId,
-        coinIdx,
-      ]);
-      if (!planchet) {
-        return;
-      }
-      planchet.lastError = makeErrorDetails(
-        TalerErrorCode.WALLET_EXCHANGE_COIN_SIGNATURE_INVALID,
-        "invalid signature from the exchange after unblinding",
-        {},
-      );
-      await tx.put(Stores.planchets, planchet);
-    });
+    await ws.db
+      .mktx((x) => ({ planchets: x.planchets }))
+      .runReadWrite(async (tx) => {
+        let planchet = await tx.planchets.indexes.byGroupAndIndex.get([
+          withdrawalGroupId,
+          coinIdx,
+        ]);
+        if (!planchet) {
+          return;
+        }
+        planchet.lastError = makeErrorDetails(
+          TalerErrorCode.WALLET_EXCHANGE_COIN_SIGNATURE_INVALID,
+          "invalid signature from the exchange after unblinding",
+          {},
+        );
+        await tx.planchets.put(planchet);
+      });
     return;
   }
 
@@ -575,7 +619,7 @@ async function processPlanchetVerifyAndStoreCoin(
     denomPubHash: planchet.denomPubHash,
     denomSig,
     coinEvHash: planchet.coinEvHash,
-    exchangeBaseUrl: withdrawalGroup.exchangeBaseUrl,
+    exchangeBaseUrl: exchangeBaseUrl,
     status: CoinStatus.Fresh,
     coinSource: {
       type: CoinSourceType.Withdraw,
@@ -588,23 +632,27 @@ async function processPlanchetVerifyAndStoreCoin(
 
   const planchetCoinPub = planchet.coinPub;
 
-  const firstSuccess = await ws.db.runWithWriteTransaction(
-    [Stores.coins, Stores.withdrawalGroups, Stores.reserves, Stores.planchets],
-    async (tx) => {
-      const ws = await tx.get(Stores.withdrawalGroups, withdrawalGroupId);
+  const firstSuccess = await ws.db
+    .mktx((x) => ({
+      coins: x.coins,
+      withdrawalGroups: x.withdrawalGroups,
+      reserves: x.reserves,
+      planchets: x.planchets,
+    }))
+    .runReadWrite(async (tx) => {
+      const ws = await tx.withdrawalGroups.get(withdrawalGroupId);
       if (!ws) {
         return false;
       }
-      const p = await tx.get(Stores.planchets, planchetCoinPub);
+      const p = await tx.planchets.get(planchetCoinPub);
       if (!p || p.withdrawalDone) {
         return false;
       }
       p.withdrawalDone = true;
-      await tx.put(Stores.planchets, p);
-      await tx.add(Stores.coins, coin);
+      await tx.planchets.put(p);
+      await tx.coins.add(coin);
       return true;
-    },
-  );
+    });
 
   if (firstSuccess) {
     ws.notify({
@@ -636,12 +684,14 @@ export async function updateWithdrawalDenoms(
   ws: InternalWalletState,
   exchangeBaseUrl: string,
 ): Promise<void> {
-  const exchangeDetails = await ws.db.runWithReadTransaction(
-    [Stores.exchanges, Stores.exchangeDetails],
-    async (tx) => {
+  const exchangeDetails = await ws.db
+    .mktx((x) => ({
+      exchanges: x.exchanges,
+      exchangeDetails: x.exchangeDetails,
+    }))
+    .runReadOnly(async (tx) => {
       return getExchangeDetails(tx, exchangeBaseUrl);
-    },
-  );
+    });
   if (!exchangeDetails) {
     logger.error("exchange details not available");
     throw Error(`exchange ${exchangeBaseUrl} details not available`);
@@ -663,7 +713,11 @@ export async function updateWithdrawalDenoms(
       } else {
         denom.status = DenominationStatus.VerifiedGood;
       }
-      await ws.db.put(Stores.denominations, denom);
+      await ws.db
+        .mktx((x) => ({ denominations: x.denominations }))
+        .runReadWrite(async (tx) => {
+          await tx.denominations.put(denom);
+        });
     }
   }
   // FIXME:  This debug info should either be made conditional on some flag
@@ -698,16 +752,18 @@ async function incrementWithdrawalRetry(
   withdrawalGroupId: string,
   err: TalerErrorDetails | undefined,
 ): Promise<void> {
-  await ws.db.runWithWriteTransaction([Stores.withdrawalGroups], async (tx) => 
{
-    const wsr = await tx.get(Stores.withdrawalGroups, withdrawalGroupId);
-    if (!wsr) {
-      return;
-    }
-    wsr.retryInfo.retryCounter++;
-    updateRetryInfoTimeout(wsr.retryInfo);
-    wsr.lastError = err;
-    await tx.put(Stores.withdrawalGroups, wsr);
-  });
+  await ws.db
+    .mktx((x) => ({ withdrawalGroups: x.withdrawalGroups }))
+    .runReadWrite(async (tx) => {
+      const wsr = await tx.withdrawalGroups.get(withdrawalGroupId);
+      if (!wsr) {
+        return;
+      }
+      wsr.retryInfo.retryCounter++;
+      updateRetryInfoTimeout(wsr.retryInfo);
+      wsr.lastError = err;
+      await tx.withdrawalGroups.put(wsr);
+    });
   if (err) {
     ws.notify({ type: NotificationType.WithdrawOperationError, error: err });
   }
@@ -730,12 +786,15 @@ async function resetWithdrawalGroupRetry(
   ws: InternalWalletState,
   withdrawalGroupId: string,
 ): Promise<void> {
-  await ws.db.mutate(Stores.withdrawalGroups, withdrawalGroupId, (x) => {
-    if (x.retryInfo.active) {
-      x.retryInfo = initRetryInfo();
-    }
-    return x;
-  });
+  await ws.db
+    .mktx((x) => ({ withdrawalGroups: x.withdrawalGroups }))
+    .runReadWrite(async (tx) => {
+      const x = await tx.withdrawalGroups.get(withdrawalGroupId);
+      if (x && x.retryInfo.active) {
+        x.retryInfo = initRetryInfo();
+        await tx.withdrawalGroups.put(x);
+      }
+    });
 }
 
 async function processWithdrawGroupImpl(
@@ -747,10 +806,11 @@ async function processWithdrawGroupImpl(
   if (forceNow) {
     await resetWithdrawalGroupRetry(ws, withdrawalGroupId);
   }
-  const withdrawalGroup = await ws.db.get(
-    Stores.withdrawalGroups,
-    withdrawalGroupId,
-  );
+  const withdrawalGroup = await ws.db
+    .mktx((x) => ({ withdrawalGroups: x.withdrawalGroups }))
+    .runReadOnly(async (tx) => {
+      return tx.withdrawalGroups.get(withdrawalGroupId);
+    });
   if (!withdrawalGroup) {
     logger.trace("withdraw session doesn't exist");
     return;
@@ -793,16 +853,21 @@ async function processWithdrawGroupImpl(
   let finishedForFirstTime = false;
   let errorsPerCoin: Record<number, TalerErrorDetails> = {};
 
-  await ws.db.runWithWriteTransaction(
-    [Stores.coins, Stores.withdrawalGroups, Stores.reserves, Stores.planchets],
-    async (tx) => {
-      const wg = await tx.get(Stores.withdrawalGroups, withdrawalGroupId);
+  await ws.db
+    .mktx((x) => ({
+      coins: x.coins,
+      withdrawalGroups: x.withdrawalGroups,
+      reserves: x.reserves,
+      planchets: x.planchets,
+    }))
+    .runReadWrite(async (tx) => {
+      const wg = await tx.withdrawalGroups.get(withdrawalGroupId);
       if (!wg) {
         return;
       }
 
-      await tx
-        .iterIndexed(Stores.planchets.byGroup, withdrawalGroupId)
+      await tx.planchets.indexes.byGroup
+        .iter(withdrawalGroupId)
         .forEach((x) => {
           if (x.withdrawalDone) {
             numFinished++;
@@ -819,9 +884,8 @@ async function processWithdrawGroupImpl(
         wg.retryInfo = initRetryInfo(false);
       }
 
-      await tx.put(Stores.withdrawalGroups, wg);
-    },
-  );
+      await tx.withdrawalGroups.put(wg);
+    });
 
   if (numFinished != numTotalCoins) {
     throw OperationFailedError.fromCode(
@@ -871,8 +935,12 @@ export async function getExchangeWithdrawalInfo(
   }
 
   const possibleDenoms = await ws.db
-    .iterIndex(Stores.denominations.exchangeBaseUrlIndex, baseUrl)
-    .filter((d) => d.isOffered);
+    .mktx((x) => ({ denominations: x.denominations }))
+    .runReadOnly(async (tx) => {
+      return tx.denominations.indexes.byExchangeBaseUrl
+        .iter()
+        .filter((d) => d.isOffered);
+    });
 
   let versionMatch;
   if (exchangeDetails.protocolVersion) {
@@ -953,23 +1021,24 @@ export async function getWithdrawalDetailsForUri(
 
   const exchanges: ExchangeListItem[] = [];
 
-  const exchangeRecords = await ws.db.iter(Stores.exchanges).toArray();
-
-  for (const r of exchangeRecords) {
-    const details = await ws.db.runWithReadTransaction(
-      [Stores.exchanges, Stores.exchangeDetails],
-      async (tx) => {
-        return getExchangeDetails(tx, r.baseUrl);
-      },
-    );
-    if (details) {
-      exchanges.push({
-        exchangeBaseUrl: details.exchangeBaseUrl,
-        currency: details.currency,
-        paytoUris: details.wireInfo.accounts.map((x) => x.payto_uri),
-      });
-    }
-  }
+  await ws.db
+    .mktx((x) => ({
+      exchanges: x.exchanges,
+      exchangeDetails: x.exchangeDetails,
+    }))
+    .runReadOnly(async (tx) => {
+      const exchangeRecords = await tx.exchanges.iter().toArray();
+      for (const r of exchangeRecords) {
+        const details = await getExchangeDetails(tx, r.baseUrl);
+        if (details) {
+          exchanges.push({
+            exchangeBaseUrl: details.exchangeBaseUrl,
+            currency: details.currency,
+            paytoUris: details.wireInfo.accounts.map((x) => x.payto_uri),
+          });
+        }
+      }
+    });
 
   return {
     amount: Amounts.stringify(info.amount),
diff --git a/packages/taler-wallet-core/src/pending-types.ts 
b/packages/taler-wallet-core/src/pending-types.ts
index c16f729b..78e01416 100644
--- a/packages/taler-wallet-core/src/pending-types.ts
+++ b/packages/taler-wallet-core/src/pending-types.ts
@@ -24,7 +24,12 @@
 /**
  * Imports.
  */
-import { TalerErrorDetails, BalancesResponse, Duration, Timestamp } from 
"@gnu-taler/taler-util";
+import {
+  TalerErrorDetails,
+  BalancesResponse,
+  Duration,
+  Timestamp,
+} from "@gnu-taler/taler-util";
 import { ReserveRecordStatus } from "./db.js";
 import { RetryInfo } from "./util/retries.js";
 
diff --git a/packages/taler-wallet-core/src/util/query.ts 
b/packages/taler-wallet-core/src/util/query.ts
index 6a3db44d..2cb0c7fe 100644
--- a/packages/taler-wallet-core/src/util/query.ts
+++ b/packages/taler-wallet-core/src/util/query.ts
@@ -33,6 +33,7 @@ import {
   IDBVersionChangeEvent,
   Event,
   IDBCursor,
+  IDBKeyPath,
 } from "@gnu-taler/idb-bridge";
 import { Logger } from "@gnu-taler/taler-util";
 
@@ -43,25 +44,6 @@ const logger = new Logger("query.ts");
  */
 export const TransactionAbort = Symbol("transaction_abort");
 
-export interface StoreParams<T> {
-  validator?: (v: T) => T;
-  autoIncrement?: boolean;
-  keyPath?: string | string[] | null;
-
-  /**
-   * Database version that this store was added in, or
-   * undefined if added in the first version.
-   */
-  versionAdded?: number;
-}
-
-/**
- * Definition of an object store.
- */
-export class Store<N extends string, T> {
-  constructor(public name: N, public storeParams?: StoreParams<T>) {}
-}
-
 /**
  * Options for an index.
  */
@@ -111,37 +93,6 @@ function transactionToPromise(tx: IDBTransaction): 
Promise<void> {
   });
 }
 
-function applyMutation<T>(
-  req: IDBRequest,
-  f: (x: T) => T | undefined,
-): Promise<void> {
-  return new Promise((resolve, reject) => {
-    req.onsuccess = () => {
-      const cursor = req.result;
-      if (cursor) {
-        const val = cursor.value;
-        const modVal = f(val);
-        if (modVal !== undefined && modVal !== null) {
-          const req2: IDBRequest = cursor.update(modVal);
-          req2.onerror = () => {
-            reject(req2.error);
-          };
-          req2.onsuccess = () => {
-            cursor.continue();
-          };
-        } else {
-          cursor.continue();
-        }
-      } else {
-        resolve();
-      }
-    };
-    req.onerror = () => {
-      reject(req.error);
-    };
-  });
-}
-
 type CursorResult<T> = CursorEmptyResult<T> | CursorValueResult<T>;
 
 interface CursorEmptyResult<T> {
@@ -269,119 +220,197 @@ class ResultStream<T> {
   }
 }
 
-export type AnyStoreMap = { [s: string]: Store<any, any> };
-
-type StoreName<S> = S extends Store<infer N, any> ? N : never;
-type StoreContent<S> = S extends Store<any, infer R> ? R : never;
-type IndexRecord<Ind> = Ind extends Index<any, any, any, infer R> ? R : never;
-
-type InferStore<S> = S extends Store<infer N, infer R> ? Store<N, R> : never;
-type InferIndex<Ind> = Ind extends Index<
-  infer StN,
-  infer IndN,
-  infer KT,
-  infer RT
->
-  ? Index<StN, IndN, KT, RT>
-  : never;
-
-export class TransactionHandle<StoreTypes extends Store<string, any>> {
-  constructor(private tx: IDBTransaction) {}
-
-  put<S extends StoreTypes>(
-    store: S,
-    value: StoreContent<S>,
-    key?: any,
-  ): Promise<any> {
-    const req = this.tx.objectStore(store.name).put(value, key);
-    return requestToPromise(req);
-  }
+/**
+ * Return a promise that resolves to the opened IndexedDB database.
+ */
+export function openDatabase(
+  idbFactory: IDBFactory,
+  databaseName: string,
+  databaseVersion: number,
+  onVersionChange: () => void,
+  onUpgradeNeeded: (
+    db: IDBDatabase,
+    oldVersion: number,
+    newVersion: number,
+    upgradeTransaction: IDBTransaction,
+  ) => void,
+): Promise<IDBDatabase> {
+  return new Promise<IDBDatabase>((resolve, reject) => {
+    const req = idbFactory.open(databaseName, databaseVersion);
+    req.onerror = (e) => {
+      logger.error("database error", e);
+      reject(new Error("database error"));
+    };
+    req.onsuccess = (e) => {
+      req.result.onversionchange = (evt: IDBVersionChangeEvent) => {
+        logger.info(
+          `handling live db version change from ${evt.oldVersion} to 
${evt.newVersion}`,
+        );
+        req.result.close();
+        onVersionChange();
+      };
+      resolve(req.result);
+    };
+    req.onupgradeneeded = (e) => {
+      const db = req.result;
+      const newVersion = e.newVersion;
+      if (!newVersion) {
+        throw Error("upgrade needed, but new version unknown");
+      }
+      const transaction = req.transaction;
+      if (!transaction) {
+        throw Error("no transaction handle available in upgrade handler");
+      }
+      onUpgradeNeeded(db, e.oldVersion, newVersion, transaction);
+    };
+  });
+}
 
-  add<S extends StoreTypes>(
-    store: S,
-    value: StoreContent<S>,
-    key?: any,
-  ): Promise<any> {
-    const req = this.tx.objectStore(store.name).add(value, key);
-    return requestToPromise(req);
-  }
+export interface IndexDescriptor {
+  name: string;
+  keyPath: IDBKeyPath | IDBKeyPath[];
+  multiEntry?: boolean;
+}
 
-  get<S extends StoreTypes>(
-    store: S,
-    key: any,
-  ): Promise<StoreContent<S> | undefined> {
-    const req = this.tx.objectStore(store.name).get(key);
-    return requestToPromise(req);
-  }
+export interface StoreDescriptor<RecordType> {
+  _dummy: undefined & RecordType;
+  name: string;
+  keyPath?: IDBKeyPath | IDBKeyPath[];
+  autoIncrement?: boolean;
+}
 
-  getIndexed<
-    St extends StoreTypes,
-    Ind extends Index<StoreName<St>, string, any, any>
-  >(index: InferIndex<Ind>, key: any): Promise<IndexRecord<Ind> | undefined> {
-    const req = this.tx
-      .objectStore(index.storeName)
-      .index(index.indexName)
-      .get(key);
-    return requestToPromise(req);
-  }
+export interface StoreOptions {
+  keyPath?: IDBKeyPath | IDBKeyPath[];
+  autoIncrement?: boolean;
+}
 
-  iter<St extends InferStore<StoreTypes>>(
-    store: St,
-    key?: any,
-  ): ResultStream<StoreContent<St>> {
-    const req = this.tx.objectStore(store.name).openCursor(key);
-    return new ResultStream<StoreContent<St>>(req);
-  }
+export function describeContents<RecordType = never>(
+  name: string,
+  options: StoreOptions,
+): StoreDescriptor<RecordType> {
+  return { name, keyPath: options.keyPath, _dummy: undefined as any };
+}
 
-  iterIndexed<
-    St extends InferStore<StoreTypes>,
-    Ind extends InferIndex<Index<StoreName<St>, string, any, any>>
-  >(index: Ind, key?: any): ResultStream<IndexRecord<Ind>> {
-    const req = this.tx
-      .objectStore(index.storeName)
-      .index(index.indexName)
-      .openCursor(key);
-    return new ResultStream<IndexRecord<Ind>>(req);
-  }
+export function describeIndex(
+  name: string,
+  keyPath: IDBKeyPath | IDBKeyPath[],
+  options: IndexOptions = {},
+): IndexDescriptor {
+  return {
+    keyPath,
+    name,
+    multiEntry: options.multiEntry,
+  };
+}
 
-  delete<St extends StoreTypes>(
-    store: InferStore<St>,
-    key: any,
-  ): Promise<void> {
-    const req = this.tx.objectStore(store.name).delete(key);
-    return requestToPromise(req);
-  }
+interface IndexReadOnlyAccessor<RecordType> {
+  iter(query?: IDBValidKey): ResultStream<RecordType>;
+  get(query: IDBValidKey): Promise<RecordType | undefined>;
+}
 
-  mutate<St extends StoreTypes>(
-    store: InferStore<St>,
-    key: any,
-    f: (x: StoreContent<St>) => StoreContent<St> | undefined,
-  ): Promise<void> {
-    const req = this.tx.objectStore(store.name).openCursor(key);
-    return applyMutation(req, f);
-  }
+type GetIndexReadOnlyAccess<RecordType, IndexMap> = {
+  [P in keyof IndexMap]: IndexReadOnlyAccessor<RecordType>;
+};
+
+interface IndexReadWriteAccessor<RecordType> {
+  iter(query: IDBValidKey): ResultStream<RecordType>;
+  get(query: IDBValidKey): Promise<RecordType | undefined>;
+}
+
+type GetIndexReadWriteAccess<RecordType, IndexMap> = {
+  [P in keyof IndexMap]: IndexReadWriteAccessor<RecordType>;
+};
+
+export interface StoreReadOnlyAccessor<RecordType, IndexMap> {
+  get(key: IDBValidKey): Promise<RecordType | undefined>;
+  iter(query?: IDBValidKey): ResultStream<RecordType>;
+  indexes: GetIndexReadOnlyAccess<RecordType, IndexMap>;
+}
+
+export interface StoreReadWriteAccessor<RecordType, IndexMap> {
+  get(key: IDBValidKey): Promise<RecordType | undefined>;
+  iter(query?: IDBValidKey): ResultStream<RecordType>;
+  put(r: RecordType): Promise<void>;
+  add(r: RecordType): Promise<void>;
+  delete(key: IDBValidKey): Promise<void>;
+  indexes: GetIndexReadWriteAccess<RecordType, IndexMap>;
+}
+
+export interface StoreWithIndexes<
+  SD extends StoreDescriptor<unknown>,
+  IndexMap
+> {
+  store: SD;
+  indexMap: IndexMap;
+
+  /**
+   * Type marker symbol, to check that the descriptor
+   * has been created through the right function.
+   */
+  mark: Symbol;
 }
 
-function runWithTransaction<T, StoreTypes extends Store<string, {}>>(
-  db: IDBDatabase,
-  stores: StoreTypes[],
-  f: (t: TransactionHandle<StoreTypes>) => Promise<T>,
-  mode: "readonly" | "readwrite",
-): Promise<T> {
+export type GetRecordType<T> = T extends StoreDescriptor<infer X> ? X : 
unknown;
+
+const storeWithIndexesSymbol = Symbol("StoreWithIndexesMark");
+
+export function describeStore<SD extends StoreDescriptor<unknown>, IndexMap>(
+  s: SD,
+  m: IndexMap,
+): StoreWithIndexes<SD, IndexMap> {
+  return {
+    store: s,
+    indexMap: m,
+    mark: storeWithIndexesSymbol,
+  };
+}
+
+export type GetReadOnlyAccess<BoundStores> = {
+  [P in keyof BoundStores]: BoundStores[P] extends StoreWithIndexes<
+    infer SD,
+    infer IM
+  >
+    ? StoreReadOnlyAccessor<GetRecordType<SD>, IM>
+    : unknown;
+};
+
+export type GetReadWriteAccess<BoundStores> = {
+  [P in keyof BoundStores]: BoundStores[P] extends StoreWithIndexes<
+    infer SD,
+    infer IM
+  >
+    ? StoreReadWriteAccessor<GetRecordType<SD>, IM>
+    : unknown;
+};
+
+type ReadOnlyTransactionFunction<BoundStores, T> = (
+  t: GetReadOnlyAccess<BoundStores>,
+) => Promise<T>;
+
+type ReadWriteTransactionFunction<BoundStores, T> = (
+  t: GetReadWriteAccess<BoundStores>,
+) => Promise<T>;
+
+export interface TransactionContext<BoundStores> {
+  runReadWrite<T>(f: ReadWriteTransactionFunction<BoundStores, T>): Promise<T>;
+  runReadOnly<T>(f: ReadOnlyTransactionFunction<BoundStores, T>): Promise<T>;
+}
+
+type CheckDescriptor<T> = T extends StoreWithIndexes<infer SD, infer IM>
+  ? StoreWithIndexes<SD, IM>
+  : unknown;
+
+type GetPickerType<F, SM> = F extends (x: SM) => infer Out
+  ? { [P in keyof Out]: CheckDescriptor<Out[P]> }
+  : unknown;
+
+function runTx<Arg, Res>(
+  tx: IDBTransaction,
+  arg: Arg,
+  f: (t: Arg) => Promise<Res>,
+): Promise<Res> {
   const stack = Error("Failed transaction was started here.");
   return new Promise((resolve, reject) => {
-    const storeName = stores.map((x) => x.name);
-
-    let txOrUndef: IDBTransaction | undefined = undefined
-    try {
-      txOrUndef = db.transaction(storeName, mode);
-    } catch (e) {
-      logger.error("error opening transaction");
-      logger.error(`${e}`);
-      return
-    }
-    const tx = txOrUndef;
-
     let funResult: any = undefined;
     let gotFunResult = false;
     tx.oncomplete = () => {
@@ -411,8 +440,7 @@ function runWithTransaction<T, StoreTypes extends 
Store<string, {}>>(
       }
       reject(TransactionAbort);
     };
-    const th = new TransactionHandle(tx);
-    const resP = Promise.resolve().then(() => f(th));
+    const resP = Promise.resolve().then(() => f(arg));
     resP
       .then((result) => {
         gotFunResult = true;
@@ -433,238 +461,139 @@ function runWithTransaction<T, StoreTypes extends 
Store<string, {}>>(
   });
 }
 
-/**
- * Definition of an index.
- */
-export class Index<
-  StoreName extends string,
-  IndexName extends string,
-  S extends IDBValidKey,
-  T
-> {
-  /**
-   * Name of the store that this index is associated with.
-   */
-  storeName: string;
-
-  /**
-   * Options to use for the index.
-   */
-  options: IndexOptions;
-
-  constructor(
-    s: Store<StoreName, T>,
-    public indexName: IndexName,
-    public keyPath: string | string[],
-    options?: IndexOptions,
-  ) {
-    const defaultOptions = {
-      multiEntry: false,
+function makeReadContext(
+  tx: IDBTransaction,
+  storePick: { [n: string]: StoreWithIndexes<any, any> },
+): any {
+  const ctx: { [s: string]: StoreReadOnlyAccessor<any, any> } = {};
+  for (const storeAlias in storePick) {
+    const indexes: { [s: string]: IndexReadOnlyAccessor<any> } = {};
+    const swi = storePick[storeAlias];
+    const storeName = swi.store.name;
+    for (const indexName in storePick[storeAlias].indexMap) {
+      indexes[indexName] = {
+        get(key) {
+          const req = tx.objectStore(storeName).index(indexName).get(key);
+          return requestToPromise(req);
+        },
+        iter(query) {
+          const req = tx
+            .objectStore(storeName)
+            .index(indexName)
+            .openCursor(query);
+          return new ResultStream<any>(req);
+        },
+      };
+    }
+    ctx[storeAlias] = {
+      indexes,
+      get(key) {
+        const req = tx.objectStore(storeName).get(key);
+        return requestToPromise(req);
+      },
+      iter(query) {
+        const req = tx.objectStore(storeName).openCursor(query);
+        return new ResultStream<any>(req);
+      },
     };
-    this.options = { ...defaultOptions, ...(options || {}) };
-    this.storeName = s.name;
   }
-
-  /**
-   * We want to have the key type parameter in use somewhere,
-   * because otherwise the compiler complains.  In iterIndex the
-   * key type is pretty useful.
-   */
-  protected _dummyKey: S | undefined;
+  return ctx;
 }
 
-/**
- * Return a promise that resolves to the opened IndexedDB database.
- */
-export function openDatabase(
-  idbFactory: IDBFactory,
-  databaseName: string,
-  databaseVersion: number,
-  onVersionChange: () => void,
-  onUpgradeNeeded: (
-    db: IDBDatabase,
-    oldVersion: number,
-    newVersion: number,
-    upgradeTransaction: IDBTransaction,
-  ) => void,
-): Promise<IDBDatabase> {
-  return new Promise<IDBDatabase>((resolve, reject) => {
-    const req = idbFactory.open(databaseName, databaseVersion);
-    req.onerror = (e) => {
-      logger.error("database error", e);
-      reject(new Error("database error"));
-    };
-    req.onsuccess = (e) => {
-      req.result.onversionchange = (evt: IDBVersionChangeEvent) => {
-        logger.info(
-          `handling live db version change from ${evt.oldVersion} to 
${evt.newVersion}`,
-        );
-        req.result.close();
-        onVersionChange();
+function makeWriteContext(
+  tx: IDBTransaction,
+  storePick: { [n: string]: StoreWithIndexes<any, any> },
+): any {
+  const ctx: { [s: string]: StoreReadWriteAccessor<any, any> } = {};
+  for (const storeAlias in storePick) {
+    const indexes: { [s: string]: IndexReadWriteAccessor<any> } = {};
+    const swi = storePick[storeAlias];
+    const storeName = swi.store.name;
+    for (const indexName in storePick[storeAlias].indexMap) {
+      indexes[indexName] = {
+        get(key) {
+          const req = tx.objectStore(storeName).index(indexName).get(key);
+          return requestToPromise(req);
+        },
+        iter(query) {
+          const req = tx
+            .objectStore(storeName)
+            .index(indexName)
+            .openCursor(query);
+          return new ResultStream<any>(req);
+        },
       };
-      resolve(req.result);
-    };
-    req.onupgradeneeded = (e) => {
-      const db = req.result;
-      const newVersion = e.newVersion;
-      if (!newVersion) {
-        throw Error("upgrade needed, but new version unknown");
-      }
-      const transaction = req.transaction;
-      if (!transaction) {
-        throw Error("no transaction handle available in upgrade handler");
-      }
-      onUpgradeNeeded(db, e.oldVersion, newVersion, transaction);
-    };
-  });
-}
-
-export class Database<StoreMap extends AnyStoreMap> {
-  constructor(private db: IDBDatabase, stores: StoreMap) {}
-
-  static deleteDatabase(idbFactory: IDBFactory, dbName: string): Promise<void> 
{
-    const req = idbFactory.deleteDatabase(dbName)
-    return requestToPromise(req)
-  }
-
-  async exportDatabase(): Promise<any> {
-    const db = this.db;
-    const dump = {
-      name: db.name,
-      stores: {} as { [s: string]: any },
-      version: db.version,
+    }
+    ctx[storeAlias] = {
+      indexes,
+      get(key) {
+        const req = tx.objectStore(storeName).get(key);
+        return requestToPromise(req);
+      },
+      iter(query) {
+        const req = tx.objectStore(storeName).openCursor(query);
+        return new ResultStream<any>(req);
+      },
+      add(r) {
+        const req = tx.objectStore(storeName).add(r);
+        return requestToPromise(req);
+      },
+      put(r) {
+        const req = tx.objectStore(storeName).put(r);
+        return requestToPromise(req);
+      },
+      delete(k) {
+        const req = tx.objectStore(storeName).delete(k);
+        return requestToPromise(req);
+      },
     };
-
-    return new Promise((resolve, reject) => {
-      const tx = db.transaction(Array.from(db.objectStoreNames));
-      tx.addEventListener("complete", () => {
-        resolve(dump);
-      });
-      // tslint:disable-next-line:prefer-for-of
-      for (let i = 0; i < db.objectStoreNames.length; i++) {
-        const name = db.objectStoreNames[i];
-        const storeDump = {} as { [s: string]: any };
-        dump.stores[name] = storeDump;
-        tx.objectStore(name)
-          .openCursor()
-          .addEventListener("success", (e: Event) => {
-            const cursor = (e.target as any).result;
-            if (cursor) {
-              storeDump[cursor.key] = cursor.value;
-              cursor.continue();
-            }
-          });
-      }
-    });
   }
+}
 
-  importDatabase(dump: any): Promise<void> {
-    const db = this.db;
-    logger.info("importing db", dump);
-    return new Promise<void>((resolve, reject) => {
-      const tx = db.transaction(Array.from(db.objectStoreNames), "readwrite");
-      if (dump.stores) {
-        for (const storeName in dump.stores) {
-          const objects = [];
-          const dumpStore = dump.stores[storeName];
-          for (const key in dumpStore) {
-            objects.push(dumpStore[key]);
-          }
-          logger.info(`importing ${objects.length} records into ${storeName}`);
-          const store = tx.objectStore(storeName);
-          for (const obj of objects) {
-            store.put(obj);
-          }
-        }
+/**
+ * Type-safe access to a database with a particular store map.
+ *
+ * A store map is the metadata that describes the store.
+ */
+export class DbAccess<StoreMap> {
+  constructor(private db: IDBDatabase, private stores: StoreMap) {}
+
+  mktx<
+    PickerType extends (x: StoreMap) => unknown,
+    BoundStores extends GetPickerType<PickerType, StoreMap>
+  >(f: PickerType): TransactionContext<BoundStores> {
+    const storePick = f(this.stores) as any;
+    if (typeof storePick !== "object" || storePick === null) {
+      throw Error();
+    }
+    const storeNames: string[] = [];
+    for (const storeAlias of Object.keys(storePick)) {
+      const swi = (storePick as any)[storeAlias] as StoreWithIndexes<any, any>;
+      if (swi.mark !== storeWithIndexesSymbol) {
+        throw Error("invalid store descriptor returned from selector 
function");
       }
-      tx.addEventListener("complete", () => {
-        resolve();
-      });
-    });
-  }
-
-  async get<N extends keyof StoreMap, S extends StoreMap[N]>(
-    store: S,
-    key: IDBValidKey,
-  ): Promise<StoreContent<S> | undefined> {
-    const tx = this.db.transaction([store.name], "readonly");
-    const req = tx.objectStore(store.name).get(key);
-    const v = await requestToPromise(req);
-    await transactionToPromise(tx);
-    return v;
-  }
-
-  async getIndexed<Ind extends Index<string, string, any, any>>(
-    index: Ind,
-    key: IDBValidKey,
-  ): Promise<IndexRecord<Ind> | undefined> {
-    const tx = this.db.transaction([index.storeName], "readonly");
-    const req = 
tx.objectStore(index.storeName).index(index.indexName).get(key);
-    const v = await requestToPromise(req);
-    await transactionToPromise(tx);
-    return v;
-  }
-
-  async put<St extends Store<string, any>>(
-    store: St,
-    value: StoreContent<St>,
-    key?: IDBValidKey,
-  ): Promise<any> {
-    const tx = this.db.transaction([store.name], "readwrite");
-    const req = tx.objectStore(store.name).put(value, key);
-    const v = await requestToPromise(req);
-    await transactionToPromise(tx);
-    return v;
-  }
-
-  async mutate<N extends string, T>(
-    store: Store<N, T>,
-    key: IDBValidKey,
-    f: (x: T) => T | undefined,
-  ): Promise<void> {
-    const tx = this.db.transaction([store.name], "readwrite");
-    const req = tx.objectStore(store.name).openCursor(key);
-    await applyMutation(req, f);
-    await transactionToPromise(tx);
-  }
-
-  iter<N extends string, T>(store: Store<N, T>): ResultStream<T> {
-    const tx = this.db.transaction([store.name], "readonly");
-    const req = tx.objectStore(store.name).openCursor();
-    return new ResultStream<T>(req);
-  }
+      storeNames.push(swi.store.name);
+    }
 
-  iterIndex<Ind extends Index<string, string, any, any>>(
-    index: InferIndex<Ind>,
-    query?: any,
-  ): ResultStream<IndexRecord<Ind>> {
-    const tx = this.db.transaction([index.storeName], "readonly");
-    const req = tx
-      .objectStore(index.storeName)
-      .index(index.indexName)
-      .openCursor(query);
-    return new ResultStream<IndexRecord<Ind>>(req);
-  }
+    const runReadOnly = <T>(
+      txf: ReadOnlyTransactionFunction<BoundStores, T>,
+    ): Promise<T> => {
+      const tx = this.db.transaction(storeNames, "readonly");
+      const readContext = makeReadContext(tx, storePick);
+      return runTx(tx, readContext, txf);
+    };
 
-  async runWithReadTransaction<
-    T,
-    N extends keyof StoreMap,
-    StoreTypes extends StoreMap[N]
-  >(
-    stores: StoreTypes[],
-    f: (t: TransactionHandle<StoreTypes>) => Promise<T>,
-  ): Promise<T> {
-    return runWithTransaction<T, StoreTypes>(this.db, stores, f, "readonly");
-  }
+    const runReadWrite = <T>(
+      txf: ReadWriteTransactionFunction<BoundStores, T>,
+    ): Promise<T> => {
+      const tx = this.db.transaction(storeNames, "readwrite");
+      const writeContext = makeWriteContext(tx, storePick);
+      return runTx(tx, writeContext, txf);
+    };
 
-  async runWithWriteTransaction<
-    T,
-    N extends keyof StoreMap,
-    StoreTypes extends StoreMap[N]
-  >(
-    stores: StoreTypes[],
-    f: (t: TransactionHandle<StoreTypes>) => Promise<T>,
-  ): Promise<T> {
-    return runWithTransaction<T, StoreTypes>(this.db, stores, f, "readwrite");
+    return {
+      runReadOnly,
+      runReadWrite,
+    };
   }
 }
diff --git a/packages/taler-wallet-core/src/wallet.ts 
b/packages/taler-wallet-core/src/wallet.ts
index 0bb7bc97..70ddaffa 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -58,6 +58,7 @@ import {
 } from "./operations/errors";
 import {
   acceptExchangeTermsOfService,
+  getExchangeDetails,
   getExchangePaytoUri,
   updateExchangeFromUrl,
 } from "./operations/exchanges";
@@ -111,7 +112,7 @@ import {
   RefundState,
   ReserveRecord,
   ReserveRecordStatus,
-  Stores,
+  WalletStoresV1,
 } from "./db.js";
 import { NotificationType, WalletNotification } from "@gnu-taler/taler-util";
 import {
@@ -179,10 +180,10 @@ import { AsyncOpMemoSingle } from "./util/asyncMemo";
 import { HttpRequestLibrary } from "./util/http";
 import { Logger } from "@gnu-taler/taler-util";
 import { AsyncCondition } from "./util/promiseUtils";
-import { Database } from "./util/query";
 import { Duration, durationMin } from "@gnu-taler/taler-util";
 import { TimerGroup } from "./util/timer";
 import { getExchangeTrust } from "./operations/currencies.js";
+import { DbAccess } from "./util/query.js";
 
 const builtinAuditors: AuditorTrustRecord[] = [
   {
@@ -205,12 +206,12 @@ export class Wallet {
   private stopped = false;
   private memoRunRetryLoop = new AsyncOpMemoSingle<void>();
 
-  get db(): Database<typeof Stores> {
+  get db(): DbAccess<typeof WalletStoresV1> {
     return this.ws.db;
   }
 
   constructor(
-    db: Database<typeof Stores>,
+    db: DbAccess<typeof WalletStoresV1>,
     http: HttpRequestLibrary,
     cryptoWorkerFactory: CryptoWorkerFactory,
   ) {
@@ -481,22 +482,21 @@ export class Wallet {
    * already been applied.
    */
   async fillDefaults(): Promise<void> {
-    await this.db.runWithWriteTransaction(
-      [Stores.config, Stores.auditorTrustStore],
-      async (tx) => {
+    await this.db
+      .mktx((x) => ({ config: x.config, auditorTrustStore: x.auditorTrust }))
+      .runReadWrite(async (tx) => {
         let applied = false;
-        await tx.iter(Stores.config).forEach((x) => {
+        await tx.config.iter().forEach((x) => {
           if (x.key == "currencyDefaultsApplied" && x.value == true) {
             applied = true;
           }
         });
         if (!applied) {
           for (const c of builtinAuditors) {
-            await tx.put(Stores.auditorTrustStore, c);
+            await tx.auditorTrustStore.put(c);
           }
         }
-      },
-    );
+      });
   }
 
   /**
@@ -553,10 +553,13 @@ export class Wallet {
         amount,
         exchange: exchangeBaseUrl,
       });
-      const exchangePaytoUris = await this.db.runWithReadTransaction(
-        [Stores.exchanges, Stores.reserves],
-        (tx) => getFundingPaytoUris(tx, resp.reservePub),
-      );
+      const exchangePaytoUris = await this.db
+        .mktx((x) => ({
+          exchanges: x.exchanges,
+          exchangeDetails: x.exchangeDetails,
+          reserves: x.reserves,
+        }))
+        .runReadWrite((tx) => getFundingPaytoUris(tx, resp.reservePub));
       return {
         reservePub: resp.reservePub,
         exchangePaytoUris,
@@ -627,29 +630,26 @@ export class Wallet {
 
   async refresh(oldCoinPub: string): Promise<void> {
     try {
-      const refreshGroupId = await this.db.runWithWriteTransaction(
-        [Stores.refreshGroups, Stores.denominations, Stores.coins],
-        async (tx) => {
+      const refreshGroupId = await this.db
+        .mktx((x) => ({
+          refreshGroups: x.refreshGroups,
+          denominations: x.denominations,
+          coins: x.coins,
+        }))
+        .runReadWrite(async (tx) => {
           return await createRefreshGroup(
             this.ws,
             tx,
             [{ coinPub: oldCoinPub }],
             RefreshReason.Manual,
           );
-        },
-      );
+        });
       await processRefreshGroup(this.ws, refreshGroupId.refreshGroupId);
     } catch (e) {
       this.latch.trigger();
     }
   }
 
-  async findExchange(
-    exchangeBaseUrl: string,
-  ): Promise<ExchangeRecord | undefined> {
-    return await this.db.get(Stores.exchanges, exchangeBaseUrl);
-  }
-
   async getPendingOperations({
     onlyDue = false,
   } = {}): Promise<PendingOperationsResponse> {
@@ -665,87 +665,59 @@ export class Wallet {
     return acceptExchangeTermsOfService(this.ws, exchangeBaseUrl, etag);
   }
 
-  async getDenoms(exchangeUrl: string): Promise<DenominationRecord[]> {
-    const denoms = await this.db
-      .iterIndex(Stores.denominations.exchangeBaseUrlIndex, exchangeUrl)
-      .toArray();
-    return denoms;
-  }
-
-  /**
-   * Get all exchanges known to the exchange.
-   *
-   * @deprecated Use getExchanges instead
-   */
-  async getExchangeRecords(): Promise<ExchangeRecord[]> {
-    return await this.db.iter(Stores.exchanges).toArray();
-  }
-
   async getExchanges(): Promise<ExchangesListRespose> {
-    const exchangeRecords = await this.db.iter(Stores.exchanges).toArray();
     const exchanges: ExchangeListItem[] = [];
-    for (const r of exchangeRecords) {
-      const dp = r.detailsPointer;
-      if (!dp) {
-        continue;
-      }
-      const { currency, masterPublicKey } = dp;
-      const exchangeDetails = await this.db.get(Stores.exchangeDetails, [
-        r.baseUrl,
-        currency,
-        masterPublicKey,
-      ]);
-      if (!exchangeDetails) {
-        continue;
-      }
-      exchanges.push({
-        exchangeBaseUrl: r.baseUrl,
-        currency,
-        paytoUris: exchangeDetails.wireInfo.accounts.map((x) => x.payto_uri),
+    await this.db
+      .mktx((x) => ({
+        exchanges: x.exchanges,
+        exchangeDetails: x.exchangeDetails,
+      }))
+      .runReadOnly(async (tx) => {
+        const exchangeRecords = await tx.exchanges.iter().toArray();
+        for (const r of exchangeRecords) {
+          const dp = r.detailsPointer;
+          if (!dp) {
+            continue;
+          }
+          const { currency, masterPublicKey } = dp;
+          const exchangeDetails = await getExchangeDetails(tx, r.baseUrl);
+          if (!exchangeDetails) {
+            continue;
+          }
+          exchanges.push({
+            exchangeBaseUrl: r.baseUrl,
+            currency,
+            paytoUris: exchangeDetails.wireInfo.accounts.map(
+              (x) => x.payto_uri,
+            ),
+          });
+        }
       });
-    }
     return { exchanges };
   }
 
   async getCurrencies(): Promise<WalletCurrencyInfo> {
-    const trustedAuditors = await this.db
-      .iter(Stores.auditorTrustStore)
-      .toArray();
-    const trustedExchanges = await this.db
-      .iter(Stores.exchangeTrustStore)
-      .toArray();
-    return {
-      trustedAuditors: trustedAuditors.map((x) => ({
-        currency: x.currency,
-        auditorBaseUrl: x.auditorBaseUrl,
-        auditorPub: x.auditorPub,
-      })),
-      trustedExchanges: trustedExchanges.map((x) => ({
-        currency: x.currency,
-        exchangeBaseUrl: x.exchangeBaseUrl,
-        exchangeMasterPub: x.exchangeMasterPub,
-      })),
-    };
-  }
-
-  async getReserves(exchangeBaseUrl?: string): Promise<ReserveRecord[]> {
-    if (exchangeBaseUrl) {
-      return await this.db
-        .iter(Stores.reserves)
-        .filter((r) => r.exchangeBaseUrl === exchangeBaseUrl);
-    } else {
-      return await this.db.iter(Stores.reserves).toArray();
-    }
-  }
-
-  async getCoinsForExchange(exchangeBaseUrl: string): Promise<CoinRecord[]> {
-    return await this.db
-      .iter(Stores.coins)
-      .filter((c) => c.exchangeBaseUrl === exchangeBaseUrl);
-  }
-
-  async getCoins(): Promise<CoinRecord[]> {
-    return await this.db.iter(Stores.coins).toArray();
+    return await this.ws.db
+      .mktx((x) => ({
+        auditorTrust: x.auditorTrust,
+        exchangeTrust: x.exchangeTrust,
+      }))
+      .runReadOnly(async (tx) => {
+        const trustedAuditors = await tx.auditorTrust.iter().toArray();
+        const trustedExchanges = await tx.exchangeTrust.iter().toArray();
+        return {
+          trustedAuditors: trustedAuditors.map((x) => ({
+            currency: x.currency,
+            auditorBaseUrl: x.auditorBaseUrl,
+            auditorPub: x.auditorPub,
+          })),
+          trustedExchanges: trustedExchanges.map((x) => ({
+            currency: x.currency,
+            exchangeBaseUrl: x.exchangeBaseUrl,
+            exchangeMasterPub: x.exchangeMasterPub,
+          })),
+        };
+      });
   }
 
   /**
@@ -772,12 +744,6 @@ export class Wallet {
     return applyRefund(this.ws, talerRefundUri);
   }
 
-  async getPurchase(
-    contractTermsHash: string,
-  ): Promise<PurchaseRecord | undefined> {
-    return this.db.get(Stores.purchases, contractTermsHash);
-  }
-
   async acceptTip(talerTipUri: string): Promise<void> {
     try {
       return acceptTip(this.ws, talerTipUri);
@@ -799,7 +765,13 @@ export class Wallet {
    * confirmation from the bank.).
    */
   public async handleNotifyReserve(): Promise<void> {
-    const reserves = await this.db.iter(Stores.reserves).toArray();
+    const reserves = await this.ws.db
+      .mktx((x) => ({
+        reserves: x.reserves,
+      }))
+      .runReadOnly(async (tx) => {
+        return tx.reserves.iter().toArray();
+      });
     for (const r of reserves) {
       if (r.reserveStatus === ReserveRecordStatus.WAIT_CONFIRM_BANK) {
         try {
@@ -837,114 +809,79 @@ export class Wallet {
     }
   }
 
-  async updateReserve(reservePub: string): Promise<ReserveRecord | undefined> {
-    await forceQueryReserve(this.ws, reservePub);
-    return await this.ws.db.get(Stores.reserves, reservePub);
-  }
-
-  async getReserve(reservePub: string): Promise<ReserveRecord | undefined> {
-    return await this.ws.db.get(Stores.reserves, reservePub);
-  }
-
   async refuseProposal(proposalId: string): Promise<void> {
     return refuseProposal(this.ws, proposalId);
   }
 
-  async getPurchaseDetails(proposalId: string): Promise<PurchaseDetails> {
-    const purchase = await this.db.get(Stores.purchases, proposalId);
-    if (!purchase) {
-      throw Error("unknown purchase");
-    }
-    const refundsDoneAmounts = Object.values(purchase.refunds)
-      .filter((x) => x.type === RefundState.Applied)
-      .map((x) => x.refundAmount);
-
-    const refundsPendingAmounts = Object.values(purchase.refunds)
-      .filter((x) => x.type === RefundState.Pending)
-      .map((x) => x.refundAmount);
-    const totalRefundAmount = Amounts.sum([
-      ...refundsDoneAmounts,
-      ...refundsPendingAmounts,
-    ]).amount;
-    const refundsDoneFees = Object.values(purchase.refunds)
-      .filter((x) => x.type === RefundState.Applied)
-      .map((x) => x.refundFee);
-    const refundsPendingFees = Object.values(purchase.refunds)
-      .filter((x) => x.type === RefundState.Pending)
-      .map((x) => x.refundFee);
-    const totalRefundFees = Amounts.sum([
-      ...refundsDoneFees,
-      ...refundsPendingFees,
-    ]).amount;
-    const totalFees = totalRefundFees;
-    return {
-      contractTerms: JSON.parse(purchase.download.contractTermsRaw),
-      hasRefund: purchase.timestampLastRefundStatus !== undefined,
-      totalRefundAmount: totalRefundAmount,
-      totalRefundAndRefreshFees: totalFees,
-    };
-  }
-
   benchmarkCrypto(repetitions: number): Promise<BenchmarkResult> {
     return this.ws.cryptoApi.benchmark(repetitions);
   }
 
   async setCoinSuspended(coinPub: string, suspended: boolean): Promise<void> {
-    await this.db.runWithWriteTransaction([Stores.coins], async (tx) => {
-      const c = await tx.get(Stores.coins, coinPub);
-      if (!c) {
-        logger.warn(`coin ${coinPub} not found, won't suspend`);
-        return;
-      }
-      c.suspended = suspended;
-      await tx.put(Stores.coins, c);
-    });
+    await this.db
+      .mktx((x) => ({
+        coins: x.coins,
+      }))
+      .runReadWrite(async (tx) => {
+        const c = await tx.coins.get(coinPub);
+        if (!c) {
+          logger.warn(`coin ${coinPub} not found, won't suspend`);
+          return;
+        }
+        c.suspended = suspended;
+        await tx.coins.put(c);
+      });
   }
 
   /**
    * Dump the public information of coins we have in an easy-to-process format.
    */
   async dumpCoins(): Promise<CoinDumpJson> {
-    const coins = await this.db.iter(Stores.coins).toArray();
     const coinsJson: CoinDumpJson = { coins: [] };
-    for (const c of coins) {
-      const denom = await this.db.get(Stores.denominations, [
-        c.exchangeBaseUrl,
-        c.denomPubHash,
-      ]);
-      if (!denom) {
-        console.error("no denom session found for coin");
-        continue;
-      }
-      const cs = c.coinSource;
-      let refreshParentCoinPub: string | undefined;
-      if (cs.type == CoinSourceType.Refresh) {
-        refreshParentCoinPub = cs.oldCoinPub;
-      }
-      let withdrawalReservePub: string | undefined;
-      if (cs.type == CoinSourceType.Withdraw) {
-        const ws = await this.db.get(
-          Stores.withdrawalGroups,
-          cs.withdrawalGroupId,
-        );
-        if (!ws) {
-          console.error("no withdrawal session found for coin");
-          continue;
+    await this.ws.db
+      .mktx((x) => ({
+        coins: x.coins,
+        denominations: x.denominations,
+        withdrawalGroups: x.withdrawalGroups,
+      }))
+      .runReadOnly(async (tx) => {
+        const coins = await tx.coins.iter().toArray();
+        for (const c of coins) {
+          const denom = await tx.denominations.get([
+            c.exchangeBaseUrl,
+            c.denomPubHash,
+          ]);
+          if (!denom) {
+            console.error("no denom session found for coin");
+            continue;
+          }
+          const cs = c.coinSource;
+          let refreshParentCoinPub: string | undefined;
+          if (cs.type == CoinSourceType.Refresh) {
+            refreshParentCoinPub = cs.oldCoinPub;
+          }
+          let withdrawalReservePub: string | undefined;
+          if (cs.type == CoinSourceType.Withdraw) {
+            const ws = await tx.withdrawalGroups.get(cs.withdrawalGroupId);
+            if (!ws) {
+              console.error("no withdrawal session found for coin");
+              continue;
+            }
+            withdrawalReservePub = ws.reservePub;
+          }
+          coinsJson.coins.push({
+            coin_pub: c.coinPub,
+            denom_pub: c.denomPub,
+            denom_pub_hash: c.denomPubHash,
+            denom_value: Amounts.stringify(denom.value),
+            exchange_base_url: c.exchangeBaseUrl,
+            refresh_parent_coin_pub: refreshParentCoinPub,
+            remaining_value: Amounts.stringify(c.currentAmount),
+            withdrawal_reserve_pub: withdrawalReservePub,
+            coin_suspended: c.suspended,
+          });
         }
-        withdrawalReservePub = ws.reservePub;
-      }
-      coinsJson.coins.push({
-        coin_pub: c.coinPub,
-        denom_pub: c.denomPub,
-        denom_pub_hash: c.denomPubHash,
-        denom_value: Amounts.stringify(denom.value),
-        exchange_base_url: c.exchangeBaseUrl,
-        refresh_parent_coin_pub: refreshParentCoinPub,
-        remaining_value: Amounts.stringify(c.currentAmount),
-        withdrawal_reserve_pub: withdrawalReservePub,
-        coin_suspended: c.suspended,
       });
-    }
     return coinsJson;
   }
 
@@ -963,6 +900,55 @@ export class Wallet {
     );
   }
 
+  async updateReserve(reservePub: string): Promise<ReserveRecord | undefined> {
+    await forceQueryReserve(this.ws, reservePub);
+    return await this.ws.db
+      .mktx((x) => ({
+        reserves: x.reserves,
+      }))
+      .runReadOnly(async (tx) => {
+        return tx.reserves.get(reservePub);
+      });
+  }
+
+  async getCoins(): Promise<CoinRecord[]> {
+    return await this.db
+      .mktx((x) => ({
+        coins: x.coins,
+      }))
+      .runReadOnly(async (tx) => {
+        return tx.coins.iter().toArray();
+      });
+  }
+
+  async getReservesForExchange(
+    exchangeBaseUrl?: string,
+  ): Promise<ReserveRecord[]> {
+    return await this.db
+      .mktx((x) => ({
+        reserves: x.reserves,
+      }))
+      .runReadOnly(async (tx) => {
+        if (exchangeBaseUrl) {
+          return await tx.reserves
+            .iter()
+            .filter((r) => r.exchangeBaseUrl === exchangeBaseUrl);
+        } else {
+          return await tx.reserves.iter().toArray();
+        }
+      });
+  }
+
+  async getReserve(reservePub: string): Promise<ReserveRecord | undefined> {
+    return await this.db
+      .mktx((x) => ({
+        reserves: x.reserves,
+      }))
+      .runReadOnly(async (tx) => {
+        return tx.reserves.get(reservePub);
+      });
+  }
+
   async runIntegrationtest(args: IntegrationTestArgs): Promise<void> {
     return runIntegrationTest(this.ws.http, this, args);
   }
@@ -1144,17 +1130,20 @@ export class Wallet {
       case "forceRefresh": {
         const req = codecForForceRefreshRequest().decode(payload);
         const coinPubs = req.coinPubList.map((x) => ({ coinPub: x }));
-        const refreshGroupId = await this.db.runWithWriteTransaction(
-          [Stores.refreshGroups, Stores.denominations, Stores.coins],
-          async (tx) => {
+        const refreshGroupId = await this.db
+          .mktx((x) => ({
+            refreshGroups: x.refreshGroups,
+            denominations: x.denominations,
+            coins: x.coins,
+          }))
+          .runReadWrite(async (tx) => {
             return await createRefreshGroup(
               this.ws,
               tx,
               coinPubs,
               RefreshReason.Manual,
             );
-          },
-        );
+          });
         return {
           refreshGroupId,
         };
diff --git a/packages/taler-wallet-webextension/src/wxBackend.ts 
b/packages/taler-wallet-webextension/src/wxBackend.ts
index 745fe036..51a44ee6 100644
--- a/packages/taler-wallet-webextension/src/wxBackend.ts
+++ b/packages/taler-wallet-webextension/src/wxBackend.ts
@@ -30,10 +30,10 @@ import {
   OpenedPromise,
   openPromise,
   openTalerDatabase,
-  Database,
-  Stores,
   makeErrorDetails,
   deleteTalerDatabase,
+  DbAccess,
+  WalletStoresV1,
 } from "@gnu-taler/taler-wallet-core";
 import {
   classifyTalerUri,
@@ -52,7 +52,7 @@ import { BrowserCryptoWorkerFactory } from 
"./browserCryptoWorkerFactory";
  */
 let currentWallet: Wallet | undefined;
 
-let currentDatabase: Database<typeof Stores> | undefined;
+let currentDatabase: DbAccess<typeof WalletStoresV1> | undefined;
 
 /**
  * Last version if an outdated DB, if applicable.

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