gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated: wallet: towards db-less bench


From: gnunet
Subject: [taler-wallet-core] branch master updated: wallet: towards db-less benchmarking, some refactoring
Date: Mon, 14 Mar 2022 18:31:39 +0100

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

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

The following commit(s) were added to refs/heads/master by this push:
     new 33274586 wallet: towards db-less benchmarking, some refactoring
33274586 is described below

commit 332745862e728dc5e79a424698b2736c4f2683bf
Author: Florian Dold <florian@dold.me>
AuthorDate: Mon Mar 14 18:31:30 2022 +0100

    wallet: towards db-less benchmarking, some refactoring
---
 packages/taler-util/src/talerCrypto.ts             |  16 +
 packages/taler-util/src/talerTypes.ts              |  46 +++
 packages/taler-util/src/time.ts                    |   3 +
 packages/taler-util/src/walletTypes.ts             |   2 +-
 packages/taler-wallet-cli/src/bench2.ts            | 106 ++++++
 packages/taler-wallet-cli/src/harness/harness.ts   | 187 +----------
 packages/taler-wallet-cli/src/harness/helpers.ts   |   5 +-
 .../src/integrationtests/test-bank-api.ts          |  12 +-
 .../integrationtests/test-exchange-management.ts   |   8 +-
 .../src/integrationtests/test-libeufin-basic.ts    |  16 +-
 .../integrationtests/test-merchant-refund-api.ts   |  23 +-
 .../src/integrationtests/test-payment-fault.ts     |  11 +-
 .../src/integrationtests/test-payment-on-demo.ts   |  54 +--
 .../src/integrationtests/test-tipping.ts           |  17 +-
 .../src/integrationtests/test-wallet-dbless.ts     | 358 ++++++++++++++++++++
 .../integrationtests/test-withdrawal-abort-bank.ts |   8 +-
 .../test-withdrawal-bank-integrated.ts             |  29 +-
 .../integrationtests/test-withdrawal-fakebank.ts   |   2 -
 .../src/integrationtests/test-withdrawal-manual.ts |  24 +-
 .../src/integrationtests/testrunner.ts             |   2 +
 packages/taler-wallet-core/src/bank-api-client.ts  | 249 ++++++++++++++
 .../src/crypto/workers/cryptoApi.ts                |  23 +-
 .../src/crypto/workers/cryptoImplementation.ts     |  33 +-
 packages/taler-wallet-core/src/index.ts            |  11 +-
 .../taler-wallet-core/src/operations/deposits.ts   |  41 +--
 .../taler-wallet-core/src/operations/exchanges.ts  |  34 +-
 packages/taler-wallet-core/src/operations/pay.ts   |  13 -
 .../taler-wallet-core/src/operations/refresh.ts    |  20 +-
 .../taler-wallet-core/src/operations/reserves.ts   |   2 +-
 .../taler-wallet-core/src/operations/testing.ts    |  14 +-
 .../taler-wallet-core/src/operations/withdraw.ts   |  15 +-
 packages/taler-wallet-core/src/util/http.ts        |   6 +
 .../src/i18n/taler-wallet-webextension.pot         | 366 +++++++++++++++++++++
 packages/taler-wallet-webextension/src/wxApi.ts    |   2 +-
 34 files changed, 1396 insertions(+), 362 deletions(-)

diff --git a/packages/taler-util/src/talerCrypto.ts 
b/packages/taler-util/src/talerCrypto.ts
index 358da9da..4d6e7367 100644
--- a/packages/taler-util/src/talerCrypto.ts
+++ b/packages/taler-util/src/talerCrypto.ts
@@ -785,6 +785,22 @@ export function setupRefreshTransferPub(
   };
 }
 
+/**
+ * 
+ * @param paytoUri
+ * @param salt 16-byte salt 
+ * @returns 
+ */
+export function hashWire(paytoUri: string, salt: string): string {
+  const r = kdf(
+    64,
+    stringToBytes(paytoUri + "\0"),
+    decodeCrock(salt),
+    stringToBytes("merchant-wire-signature"),
+  );
+  return encodeCrock(r);
+}
+
 export enum TalerSignaturePurpose {
   MERCHANT_TRACK_TRANSACTION = 1103,
   WALLET_RESERVE_WITHDRAW = 1200,
diff --git a/packages/taler-util/src/talerTypes.ts 
b/packages/taler-util/src/talerTypes.ts
index b38f788a..4ccfffce 100644
--- a/packages/taler-util/src/talerTypes.ts
+++ b/packages/taler-util/src/talerTypes.ts
@@ -951,6 +951,15 @@ export interface MerchantPayResponse {
   sig: string;
 }
 
+export interface ExchangeMeltRequest {
+  coin_pub: CoinPublicKeyString;
+  confirm_sig: EddsaSignatureString;
+  denom_pub_hash: HashCodeString;
+  denom_sig: UnblindedSignature;
+  rc: string;
+  value_with_fee: AmountString;
+}
+
 export interface ExchangeMeltResponse {
   /**
    * Which of the kappa indices does the client not have to reveal.
@@ -1710,3 +1719,40 @@ export interface ExchangeRefreshRevealRequest {
 
   link_sigs: EddsaSignatureString[];
 }
+
+export interface DepositSuccess {
+  // Optional base URL of the exchange for looking up wire transfers
+  // associated with this transaction.  If not given,
+  // the base URL is the same as the one used for this request.
+  // Can be used if the base URL for /transactions/ differs from that
+  // for /coins/, i.e. for load balancing.  Clients SHOULD
+  // respect the transaction_base_url if provided.  Any HTTP server
+  // belonging to an exchange MUST generate a 307 or 308 redirection
+  // to the correct base URL should a client uses the wrong base
+  // URL, or if the base URL has changed since the deposit.
+  transaction_base_url?: string;
+
+  // timestamp when the deposit was received by the exchange.
+  exchange_timestamp: Timestamp;
+
+  // the EdDSA signature of TALER_DepositConfirmationPS using a current
+  // signing key of the exchange affirming the successful
+  // deposit and that the exchange will transfer the funds after the refund
+  // deadline, or as soon as possible if the refund deadline is zero.
+  exchange_sig: string;
+
+  // public EdDSA key of the exchange that was used to
+  // generate the signature.
+  // Should match one of the exchange's signing keys from /keys.  It is given
+  // explicitly as the client might otherwise be confused by clock skew as to
+  // which signing key was used.
+  exchange_pub: string;
+}
+
+export const codecForDepositSuccess = (): Codec<DepositSuccess> =>
+  buildCodecForObject<DepositSuccess>()
+    .property("exchange_pub", codecForString())
+    .property("exchange_sig", codecForString())
+    .property("exchange_timestamp", codecForTimestamp)
+    .property("transaction_base_url", codecOptional(codecForString()))
+    .build("DepositSuccess");
diff --git a/packages/taler-util/src/time.ts b/packages/taler-util/src/time.ts
index 5fef0bf4..3b80b4ee 100644
--- a/packages/taler-util/src/time.ts
+++ b/packages/taler-util/src/time.ts
@@ -78,6 +78,9 @@ export namespace Duration {
     return Math.ceil(d.d_ms / 1000 / 60 / 60 / 24 / 365);
   }
   export const fromSpec = durationFromSpec;
+  export function getForever(): Duration {
+    return { d_ms: "forever" };
+  }
 }
 
 export namespace Timestamp {
diff --git a/packages/taler-util/src/walletTypes.ts 
b/packages/taler-util/src/walletTypes.ts
index b8433e26..444fac15 100644
--- a/packages/taler-util/src/walletTypes.ts
+++ b/packages/taler-util/src/walletTypes.ts
@@ -458,7 +458,7 @@ export interface TalerErrorDetails {
   details: unknown;
 }
 
-export interface PlanchetCreationResult {
+export interface WithdrawalPlanchet {
   coinPub: string;
   coinPriv: string;
   reservePub: string;
diff --git a/packages/taler-wallet-cli/src/bench2.ts 
b/packages/taler-wallet-cli/src/bench2.ts
new file mode 100644
index 00000000..88470820
--- /dev/null
+++ b/packages/taler-wallet-cli/src/bench2.ts
@@ -0,0 +1,106 @@
+/*
+ This file is part of GNU Taler
+ (C) 2022 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ * Imports.
+ */
+import {
+  buildCodecForObject,
+  codecForNumber,
+  codecForString,
+  codecOptional,
+  j2s,
+  Logger,
+} from "@gnu-taler/taler-util";
+import {
+  getDefaultNodeWallet2,
+  NodeHttpLib,
+  WalletApiOperation,
+  Wallet,
+  AccessStats,
+  downloadExchangeInfo,
+} from "@gnu-taler/taler-wallet-core";
+
+/**
+ * Entry point for the benchmark.
+ *
+ * The benchmark runs against an existing Taler deployment and does not
+ * set up its own services.
+ */
+export async function runBench2(configJson: any): Promise<void> {
+  const logger = new Logger("Bench1");
+
+  // Validate the configuration file for this benchmark.
+  const benchConf = codecForBench1Config().decode(configJson);
+
+  const myHttpLib = new NodeHttpLib();
+  myHttpLib.setThrottling(false);
+
+  const exchangeInfo = await downloadExchangeInfo(
+    benchConf.exchange,
+    myHttpLib,
+  );
+}
+
+/**
+ * Format of the configuration file passed to the benchmark
+ */
+interface Bench2Config {
+  /**
+   * Base URL of the bank.
+   */
+  bank: string;
+
+  /**
+   * Payto url for deposits.
+   */
+  payto: string;
+
+  /**
+   * Base URL of the exchange.
+   */
+  exchange: string;
+
+  /**
+   * How many withdraw/deposit iterations should be made?
+   * Defaults to 1.
+   */
+  iterations?: number;
+
+  currency: string;
+
+  deposits?: number;
+
+  /**
+   * How any iterations run until the wallet db gets purged
+   * Defaults to 20.
+   */
+  restartAfter?: number;
+}
+
+/**
+ * Schema validation codec for Bench1Config.
+ */
+const codecForBench1Config = () =>
+  buildCodecForObject<Bench2Config>()
+    .property("bank", codecForString())
+    .property("payto", codecForString())
+    .property("exchange", codecForString())
+    .property("iterations", codecOptional(codecForNumber()))
+    .property("deposits", codecOptional(codecForNumber()))
+    .property("currency", codecForString())
+    .property("restartAfter", codecOptional(codecForNumber()))
+    .build("Bench1Config");
diff --git a/packages/taler-wallet-cli/src/harness/harness.ts 
b/packages/taler-wallet-cli/src/harness/harness.ts
index f4e42269..63bb17fc 100644
--- a/packages/taler-wallet-cli/src/harness/harness.ts
+++ b/packages/taler-wallet-cli/src/harness/harness.ts
@@ -45,6 +45,9 @@ import {
   MerchantInstancesResponse,
 } from "./merchantApiTypes";
 import {
+  BankServiceHandle,
+  HarnessExchangeBankAccount,
+  NodeHttpLib,
   openPromise,
   OperationFailedError,
   WalletCoreApiClient,
@@ -468,164 +471,6 @@ export async function pingProc(
   }
 }
 
-export interface HarnessExchangeBankAccount {
-  accountName: string;
-  accountPassword: string;
-  accountPaytoUri: string;
-  wireGatewayApiBaseUrl: string;
-}
-
-export interface BankServiceInterface {
-  readonly baseUrl: string;
-  readonly port: number;
-}
-
-export enum CreditDebitIndicator {
-  Credit = "credit",
-  Debit = "debit",
-}
-
-export interface BankAccountBalanceResponse {
-  balance: {
-    amount: AmountString;
-    credit_debit_indicator: CreditDebitIndicator;
-  };
-}
-
-export namespace BankAccessApi {
-  export async function getAccountBalance(
-    bank: BankServiceInterface,
-    bankUser: BankUser,
-  ): Promise<BankAccountBalanceResponse> {
-    const url = new URL(`accounts/${bankUser.username}`, bank.baseUrl);
-    const resp = await axios.get(url.href, {
-      auth: bankUser,
-    });
-    return resp.data;
-  }
-
-  export async function createWithdrawalOperation(
-    bank: BankServiceInterface,
-    bankUser: BankUser,
-    amount: string,
-  ): Promise<WithdrawalOperationInfo> {
-    const url = new URL(
-      `accounts/${bankUser.username}/withdrawals`,
-      bank.baseUrl,
-    );
-    const resp = await axios.post(
-      url.href,
-      {
-        amount,
-      },
-      {
-        auth: bankUser,
-      },
-    );
-    return codecForWithdrawalOperationInfo().decode(resp.data);
-  }
-}
-
-export namespace BankApi {
-  export async function registerAccount(
-    bank: BankServiceInterface,
-    username: string,
-    password: string,
-  ): Promise<BankUser> {
-    const url = new URL("testing/register", bank.baseUrl);
-    let resp = await axios.post(url.href, {
-      username,
-      password,
-    });
-    let paytoUri = `payto://x-taler-bank/localhost/${username}`;
-    if (process.env.WALLET_HARNESS_WITH_EUFIN) {
-      paytoUri = resp.data.paytoUri;
-    }
-    return {
-      password,
-      username,
-      accountPaytoUri: paytoUri,
-    };
-  }
-
-  export async function createRandomBankUser(
-    bank: BankServiceInterface,
-  ): Promise<BankUser> {
-    const username = "user-" + encodeCrock(getRandomBytes(10)).toLowerCase();
-    const password = "pw-" + encodeCrock(getRandomBytes(10)).toLowerCase();
-    return await registerAccount(bank, username, password);
-  }
-
-  export async function adminAddIncoming(
-    bank: BankServiceInterface,
-    params: {
-      exchangeBankAccount: HarnessExchangeBankAccount;
-      amount: string;
-      reservePub: string;
-      debitAccountPayto: string;
-    },
-  ) {
-    let maybeBaseUrl = bank.baseUrl;
-    if (process.env.WALLET_HARNESS_WITH_EUFIN) {
-      maybeBaseUrl = (bank as EufinBankService).baseUrlDemobank;
-    }
-    let url = new URL(
-      
`taler-wire-gateway/${params.exchangeBankAccount.accountName}/admin/add-incoming`,
-      maybeBaseUrl,
-    );
-    await axios.post(
-      url.href,
-      {
-        amount: params.amount,
-        reserve_pub: params.reservePub,
-        debit_account: params.debitAccountPayto,
-      },
-      {
-        auth: {
-          username: params.exchangeBankAccount.accountName,
-          password: params.exchangeBankAccount.accountPassword,
-        },
-      },
-    );
-  }
-
-  export async function confirmWithdrawalOperation(
-    bank: BankServiceInterface,
-    bankUser: BankUser,
-    wopi: WithdrawalOperationInfo,
-  ): Promise<void> {
-    const url = new URL(
-      
`accounts/${bankUser.username}/withdrawals/${wopi.withdrawal_id}/confirm`,
-      bank.baseUrl,
-    );
-    await axios.post(
-      url.href,
-      {},
-      {
-        auth: bankUser,
-      },
-    );
-  }
-
-  export async function abortWithdrawalOperation(
-    bank: BankServiceInterface,
-    bankUser: BankUser,
-    wopi: WithdrawalOperationInfo,
-  ): Promise<void> {
-    const url = new URL(
-      `accounts/${bankUser.username}/withdrawals/${wopi.withdrawal_id}/abort`,
-      bank.baseUrl,
-    );
-    await axios.post(
-      url.href,
-      {},
-      {
-        auth: bankUser,
-      },
-    );
-  }
-}
-
 class BankServiceBase {
   proc: ProcessWrapper | undefined;
 
@@ -640,10 +485,12 @@ class BankServiceBase {
  * Work in progress.  The key point is that both Sandbox and Nexus
  * will be configured and started by this class.
  */
-class EufinBankService extends BankServiceBase implements BankServiceInterface 
{
+class EufinBankService extends BankServiceBase implements BankServiceHandle {
   sandboxProc: ProcessWrapper | undefined;
   nexusProc: ProcessWrapper | undefined;
 
+  http = new NodeHttpLib();
+
   static async create(
     gc: GlobalTestState,
     bc: BankConfig,
@@ -914,9 +761,11 @@ class EufinBankService extends BankServiceBase implements 
BankServiceInterface {
   }
 }
 
-class PybankService extends BankServiceBase implements BankServiceInterface {
+class PybankService extends BankServiceBase implements BankServiceHandle {
   proc: ProcessWrapper | undefined;
 
+  http = new NodeHttpLib();
+
   static async create(
     gc: GlobalTestState,
     bc: BankConfig,
@@ -955,6 +804,7 @@ class PybankService extends BankServiceBase implements 
BankServiceInterface {
     const config = Configuration.load(this.configFile);
     config.setString("bank", "suggested_exchange", e.baseUrl);
     config.setString("bank", "suggested_exchange_payto", exchangePayto);
+    config.write(this.configFile);
   }
 
   get baseUrl(): string {
@@ -1087,23 +937,6 @@ export class FakeBankService {
   }
 }
 
-export interface BankUser {
-  username: string;
-  password: string;
-  accountPaytoUri: string;
-}
-
-export interface WithdrawalOperationInfo {
-  withdrawal_id: string;
-  taler_withdraw_uri: string;
-}
-
-const codecForWithdrawalOperationInfo = (): Codec<WithdrawalOperationInfo> =>
-  buildCodecForObject<WithdrawalOperationInfo>()
-    .property("withdrawal_id", codecForString())
-    .property("taler_withdraw_uri", codecForString())
-    .build("WithdrawalOperationInfo");
-
 export interface ExchangeConfig {
   name: string;
   currency: string;
diff --git a/packages/taler-wallet-cli/src/harness/helpers.ts 
b/packages/taler-wallet-cli/src/harness/helpers.ts
index f19c6a11..117bcdcf 100644
--- a/packages/taler-wallet-cli/src/harness/helpers.ts
+++ b/packages/taler-wallet-cli/src/harness/helpers.ts
@@ -30,22 +30,19 @@ import {
   Duration,
   PreparePayResultType,
 } from "@gnu-taler/taler-util";
-import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import { BankAccessApi, BankApi, HarnessExchangeBankAccount, 
WalletApiOperation } from "@gnu-taler/taler-wallet-core";
 import { CoinConfig, defaultCoinConfig } from "./denomStructures.js";
 import {
   FaultInjectedExchangeService,
   FaultInjectedMerchantService,
 } from "./faultInjection.js";
 import {
-  BankAccessApi,
-  BankApi,
   BankService,
   DbInfo,
   ExchangeService,
   ExchangeServiceInterface,
   getPayto,
   GlobalTestState,
-  HarnessExchangeBankAccount,
   MerchantPrivateApi,
   MerchantService,
   MerchantServiceInterface,
diff --git a/packages/taler-wallet-cli/src/integrationtests/test-bank-api.ts 
b/packages/taler-wallet-cli/src/integrationtests/test-bank-api.ts
index 2259dd8b..8e410975 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-bank-api.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-bank-api.ts
@@ -24,13 +24,15 @@ import {
   setupDb,
   BankService,
   MerchantService,
-  BankApi,
-  BankAccessApi,
-  CreditDebitIndicator,
-  getPayto
+  getPayto,
 } from "../harness/harness.js";
 import { createEddsaKeyPair, encodeCrock } from "@gnu-taler/taler-util";
 import { defaultCoinConfig } from "../harness/denomStructures";
+import {
+  BankApi,
+  BankAccessApi,
+  CreditDebitIndicator,
+} from "@gnu-taler/taler-wallet-core";
 
 /**
  * Run test for basic, bank-integrated withdrawal.
@@ -97,8 +99,6 @@ export async function runBankApiTest(t: GlobalTestState) {
 
   console.log("setup done!");
 
-  const wallet = new WalletCli(t);
-
   const bankUser = await BankApi.registerAccount(bank, "user1", "pw1");
 
   // Make sure that registering twice results in a 409 Conflict
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 91e9bdec..f9c7c4b9 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-exchange-management.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-exchange-management.ts
@@ -24,11 +24,13 @@ import {
   BankService,
   ExchangeService,
   MerchantService,
+  getPayto,
+} from "../harness/harness.js";
+import {
+  WalletApiOperation,
   BankApi,
   BankAccessApi,
-  getPayto
-} from "../harness/harness.js";
-import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+} from "@gnu-taler/taler-wallet-core";
 import {
   ExchangesListRespose,
   URL,
diff --git 
a/packages/taler-wallet-cli/src/integrationtests/test-libeufin-basic.ts 
b/packages/taler-wallet-cli/src/integrationtests/test-libeufin-basic.ts
index 3f7e1a9d..33aad80d 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-libeufin-basic.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-libeufin-basic.ts
@@ -19,15 +19,16 @@
  */
 import {
   ContractTerms,
-  CoreApiResponse,
   getTimestampNow,
   timestampTruncateToSecond,
 } from "@gnu-taler/taler-util";
-import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import {
+  WalletApiOperation,
+  HarnessExchangeBankAccount,
+} from "@gnu-taler/taler-wallet-core";
 import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures";
 import {
   DbInfo,
-  HarnessExchangeBankAccount,
   ExchangeService,
   GlobalTestState,
   MerchantService,
@@ -233,13 +234,8 @@ export async function createLibeufinTestEnvironment(
 export async function runLibeufinBasicTest(t: GlobalTestState) {
   // Set up test environment
 
-  const {
-    wallet,
-    exchange,
-    merchant,
-    libeufinSandbox,
-    libeufinNexus,
-  } = await createLibeufinTestEnvironment(t);
+  const { wallet, exchange, merchant, libeufinSandbox, libeufinNexus } =
+    await createLibeufinTestEnvironment(t);
 
   await wallet.client.call(WalletApiOperation.AddExchange, {
     exchangeBaseUrl: exchange.baseUrl,
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 466b1efb..a9dbeef9 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
@@ -20,25 +20,30 @@
 import {
   GlobalTestState,
   MerchantPrivateApi,
-  BankServiceInterface,
   MerchantServiceInterface,
   WalletCli,
   ExchangeServiceInterface,
 } from "../harness/harness.js";
-import { createSimpleTestkudosEnvironment, withdrawViaBank } from 
"../harness/helpers.js";
+import {
+  createSimpleTestkudosEnvironment,
+  withdrawViaBank,
+} from "../harness/helpers.js";
 import {
   URL,
   durationFromSpec,
   PreparePayResultType,
 } from "@gnu-taler/taler-util";
 import axios from "axios";
-import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import {
+  WalletApiOperation,
+  BankServiceHandle,
+} from "@gnu-taler/taler-wallet-core";
 
 async function testRefundApiWithFulfillmentUrl(
   t: GlobalTestState,
   env: {
     merchant: MerchantServiceInterface;
-    bank: BankServiceInterface;
+    bank: BankServiceHandle;
     wallet: WalletCli;
     exchange: ExchangeServiceInterface;
   },
@@ -152,7 +157,7 @@ async function testRefundApiWithFulfillmentMessage(
   t: GlobalTestState,
   env: {
     merchant: MerchantServiceInterface;
-    bank: BankServiceInterface;
+    bank: BankServiceHandle;
     wallet: WalletCli;
     exchange: ExchangeServiceInterface;
   },
@@ -267,12 +272,8 @@ async function testRefundApiWithFulfillmentMessage(
 export async function runMerchantRefundApiTest(t: GlobalTestState) {
   // Set up test environment
 
-  const {
-    wallet,
-    bank,
-    exchange,
-    merchant,
-  } = await createSimpleTestkudosEnvironment(t);
+  const { wallet, bank, exchange, merchant } =
+    await createSimpleTestkudosEnvironment(t);
 
   // Withdraw digital cash into the wallet.
 
diff --git 
a/packages/taler-wallet-cli/src/integrationtests/test-payment-fault.ts 
b/packages/taler-wallet-cli/src/integrationtests/test-payment-fault.ts
index 7e421cc3..c78f030c 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-payment-fault.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-payment-fault.ts
@@ -29,9 +29,7 @@ import {
   BankService,
   WalletCli,
   MerchantPrivateApi,
-  BankApi,
-  BankAccessApi,
-  getPayto
+  getPayto,
 } from "../harness/harness.js";
 import {
   FaultInjectedExchangeService,
@@ -40,7 +38,11 @@ import {
 } from "../harness/faultInjection";
 import { CoreApiResponse } from "@gnu-taler/taler-util";
 import { defaultCoinConfig } from "../harness/denomStructures";
-import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import {
+  WalletApiOperation,
+  BankApi,
+  BankAccessApi,
+} from "@gnu-taler/taler-wallet-core";
 
 /**
  * Run test for basic, bank-integrated withdrawal.
@@ -146,7 +148,6 @@ export async function runPaymentFaultTest(t: 
GlobalTestState) {
 
   await wallet.runUntilDone();
 
-
   // Check balance
 
   await wallet.client.call(WalletApiOperation.GetBalances, {});
diff --git 
a/packages/taler-wallet-cli/src/integrationtests/test-payment-on-demo.ts 
b/packages/taler-wallet-cli/src/integrationtests/test-payment-on-demo.ts
index 1d419fd9..50a18944 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-payment-on-demo.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-payment-on-demo.ts
@@ -17,31 +17,33 @@
 /**
  * Imports.
  */
+import { GlobalTestState, WalletCli } from "../harness/harness.js";
+import { makeTestPayment } from "../harness/helpers.js";
 import {
-  GlobalTestState,
+  WalletApiOperation,
   BankApi,
-  WalletCli,
-  BankAccessApi
-} from "../harness/harness.js";
-import {
-  makeTestPayment,
-} from "../harness/helpers.js";
-import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+  BankAccessApi,
+  BankServiceHandle,
+  NodeHttpLib,
+} from "@gnu-taler/taler-wallet-core";
 
 /**
  * Run test for basic, bank-integrated withdrawal and payment.
  */
 export async function runPaymentDemoTest(t: GlobalTestState) {
-
   // Withdraw digital cash into the wallet.
-  let bankInterface = {
+  let bankInterface: BankServiceHandle = {
     baseUrl: "https://bank.demo.taler.net/";,
-    port: 0 // unused.
+    http: new NodeHttpLib(),
   };
   let user = await BankApi.createRandomBankUser(bankInterface);
-  let wop = await BankAccessApi.createWithdrawalOperation(bankInterface, user, 
"KUDOS:20");
+  let wop = await BankAccessApi.createWithdrawalOperation(
+    bankInterface,
+    user,
+    "KUDOS:20",
+  );
 
-   let wallet = new WalletCli(t);
+  let wallet = new WalletCli(t);
   await wallet.client.call(WalletApiOperation.GetWithdrawalDetailsForUri, {
     talerWithdrawUri: wop.taler_withdraw_uri,
   });
@@ -60,7 +62,10 @@ export async function runPaymentDemoTest(t: GlobalTestState) 
{
   });
   await wallet.runUntilDone();
 
-  let balanceBefore = await wallet.client.call(WalletApiOperation.GetBalances, 
{});
+  let balanceBefore = await wallet.client.call(
+    WalletApiOperation.GetBalances,
+    {},
+  );
   t.assertTrue(balanceBefore["balances"].length == 1);
 
   const order = {
@@ -70,7 +75,7 @@ export async function runPaymentDemoTest(t: GlobalTestState) {
   };
 
   let merchant = {
-    makeInstanceBaseUrl: function(instanceName?: string) {
+    makeInstanceBaseUrl: function (instanceName?: string) {
       return "https://backend.demo.taler.net/instances/donations/";;
     },
     port: 0,
@@ -82,17 +87,26 @@ export async function runPaymentDemoTest(t: 
GlobalTestState) {
   await makeTestPayment(
     t,
     {
-      merchant, wallet, order
+      merchant,
+      wallet,
+      order,
     },
     {
-      "Authorization": `Bearer ${process.env["TALER_ENV_FRONTENDS_APITOKEN"]}`,
-    });
+      Authorization: `Bearer ${process.env["TALER_ENV_FRONTENDS_APITOKEN"]}`,
+    },
+  );
 
   await wallet.runUntilDone();
 
-  let balanceAfter = await wallet.client.call(WalletApiOperation.GetBalances, 
{});
+  let balanceAfter = await wallet.client.call(
+    WalletApiOperation.GetBalances,
+    {},
+  );
   t.assertTrue(balanceAfter["balances"].length == 1);
-  t.assertTrue(balanceBefore["balances"][0]["available"] > 
balanceAfter["balances"][0]["available"]);
+  t.assertTrue(
+    balanceBefore["balances"][0]["available"] >
+      balanceAfter["balances"][0]["available"],
+  );
 }
 
 runPaymentDemoTest.excludeByDefault = true;
diff --git a/packages/taler-wallet-cli/src/integrationtests/test-tipping.ts 
b/packages/taler-wallet-cli/src/integrationtests/test-tipping.ts
index f31220e2..f04293ed 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-tipping.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-tipping.ts
@@ -17,8 +17,12 @@
 /**
  * Imports.
  */
-import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
-import { GlobalTestState, MerchantPrivateApi, BankApi, getWireMethod } from 
"../harness/harness.js";
+import { WalletApiOperation, BankApi } from "@gnu-taler/taler-wallet-core";
+import {
+  GlobalTestState,
+  MerchantPrivateApi,
+  getWireMethod,
+} from "../harness/harness.js";
 import { createSimpleTestkudosEnvironment } from "../harness/helpers.js";
 
 /**
@@ -27,13 +31,8 @@ import { createSimpleTestkudosEnvironment } from 
"../harness/helpers.js";
 export async function runTippingTest(t: GlobalTestState) {
   // Set up test environment
 
-  const {
-    wallet,
-    bank,
-    exchange,
-    merchant,
-    exchangeBankAccount,
-  } = await createSimpleTestkudosEnvironment(t);
+  const { wallet, bank, exchange, merchant, exchangeBankAccount } =
+    await createSimpleTestkudosEnvironment(t);
 
   const mbu = await BankApi.createRandomBankUser(bank);
 
diff --git 
a/packages/taler-wallet-cli/src/integrationtests/test-wallet-dbless.ts 
b/packages/taler-wallet-cli/src/integrationtests/test-wallet-dbless.ts
new file mode 100644
index 00000000..9ff605df
--- /dev/null
+++ b/packages/taler-wallet-cli/src/integrationtests/test-wallet-dbless.ts
@@ -0,0 +1,358 @@
+/*
+ This file is part of GNU Taler
+ (C) 2020 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ * Imports.
+ */
+import {
+  AmountJson,
+  AmountLike,
+  Amounts,
+  AmountString,
+  codecForBankWithdrawalOperationPostResponse,
+  codecForDepositSuccess,
+  codecForExchangeMeltResponse,
+  codecForWithdrawResponse,
+  DenominationPubKey,
+  eddsaGetPublic,
+  encodeCrock,
+  ExchangeMeltRequest,
+  ExchangeProtocolVersion,
+  ExchangeWithdrawRequest,
+  getRandomBytes,
+  getTimestampNow,
+  hashWire,
+  j2s,
+  Timestamp,
+  UnblindedSignature,
+} from "@gnu-taler/taler-util";
+import {
+  BankAccessApi,
+  BankApi,
+  BankServiceHandle,
+  CryptoApi,
+  DenominationRecord,
+  downloadExchangeInfo,
+  ExchangeInfo,
+  getBankWithdrawalInfo,
+  HttpRequestLibrary,
+  isWithdrawableDenom,
+  NodeHttpLib,
+  OperationFailedError,
+  readSuccessResponseJsonOrThrow,
+  SynchronousCryptoWorkerFactory,
+} from "@gnu-taler/taler-wallet-core";
+import { GlobalTestState } from "../harness/harness.js";
+import { createSimpleTestkudosEnvironment } from "../harness/helpers.js";
+
+const httpLib = new NodeHttpLib();
+
+export interface ReserveKeypair {
+  reservePub: string;
+  reservePriv: string;
+}
+
+/**
+ * Denormalized info about a coin.
+ */
+export interface CoinInfo {
+  coinPub: string;
+  coinPriv: string;
+  exchangeBaseUrl: string;
+  denomSig: UnblindedSignature;
+  denomPub: DenominationPubKey;
+  denomPubHash: string;
+  feeDeposit: string;
+  feeRefresh: string;
+}
+
+export function generateReserveKeypair(): ReserveKeypair {
+  const priv = getRandomBytes(32);
+  const pub = eddsaGetPublic(priv);
+  return {
+    reservePriv: encodeCrock(priv),
+    reservePub: encodeCrock(pub),
+  };
+}
+
+async function topupReserveWithDemobank(
+  reservePub: string,
+  bankBaseUrl: string,
+  exchangeInfo: ExchangeInfo,
+  amount: AmountString,
+) {
+  const bankHandle: BankServiceHandle = {
+    baseUrl: bankBaseUrl,
+    http: httpLib,
+  };
+  const bankUser = await BankApi.createRandomBankUser(bankHandle);
+  const wopi = await BankAccessApi.createWithdrawalOperation(
+    bankHandle,
+    bankUser,
+    amount,
+  );
+  const bankInfo = await getBankWithdrawalInfo(
+    httpLib,
+    wopi.taler_withdraw_uri,
+  );
+  const bankStatusUrl = bankInfo.extractedStatusUrl;
+  if (!bankInfo.suggestedExchange) {
+    throw Error("no suggested exchange");
+  }
+  const plainPaytoUris =
+    exchangeInfo.wire.accounts.map((x) => x.payto_uri) ?? [];
+  if (plainPaytoUris.length <= 0) {
+    throw new Error();
+  }
+  const httpResp = await httpLib.postJson(bankStatusUrl, {
+    reserve_pub: reservePub,
+    selected_exchange: plainPaytoUris[0],
+  });
+  await readSuccessResponseJsonOrThrow(
+    httpResp,
+    codecForBankWithdrawalOperationPostResponse(),
+  );
+  await BankApi.confirmWithdrawalOperation(bankHandle, bankUser, wopi);
+}
+
+async function withdrawCoin(args: {
+  http: HttpRequestLibrary;
+  cryptoApi: CryptoApi;
+  reserveKeyPair: ReserveKeypair;
+  denom: DenominationRecord;
+  exchangeBaseUrl: string;
+}): Promise<CoinInfo> {
+  const { http, cryptoApi, reserveKeyPair, denom, exchangeBaseUrl } = args;
+  const planchet = await cryptoApi.createPlanchet({
+    coinIndex: 0,
+    denomPub: denom.denomPub,
+    feeWithdraw: denom.feeWithdraw,
+    reservePriv: reserveKeyPair.reservePriv,
+    reservePub: reserveKeyPair.reservePub,
+    secretSeed: encodeCrock(getRandomBytes(32)),
+    value: denom.value,
+  });
+
+  const reqBody: ExchangeWithdrawRequest = {
+    denom_pub_hash: planchet.denomPubHash,
+    reserve_sig: planchet.withdrawSig,
+    coin_ev: planchet.coinEv,
+  };
+  const reqUrl = new URL(
+    `reserves/${planchet.reservePub}/withdraw`,
+    exchangeBaseUrl,
+  ).href;
+
+  const resp = await http.postJson(reqUrl, reqBody);
+  const r = await readSuccessResponseJsonOrThrow(
+    resp,
+    codecForWithdrawResponse(),
+  );
+
+  const ubSig = await cryptoApi.unblindDenominationSignature({
+    planchet,
+    evSig: r.ev_sig,
+  });
+
+  return {
+    coinPriv: planchet.coinPriv,
+    coinPub: planchet.coinPub,
+    denomSig: ubSig,
+    denomPub: denom.denomPub,
+    denomPubHash: denom.denomPubHash,
+    feeDeposit: Amounts.stringify(denom.feeDeposit),
+    feeRefresh: Amounts.stringify(denom.feeRefresh),
+    exchangeBaseUrl: args.exchangeBaseUrl,
+  };
+}
+
+function findDenomOrThrow(
+  exchangeInfo: ExchangeInfo,
+  amount: AmountString,
+): DenominationRecord {
+  for (const d of exchangeInfo.keys.currentDenominations) {
+    if (Amounts.cmp(d.value, amount) === 0 && isWithdrawableDenom(d)) {
+      return d;
+    }
+  }
+  throw new Error("no matching denomination found");
+}
+
+async function depositCoin(args: {
+  http: HttpRequestLibrary;
+  cryptoApi: CryptoApi;
+  exchangeBaseUrl: string;
+  coin: CoinInfo;
+  amount: AmountString;
+}) {
+  const { coin, http, cryptoApi } = args;
+  const depositPayto = "payto://x-taler-bank/localhost/foo";
+  const wireSalt = encodeCrock(getRandomBytes(16));
+  const contractTermsHash = encodeCrock(getRandomBytes(64));
+  const depositTimestamp = getTimestampNow();
+  const refundDeadline = getTimestampNow();
+  const merchantPub = encodeCrock(getRandomBytes(32));
+  const dp = await cryptoApi.signDepositPermission({
+    coinPriv: coin.coinPriv,
+    coinPub: coin.coinPub,
+    contractTermsHash,
+    denomKeyType: coin.denomPub.cipher,
+    denomPubHash: coin.denomPubHash,
+    denomSig: coin.denomSig,
+    exchangeBaseUrl: args.exchangeBaseUrl,
+    feeDeposit: Amounts.parseOrThrow(coin.feeDeposit),
+    merchantPub,
+    spendAmount: Amounts.parseOrThrow(args.amount),
+    timestamp: depositTimestamp,
+    refundDeadline: refundDeadline,
+    wireInfoHash: hashWire(depositPayto, wireSalt),
+  });
+  const requestBody = {
+    contribution: Amounts.stringify(dp.contribution),
+    merchant_payto_uri: depositPayto,
+    wire_salt: wireSalt,
+    h_contract_terms: contractTermsHash,
+    ub_sig: coin.denomSig,
+    timestamp: depositTimestamp,
+    wire_transfer_deadline: getTimestampNow(),
+    refund_deadline: refundDeadline,
+    coin_sig: dp.coin_sig,
+    denom_pub_hash: dp.h_denom,
+    merchant_pub: merchantPub,
+  };
+  const url = new URL(`coins/${dp.coin_pub}/deposit`, dp.exchange_url);
+  const httpResp = await http.postJson(url.href, requestBody);
+  await readSuccessResponseJsonOrThrow(httpResp, codecForDepositSuccess());
+}
+
+async function refreshCoin(req: {
+  http: HttpRequestLibrary;
+  cryptoApi: CryptoApi;
+  oldCoin: CoinInfo;
+  newDenoms: DenominationRecord[];
+}): Promise<void> {
+  const { cryptoApi, oldCoin, http } = req;
+  const refreshSessionSeed = encodeCrock(getRandomBytes(32));
+  const session = await cryptoApi.deriveRefreshSession({
+    exchangeProtocolVersion: ExchangeProtocolVersion.V12,
+    feeRefresh: Amounts.parseOrThrow(oldCoin.feeRefresh),
+    kappa: 3,
+    meltCoinDenomPubHash: oldCoin.denomPubHash,
+    meltCoinPriv: oldCoin.coinPriv,
+    meltCoinPub: oldCoin.coinPub,
+    sessionSecretSeed: refreshSessionSeed,
+    newCoinDenoms: req.newDenoms.map((x) => ({
+      count: 1,
+      denomPub: x.denomPub,
+      feeWithdraw: x.feeWithdraw,
+      value: x.value,
+    })),
+  });
+
+  const meltReqBody: ExchangeMeltRequest = {
+    coin_pub: oldCoin.coinPub,
+    confirm_sig: session.confirmSig,
+    denom_pub_hash: oldCoin.denomPubHash,
+    denom_sig: oldCoin.denomSig,
+    rc: session.hash,
+    value_with_fee: Amounts.stringify(session.meltValueWithFee),
+  };
+
+  const reqUrl = new URL(
+    `coins/${oldCoin.coinPub}/melt`,
+    oldCoin.exchangeBaseUrl,
+  );
+
+  const resp = await http.postJson(reqUrl.href, meltReqBody);
+
+  const meltResponse = await readSuccessResponseJsonOrThrow(
+    resp,
+    codecForExchangeMeltResponse(),
+  );
+
+  const norevealIndex = meltResponse.noreveal_index;
+
+  
+}
+
+/**
+ * Run test for basic, bank-integrated withdrawal and payment.
+ */
+export async function runWalletDblessTest(t: GlobalTestState) {
+  // Set up test environment
+
+  const { bank, exchange } = await createSimpleTestkudosEnvironment(t);
+
+  const http = new NodeHttpLib();
+  const cryptoApi = new CryptoApi(new SynchronousCryptoWorkerFactory());
+
+  try {
+    // Withdraw digital cash into the wallet.
+
+    const exchangeInfo = await downloadExchangeInfo(exchange.baseUrl, http);
+
+    const reserveKeyPair = generateReserveKeypair();
+
+    await topupReserveWithDemobank(
+      reserveKeyPair.reservePub,
+      bank.baseUrl,
+      exchangeInfo,
+      "TESTKUDOS:10",
+    );
+
+    await exchange.runWirewatchOnce();
+
+    const d1 = findDenomOrThrow(exchangeInfo, "TESTKUDOS:8");
+
+    const coin = await withdrawCoin({
+      http,
+      cryptoApi,
+      reserveKeyPair,
+      denom: d1,
+      exchangeBaseUrl: exchange.baseUrl,
+    });
+
+    await depositCoin({
+      amount: "TESTKUDOS:4",
+      coin: coin,
+      cryptoApi,
+      exchangeBaseUrl: exchange.baseUrl,
+      http,
+    });
+
+    const refreshDenoms = [
+      findDenomOrThrow(exchangeInfo, "TESTKUDOS:1"),
+      findDenomOrThrow(exchangeInfo, "TESTKUDOS:1"),
+    ];
+
+    const freshCoins = await refreshCoin({
+      oldCoin: coin,
+      cryptoApi,
+      http,
+      newDenoms: refreshDenoms,
+    });
+  } catch (e) {
+    if (e instanceof OperationFailedError) {
+      console.log(e);
+      console.log(j2s(e.operationError));
+    } else {
+      console.log(e);
+    }
+    throw e;
+  }
+}
+
+runWalletDblessTest.suites = ["wallet"];
diff --git 
a/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-abort-bank.ts 
b/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-abort-bank.ts
index 5ba1fa89..19668d76 100644
--- 
a/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-abort-bank.ts
+++ 
b/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-abort-bank.ts
@@ -18,8 +18,12 @@
  * Imports.
  */
 import { TalerErrorCode } from "@gnu-taler/taler-util";
-import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
-import { GlobalTestState, BankApi, BankAccessApi } from 
"../harness/harness.js";
+import {
+  WalletApiOperation,
+  BankApi,
+  BankAccessApi,
+} from "@gnu-taler/taler-wallet-core";
+import { GlobalTestState } from "../harness/harness.js";
 import { createSimpleTestkudosEnvironment } from "../harness/helpers.js";
 
 /**
diff --git 
a/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-bank-integrated.ts
 
b/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-bank-integrated.ts
index 25df19e4..e8a8c502 100644
--- 
a/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-bank-integrated.ts
+++ 
b/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-bank-integrated.ts
@@ -17,10 +17,13 @@
 /**
  * Imports.
  */
-import { GlobalTestState, BankApi, BankAccessApi } from 
"../harness/harness.js";
+import { GlobalTestState } from "../harness/harness.js";
 import { createSimpleTestkudosEnvironment } from "../harness/helpers.js";
-import { codecForBalancesResponse } from "@gnu-taler/taler-util";
-import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import {
+  WalletApiOperation,
+  BankApi,
+  BankAccessApi,
+} from "@gnu-taler/taler-wallet-core";
 
 /**
  * Run test for basic, bank-integrated withdrawal.
@@ -41,18 +44,24 @@ export async function runWithdrawalBankIntegratedTest(t: 
GlobalTestState) {
 
   // Hand it to the wallet
 
-  const r1 = await 
wallet.client.call(WalletApiOperation.GetWithdrawalDetailsForUri, {
-    talerWithdrawUri: wop.taler_withdraw_uri,
-  });
+  const r1 = await wallet.client.call(
+    WalletApiOperation.GetWithdrawalDetailsForUri,
+    {
+      talerWithdrawUri: wop.taler_withdraw_uri,
+    },
+  );
 
   await wallet.runPending();
 
   // Withdraw
 
-  const r2 = await 
wallet.client.call(WalletApiOperation.AcceptBankIntegratedWithdrawal, {
-    exchangeBaseUrl: exchange.baseUrl,
-    talerWithdrawUri: wop.taler_withdraw_uri,
-  });
+  const r2 = await wallet.client.call(
+    WalletApiOperation.AcceptBankIntegratedWithdrawal,
+    {
+      exchangeBaseUrl: exchange.baseUrl,
+      talerWithdrawUri: wop.taler_withdraw_uri,
+    },
+  );
   await wallet.runPending();
 
   // Confirm it
diff --git 
a/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-fakebank.ts 
b/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-fakebank.ts
index abd25d28..5860aaf8 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-fakebank.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-fakebank.ts
@@ -19,13 +19,11 @@
  */
 import {
   GlobalTestState,
-  BankApi,
   WalletCli,
   setupDb,
   ExchangeService,
   FakeBankService,
 } from "../harness/harness.js";
-import { createSimpleTestkudosEnvironment } from "../harness/helpers.js";
 import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
 import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
 import { URL } from "@gnu-taler/taler-util";
diff --git 
a/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-manual.ts 
b/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-manual.ts
index 2f88b302..6ae0e65e 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-manual.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-manual.ts
@@ -17,9 +17,9 @@
 /**
  * Imports.
  */
-import { GlobalTestState, BankApi } from "../harness/harness.js";
+import { GlobalTestState } from "../harness/harness.js";
 import { createSimpleTestkudosEnvironment } from "../harness/helpers.js";
-import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import { WalletApiOperation, BankApi } from "@gnu-taler/taler-wallet-core";
 
 /**
  * Run test for basic, bank-integrated withdrawal.
@@ -27,12 +27,8 @@ import { WalletApiOperation } from 
"@gnu-taler/taler-wallet-core";
 export async function runTestWithdrawalManualTest(t: GlobalTestState) {
   // Set up test environment
 
-  const {
-    wallet,
-    bank,
-    exchange,
-    exchangeBankAccount,
-  } = await createSimpleTestkudosEnvironment(t);
+  const { wallet, bank, exchange, exchangeBankAccount } =
+    await createSimpleTestkudosEnvironment(t);
 
   // Create a withdrawal operation
 
@@ -42,11 +38,13 @@ export async function runTestWithdrawalManualTest(t: 
GlobalTestState) {
     exchangeBaseUrl: exchange.baseUrl,
   });
 
-
-  const wres = await 
wallet.client.call(WalletApiOperation.AcceptManualWithdrawal, {
-    exchangeBaseUrl: exchange.baseUrl,
-    amount: "TESTKUDOS:10",
-  });
+  const wres = await wallet.client.call(
+    WalletApiOperation.AcceptManualWithdrawal,
+    {
+      exchangeBaseUrl: exchange.baseUrl,
+      amount: "TESTKUDOS:10",
+    },
+  );
 
   const reservePub: string = wres.reservePub;
 
diff --git a/packages/taler-wallet-cli/src/integrationtests/testrunner.ts 
b/packages/taler-wallet-cli/src/integrationtests/testrunner.ts
index 84490413..3839266c 100644
--- a/packages/taler-wallet-cli/src/integrationtests/testrunner.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/testrunner.ts
@@ -87,6 +87,7 @@ import { runExchangeTimetravelTest } from 
"./test-exchange-timetravel.js";
 import { runDenomUnofferedTest } from "./test-denom-unoffered.js";
 import { runWithdrawalFakebankTest } from "./test-withdrawal-fakebank.js";
 import { runClauseSchnorrTest } from "./test-clause-schnorr.js";
+import { runWalletDblessTest } from "./test-wallet-dbless.js";
 
 /**
  * Test runner.
@@ -162,6 +163,7 @@ const allTests: TestMainFunction[] = [
   runWalletBackupBasicTest,
   runWalletBackupDoublespendTest,
   runWallettestingTest,
+  runWalletDblessTest,
   runWithdrawalAbortBankTest,
   runWithdrawalBankIntegratedTest,
 ];
diff --git a/packages/taler-wallet-core/src/bank-api-client.ts 
b/packages/taler-wallet-core/src/bank-api-client.ts
new file mode 100644
index 00000000..744c3b83
--- /dev/null
+++ b/packages/taler-wallet-core/src/bank-api-client.ts
@@ -0,0 +1,249 @@
+/*
+ This file is part of GNU Taler
+ (C) 2022 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ * Client for the Taler (demo-)bank.
+ */
+
+/**
+ * Imports.
+ */
+import {
+  AmountString,
+  buildCodecForObject,
+  Codec,
+  codecForString,
+  encodeCrock,
+  getRandomBytes,
+} from "@gnu-taler/taler-util";
+import {
+  HttpRequestLibrary,
+  readSuccessResponseJsonOrErrorCode,
+  readSuccessResponseJsonOrThrow,
+} from "./index.browser.js";
+
+export enum CreditDebitIndicator {
+  Credit = "credit",
+  Debit = "debit",
+}
+
+export interface BankAccountBalanceResponse {
+  balance: {
+    amount: AmountString;
+    credit_debit_indicator: CreditDebitIndicator;
+  };
+}
+
+export interface BankServiceHandle {
+  readonly baseUrl: string;
+  readonly http: HttpRequestLibrary;
+}
+
+export interface BankUser {
+  username: string;
+  password: string;
+  accountPaytoUri: string;
+}
+
+export interface WithdrawalOperationInfo {
+  withdrawal_id: string;
+  taler_withdraw_uri: string;
+}
+
+/**
+ * FIXME: Rename, this is not part of the integration test harness anymore.
+ */
+export interface HarnessExchangeBankAccount {
+  accountName: string;
+  accountPassword: string;
+  accountPaytoUri: string;
+  wireGatewayApiBaseUrl: string;
+}
+
+/**
+ * Helper function to generate the "Authorization" HTTP header.
+ */
+function makeBasicAuthHeader(username: string, password: string): string {
+  const auth = `${username}:${password}`;
+  const authEncoded: string = Buffer.from(auth).toString("base64");
+  return `Basic ${authEncoded}`;
+}
+
+const codecForWithdrawalOperationInfo = (): Codec<WithdrawalOperationInfo> =>
+  buildCodecForObject<WithdrawalOperationInfo>()
+    .property("withdrawal_id", codecForString())
+    .property("taler_withdraw_uri", codecForString())
+    .build("WithdrawalOperationInfo");
+
+export namespace BankApi {
+  export async function registerAccount(
+    bank: BankServiceHandle,
+    username: string,
+    password: string,
+  ): Promise<BankUser> {
+    const url = new URL("testing/register", bank.baseUrl);
+    const resp = await bank.http.postJson(url.href, { username, password });
+    let paytoUri = `payto://x-taler-bank/localhost/${username}`;
+    if (resp.status !== 200 && resp.status !== 202) {
+      throw new Error();
+    }
+    try {
+      const respJson = await resp.json();
+      // LibEuFin demobank returns payto URI in response
+      if (respJson.paytoUri) {
+        paytoUri = respJson.paytoUri;
+      }
+    } catch (e) {}
+    return {
+      password,
+      username,
+      accountPaytoUri: paytoUri,
+    };
+  }
+
+  export async function createRandomBankUser(
+    bank: BankServiceHandle,
+  ): Promise<BankUser> {
+    const username = "user-" + encodeCrock(getRandomBytes(10)).toLowerCase();
+    const password = "pw-" + encodeCrock(getRandomBytes(10)).toLowerCase();
+    return await registerAccount(bank, username, password);
+  }
+
+  export async function adminAddIncoming(
+    bank: BankServiceHandle,
+    params: {
+      exchangeBankAccount: HarnessExchangeBankAccount;
+      amount: string;
+      reservePub: string;
+      debitAccountPayto: string;
+    },
+  ) {
+    let maybeBaseUrl = bank.baseUrl;
+    let url = new URL(
+      
`taler-wire-gateway/${params.exchangeBankAccount.accountName}/admin/add-incoming`,
+      maybeBaseUrl,
+    );
+    await bank.http.postJson(
+      url.href,
+      {
+        amount: params.amount,
+        reserve_pub: params.reservePub,
+        debit_account: params.debitAccountPayto,
+      },
+      {
+        headers: {
+          Authorization: makeBasicAuthHeader(
+            params.exchangeBankAccount.accountName,
+            params.exchangeBankAccount.accountPassword,
+          ),
+        },
+      },
+    );
+  }
+
+  export async function confirmWithdrawalOperation(
+    bank: BankServiceHandle,
+    bankUser: BankUser,
+    wopi: WithdrawalOperationInfo,
+  ): Promise<void> {
+    const url = new URL(
+      
`accounts/${bankUser.username}/withdrawals/${wopi.withdrawal_id}/confirm`,
+      bank.baseUrl,
+    );
+    await bank.http.postJson(
+      url.href,
+      {},
+      {
+        headers: {
+          Authorization: makeBasicAuthHeader(
+            bankUser.username,
+            bankUser.password,
+          ),
+        },
+      },
+    );
+  }
+
+  export async function abortWithdrawalOperation(
+    bank: BankServiceHandle,
+    bankUser: BankUser,
+    wopi: WithdrawalOperationInfo,
+  ): Promise<void> {
+    const url = new URL(
+      `accounts/${bankUser.username}/withdrawals/${wopi.withdrawal_id}/abort`,
+      bank.baseUrl,
+    );
+    await bank.http.postJson(
+      url.href,
+      {},
+      {
+        headers: {
+          Authorization: makeBasicAuthHeader(
+            bankUser.username,
+            bankUser.password,
+          ),
+        },
+      },
+    );
+  }
+}
+
+export namespace BankAccessApi {
+  export async function getAccountBalance(
+    bank: BankServiceHandle,
+    bankUser: BankUser,
+  ): Promise<BankAccountBalanceResponse> {
+    const url = new URL(`accounts/${bankUser.username}`, bank.baseUrl);
+    const resp = await bank.http.get(url.href, {
+      headers: {
+        Authorization: makeBasicAuthHeader(
+          bankUser.username,
+          bankUser.password,
+        ),
+      },
+    });
+    return await resp.json();
+  }
+
+  export async function createWithdrawalOperation(
+    bank: BankServiceHandle,
+    bankUser: BankUser,
+    amount: string,
+  ): Promise<WithdrawalOperationInfo> {
+    const url = new URL(
+      `accounts/${bankUser.username}/withdrawals`,
+      bank.baseUrl,
+    );
+    const resp = await bank.http.postJson(
+      url.href,
+      {
+        amount,
+      },
+      {
+        headers: {
+          Authorization: makeBasicAuthHeader(
+            bankUser.username,
+            bankUser.password,
+          ),
+        },
+      },
+    );
+    return readSuccessResponseJsonOrThrow(
+      resp,
+      codecForWithdrawalOperationInfo(),
+    );
+  }
+}
diff --git a/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts 
b/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts
index 16446bb9..b5a5950b 100644
--- a/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts
+++ b/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts
@@ -22,20 +22,22 @@
 /**
  * Imports.
  */
-import { CoinRecord, DenominationRecord, WireFee } from "../../db.js";
+import { DenominationRecord, WireFee } from "../../db.js";
 
 import { CryptoWorker } from "./cryptoWorkerInterface.js";
 
 import {
+  BlindedDenominationSignature,
   CoinDepositPermission,
   CoinEnvelope,
   RecoupRefreshRequest,
   RecoupRequest,
+  UnblindedSignature,
 } from "@gnu-taler/taler-util";
 
 import {
   BenchmarkResult,
-  PlanchetCreationResult,
+  WithdrawalPlanchet,
   PlanchetCreationRequest,
   DepositInfo,
   MakeSyncSignatureRequest,
@@ -324,10 +326,19 @@ export class CryptoApi {
     return p;
   }
 
-  createPlanchet(
-    req: PlanchetCreationRequest,
-  ): Promise<PlanchetCreationResult> {
-    return this.doRpc<PlanchetCreationResult>("createPlanchet", 1, req);
+  createPlanchet(req: PlanchetCreationRequest): Promise<WithdrawalPlanchet> {
+    return this.doRpc<WithdrawalPlanchet>("createPlanchet", 1, req);
+  }
+
+  unblindDenominationSignature(req: {
+    planchet: WithdrawalPlanchet;
+    evSig: BlindedDenominationSignature;
+  }): Promise<UnblindedSignature> {
+    return this.doRpc<UnblindedSignature>(
+      "unblindDenominationSignature",
+      1,
+      req,
+    );
   }
 
   createTipPlanchet(req: DeriveTipRequest): Promise<DerivedTipPlanchet> {
diff --git 
a/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts 
b/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts
index af77e2be..15a086ae 100644
--- a/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts
+++ b/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts
@@ -53,7 +53,7 @@ import {
   Logger,
   MakeSyncSignatureRequest,
   PlanchetCreationRequest,
-  PlanchetCreationResult,
+  WithdrawalPlanchet,
   randomBytes,
   RecoupRefreshRequest,
   RecoupRequest,
@@ -70,6 +70,9 @@ import {
   Timestamp,
   timestampTruncateToSecond,
   typedArrayConcat,
+  BlindedDenominationSignature,
+  RsaUnblindedSignature,
+  UnblindedSignature,
 } from "@gnu-taler/taler-util";
 import bigint from "big-integer";
 import { DenominationRecord, WireFee } from "../../db.js";
@@ -169,7 +172,7 @@ export class CryptoImplementation {
    */
   async createPlanchet(
     req: PlanchetCreationRequest,
-  ): Promise<PlanchetCreationResult> {
+  ): Promise<WithdrawalPlanchet> {
     const denomPub = req.denomPub;
     if (denomPub.cipher === DenomKeyType.Rsa) {
       const reservePub = decodeCrock(req.reservePub);
@@ -200,7 +203,7 @@ export class CryptoImplementation {
         priv: req.reservePriv,
       });
 
-      const planchet: PlanchetCreationResult = {
+      const planchet: WithdrawalPlanchet = {
         blindingKey: encodeCrock(derivedPlanchet.bks),
         coinEv,
         coinPriv: encodeCrock(derivedPlanchet.coinPriv),
@@ -428,6 +431,30 @@ export class CryptoImplementation {
     };
   }
 
+  unblindDenominationSignature(req: {
+    planchet: WithdrawalPlanchet;
+    evSig: BlindedDenominationSignature;
+  }): UnblindedSignature {
+    if (req.evSig.cipher === DenomKeyType.Rsa) {
+      if (req.planchet.denomPub.cipher !== DenomKeyType.Rsa) {
+        throw new Error(
+          "planchet cipher does not match blind signature cipher",
+        );
+      }
+      const denomSig = rsaUnblind(
+        decodeCrock(req.evSig.blinded_rsa_signature),
+        decodeCrock(req.planchet.denomPub.rsa_public_key),
+        decodeCrock(req.planchet.blindingKey),
+      );
+      return {
+        cipher: DenomKeyType.Rsa,
+        rsa_signature: encodeCrock(denomSig),
+      };
+    } else {
+      throw Error(`unblinding for cipher ${req.evSig.cipher} not implemented`);
+    }
+  }
+
   /**
    * Unblind a blindly signed value.
    */
diff --git a/packages/taler-wallet-core/src/index.ts 
b/packages/taler-wallet-core/src/index.ts
index 179ba6b8..c657290f 100644
--- a/packages/taler-wallet-core/src/index.ts
+++ b/packages/taler-wallet-core/src/index.ts
@@ -36,7 +36,7 @@ export * from "./db-utils.js";
 export { CryptoImplementation } from 
"./crypto/workers/cryptoImplementation.js";
 export type { CryptoWorker } from "./crypto/workers/cryptoWorkerInterface.js";
 export { CryptoWorkerFactory, CryptoApi } from "./crypto/workers/cryptoApi.js";
-export { SynchronousCryptoWorker } from "./crypto/workers/synchronousWorker.js"
+export { SynchronousCryptoWorker } from 
"./crypto/workers/synchronousWorker.js";
 
 export * from "./pending-types.js";
 
@@ -47,3 +47,12 @@ export * from "./wallet.js";
 
 export * from "./operations/backup/index.js";
 export { makeEventId } from "./operations/transactions.js";
+
+export * from "./operations/exchanges.js";
+
+export * from "./bank-api-client.js";
+
+export * from "./operations/reserves.js";
+export * from "./operations/withdraw.js";
+
+export * from "./crypto/workers/synchronousWorkerFactory.js";
diff --git a/packages/taler-wallet-core/src/operations/deposits.ts 
b/packages/taler-wallet-core/src/operations/deposits.ts
index e45da7b4..a5d6c93c 100644
--- a/packages/taler-wallet-core/src/operations/deposits.ts
+++ b/packages/taler-wallet-core/src/operations/deposits.ts
@@ -20,6 +20,7 @@ import {
   buildCodecForObject,
   canonicalJson,
   Codec,
+  codecForDepositSuccess,
   codecForString,
   codecForTimestamp,
   codecOptional,
@@ -32,6 +33,7 @@ import {
   GetFeeForDepositRequest,
   getRandomBytes,
   getTimestampNow,
+  hashWire,
   Logger,
   NotificationType,
   parsePaytoUri,
@@ -57,7 +59,6 @@ import {
   generateDepositPermissions,
   getCandidatePayCoins,
   getTotalPaymentCost,
-  hashWire,
 } from "./pay.js";
 import { getTotalRefreshCost } from "./refresh.js";
 
@@ -66,43 +67,6 @@ import { getTotalRefreshCost } from "./refresh.js";
  */
 const logger = new Logger("deposits.ts");
 
-interface DepositSuccess {
-  // Optional base URL of the exchange for looking up wire transfers
-  // associated with this transaction.  If not given,
-  // the base URL is the same as the one used for this request.
-  // Can be used if the base URL for /transactions/ differs from that
-  // for /coins/, i.e. for load balancing.  Clients SHOULD
-  // respect the transaction_base_url if provided.  Any HTTP server
-  // belonging to an exchange MUST generate a 307 or 308 redirection
-  // to the correct base URL should a client uses the wrong base
-  // URL, or if the base URL has changed since the deposit.
-  transaction_base_url?: string;
-
-  // timestamp when the deposit was received by the exchange.
-  exchange_timestamp: Timestamp;
-
-  // the EdDSA signature of TALER_DepositConfirmationPS using a current
-  // signing key of the exchange affirming the successful
-  // deposit and that the exchange will transfer the funds after the refund
-  // deadline, or as soon as possible if the refund deadline is zero.
-  exchange_sig: string;
-
-  // public EdDSA key of the exchange that was used to
-  // generate the signature.
-  // Should match one of the exchange's signing keys from /keys.  It is given
-  // explicitly as the client might otherwise be confused by clock skew as to
-  // which signing key was used.
-  exchange_pub: string;
-}
-
-const codecForDepositSuccess = (): Codec<DepositSuccess> =>
-  buildCodecForObject<DepositSuccess>()
-    .property("exchange_pub", codecForString())
-    .property("exchange_sig", codecForString())
-    .property("exchange_timestamp", codecForTimestamp)
-    .property("transaction_base_url", codecOptional(codecForString()))
-    .build("DepositSuccess");
-
 async function resetDepositGroupRetry(
   ws: InternalWalletState,
   depositGroupId: string,
@@ -202,7 +166,6 @@ async function processDepositGroupImpl(
     }
     const perm = depositPermissions[i];
     let requestBody: any;
-    logger.info("creating v10 deposit request");
     requestBody = {
       contribution: Amounts.stringify(perm.contribution),
       merchant_payto_uri: depositGroup.wire.payto_uri,
diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts 
b/packages/taler-wallet-core/src/operations/exchanges.ts
index 9d4a56ff..2006b792 100644
--- a/packages/taler-wallet-core/src/operations/exchanges.ts
+++ b/packages/taler-wallet-core/src/operations/exchanges.ts
@@ -43,6 +43,7 @@ import {
   codecForAny,
   DenominationPubKey,
   DenomKeyType,
+  ExchangeKeysJson,
 } from "@gnu-taler/taler-util";
 import { decodeCrock, encodeCrock, hash } from "@gnu-taler/taler-util";
 import { CryptoApi } from "../crypto/workers/cryptoApi.js";
@@ -292,12 +293,37 @@ async function validateWireInfo(
   };
 }
 
+export interface ExchangeInfo {
+  wire: ExchangeWireJson;
+  keys: ExchangeKeysDownloadResult;
+}
+
+export async function downloadExchangeInfo(
+  exchangeBaseUrl: string,
+  http: HttpRequestLibrary,
+): Promise<ExchangeInfo> {
+  const wireInfo = await downloadExchangeWireInfo(
+    exchangeBaseUrl,
+    http,
+    Duration.getForever(),
+  );
+  const keysInfo = await downloadExchangeKeysInfo(
+    exchangeBaseUrl,
+    http,
+    Duration.getForever(),
+  );
+  return {
+    keys: keysInfo,
+    wire: wireInfo,
+  };
+}
+
 /**
  * Fetch wire information for an exchange.
  *
  * @param exchangeBaseUrl Exchange base URL, assumed to be already normalized.
  */
-async function downloadExchangeWithWireInfo(
+async function downloadExchangeWireInfo(
   exchangeBaseUrl: string,
   http: HttpRequestLibrary,
   timeout: Duration,
@@ -374,7 +400,7 @@ interface ExchangeKeysDownloadResult {
 /**
  * Download and validate an exchange's /keys data.
  */
-async function downloadKeysInfo(
+async function downloadExchangeKeysInfo(
   baseUrl: string,
   http: HttpRequestLibrary,
   timeout: Duration,
@@ -526,10 +552,10 @@ async function updateExchangeFromUrlImpl(
 
   const timeout = getExchangeRequestTimeout();
 
-  const keysInfo = await downloadKeysInfo(baseUrl, ws.http, timeout);
+  const keysInfo = await downloadExchangeKeysInfo(baseUrl, ws.http, timeout);
 
   logger.info("updating exchange /wire info");
-  const wireInfoDownload = await downloadExchangeWithWireInfo(
+  const wireInfoDownload = await downloadExchangeWireInfo(
     baseUrl,
     ws.http,
     timeout,
diff --git a/packages/taler-wallet-core/src/operations/pay.ts 
b/packages/taler-wallet-core/src/operations/pay.ts
index 6001cac4..9844dc52 100644
--- a/packages/taler-wallet-core/src/operations/pay.ts
+++ b/packages/taler-wallet-core/src/operations/pay.ts
@@ -112,19 +112,6 @@ import { createRefreshGroup, getTotalRefreshCost } from 
"./refresh.js";
  */
 const logger = new Logger("pay.ts");
 
-/**
- * FIXME: Move this to crypto worker or at least talerCrypto.ts
- */
-export function hashWire(paytoUri: string, salt: string): string {
-  const r = kdf(
-    64,
-    stringToBytes(paytoUri + "\0"),
-    decodeCrock(salt),
-    stringToBytes("merchant-wire-signature"),
-  );
-  return encodeCrock(r);
-}
-
 /**
  * Compute the total cost of a payment to the customer.
  *
diff --git a/packages/taler-wallet-core/src/operations/refresh.ts 
b/packages/taler-wallet-core/src/operations/refresh.ts
index 550119de..cc2a1c56 100644
--- a/packages/taler-wallet-core/src/operations/refresh.ts
+++ b/packages/taler-wallet-core/src/operations/refresh.ts
@@ -17,6 +17,7 @@
 import {
   DenomKeyType,
   encodeCrock,
+  ExchangeMeltRequest,
   ExchangeProtocolVersion,
   ExchangeRefreshRevealRequest,
   getRandomBytes,
@@ -394,17 +395,14 @@ async function refreshMelt(
     `coins/${oldCoin.coinPub}/melt`,
     oldCoin.exchangeBaseUrl,
   );
-  let meltReqBody: any;
-  if (oldDenom.denomPub.cipher === DenomKeyType.Rsa) {
-    meltReqBody = {
-      coin_pub: oldCoin.coinPub,
-      confirm_sig: derived.confirmSig,
-      denom_pub_hash: oldCoin.denomPubHash,
-      denom_sig: oldCoin.denomSig,
-      rc: derived.hash,
-      value_with_fee: Amounts.stringify(derived.meltValueWithFee),
-    };
-  }
+  const meltReqBody: ExchangeMeltRequest = {
+    coin_pub: oldCoin.coinPub,
+    confirm_sig: derived.confirmSig,
+    denom_pub_hash: oldCoin.denomPubHash,
+    denom_sig: oldCoin.denomSig,
+    rc: derived.hash,
+    value_with_fee: Amounts.stringify(derived.meltValueWithFee),
+  };
 
   const resp = await ws.runSequentialized([EXCHANGE_COINS_LOCK], async () => {
     return await ws.http.postJson(reqUrl.href, meltReqBody, {
diff --git a/packages/taler-wallet-core/src/operations/reserves.ts 
b/packages/taler-wallet-core/src/operations/reserves.ts
index a16d3ec3..d91ce89f 100644
--- a/packages/taler-wallet-core/src/operations/reserves.ts
+++ b/packages/taler-wallet-core/src/operations/reserves.ts
@@ -780,7 +780,7 @@ export async function createTalerWithdrawReserve(
   selectedExchange: string,
 ): Promise<AcceptWithdrawalResponse> {
   await updateExchangeFromUrl(ws, selectedExchange);
-  const withdrawInfo = await getBankWithdrawalInfo(ws, talerWithdrawUri);
+  const withdrawInfo = await getBankWithdrawalInfo(ws.http, talerWithdrawUri);
   const exchangePaytoUri = await getExchangePaytoUri(
     ws,
     selectedExchange,
diff --git a/packages/taler-wallet-core/src/operations/testing.ts 
b/packages/taler-wallet-core/src/operations/testing.ts
index d6f0626d..93f48fb8 100644
--- a/packages/taler-wallet-core/src/operations/testing.ts
+++ b/packages/taler-wallet-core/src/operations/testing.ts
@@ -74,7 +74,7 @@ function makeId(length: number): string {
 /**
  * Helper function to generate the "Authorization" HTTP header.
  */
-function makeAuth(username: string, password: string): string {
+function makeBasicAuthHeader(username: string, password: string): string {
   const auth = `${username}:${password}`;
   const authEncoded: string = Buffer.from(auth).toString("base64");
   return `Basic ${authEncoded}`;
@@ -89,7 +89,7 @@ export async function withdrawTestBalance(
   const bankUser = await registerRandomBankUser(ws.http, bankBaseUrl);
   logger.trace(`Registered bank user ${JSON.stringify(bankUser)}`);
 
-  const wresp = await createBankWithdrawalUri(
+  const wresp = await createDemoBankWithdrawalUri(
     ws.http,
     bankBaseUrl,
     bankUser,
@@ -119,7 +119,11 @@ function getMerchantAuthHeader(m: MerchantBackendInfo): 
Record<string, string> {
   return {};
 }
 
-async function createBankWithdrawalUri(
+/**
+ * Use the testing API of a demobank to create a taler://withdraw URI
+ * that the wallet can then use to make a withdrawal.
+ */
+export async function createDemoBankWithdrawalUri(
   http: HttpRequestLibrary,
   bankBaseUrl: string,
   bankUser: BankUser,
@@ -136,7 +140,7 @@ async function createBankWithdrawalUri(
     },
     {
       headers: {
-        Authorization: makeAuth(bankUser.username, bankUser.password),
+        Authorization: makeBasicAuthHeader(bankUser.username, 
bankUser.password),
       },
     },
   );
@@ -159,7 +163,7 @@ async function confirmBankWithdrawalUri(
     {},
     {
       headers: {
-        Authorization: makeAuth(bankUser.username, bankUser.password),
+        Authorization: makeBasicAuthHeader(bankUser.username, 
bankUser.password),
       },
     },
   );
diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts 
b/packages/taler-wallet-core/src/operations/withdraw.ts
index ae3763a0..392cecf0 100644
--- a/packages/taler-wallet-core/src/operations/withdraw.ts
+++ b/packages/taler-wallet-core/src/operations/withdraw.ts
@@ -59,7 +59,10 @@ import {
   WithdrawalGroupRecord,
 } from "../db.js";
 import { walletCoreDebugFlags } from "../util/debugFlags.js";
-import { readSuccessResponseJsonOrThrow } from "../util/http.js";
+import {
+  HttpRequestLibrary,
+  readSuccessResponseJsonOrThrow,
+} from "../util/http.js";
 import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries.js";
 import {
   guardOperationException,
@@ -271,9 +274,11 @@ export function selectWithdrawalDenominations(
 /**
  * Get information about a withdrawal from
  * a taler://withdraw URI by asking the bank.
+ * 
+ * FIXME: Move into bank client.
  */
 export async function getBankWithdrawalInfo(
-  ws: InternalWalletState,
+  http: HttpRequestLibrary,
   talerWithdrawUri: string,
 ): Promise<BankWithdrawDetails> {
   const uriResult = parseWithdrawUri(talerWithdrawUri);
@@ -283,7 +288,7 @@ export async function getBankWithdrawalInfo(
 
   const configReqUrl = new URL("config", uriResult.bankIntegrationApiBaseUrl);
 
-  const configResp = await ws.http.get(configReqUrl.href);
+  const configResp = await http.get(configReqUrl.href);
   const config = await readSuccessResponseJsonOrThrow(
     configResp,
     codecForTalerConfigResponse(),
@@ -309,7 +314,7 @@ export async function getBankWithdrawalInfo(
     `withdrawal-operation/${uriResult.withdrawalOperationId}`,
     uriResult.bankIntegrationApiBaseUrl,
   );
-  const resp = await ws.http.get(reqUrl.href);
+  const resp = await http.get(reqUrl.href);
   const status = await readSuccessResponseJsonOrThrow(
     resp,
     codecForWithdrawOperationStatusResponse(),
@@ -1076,7 +1081,7 @@ export async function getWithdrawalDetailsForUri(
   talerWithdrawUri: string,
 ): Promise<WithdrawUriInfoResponse> {
   logger.trace(`getting withdrawal details for URI ${talerWithdrawUri}`);
-  const info = await getBankWithdrawalInfo(ws, talerWithdrawUri);
+  const info = await getBankWithdrawalInfo(ws.http, talerWithdrawUri);
   logger.trace(`got bank info`);
   if (info.suggestedExchange) {
     // FIXME: right now the exchange gets permanently added,
diff --git a/packages/taler-wallet-core/src/util/http.ts 
b/packages/taler-wallet-core/src/util/http.ts
index 3a7062c9..43fe29bb 100644
--- a/packages/taler-wallet-core/src/util/http.ts
+++ b/packages/taler-wallet-core/src/util/http.ts
@@ -34,6 +34,7 @@ import {
   timestampMax,
   TalerErrorDetails,
   Codec,
+  j2s,
 } from "@gnu-taler/taler-util";
 import { TalerErrorCode } from "@gnu-taler/taler-util";
 
@@ -131,6 +132,11 @@ export async function readTalerErrorResponse(
   const errJson = await httpResponse.json();
   const talerErrorCode = errJson.code;
   if (typeof talerErrorCode !== "number") {
+    logger.warn(
+      `malformed error response (status ${httpResponse.status}): ${j2s(
+        errJson,
+      )}`,
+    );
     throw new OperationFailedError(
       makeErrorDetails(
         TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE,
diff --git 
a/packages/taler-wallet-webextension/src/i18n/taler-wallet-webextension.pot 
b/packages/taler-wallet-webextension/src/i18n/taler-wallet-webextension.pot
new file mode 100644
index 00000000..4fd500d2
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/i18n/taler-wallet-webextension.pot
@@ -0,0 +1,366 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2016-11-23 00:00+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/NavigationBar.tsx:86
+#, c-format
+msgid "Balance"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/NavigationBar.tsx:87
+#, c-format
+msgid "Pending"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/NavigationBar.tsx:88
+#, c-format
+msgid "Backup"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/NavigationBar.tsx:89
+#, c-format
+msgid "Settings"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/NavigationBar.tsx:90
+#, c-format
+msgid "Dev"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx:127
+#, c-format
+msgid "Add provider"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx:137
+#, c-format
+msgid "Sync all backups"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx:139
+#, c-format
+msgid "Sync now"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/popup/BalancePage.tsx:79
+#, c-format
+msgid "You have no balance to show. Need some %1$s getting started?"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx:145
+#, c-format
+msgid "&lt; Back"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx:156
+#, c-format
+msgid "Next"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx:210
+#, c-format
+msgid "&lt; Back"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx:213
+#, c-format
+msgid "Add provider"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:57
+#, c-format
+msgid "Loading..."
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:64
+#, c-format
+msgid "There was an error loading the provider detail for "%1$s""
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:75
+#, c-format
+msgid "There is not known provider with url "%1$s". Redirecting back..."
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:131
+#, c-format
+msgid "Back up"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:142
+#, c-format
+msgid "Extend"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:148
+#, c-format
+msgid ""
+"terms has changed, extending the service will imply accepting the new terms 
of "
+"service"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:158
+#, c-format
+msgid "old"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:162
+#, c-format
+msgid "new"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:169
+#, c-format
+msgid "fee"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:177
+#, c-format
+msgid "storage"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:190
+#, c-format
+msgid "&lt; back"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:194
+#, c-format
+msgid "remove provider"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:213
+#, c-format
+msgid "There is conflict with another backup from %1$s"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:228
+#, c-format
+msgid "Unknown backup problem: %1$s"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:247
+#, c-format
+msgid "service paid"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/popup/Settings.tsx:46
+#, c-format
+msgid "Permissions"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/TermsOfServiceSection.tsx:37
+#, c-format
+msgid "Exchange doesn't have terms of service"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/TermsOfServiceSection.tsx:49
+#, c-format
+msgid "Exchange doesn't have terms of service"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/TermsOfServiceSection.tsx:56
+#, c-format
+msgid "Review exchange terms of service"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/TermsOfServiceSection.tsx:63
+#, c-format
+msgid "Review new version of terms of service"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/TermsOfServiceSection.tsx:75
+#, c-format
+msgid "Show terms of service"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/TermsOfServiceSection.tsx:83
+#, c-format
+msgid "I accept the exchange terms of service"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/TermsOfServiceSection.tsx:127
+#, c-format
+msgid "Hide terms of service"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/TermsOfServiceSection.tsx:136
+#, c-format
+msgid "I accept the exchange terms of service"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ExchangeAddConfirm.tsx:110
+#, c-format
+msgid "Cancel"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ExchangeAddConfirm.tsx:114
+#, c-format
+msgid "Loading terms.."
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ExchangeAddConfirm.tsx:121
+#, c-format
+msgid "Add exchange"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ExchangeAddConfirm.tsx:126
+#, c-format
+msgid "Add exchange"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ExchangeAddConfirm.tsx:131
+#, c-format
+msgid "Add exchange anyway"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ExchangeSetUrl.tsx:133
+#, c-format
+msgid "Cancel"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ExchangeSetUrl.tsx:149
+#, c-format
+msgid "Next"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/BalancePage.tsx:83
+#, c-format
+msgid "You have no balance to show. Need some %1$s getting started?"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx:104
+#, c-format
+msgid "Add exchange"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx:144
+#, c-format
+msgid "Add exchange"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/Settings.tsx:84
+#, c-format
+msgid "Permissions"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/Settings.tsx:95
+#, c-format
+msgid "Known exchanges"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/Transaction.tsx:154
+#, c-format
+msgid "&lt; Back"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/Transaction.tsx:159
+#, c-format
+msgid "retry"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/Transaction.tsx:163
+#, c-format
+msgid "Forget"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/Transaction.tsx:194
+#, c-format
+msgid "Cancel"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/Transaction.tsx:198
+#, c-format
+msgid "Confirm"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Pay.tsx:211
+#, c-format
+msgid "Pay with a mobile phone"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Pay.tsx:211
+#, c-format
+msgid "Hide QR"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Pay.tsx:241
+#, c-format
+msgid "Pay"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Pay.tsx:265
+#, c-format
+msgid "Withdraw digital cash"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Pay.tsx:295
+#, c-format
+msgid "Digital cash payment"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Withdraw.tsx:101
+#, c-format
+msgid "Digital cash withdrawal"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Withdraw.tsx:149
+#, c-format
+msgid "Cancel exchange selection"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Withdraw.tsx:150
+#, c-format
+msgid "Confirm exchange selection"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Withdraw.tsx:155
+#, c-format
+msgid "Switch exchange"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Withdraw.tsx:174
+#, c-format
+msgid "Confirm withdrawal"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Withdraw.tsx:183
+#, c-format
+msgid "Withdraw anyway"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Withdraw.tsx:310
+#, c-format
+msgid "missing withdraw uri"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Deposit.tsx:119
+#, c-format
+msgid "Digital cash payment"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Deposit.tsx:133
+#, c-format
+msgid "Digital cash payment"
+msgstr ""
+
+#: 
/home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Deposit.tsx:186
+#, c-format
+msgid "Digital cash deposit"
+msgstr ""
+
diff --git a/packages/taler-wallet-webextension/src/wxApi.ts 
b/packages/taler-wallet-webextension/src/wxApi.ts
index c306b17a..31b46d88 100644
--- a/packages/taler-wallet-webextension/src/wxApi.ts
+++ b/packages/taler-wallet-webextension/src/wxApi.ts
@@ -38,7 +38,7 @@ import {
   RemoveBackupProviderRequest
 } from "@gnu-taler/taler-wallet-core";
 import { DepositFee } from 
"@gnu-taler/taler-wallet-core/src/operations/deposits";
-import { ExchangeWithdrawDetails } from 
"@gnu-taler/taler-wallet-core/src/operations/withdraw";
+import type { ExchangeWithdrawDetails } from 
"@gnu-taler/taler-wallet-core/src/operations/withdraw";
 import { MessageFromBackend } from "./wxBackend";
 
 /**

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