gnunet-svn
[Top][All Lists]
Advanced

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

[taler-exchange] branch master updated (c72cf2ce -> e389cb41)


From: gnunet
Subject: [taler-exchange] branch master updated (c72cf2ce -> e389cb41)
Date: Mon, 16 Oct 2023 08:49:55 +0200

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

grothoff pushed a change to branch master
in repository exchange.

    from c72cf2ce -address FIXME
     new 917dd4d7 avoid extra transaction to fetch balance if reserve is out of 
funds, remove legacy /withdraw endpoint
     new d06d8596 remove dead do_withdraw code
     new e389cb41 -mark up FIXME better

The 3 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:
 src/exchange/Makefile.am                           |   3 +-
 src/exchange/taler-exchange-httpd.c                |   5 -
 src/exchange/taler-exchange-httpd_age-withdraw.c   |  15 +-
 src/exchange/taler-exchange-httpd_batch-deposit.c  |   4 +-
 src/exchange/taler-exchange-httpd_batch-withdraw.c |   5 +-
 src/exchange/taler-exchange-httpd_reserves_open.c  |   3 +
 src/exchange/taler-exchange-httpd_responses.c      |  19 +-
 src/exchange/taler-exchange-httpd_responses.h      |   2 +
 src/exchange/taler-exchange-httpd_withdraw.c       | 700 ---------------------
 src/exchange/taler-exchange-httpd_withdraw.h       |  47 --
 src/exchangedb/Makefile.am                         |   1 -
 src/exchangedb/exchange_do_age_withdraw.sql        |  10 +-
 src/exchangedb/exchange_do_batch_withdraw.sql      |  24 +-
 src/exchangedb/exchange_do_reserve_open.sql        |  32 +-
 src/exchangedb/exchange_do_withdraw.sql            | 213 -------
 src/exchangedb/perf_deposits_get_ready.c           |  36 +-
 src/exchangedb/pg_do_age_withdraw.c                |   8 +-
 src/exchangedb/pg_do_age_withdraw.h                |   2 +
 src/exchangedb/pg_do_batch_withdraw.c              |   4 +
 src/exchangedb/pg_do_batch_withdraw.h              |   2 +
 src/exchangedb/pg_do_reserve_open.c                |  23 +-
 src/exchangedb/pg_do_reserve_open.h                |   2 +
 src/exchangedb/pg_do_withdraw.c                    |  95 ---
 src/exchangedb/pg_do_withdraw.h                    |  59 --
 src/exchangedb/plugin_exchangedb_postgres.c        |   3 -
 src/exchangedb/procedures.sql.in                   |   1 -
 src/exchangedb/test_exchangedb.c                   |  39 +-
 src/include/taler_exchange_service.h               | 138 +---
 src/include/taler_exchangedb_plugin.h              |  38 +-
 src/lib/Makefile.am                                |   4 +-
 src/lib/exchange_api_age_withdraw.c                |  42 +-
 src/lib/exchange_api_batch_withdraw2.c             |  45 +-
 src/lib/exchange_api_withdraw.c                    | 364 -----------
 src/lib/exchange_api_withdraw2.c                   | 389 ------------
 src/testing/testing_api_cmd_withdraw.c             |  19 +-
 35 files changed, 225 insertions(+), 2171 deletions(-)
 delete mode 100644 src/exchange/taler-exchange-httpd_withdraw.c
 delete mode 100644 src/exchange/taler-exchange-httpd_withdraw.h
 delete mode 100644 src/exchangedb/exchange_do_withdraw.sql
 delete mode 100644 src/exchangedb/pg_do_withdraw.c
 delete mode 100644 src/exchangedb/pg_do_withdraw.h
 delete mode 100644 src/lib/exchange_api_withdraw.c
 delete mode 100644 src/lib/exchange_api_withdraw2.c

diff --git a/src/exchange/Makefile.am b/src/exchange/Makefile.am
index 86829edd..12ea34e1 100644
--- a/src/exchange/Makefile.am
+++ b/src/exchange/Makefile.am
@@ -182,8 +182,7 @@ taler_exchange_httpd_SOURCES = \
   taler-exchange-httpd_reserves_purse.c taler-exchange-httpd_reserves_purse.h \
   taler-exchange-httpd_responses.c taler-exchange-httpd_responses.h \
   taler-exchange-httpd_terms.c taler-exchange-httpd_terms.h \
-  taler-exchange-httpd_transfers_get.c taler-exchange-httpd_transfers_get.h \
-  taler-exchange-httpd_withdraw.c taler-exchange-httpd_withdraw.h
+  taler-exchange-httpd_transfers_get.c taler-exchange-httpd_transfers_get.h 
 
 taler_exchange_httpd_LDADD = \
   $(LIBGCRYPT_LIBS) \
diff --git a/src/exchange/taler-exchange-httpd.c 
b/src/exchange/taler-exchange-httpd.c
index 5bc118e3..46d5833a 100644
--- a/src/exchange/taler-exchange-httpd.c
+++ b/src/exchange/taler-exchange-httpd.c
@@ -69,7 +69,6 @@
 #include "taler-exchange-httpd_reserves_purse.h"
 #include "taler-exchange-httpd_terms.h"
 #include "taler-exchange-httpd_transfers_get.h"
-#include "taler-exchange-httpd_withdraw.h"
 #include "taler_exchangedb_lib.h"
 #include "taler_exchangedb_plugin.h"
 #include "taler_extensions.h"
@@ -746,10 +745,6 @@ handle_post_reserves (struct TEH_RequestContext *rc,
       .op = "age-withdraw",
       .handler = &TEH_handler_age_withdraw
     },
-    {
-      .op = "withdraw",
-      .handler = &TEH_handler_withdraw
-    },
     {
       .op = "purse",
       .handler = &TEH_handler_reserves_purse
diff --git a/src/exchange/taler-exchange-httpd_age-withdraw.c 
b/src/exchange/taler-exchange-httpd_age-withdraw.c
index 47cff626..4a7d6b1a 100644
--- a/src/exchange/taler-exchange-httpd_age-withdraw.c
+++ b/src/exchange/taler-exchange-httpd_age-withdraw.c
@@ -399,8 +399,8 @@ denomination_is_valid (
  * @param[out] denom_serials On success, will be filled with the serial-id's 
of the denomination keys.  Caller must deallocate.
  * @param[out] amount_with_fee On success, will contain the committed amount 
including fees
  * @param[out] result In the error cases, a response will be queued with MHD 
and this will be the result.
- * @return GNUNET_OK if the denominations are valid and support age-restriction
- *   GNUNET_SYSERR otherwise
+ * @return #GNUNET_OK if the denominations are valid and support 
age-restriction
+ *   #GNUNET_SYSERR otherwise
  */
 static enum GNUNET_GenericReturnValue
 are_denominations_valid (
@@ -737,12 +737,14 @@ age_withdraw_transaction (void *cls,
     bool conflict = false;
     uint16_t allowed_maximum_age = 0;
     uint32_t reserve_birthday = 0;
+    struct TALER_Amount reserve_balance;
 
     qs = TEH_plugin->do_age_withdraw (TEH_plugin->cls,
                                       &awc->commitment,
                                       awc->now,
                                       &found,
                                       &balance_ok,
+                                      &reserve_balance,
                                       &age_ok,
                                       &allowed_maximum_age,
                                       &reserve_birthday,
@@ -755,14 +757,14 @@ age_withdraw_transaction (void *cls,
                                             "do_age_withdraw");
       return qs;
     }
-    else if (! found)
+    if (! found)
     {
       *mhd_ret = TALER_MHD_reply_with_ec (connection,
                                           
TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
                                           NULL);
       return GNUNET_DB_STATUS_HARD_ERROR;
     }
-    else if (! age_ok)
+    if (! age_ok)
     {
       enum TALER_ErrorCode ec =
         TALER_EC_EXCHANGE_AGE_WITHDRAW_MAXIMUM_AGE_TOO_LARGE;
@@ -779,19 +781,20 @@ age_withdraw_transaction (void *cls,
 
       return GNUNET_DB_STATUS_HARD_ERROR;
     }
-    else if (! balance_ok)
+    if (! balance_ok)
     {
       TEH_plugin->rollback (TEH_plugin->cls);
 
       *mhd_ret = TEH_RESPONSE_reply_reserve_insufficient_balance (
         connection,
         TALER_EC_EXCHANGE_AGE_WITHDRAW_INSUFFICIENT_FUNDS,
+        &reserve_balance,
         &awc->commitment.amount_with_fee,
         &awc->commitment.reserve_pub);
 
       return GNUNET_DB_STATUS_HARD_ERROR;
     }
-    else if (conflict)
+    if (conflict)
     {
       /* do_age_withdraw signaled a conflict, so there MUST be an entry
        * in the DB.  Put that into the response */
diff --git a/src/exchange/taler-exchange-httpd_batch-deposit.c 
b/src/exchange/taler-exchange-httpd_batch-deposit.c
index bc8b20de..b74150b8 100644
--- a/src/exchange/taler-exchange-httpd_batch-deposit.c
+++ b/src/exchange/taler-exchange-httpd_batch-deposit.c
@@ -240,8 +240,8 @@ batch_deposit_transaction (void *cls,
       = TEH_RESPONSE_reply_coin_insufficient_funds (
           connection,
           TALER_EC_EXCHANGE_DEPOSIT_CONFLICTING_CONTRACT,
-          &bd->cdis[0 /* SEE FIXME above! */].coin.denom_pub_hash,
-          &bd->cdis[0 /* SEE FIXME above! */].coin.coin_pub);
+          &bd->cdis[0 /* SEE FIXME-#7267 Oec above! */].coin.denom_pub_hash,
+          &bd->cdis[0 /* SEE FIXME-#7267 Oec above! */].coin.coin_pub);
     return GNUNET_DB_STATUS_HARD_ERROR;
   }
   if (! balance_ok)
diff --git a/src/exchange/taler-exchange-httpd_batch-withdraw.c 
b/src/exchange/taler-exchange-httpd_batch-withdraw.c
index 38a7f43c..fe210876 100644
--- a/src/exchange/taler-exchange-httpd_batch-withdraw.c
+++ b/src/exchange/taler-exchange-httpd_batch-withdraw.c
@@ -312,6 +312,7 @@ batch_withdraw_transaction (void *cls,
   bool balance_ok = false;
   bool age_ok = false;
   uint16_t allowed_maximum_age = 0;
+  struct TALER_Amount reserve_balance;
   char *kyc_required;
   struct TALER_PaytoHashP reserve_h_payto;
 
@@ -476,6 +477,7 @@ batch_withdraw_transaction (void *cls,
                                       TEH_age_restriction_enabled,
                                       &found,
                                       &balance_ok,
+                                      &reserve_balance,
                                       &age_ok,
                                       &allowed_maximum_age,
                                       &ruuid);
@@ -521,6 +523,7 @@ batch_withdraw_transaction (void *cls,
     *mhd_ret = TEH_RESPONSE_reply_reserve_insufficient_balance (
       connection,
       TALER_EC_EXCHANGE_WITHDRAW_INSUFFICIENT_FUNDS,
+      &reserve_balance,
       &wc->batch_total,
       wc->reserve_pub);
     return GNUNET_DB_STATUS_HARD_ERROR;
@@ -553,7 +556,7 @@ batch_withdraw_transaction (void *cls,
         *mhd_ret = TALER_MHD_reply_with_error (connection,
                                                MHD_HTTP_INTERNAL_SERVER_ERROR,
                                                
TALER_EC_GENERIC_DB_FETCH_FAILED,
-                                               "do_withdraw");
+                                               "do_batch_withdraw_insert");
       return qs;
     }
     if (denom_unknown)
diff --git a/src/exchange/taler-exchange-httpd_reserves_open.c 
b/src/exchange/taler-exchange-httpd_reserves_open.c
index 50487990..5aadc9e4 100644
--- a/src/exchange/taler-exchange-httpd_reserves_open.c
+++ b/src/exchange/taler-exchange-httpd_reserves_open.c
@@ -188,6 +188,7 @@ reserve_open_transaction (void *cls,
 {
   struct ReserveOpenContext *rsc = cls;
   enum GNUNET_DB_QueryStatus qs;
+  struct TALER_Amount reserve_balance;
 
   for (unsigned int i = 0; i<rsc->payments_len; i++)
   {
@@ -258,6 +259,7 @@ reserve_open_transaction (void *cls,
                                     &rsc->gf->fees.account,
                                     /* outputs */
                                     &rsc->no_funds,
+                                    &reserve_balance,
                                     &rsc->open_cost,
                                     &rsc->reserve_expiration);
   switch (qs)
@@ -289,6 +291,7 @@ reserve_open_transaction (void *cls,
       = TEH_RESPONSE_reply_reserve_insufficient_balance (
           connection,
           TALER_EC_EXCHANGE_RESERVES_OPEN_INSUFFICIENT_FUNDS,
+          &reserve_balance,
           &rsc->reserve_payment,
           rsc->reserve_pub);
     return GNUNET_DB_STATUS_HARD_ERROR;
diff --git a/src/exchange/taler-exchange-httpd_responses.c 
b/src/exchange/taler-exchange-httpd_responses.c
index 2d5d8dce..322da387 100644
--- a/src/exchange/taler-exchange-httpd_responses.c
+++ b/src/exchange/taler-exchange-httpd_responses.c
@@ -181,31 +181,16 @@ MHD_RESULT
 TEH_RESPONSE_reply_reserve_insufficient_balance (
   struct MHD_Connection *connection,
   enum TALER_ErrorCode ec,
+  const struct TALER_Amount *reserve_balance,
   const struct TALER_Amount *balance_required,
   const struct TALER_ReservePublicKeyP *reserve_pub)
 {
-  struct TALER_Amount balance;
-  enum GNUNET_DB_QueryStatus qs;
-
-  // FIXME: pass balance as argument to this function,
-  // instead of getting it in another transaction!
-  qs = TEH_plugin->get_reserve_balance (TEH_plugin->cls,
-                                        reserve_pub,
-                                        &balance);
-  if (qs < 0)
-  {
-    GNUNET_break (0);
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                       TALER_EC_GENERIC_DB_FETCH_FAILED,
-                                       "reserve balance");
-  }
   return TALER_MHD_REPLY_JSON_PACK (
     connection,
     MHD_HTTP_CONFLICT,
     TALER_JSON_pack_ec (ec),
     TALER_JSON_pack_amount ("balance",
-                            &balance),
+                            reserve_balance),
     TALER_JSON_pack_amount ("requested_amount",
                             balance_required));
 }
diff --git a/src/exchange/taler-exchange-httpd_responses.h 
b/src/exchange/taler-exchange-httpd_responses.h
index 877e8878..8adf1136 100644
--- a/src/exchange/taler-exchange-httpd_responses.h
+++ b/src/exchange/taler-exchange-httpd_responses.h
@@ -53,6 +53,7 @@ TEH_RESPONSE_reply_unknown_denom_pub_hash (
  *
  * @param connection connection to the client
  * @param ec specific error code to return with the reserve history
+ * @param reserve_balance balance remaining in the reserve
  * @param balance_required the balance required for the operation
  * @param reserve_pub the reserve with insufficient balance
  * @return MHD result code
@@ -61,6 +62,7 @@ MHD_RESULT
 TEH_RESPONSE_reply_reserve_insufficient_balance (
   struct MHD_Connection *connection,
   enum TALER_ErrorCode ec,
+  const struct TALER_Amount *reserve_balance,
   const struct TALER_Amount *balance_required,
   const struct TALER_ReservePublicKeyP *reserve_pub);
 
diff --git a/src/exchange/taler-exchange-httpd_withdraw.c 
b/src/exchange/taler-exchange-httpd_withdraw.c
deleted file mode 100644
index 07fcc846..00000000
--- a/src/exchange/taler-exchange-httpd_withdraw.c
+++ /dev/null
@@ -1,700 +0,0 @@
-/*
-  This file is part of TALER
-  Copyright (C) 2014-2023 Taler Systems SA
-
-  TALER is free software; you can redistribute it and/or modify
-  it under the terms of the GNU Affero General Public License as
-  published by the Free Software Foundation; either version 3,
-  or (at your option) any later version.
-
-  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 Affero General Public License for more details.
-
-  You should have received a copy of the GNU Affero General
-  Public License along with TALER; see the file COPYING.  If not,
-  see <http://www.gnu.org/licenses/>
-*/
-/**
- * @file taler-exchange-httpd_withdraw.c
- * @brief Handle /reserves/$RESERVE_PUB/withdraw requests
- * @author Florian Dold
- * @author Benedikt Mueller
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <jansson.h>
-#include "taler-exchange-httpd.h"
-#include "taler_json_lib.h"
-#include "taler_kyclogic_lib.h"
-#include "taler_mhd_lib.h"
-#include "taler-exchange-httpd_withdraw.h"
-#include "taler-exchange-httpd_responses.h"
-#include "taler-exchange-httpd_keys.h"
-
-
-/**
- * Context for #withdraw_transaction.
- */
-struct WithdrawContext
-{
-
-  /**
-   * Hash of the (blinded) message to be signed by the Exchange.
-   */
-  struct TALER_BlindedCoinHashP h_coin_envelope;
-
-  /**
-   * Blinded planchet.
-   */
-  struct TALER_BlindedPlanchet blinded_planchet;
-
-  /**
-   * Set to the resulting signed coin data to be returned to the client.
-   */
-  struct TALER_EXCHANGEDB_CollectableBlindcoin collectable;
-
-  /**
-   * KYC status for the operation.
-   */
-  struct TALER_EXCHANGEDB_KycStatus kyc;
-
-  /**
-   * Hash of the payto-URI representing the account
-   * from which the money was put into the reserve.
-   */
-  struct TALER_PaytoHashP h_account_payto;
-
-  /**
-   * Current time for the DB transaction.
-   */
-  struct GNUNET_TIME_Timestamp now;
-
-  /**
-   * AML decision, #TALER_AML_NORMAL if we may proceed.
-   */
-  enum TALER_AmlDecisionState aml_decision;
-
-};
-
-
-/**
- * Function called to iterate over KYC-relevant
- * transaction amounts for a particular time range.
- * Called within a database transaction, so must
- * not start a new one.
- *
- * @param cls closure, identifies the event type and
- *        account to iterate over events for
- * @param limit maximum time-range for which events
- *        should be fetched (timestamp in the past)
- * @param cb function to call on each event found,
- *        events must be returned in reverse chronological
- *        order
- * @param cb_cls closure for @a cb
- */
-static void
-withdraw_amount_cb (void *cls,
-                    struct GNUNET_TIME_Absolute limit,
-                    TALER_EXCHANGEDB_KycAmountCallback cb,
-                    void *cb_cls)
-{
-  struct WithdrawContext *wc = cls;
-  enum GNUNET_DB_QueryStatus qs;
-
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "Signaling amount %s for KYC check\n",
-              TALER_amount2s (&wc->collectable.amount_with_fee));
-  if (GNUNET_OK !=
-      cb (cb_cls,
-          &wc->collectable.amount_with_fee,
-          wc->now.abs_time))
-    return;
-  qs = TEH_plugin->select_withdraw_amounts_for_kyc_check (
-    TEH_plugin->cls,
-    &wc->h_account_payto,
-    limit,
-    cb,
-    cb_cls);
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "Got %d additional transactions for this withdrawal and limit 
%llu\n",
-              qs,
-              (unsigned long long) limit.abs_value_us);
-  GNUNET_break (qs >= 0);
-}
-
-
-/**
- * Function called on each @a amount that was found to
- * be relevant for the AML check as it was merged into
- * the reserve.
- *
- * @param cls `struct TALER_Amount *` to total up the amounts
- * @param amount encountered transaction amount
- * @param date when was the amount encountered
- * @return #GNUNET_OK to continue to iterate,
- *         #GNUNET_NO to abort iteration
- *         #GNUNET_SYSERR on internal error (also abort itaration)
- */
-static enum GNUNET_GenericReturnValue
-aml_amount_cb (
-  void *cls,
-  const struct TALER_Amount *amount,
-  struct GNUNET_TIME_Absolute date)
-{
-  struct TALER_Amount *total = cls;
-
-  GNUNET_assert (0 <=
-                 TALER_amount_add (total,
-                                   total,
-                                   amount));
-  return GNUNET_OK;
-}
-
-
-/**
- * Function implementing withdraw transaction.  Runs the
- * transaction logic; IF it returns a non-error code, the transaction
- * logic MUST NOT queue a MHD response.  IF it returns an hard error,
- * the transaction logic MUST queue a MHD response and set @a mhd_ret.
- * IF it returns the soft error code, the function MAY be called again
- * to retry and MUST not queue a MHD response.
- *
- * Note that "wc->collectable.sig" is set before entering this function as we
- * signed before entering the transaction.
- *
- * @param cls a `struct WithdrawContext *`
- * @param connection MHD request which triggered the transaction
- * @param[out] mhd_ret set to MHD response status for @a connection,
- *             if transaction failed (!)
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-withdraw_transaction (void *cls,
-                      struct MHD_Connection *connection,
-                      MHD_RESULT *mhd_ret)
-{
-  struct WithdrawContext *wc = cls;
-  enum GNUNET_DB_QueryStatus qs;
-  bool found = false;
-  bool balance_ok = false;
-  bool nonce_ok = false;
-  bool age_ok = false;
-  uint16_t allowed_maximum_age = 0;
-  uint64_t ruuid;
-  const struct TALER_CsNonce *nonce;
-  const struct TALER_BlindedPlanchet *bp;
-  struct TALER_PaytoHashP reserve_h_payto;
-
-  wc->now = GNUNET_TIME_timestamp_get ();
-  /* Do AML check: compute total merged amount and check
-     against applicable AML threshold */
-  {
-    char *reserve_payto;
-
-    reserve_payto = TALER_reserve_make_payto (TEH_base_url,
-                                              &wc->collectable.reserve_pub);
-    TALER_payto_hash (reserve_payto,
-                      &reserve_h_payto);
-    GNUNET_free (reserve_payto);
-  }
-  {
-    struct TALER_Amount merge_amount;
-    struct TALER_Amount threshold;
-    struct GNUNET_TIME_Absolute now_minus_one_month;
-
-    now_minus_one_month
-      = GNUNET_TIME_absolute_subtract (wc->now.abs_time,
-                                       GNUNET_TIME_UNIT_MONTHS);
-    GNUNET_assert (GNUNET_OK ==
-                   TALER_amount_set_zero (TEH_currency,
-                                          &merge_amount));
-    qs = TEH_plugin->select_merge_amounts_for_kyc_check (TEH_plugin->cls,
-                                                         &reserve_h_payto,
-                                                         now_minus_one_month,
-                                                         &aml_amount_cb,
-                                                         &merge_amount);
-    if (qs < 0)
-    {
-      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
-      if (GNUNET_DB_STATUS_HARD_ERROR == qs)
-        *mhd_ret = TALER_MHD_reply_with_error (connection,
-                                               MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                               
TALER_EC_GENERIC_DB_FETCH_FAILED,
-                                               
"select_merge_amounts_for_kyc_check");
-      return qs;
-    }
-    qs = TEH_plugin->select_aml_threshold (TEH_plugin->cls,
-                                           &reserve_h_payto,
-                                           &wc->aml_decision,
-                                           &wc->kyc,
-                                           &threshold);
-    if (qs < 0)
-    {
-      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
-      if (GNUNET_DB_STATUS_HARD_ERROR == qs)
-        *mhd_ret = TALER_MHD_reply_with_error (connection,
-                                               MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                               
TALER_EC_GENERIC_DB_FETCH_FAILED,
-                                               "select_aml_threshold");
-      return qs;
-    }
-    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
-    {
-      threshold = TEH_aml_threshold; /* use default */
-      wc->aml_decision = TALER_AML_NORMAL;
-    }
-
-    switch (wc->aml_decision)
-    {
-    case TALER_AML_NORMAL:
-      if (0 >= TALER_amount_cmp (&merge_amount,
-                                 &threshold))
-      {
-        /* merge_amount <= threshold, continue withdraw below */
-        break;
-      }
-      wc->aml_decision = TALER_AML_PENDING;
-      qs = TEH_plugin->trigger_aml_process (TEH_plugin->cls,
-                                            &reserve_h_payto,
-                                            &merge_amount);
-      if (qs <= 0)
-      {
-        GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
-        if (GNUNET_DB_STATUS_HARD_ERROR == qs)
-          *mhd_ret = TALER_MHD_reply_with_error (connection,
-                                                 
MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                                 
TALER_EC_GENERIC_DB_STORE_FAILED,
-                                                 "trigger_aml_process");
-        return qs;
-      }
-      return qs;
-    case TALER_AML_PENDING:
-      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                  "AML already pending, doing nothing\n");
-      return qs;
-    case TALER_AML_FROZEN:
-      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                  "Account frozen, doing nothing\n");
-      return qs;
-    }
-  }
-
-  /* Check if the money came from a wire transfer */
-  qs = TEH_plugin->reserves_get_origin (TEH_plugin->cls,
-                                        &wc->collectable.reserve_pub,
-                                        &wc->h_account_payto);
-  if (qs < 0)
-    return qs;
-  /* If no results, reserve was created by merge, in which case no KYC check
-     is required as the merge already did that. */
-  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
-  {
-    char *kyc_required;
-
-    qs = TALER_KYCLOGIC_kyc_test_required (
-      TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW,
-      &wc->h_account_payto,
-      TEH_plugin->select_satisfied_kyc_processes,
-      TEH_plugin->cls,
-      &withdraw_amount_cb,
-      wc,
-      &kyc_required);
-    if (qs < 0)
-    {
-      if (GNUNET_DB_STATUS_HARD_ERROR == qs)
-      {
-        GNUNET_break (0);
-        *mhd_ret = TALER_MHD_reply_with_error (connection,
-                                               MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                               
TALER_EC_GENERIC_DB_FETCH_FAILED,
-                                               "kyc_test_required");
-      }
-      return qs;
-    }
-    if (NULL != kyc_required)
-    {
-      /* insert KYC requirement into DB! */
-      wc->kyc.ok = false;
-      qs = TEH_plugin->insert_kyc_requirement_for_account (
-        TEH_plugin->cls,
-        kyc_required,
-        &wc->h_account_payto,
-        &wc->collectable.reserve_pub,
-        &wc->kyc.requirement_row);
-      GNUNET_free (kyc_required);
-      if (GNUNET_DB_STATUS_HARD_ERROR == qs)
-      {
-        GNUNET_break (0);
-        *mhd_ret = TALER_MHD_reply_with_error (connection,
-                                               MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                               
TALER_EC_GENERIC_DB_STORE_FAILED,
-                                               
"insert_kyc_requirement_for_account");
-      }
-      return qs;
-    }
-  }
-  wc->kyc.ok = true;
-  bp = &wc->blinded_planchet;
-  nonce = (TALER_DENOMINATION_CS == bp->cipher)
-    ? &bp->details.cs_blinded_planchet.nonce
-    : NULL;
-  qs = TEH_plugin->do_withdraw (TEH_plugin->cls,
-                                nonce,
-                                &wc->collectable,
-                                wc->now,
-                                TEH_age_restriction_enabled,
-                                &found,
-                                &balance_ok,
-                                &nonce_ok,
-                                &age_ok,
-                                &allowed_maximum_age,
-                                &ruuid);
-  if (0 > qs)
-  {
-    if (GNUNET_DB_STATUS_HARD_ERROR == qs)
-    {
-      GNUNET_break (0);
-      *mhd_ret = TALER_MHD_reply_with_error (connection,
-                                             MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                             TALER_EC_GENERIC_DB_FETCH_FAILED,
-                                             "do_withdraw");
-    }
-    return qs;
-  }
-  if (! found)
-  {
-    *mhd_ret = TALER_MHD_reply_with_error (connection,
-                                           MHD_HTTP_NOT_FOUND,
-                                           
TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
-                                           NULL);
-    return GNUNET_DB_STATUS_HARD_ERROR;
-  }
-  if (! age_ok)
-  {
-    /* We respond with the lowest age in the corresponding age group
-     * of the required age */
-    uint16_t lowest_age = TALER_get_lowest_age (
-      &TEH_age_restriction_config.mask,
-      allowed_maximum_age);
-
-    TEH_plugin->rollback (TEH_plugin->cls);
-    *mhd_ret = TEH_RESPONSE_reply_reserve_age_restriction_required (
-      connection,
-      lowest_age);
-    return GNUNET_DB_STATUS_HARD_ERROR;
-  }
-  if (! balance_ok)
-  {
-    TEH_plugin->rollback (TEH_plugin->cls);
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Balance insufficient for /withdraw\n");
-    *mhd_ret = TEH_RESPONSE_reply_reserve_insufficient_balance (
-      connection,
-      TALER_EC_EXCHANGE_WITHDRAW_INSUFFICIENT_FUNDS,
-      &wc->collectable.amount_with_fee,
-      &wc->collectable.reserve_pub);
-    return GNUNET_DB_STATUS_HARD_ERROR;
-  }
-  if (! nonce_ok)
-  {
-    TEH_plugin->rollback (TEH_plugin->cls);
-    *mhd_ret = TALER_MHD_reply_with_error (connection,
-                                           MHD_HTTP_CONFLICT,
-                                           
TALER_EC_EXCHANGE_WITHDRAW_NONCE_REUSE,
-                                           NULL);
-    return GNUNET_DB_STATUS_HARD_ERROR;
-  }
-  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
-    TEH_METRICS_num_success[TEH_MT_SUCCESS_WITHDRAW]++;
-  return qs;
-}
-
-
-/**
- * Check if the @a rc is replayed and we already have an
- * answer. If so, replay the existing answer and return the
- * HTTP response.
- *
- * @param rc request context
- * @param[in,out] wc parsed request data
- * @param[out] mret HTTP status, set if we return true
- * @return true if the request is idempotent with an existing request
- *    false if we did not find the request in the DB and did not set @a mret
- */
-static bool
-check_request_idempotent (struct TEH_RequestContext *rc,
-                          struct WithdrawContext *wc,
-                          MHD_RESULT *mret)
-{
-  enum GNUNET_DB_QueryStatus qs;
-
-  qs = TEH_plugin->get_withdraw_info (TEH_plugin->cls,
-                                      &wc->h_coin_envelope,
-                                      &wc->collectable);
-  if (0 > qs)
-  {
-    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
-    if (GNUNET_DB_STATUS_HARD_ERROR == qs)
-      *mret = TALER_MHD_reply_with_error (rc->connection,
-                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                          TALER_EC_GENERIC_DB_FETCH_FAILED,
-                                          "get_withdraw_info");
-    return true; /* well, kind-of */
-  }
-  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
-    return false;
-  /* generate idempotent reply */
-  TEH_METRICS_num_requests[TEH_MT_REQUEST_IDEMPOTENT_WITHDRAW]++;
-  *mret = TALER_MHD_REPLY_JSON_PACK (
-    rc->connection,
-    MHD_HTTP_OK,
-    TALER_JSON_pack_blinded_denom_sig ("ev_sig",
-                                       &wc->collectable.sig));
-  TALER_blinded_denom_sig_free (&wc->collectable.sig);
-  return true;
-}
-
-
-MHD_RESULT
-TEH_handler_withdraw (struct TEH_RequestContext *rc,
-                      const struct TALER_ReservePublicKeyP *reserve_pub,
-                      const json_t *root)
-{
-  struct WithdrawContext wc;
-  struct GNUNET_JSON_Specification spec[] = {
-    GNUNET_JSON_spec_fixed_auto ("reserve_sig",
-                                 &wc.collectable.reserve_sig),
-    GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
-                                 &wc.collectable.denom_pub_hash),
-    TALER_JSON_spec_blinded_planchet ("coin_ev",
-                                      &wc.blinded_planchet),
-    GNUNET_JSON_spec_end ()
-  };
-  enum TALER_ErrorCode ec;
-  struct TEH_DenominationKey *dk;
-
-  memset (&wc,
-          0,
-          sizeof (wc));
-  wc.collectable.reserve_pub = *reserve_pub;
-  {
-    enum GNUNET_GenericReturnValue res;
-
-    res = TALER_MHD_parse_json_data (rc->connection,
-                                     root,
-                                     spec);
-    if (GNUNET_OK != res)
-      return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
-  }
-  {
-    MHD_RESULT mret;
-    struct TEH_KeyStateHandle *ksh;
-
-    ksh = TEH_keys_get_state ();
-    if (NULL == ksh)
-    {
-      if (! check_request_idempotent (rc,
-                                      &wc,
-                                      &mret))
-      {
-        GNUNET_JSON_parse_free (spec);
-        return TALER_MHD_reply_with_error (rc->connection,
-                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                           
TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
-                                           NULL);
-      }
-      GNUNET_JSON_parse_free (spec);
-      return mret;
-    }
-
-    dk = TEH_keys_denomination_by_hash_from_state (
-      ksh,
-      &wc.collectable.denom_pub_hash,
-      NULL,
-      NULL);
-
-    if (NULL == dk)
-    {
-      if (! check_request_idempotent (rc,
-                                      &wc,
-                                      &mret))
-      {
-        GNUNET_JSON_parse_free (spec);
-        return TEH_RESPONSE_reply_unknown_denom_pub_hash (
-          rc->connection,
-          &wc.collectable.denom_pub_hash);
-      }
-      GNUNET_JSON_parse_free (spec);
-      return mret;
-    }
-    if (GNUNET_TIME_absolute_is_past (dk->meta.expire_withdraw.abs_time))
-    {
-      /* This denomination is past the expiration time for withdraws */
-      if (! check_request_idempotent (rc,
-                                      &wc,
-                                      &mret))
-      {
-        GNUNET_JSON_parse_free (spec);
-        return TEH_RESPONSE_reply_expired_denom_pub_hash (
-          rc->connection,
-          &wc.collectable.denom_pub_hash,
-          TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
-          "WITHDRAW");
-      }
-      GNUNET_JSON_parse_free (spec);
-      return mret;
-    }
-    if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
-    {
-      /* This denomination is not yet valid, no need to check
-         for idempotency! */
-      GNUNET_JSON_parse_free (spec);
-      return TEH_RESPONSE_reply_expired_denom_pub_hash (
-        rc->connection,
-        &wc.collectable.denom_pub_hash,
-        TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
-        "WITHDRAW");
-    }
-    if (dk->recoup_possible)
-    {
-      /* This denomination has been revoked */
-      if (! check_request_idempotent (rc,
-                                      &wc,
-                                      &mret))
-      {
-        GNUNET_JSON_parse_free (spec);
-        return TEH_RESPONSE_reply_expired_denom_pub_hash (
-          rc->connection,
-          &wc.collectable.denom_pub_hash,
-          TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
-          "WITHDRAW");
-      }
-      GNUNET_JSON_parse_free (spec);
-      return mret;
-    }
-    if (dk->denom_pub.cipher != wc.blinded_planchet.cipher)
-    {
-      /* denomination cipher and blinded planchet cipher not the same */
-      GNUNET_JSON_parse_free (spec);
-      return TALER_MHD_reply_with_error (rc->connection,
-                                         MHD_HTTP_BAD_REQUEST,
-                                         
TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
-                                         NULL);
-    }
-  }
-
-  if (0 >
-      TALER_amount_add (&wc.collectable.amount_with_fee,
-                        &dk->meta.value,
-                        &dk->meta.fees.withdraw))
-  {
-    GNUNET_JSON_parse_free (spec);
-    return TALER_MHD_reply_with_error (rc->connection,
-                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                       
TALER_EC_EXCHANGE_WITHDRAW_AMOUNT_FEE_OVERFLOW,
-                                       NULL);
-  }
-
-  if (GNUNET_OK !=
-      TALER_coin_ev_hash (&wc.blinded_planchet,
-                          &wc.collectable.denom_pub_hash,
-                          &wc.collectable.h_coin_envelope))
-  {
-    GNUNET_break (0);
-    GNUNET_JSON_parse_free (spec);
-    return TALER_MHD_reply_with_error (rc->connection,
-                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                       
TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
-                                       NULL);
-  }
-
-  TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
-  if (GNUNET_OK !=
-      TALER_wallet_withdraw_verify (&wc.collectable.denom_pub_hash,
-                                    &wc.collectable.amount_with_fee,
-                                    &wc.collectable.h_coin_envelope,
-                                    &wc.collectable.reserve_pub,
-                                    &wc.collectable.reserve_sig))
-  {
-    GNUNET_break_op (0);
-    GNUNET_JSON_parse_free (spec);
-    return TALER_MHD_reply_with_error (rc->connection,
-                                       MHD_HTTP_FORBIDDEN,
-                                       
TALER_EC_EXCHANGE_WITHDRAW_RESERVE_SIGNATURE_INVALID,
-                                       NULL);
-  }
-
-  {
-    struct TEH_CoinSignData csd = {
-      .h_denom_pub = &wc.collectable.denom_pub_hash,
-      .bp = &wc.blinded_planchet
-    };
-
-    /* Sign before transaction! */
-    ec = TEH_keys_denomination_sign (
-      &csd,
-      false,
-      &wc.collectable.sig);
-  }
-  if (TALER_EC_NONE != ec)
-  {
-    GNUNET_break (0);
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Failed to sign coin: %d\n",
-                ec);
-    GNUNET_JSON_parse_free (spec);
-    return TALER_MHD_reply_with_ec (rc->connection,
-                                    ec,
-                                    NULL);
-  }
-
-  /* run transaction */
-  {
-    MHD_RESULT mhd_ret;
-
-    if (GNUNET_OK !=
-        TEH_DB_run_transaction (rc->connection,
-                                "run withdraw",
-                                TEH_MT_REQUEST_WITHDRAW,
-                                &mhd_ret,
-                                &withdraw_transaction,
-                                &wc))
-    {
-      /* Even if #withdraw_transaction() failed, it may have created a 
signature
-         (or we might have done it optimistically above). */
-      TALER_blinded_denom_sig_free (&wc.collectable.sig);
-      GNUNET_JSON_parse_free (spec);
-      return mhd_ret;
-    }
-  }
-
-  /* Clean up and send back final response */
-  GNUNET_JSON_parse_free (spec);
-
-  if (! wc.kyc.ok)
-    return TEH_RESPONSE_reply_kyc_required (rc->connection,
-                                            &wc.h_account_payto,
-                                            &wc.kyc);
-
-  if (TALER_AML_NORMAL != wc.aml_decision)
-    return TEH_RESPONSE_reply_aml_blocked (rc->connection,
-                                           wc.aml_decision);
-
-  {
-    MHD_RESULT ret;
-
-    ret = TALER_MHD_REPLY_JSON_PACK (
-      rc->connection,
-      MHD_HTTP_OK,
-      TALER_JSON_pack_blinded_denom_sig ("ev_sig",
-                                         &wc.collectable.sig));
-    TALER_blinded_denom_sig_free (&wc.collectable.sig);
-    return ret;
-  }
-}
-
-
-/* end of taler-exchange-httpd_withdraw.c */
diff --git a/src/exchange/taler-exchange-httpd_withdraw.h 
b/src/exchange/taler-exchange-httpd_withdraw.h
deleted file mode 100644
index 2ec76bb9..00000000
--- a/src/exchange/taler-exchange-httpd_withdraw.h
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
-  This file is part of TALER
-  Copyright (C) 2014-2021 Taler Systems SA
-
-  TALER is free software; you can redistribute it and/or modify it under the
-  terms of the GNU Affero General Public License as published by the Free 
Software
-  Foundation; either version 3, or (at your option) any later version.
-
-  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 Affero General Public License for more 
details.
-
-  You should have received a copy of the GNU Affero General Public License 
along with
-  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
-*/
-/**
- * @file taler-exchange-httpd_withdraw.h
- * @brief Handle /reserve/withdraw requests
- * @author Florian Dold
- * @author Benedikt Mueller
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_WITHDRAW_H
-#define TALER_EXCHANGE_HTTPD_WITHDRAW_H
-
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Handle a "/reserves/$RESERVE_PUB/withdraw" request.  Parses the requested 
"denom_pub" which
- * specifies the key/value of the coin to be withdrawn, and checks that the
- * signature "reserve_sig" makes this a valid withdrawal request from the
- * specified reserve.  If so, the envelope with the blinded coin "coin_ev" is
- * passed down to execute the withdrawal operation.
- *
- * @param rc request context
- * @param root uploaded JSON data
- * @param reserve_pub public key of the reserve
- * @return MHD result code
-  */
-MHD_RESULT
-TEH_handler_withdraw (struct TEH_RequestContext *rc,
-                      const struct TALER_ReservePublicKeyP *reserve_pub,
-                      const json_t *root);
-
-#endif
diff --git a/src/exchangedb/Makefile.am b/src/exchangedb/Makefile.am
index 3a4120af..33a5722c 100644
--- a/src/exchangedb/Makefile.am
+++ b/src/exchangedb/Makefile.am
@@ -221,7 +221,6 @@ libtaler_plugin_exchangedb_postgres_la_SOURCES = \
   pg_do_batch_withdraw_insert.h pg_do_batch_withdraw_insert.c \
   pg_do_reserve_open.c pg_do_reserve_open.h \
   pg_do_purse_delete.c pg_do_purse_delete.h \
-  pg_do_withdraw.h pg_do_withdraw.c \
   pg_preflight.h pg_preflight.c \
   pg_iterate_active_signkeys.h pg_iterate_active_signkeys.c \
   pg_commit.h pg_commit.c \
diff --git a/src/exchangedb/exchange_do_age_withdraw.sql 
b/src/exchangedb/exchange_do_age_withdraw.sql
index 184a3e49..c696bd80 100644
--- a/src/exchangedb/exchange_do_age_withdraw.sql
+++ b/src/exchangedb/exchange_do_age_withdraw.sql
@@ -29,6 +29,7 @@ CREATE OR REPLACE FUNCTION exchange_do_age_withdraw(
   IN denom_sigs BYTEA[],
   OUT reserve_found BOOLEAN,
   OUT balance_ok BOOLEAN,
+  OUT reserve_balance taler_amount,
   OUT age_ok BOOLEAN,
   OUT required_age INT2, -- in years ϵ [0,1..)
   OUT reserve_birthday INT4,
@@ -38,7 +39,7 @@ AS $$
 DECLARE
   reserve RECORD;
   difference RECORD;
-  balance  taler_amount;
+  balance taler_amount;
   not_before date;
   earliest_date date;
 BEGIN
@@ -48,6 +49,7 @@ BEGIN
 --         reserves_in by reserve_pub (SELECT)
 --         wire_targets by wire_target_h_payto
 
+-- FIXME-Oec: never select-*!
 SELECT *
   INTO reserve
   FROM exchange.reserves
@@ -59,6 +61,8 @@ THEN
   age_ok = FALSE;
   required_age=-1;
   conflict=FALSE;
+  reserve_balance.val = 0;
+  reserve_balance.frac = 0;
   balance_ok=FALSE;
   RETURN;
 END IF;
@@ -66,7 +70,7 @@ END IF;
 reserve_found = TRUE;
 conflict=FALSE;  -- not really yet determined
 
-balance = reserve.current_balance;
+reserve_balance = reserve.current_balance;
 reserve_birthday = reserve.birthday;
 
 -- Check age requirements
@@ -99,7 +103,7 @@ required_age=0;
 -- Check reserve balance is sufficient.
 SELECT *
 INTO difference
-FROM amount_left_minus_right(balance
+FROM amount_left_minus_right(reserve_balance
                             ,amount_with_fee);
 
 balance_ok = difference.ok;
diff --git a/src/exchangedb/exchange_do_batch_withdraw.sql 
b/src/exchangedb/exchange_do_batch_withdraw.sql
index be279ab7..a27f2348 100644
--- a/src/exchangedb/exchange_do_batch_withdraw.sql
+++ b/src/exchangedb/exchange_do_batch_withdraw.sql
@@ -24,6 +24,7 @@ CREATE OR REPLACE FUNCTION exchange_do_batch_withdraw(
   IN do_age_check BOOLEAN,
   OUT reserve_found BOOLEAN,
   OUT balance_ok BOOLEAN,
+  OUT reserve_balance taler_amount,
   OUT age_ok BOOLEAN,
   OUT allowed_maximum_age INT2, -- in years
   OUT ruuid INT8)
@@ -40,7 +41,7 @@ BEGIN
 --         reserves_in by reserve_pub (SELECT)
 --         wire_targets by wire_target_h_payto
 
-
+-- FIXME-Oec: do not use select-*!
 SELECT *
   INTO reserve
   FROM exchange.reserves
@@ -51,13 +52,15 @@ THEN
   -- reserve unknown
   reserve_found=FALSE;
   balance_ok=FALSE;
+  reserve_balance.frac = 0;
+  reserve_balance.val = 0;
   age_ok=FALSE;
   allowed_maximum_age=0;
   ruuid=2;
   RETURN;
 END IF;
 reserve_found=TRUE;
-
+reserve_balance = reserve.current_balance;
 ruuid = reserve.reserve_uuid;
 
 -- Check if age requirements are present
@@ -79,24 +82,23 @@ ELSE
   RETURN;
 END IF;
 
-balance = reserve.current_balance;
 
 -- Check reserve balance is sufficient.
-IF (balance.val > amount.val)
+IF (reserve_balance.val > amount.val)
 THEN
-  IF (balance.frac >= amount.frac)
+  IF (reserve_balance.frac >= amount.frac)
   THEN
-    balance.val=balance.val - amount.val;
-    balance.frac=balance.frac - amount.frac;
+    balance.val=reserve_balance.val - amount.val;
+    balance.frac=reserve_balance.frac - amount.frac;
   ELSE
-    balance.val=balance.val - amount.val - 1;
-    balance.frac=balance.frac + 100000000 - amount.frac;
+    balance.val=reserve_balance.val - amount.val - 1;
+    balance.frac=reserve_balance.frac + 100000000 - amount.frac;
   END IF;
 ELSE
-  IF (balance.val = amount.val) AND (balance.frac >= amount.frac)
+  IF (reserve_balance.val = amount.val) AND (reserve_balance.frac >= 
amount.frac)
   THEN
     balance.val=0;
-    balance.frac=balance.frac - amount.frac;
+    balance.frac=reserve_balance.frac - amount.frac;
   ELSE
     balance_ok=FALSE;
     RETURN;
diff --git a/src/exchangedb/exchange_do_reserve_open.sql 
b/src/exchangedb/exchange_do_reserve_open.sql
index 7aca78b8..5d36d1af 100644
--- a/src/exchangedb/exchange_do_reserve_open.sql
+++ b/src/exchangedb/exchange_do_reserve_open.sql
@@ -27,7 +27,9 @@ CREATE OR REPLACE FUNCTION exchange_do_reserve_open(
   IN in_open_fee taler_amount,
   OUT out_open_cost taler_amount,
   OUT out_final_expiration INT8,
-  OUT out_no_funds BOOLEAN)
+  OUT out_no_reserve BOOLEAN,
+  OUT out_no_funds BOOLEAN,
+  OUT out_reserve_balance taler_amount)
 LANGUAGE plpgsql
 AS $$
 DECLARE
@@ -41,6 +43,7 @@ DECLARE
   reserve RECORD;
 BEGIN
 
+-- FIXME: do not use SELECT-*
 -- FIXME: use SELECT FOR UPDATE?
 SELECT *
   INTO reserve
@@ -49,12 +52,19 @@ SELECT *
 
 IF NOT FOUND
 THEN
-  -- FIXME: do we need to set a 'not found'?
   RAISE NOTICE 'reserve not found';
+  out_no_reserve = TRUE;
+  out_no_funds = TRUE;
+  out_reserve_balance.val = 0;
+  out_reserve_balance.frac = 0;
+  out_open_cost.val = 0;
+  out_open_cost.frac = 0;
+  out_final_expiration = 0;
   RETURN;
 END IF;
 
-my_balance = reserve.current_balance;
+out_no_reserve = FALSE;
+out_reserve_balance = reserve.current_balance;
 
 -- Do not allow expiration time to start in the past already
 IF (reserve.expiration_date < in_now)
@@ -143,21 +153,21 @@ THEN
 END IF;
 
 -- Check reserve balance is sufficient.
-IF (my_balance.val > in_reserve_payment.val)
+IF (out_reserve_balance.val > in_reserve_payment.val)
 THEN
-  IF (my_balance.frac >= in_reserve_payment.frac)
+  IF (out_reserve_balance.frac >= in_reserve_payment.frac)
   THEN
-    my_balance.val=my_balance.val - in_reserve_payment.val;
-    my_balance.frac=my_balance.frac - in_reserve_payment.frac;
+    my_balance.val=out_reserve_balance.val - in_reserve_payment.val;
+    my_balance.frac=out_reserve_balance.frac - in_reserve_payment.frac;
   ELSE
-    my_balance.val=my_balance.val - in_reserve_payment.val - 1;
-    my_balance.frac=my_balance.frac + 100000000 - in_reserve_payment.frac;
+    my_balance.val=out_reserve_balance.val - in_reserve_payment.val - 1;
+    my_balance.frac=out_reserve_balance.frac + 100000000 - 
in_reserve_payment.frac;
   END IF;
 ELSE
-  IF (my_balance.val = in_reserve_payment.val) AND (my_balance.frac >= 
in_reserve_payment.frac)
+  IF (out_reserve_balance.val = in_reserve_payment.val) AND 
(out_reserve_balance.frac >= in_reserve_payment.frac)
   THEN
     my_balance.val=0;
-    my_balance.frac=my_balance.frac - in_reserve_payment.frac;
+    my_balance.frac=out_reserve_balance.frac - in_reserve_payment.frac;
   ELSE
     out_final_expiration = reserve.expiration_date;
     out_open_cost.val = my_cost.val;
diff --git a/src/exchangedb/exchange_do_withdraw.sql 
b/src/exchangedb/exchange_do_withdraw.sql
deleted file mode 100644
index c25267b7..00000000
--- a/src/exchangedb/exchange_do_withdraw.sql
+++ /dev/null
@@ -1,213 +0,0 @@
---
--- This file is part of TALER
--- Copyright (C) 2014--2022 Taler Systems SA
---
--- 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.
---
--- 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
--- TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
---
-
-
-CREATE OR REPLACE FUNCTION exchange_do_withdraw(
-  IN cs_nonce BYTEA,
-  IN amount taler_amount,
-  IN h_denom_pub BYTEA,
-  IN rpub BYTEA,
-  IN reserve_sig BYTEA,
-  IN h_coin_envelope BYTEA,
-  IN denom_sig BYTEA,
-  IN now INT8,
-  IN min_reserve_gc INT8,
-  IN do_age_check BOOLEAN,
-  OUT reserve_found BOOLEAN,
-  OUT balance_ok BOOLEAN,
-  OUT nonce_ok BOOLEAN,
-  OUT age_ok BOOLEAN,
-  OUT allowed_maximum_age INT2, -- in years
-  OUT ruuid INT8)
-LANGUAGE plpgsql
-AS $$
-DECLARE
-  reserve RECORD;
-  denom_serial INT8;
-  balance taler_amount;
-  not_before date;
-BEGIN
--- Shards: reserves by reserve_pub (SELECT)
---         reserves_out (INSERT, with CONFLICT detection) by wih
---         reserves by reserve_pub (UPDATE)
---         reserves_in by reserve_pub (SELECT)
---         wire_targets by wire_target_h_payto
-
-SELECT denominations_serial
-  INTO denom_serial
-  FROM exchange.denominations
- WHERE denom_pub_hash=h_denom_pub;
-
-IF NOT FOUND
-THEN
-  -- denomination unknown, should be impossible!
-  reserve_found=FALSE;
-  balance_ok=FALSE;
-  age_ok=FALSE;
-  allowed_maximum_age=0;
-  ruuid=0;
-  ASSERT false, 'denomination unknown';
-  RETURN;
-END IF;
-
-
-SELECT *
-  INTO reserve
-  FROM exchange.reserves
- WHERE reserves.reserve_pub=rpub;
-
-IF NOT FOUND
-THEN
-  -- reserve unknown
-  reserve_found=FALSE;
-  balance_ok=FALSE;
-  nonce_ok=TRUE;
-  age_ok=FALSE;
-  allowed_maximum_age=0;
-  ruuid=2;
-  RETURN;
-END IF;
-
-balance = reserve.current_balance;
-ruuid = reserve.reserve_uuid;
-
--- Check if age requirements are present
-IF ((NOT do_age_check) OR (reserve.birthday = 0))
-THEN
-  age_ok = TRUE;
-  allowed_maximum_age = -1;
-ELSE
-  -- Age requirements are formally not met:  The exchange is setup to support
-  -- age restrictions (do_age_check == TRUE) and the reserve has a
-  -- birthday set (reserve_birthday != 0), but the client called the
-  -- batch-withdraw endpoint instead of the age-withdraw endpoint, which it
-  -- should have.
-  not_before=date '1970-01-01' + reserve.birthday;
-  allowed_maximum_age = extract(year from age(current_date, not_before));
-
-  reserve_found=TRUE;
-  nonce_ok=TRUE; -- we do not really know
-  balance_ok=TRUE;-- we do not really know
-  age_ok = FALSE;
-  RETURN;
-END IF;
-
--- We optimistically insert, and then on conflict declare
--- the query successful due to idempotency.
-INSERT INTO exchange.reserves_out
-  (h_blind_ev
-  ,denominations_serial
-  ,denom_sig
-  ,reserve_uuid
-  ,reserve_sig
-  ,execution_date
-  ,amount_with_fee)
-VALUES
-  (h_coin_envelope
-  ,denom_serial
-  ,denom_sig
-  ,ruuid
-  ,reserve_sig
-  ,now
-  ,amount)
-ON CONFLICT DO NOTHING;
-
-IF NOT FOUND
-THEN
-  -- idempotent query, all constraints must be satisfied
-  reserve_found=TRUE;
-  balance_ok=TRUE;
-  nonce_ok=TRUE;
-  RETURN;
-END IF;
-
--- Check reserve balance is sufficient.
-IF (balance.val > amount.val)
-THEN
-  IF (balance.frac >= amount.frac)
-  THEN
-    balance.val=balance.val - amount.val;
-    balance.frac=balance.frac - amount.frac;
-  ELSE
-    balance.val=balance.val - amount.val - 1;
-    balance.frac=balance.frac + 100000000 - amount.frac;
-  END IF;
-ELSE
-  IF (balance.val = amount.val) AND (balance.frac >= amount.frac)
-  THEN
-    balance.val=0;
-    balance.frac=balance.frac - amount.frac;
-  ELSE
-    reserve_found=TRUE;
-    nonce_ok=TRUE; -- we do not really know
-    balance_ok=FALSE;
-    RETURN;
-  END IF;
-END IF;
-
--- Calculate new expiration dates.
-min_reserve_gc=GREATEST(min_reserve_gc,reserve.gc_date);
-
--- Update reserve balance.
-UPDATE reserves SET
-  gc_date=min_reserve_gc
- ,current_balance=balance
-WHERE
-  reserves.reserve_pub=rpub;
-
-reserve_found=TRUE;
-balance_ok=TRUE;
-
-
-
--- Special actions needed for a CS withdraw?
-IF NOT NULL cs_nonce
-THEN
-  -- Cache CS signature to prevent replays in the future
-  -- (and check if cached signature exists at the same time).
-  INSERT INTO exchange.cs_nonce_locks
-    (nonce
-    ,max_denomination_serial
-    ,op_hash)
-  VALUES
-    (cs_nonce
-    ,denom_serial
-    ,h_coin_envelope)
-  ON CONFLICT DO NOTHING;
-
-  IF NOT FOUND
-  THEN
-    -- See if the existing entry is identical.
-    SELECT 1
-      FROM exchange.cs_nonce_locks
-     WHERE nonce=cs_nonce
-       AND op_hash=h_coin_envelope;
-    IF NOT FOUND
-    THEN
-      reserve_found=FALSE;
-      balance_ok=FALSE;
-      nonce_ok=FALSE;
-      RETURN;
-    END IF;
-  END IF;
-ELSE
-  nonce_ok=TRUE; -- no nonce, hence OK!
-END IF;
-
-END $$;
-
-COMMENT ON FUNCTION exchange_do_withdraw(BYTEA, taler_amount, BYTEA, BYTEA, 
BYTEA, BYTEA, BYTEA, INT8, INT8, BOOLEAN)
-  IS 'Checks whether the reserve has sufficient balance for a withdraw 
operation (or the request is repeated and was previously approved) and if the 
age requirements are formally met.  If so updates the database with the result';
diff --git a/src/exchangedb/perf_deposits_get_ready.c 
b/src/exchangedb/perf_deposits_get_ready.c
index 2effee73..f1566548 100644
--- a/src/exchangedb/perf_deposits_get_ready.c
+++ b/src/exchangedb/perf_deposits_get_ready.c
@@ -364,26 +364,38 @@ run (void *cls)
                                           &cbc.withdraw_fee));
     {
       bool found;
-      bool nonce_ok;
+      bool nonce_reuse;
       bool balance_ok;
       bool age_ok;
+      bool conflict;
+      bool denom_unknown;
+      struct TALER_Amount reserve_balance;
       uint16_t allowed_minimum_age;
       uint64_t ruuid;
       struct GNUNET_TIME_Timestamp now;
 
       now = GNUNET_TIME_timestamp_get ();
       FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
-              plugin->do_withdraw (plugin->cls,
-                                   NULL,
-                                   &cbc,
-                                   now,
-                                   false,
-                                   &found,
-                                   &balance_ok,
-                                   &nonce_ok,
-                                   &age_ok,
-                                   &allowed_minimum_age,
-                                   &ruuid));
+              plugin->do_batch_withdraw (plugin->cls,
+                                         now,
+                                         &reserve_pub,
+                                         &value,
+                                         true,
+                                         &found,
+                                         &balance_ok,
+                                         &reserve_balance,
+                                         &age_ok,
+                                         &allowed_minimum_age,
+                                         &ruuid));
+      FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+              plugin->do_batch_withdraw_insert (plugin->cls,
+                                                NULL,
+                                                &cbc,
+                                                now,
+                                                ruuid,
+                                                &denom_unknown,
+                                                &conflict,
+                                                &nonce_reuse));
     }
     {
       /* ENSURE_COIN_KNOWN */
diff --git a/src/exchangedb/pg_do_age_withdraw.c 
b/src/exchangedb/pg_do_age_withdraw.c
index 93e15716..99584098 100644
--- a/src/exchangedb/pg_do_age_withdraw.c
+++ b/src/exchangedb/pg_do_age_withdraw.c
@@ -36,6 +36,7 @@ TEH_PG_do_age_withdraw (
   struct GNUNET_TIME_Timestamp now,
   bool *found,
   bool *balance_ok,
+  struct TALER_Amount *reserve_balance,
   bool *age_ok,
   uint16_t *required_age,
   uint32_t *reserve_birthday,
@@ -69,6 +70,8 @@ TEH_PG_do_age_withdraw (
                                 found),
     GNUNET_PQ_result_spec_bool ("balance_ok",
                                 balance_ok),
+    TALER_PQ_RESULT_SPEC_AMOUNT ("reserve_balance",
+                                 reserve_balance),
     GNUNET_PQ_result_spec_bool ("age_ok",
                                 age_ok),
     GNUNET_PQ_result_spec_uint16 ("required_age",
@@ -83,15 +86,12 @@ TEH_PG_do_age_withdraw (
   gc = GNUNET_TIME_absolute_to_timestamp (
     GNUNET_TIME_absolute_add (now.abs_time,
                               pg->legal_reserve_expiration_time));
-
-
-  /* Used in #postgres_do_age_withdraw() to
-        update the reserve balance and check its status */
   PREPARE (pg,
            "call_age_withdraw",
            "SELECT "
            " reserve_found"
            ",balance_ok"
+           ",reserve_balance"
            ",age_ok"
            ",required_age"
            ",reserve_birthday"
diff --git a/src/exchangedb/pg_do_age_withdraw.h 
b/src/exchangedb/pg_do_age_withdraw.h
index 71376022..fb435a30 100644
--- a/src/exchangedb/pg_do_age_withdraw.h
+++ b/src/exchangedb/pg_do_age_withdraw.h
@@ -34,6 +34,7 @@
  * @param now current time (rounded)
  * @param[out] found set to true if the reserve was found
  * @param[out] balance_ok set to true if the balance was sufficient
+ * @param[out] reserve_balance set to the original reserve balance (at the 
start of this transaction)
  * @param[out] age_ok set to true if no age requirements are present on the 
reserve
  * @param[out] required_age if @e age_ok is false, set to the maximum allowed 
age when withdrawing from this reserve
  * @param[out] reserve_birthday if @e age_ok is false, set to the birthday of 
the reserve
@@ -47,6 +48,7 @@ TEH_PG_do_age_withdraw (
   const struct GNUNET_TIME_Timestamp now,
   bool *found,
   bool *balance_ok,
+  struct TALER_Amount *reserve_balance,
   bool *age_ok,
   uint16_t *required_age,
   uint32_t *reserve_birthday,
diff --git a/src/exchangedb/pg_do_batch_withdraw.c 
b/src/exchangedb/pg_do_batch_withdraw.c
index f89f3277..f5571ddb 100644
--- a/src/exchangedb/pg_do_batch_withdraw.c
+++ b/src/exchangedb/pg_do_batch_withdraw.c
@@ -36,6 +36,7 @@ TEH_PG_do_batch_withdraw (
   bool do_age_check,
   bool *found,
   bool *balance_ok,
+  struct TALER_Amount *reserve_balance,
   bool *age_ok,
   uint16_t *allowed_maximum_age,
   uint64_t *ruuid)
@@ -56,6 +57,8 @@ TEH_PG_do_batch_withdraw (
                                 found),
     GNUNET_PQ_result_spec_bool ("balance_ok",
                                 balance_ok),
+    TALER_PQ_RESULT_SPEC_AMOUNT ("reserve_balance",
+                                 reserve_balance),
     GNUNET_PQ_result_spec_bool ("age_ok",
                                 age_ok),
     GNUNET_PQ_result_spec_uint16 ("allowed_maximum_age",
@@ -73,6 +76,7 @@ TEH_PG_do_batch_withdraw (
            "SELECT "
            " reserve_found"
            ",balance_ok"
+           ",reserve_balance"
            ",age_ok"
            ",allowed_maximum_age"
            ",ruuid"
diff --git a/src/exchangedb/pg_do_batch_withdraw.h 
b/src/exchangedb/pg_do_batch_withdraw.h
index d0b86574..486f8d1b 100644
--- a/src/exchangedb/pg_do_batch_withdraw.h
+++ b/src/exchangedb/pg_do_batch_withdraw.h
@@ -36,6 +36,7 @@
  * @param age_check_required if true, fail if age requirements are set on the 
reserve
  * @param[out] found set to true if the reserve was found
  * @param[out] balance_ok set to true if the balance was sufficient
+ * @param[out] reserve_balance set to the original reserve balance (at the 
start of this transaction)
  * @param[out] age_ok set to true if no age requirements are present on the 
reserve
  * @param[out] allowed_maximum_age if @e age_ok is false, set to the maximum 
allowed age when withdrawing from this reserve (client needs to call 
age-withdraw)
  * @param[out] ruuid set to the reserve's UUID (reserves table row)
@@ -50,6 +51,7 @@ TEH_PG_do_batch_withdraw (
   bool age_check_required,
   bool *found,
   bool *balance_ok,
+  struct TALER_Amount *reserve_balance,
   bool *age_ok,
   uint16_t *allowed_maximum_age,
   uint64_t *ruuid);
diff --git a/src/exchangedb/pg_do_reserve_open.c 
b/src/exchangedb/pg_do_reserve_open.c
index e82a3e9c..b15c96dd 100644
--- a/src/exchangedb/pg_do_reserve_open.c
+++ b/src/exchangedb/pg_do_reserve_open.c
@@ -38,6 +38,7 @@ TEH_PG_do_reserve_open (
   struct GNUNET_TIME_Timestamp now,
   const struct TALER_Amount *open_fee,
   bool *no_funds,
+  struct TALER_Amount *reserve_balance,
   struct TALER_Amount *open_cost,
   struct GNUNET_TIME_Timestamp *final_expiration)
 {
@@ -60,16 +61,23 @@ TEH_PG_do_reserve_open (
                                  open_fee),
     GNUNET_PQ_query_param_end
   };
+  bool no_reserve = true;
   struct GNUNET_PQ_ResultSpec rs[] = {
     TALER_PQ_result_spec_amount ("out_open_cost",
                                  pg->currency,
                                  open_cost),
+    TALER_PQ_result_spec_amount ("out_reserve_balance",
+                                 pg->currency,
+                                 reserve_balance),
     GNUNET_PQ_result_spec_timestamp ("out_final_expiration",
                                      final_expiration),
+    GNUNET_PQ_result_spec_bool ("out_no_reserve",
+                                &no_reserve),
     GNUNET_PQ_result_spec_bool ("out_no_funds",
                                 no_funds),
     GNUNET_PQ_result_spec_end
   };
+  enum GNUNET_DB_QueryStatus qs;
 
   PREPARE (pg,
            "do_reserve_open",
@@ -77,10 +85,17 @@ TEH_PG_do_reserve_open (
            " out_open_cost"
            ",out_final_expiration"
            ",out_no_funds"
+           ",out_no_reserve"
+           ",out_reserve_balance"
            " FROM exchange_do_reserve_open"
            " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10);");
-  return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
-                                                   "do_reserve_open",
-                                                   params,
-                                                   rs);
+  qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                 "do_reserve_open",
+                                                 params,
+                                                 rs);
+  if (qs <= 0)
+    return qs;
+  if (no_reserve)
+    return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+  return qs;
 }
diff --git a/src/exchangedb/pg_do_reserve_open.h 
b/src/exchangedb/pg_do_reserve_open.h
index acf2d67e..432f3f66 100644
--- a/src/exchangedb/pg_do_reserve_open.h
+++ b/src/exchangedb/pg_do_reserve_open.h
@@ -39,6 +39,7 @@
  * @param now when did we the client initiate the action
  * @param open_fee annual fee to be charged for the open operation by the 
exchange
  * @param[out] no_funds set to true if reserve balance is insufficient
+ * @param[out] reserve_balance set to the original reserve balance (at the 
start of this transaction)
  * @param[out] open_cost set to the actual cost
  * @param[out] final_expiration when will the reserve expire now
  * @return transaction status code
@@ -55,6 +56,7 @@ TEH_PG_do_reserve_open (
   struct GNUNET_TIME_Timestamp now,
   const struct TALER_Amount *open_fee,
   bool *no_funds,
+  struct TALER_Amount *reserve_balance,
   struct TALER_Amount *open_cost,
   struct GNUNET_TIME_Timestamp *final_expiration);
 
diff --git a/src/exchangedb/pg_do_withdraw.c b/src/exchangedb/pg_do_withdraw.c
deleted file mode 100644
index e32afcda..00000000
--- a/src/exchangedb/pg_do_withdraw.c
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
-   This file is part of TALER
-   Copyright (C) 2022 Taler Systems SA
-
-   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.
-
-   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
-   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-/**
- * @file exchangedb/pg_do_withdraw.c
- * @brief Implementation of the do_withdraw function for Postgres
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include "taler_error_codes.h"
-#include "taler_dbevents.h"
-#include "taler_pq_lib.h"
-#include "pg_do_withdraw.h"
-#include "pg_helper.h"
-
-
-enum GNUNET_DB_QueryStatus
-TEH_PG_do_withdraw (
-  void *cls,
-  const struct TALER_CsNonce *nonce,
-  const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable,
-  struct GNUNET_TIME_Timestamp now,
-  bool do_age_check,
-  bool *found,
-  bool *balance_ok,
-  bool *nonce_ok,
-  bool *age_ok,
-  uint16_t *allowed_maximum_age,
-  uint64_t *ruuid)
-{
-  struct PostgresClosure *pg = cls;
-  struct GNUNET_TIME_Timestamp gc;
-  struct GNUNET_PQ_QueryParam params[] = {
-    NULL == nonce
-    ? GNUNET_PQ_query_param_null ()
-    : GNUNET_PQ_query_param_auto_from_type (nonce),
-    TALER_PQ_query_param_amount (pg->conn,
-                                 &collectable->amount_with_fee),
-    GNUNET_PQ_query_param_auto_from_type (&collectable->denom_pub_hash),
-    GNUNET_PQ_query_param_auto_from_type (&collectable->reserve_pub),
-    GNUNET_PQ_query_param_auto_from_type (&collectable->reserve_sig),
-    GNUNET_PQ_query_param_auto_from_type (&collectable->h_coin_envelope),
-    TALER_PQ_query_param_blinded_denom_sig (&collectable->sig),
-    GNUNET_PQ_query_param_timestamp (&now),
-    GNUNET_PQ_query_param_timestamp (&gc),
-    GNUNET_PQ_query_param_bool (do_age_check),
-    GNUNET_PQ_query_param_end
-  };
-  struct GNUNET_PQ_ResultSpec rs[] = {
-    GNUNET_PQ_result_spec_bool ("reserve_found",
-                                found),
-    GNUNET_PQ_result_spec_bool ("balance_ok",
-                                balance_ok),
-    GNUNET_PQ_result_spec_bool ("nonce_ok",
-                                nonce_ok),
-    GNUNET_PQ_result_spec_bool ("age_ok",
-                                age_ok),
-    GNUNET_PQ_result_spec_uint16 ("allowed_maximum_age",
-                                  allowed_maximum_age),
-    GNUNET_PQ_result_spec_uint64 ("ruuid",
-                                  ruuid),
-    GNUNET_PQ_result_spec_end
-  };
-
-  PREPARE (pg,
-           "call_withdraw",
-           "SELECT "
-           " reserve_found"
-           ",balance_ok"
-           ",nonce_ok"
-           ",age_ok"
-           ",allowed_maximum_age"
-           ",ruuid"
-           " FROM exchange_do_withdraw"
-           " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10);");
-  gc = GNUNET_TIME_absolute_to_timestamp (
-    GNUNET_TIME_absolute_add (now.abs_time,
-                              pg->legal_reserve_expiration_time));
-  return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
-                                                   "call_withdraw",
-                                                   params,
-                                                   rs);
-}
diff --git a/src/exchangedb/pg_do_withdraw.h b/src/exchangedb/pg_do_withdraw.h
deleted file mode 100644
index e771b1ac..00000000
--- a/src/exchangedb/pg_do_withdraw.h
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
-   This file is part of TALER
-   Copyright (C) 2022 Taler Systems SA
-
-   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.
-
-   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
-   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-/**
- * @file exchangedb/pg_do_withdraw.h
- * @brief implementation of the do_withdraw function for Postgres
- * @author Christian Grothoff
- */
-#ifndef PG_DO_WITHDRAW_H
-#define PG_DO_WITHDRAW_H
-
-#include "taler_util.h"
-#include "taler_json_lib.h"
-#include "taler_exchangedb_plugin.h"
-
-/**
- * Perform withdraw operation, checking for sufficient balance
- * and possibly persisting the withdrawal details.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param nonce client-contributed input for CS denominations that must be 
checked for idempotency, or NULL for non-CS withdrawals
- * @param[in,out] collectable corresponding collectable coin (blind signature) 
if a coin is found; possibly updated if a (different) signature exists already
- * @param now current time (rounded)
- * @param do_age_check set to true if age requirements must be verified
- * @param[out] found set to true if the reserve was found
- * @param[out] balance_ok set to true if the balance was sufficient
- * @param[out] nonce_ok set to false if the nonce was reused
- * @param[out] age_ok set to true if age requirements are met
- * @param[out] allowed_maximum_age if @e age_ok is false, the maximum age (in 
years) that is allowed during age-withdraw
- * @param[out] ruuid set to the reserve's UUID (reserves table row)
- * @return query execution status
- */
-enum GNUNET_DB_QueryStatus
-TEH_PG_do_withdraw (
-  void *cls,
-  const struct TALER_CsNonce *nonce,
-  const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable,
-  struct GNUNET_TIME_Timestamp now,
-  bool do_age_check,
-  bool *found,
-  bool *balance_ok,
-  bool *nonce_ok,
-  bool *age_ok,
-  uint16_t *allowed_maximum_age,
-  uint64_t *ruuid);
-
-#endif
diff --git a/src/exchangedb/plugin_exchangedb_postgres.c 
b/src/exchangedb/plugin_exchangedb_postgres.c
index aacc2968..ea43c8ff 100644
--- a/src/exchangedb/plugin_exchangedb_postgres.c
+++ b/src/exchangedb/plugin_exchangedb_postgres.c
@@ -38,7 +38,6 @@
 #include "pg_get_link_data.h"
 #include "pg_helper.h"
 #include "pg_do_reserve_open.h"
-#include "pg_do_withdraw.h"
 #include "pg_get_coin_transactions.h"
 #include "pg_get_expired_reserves.h"
 #include "pg_get_purse_request.h"
@@ -413,8 +412,6 @@ libtaler_plugin_exchangedb_postgres_init (void *cls)
     = &TEH_PG_do_reserve_open;
   plugin->drop_tables
     = &TEH_PG_drop_tables;
-  plugin->do_withdraw
-    = &TEH_PG_do_withdraw;
   plugin->free_coin_transaction_list
     = &TEH_COMMON_free_coin_transaction_list;
   plugin->free_reserve_history
diff --git a/src/exchangedb/procedures.sql.in b/src/exchangedb/procedures.sql.in
index c697afe9..7afb01f0 100644
--- a/src/exchangedb/procedures.sql.in
+++ b/src/exchangedb/procedures.sql.in
@@ -19,7 +19,6 @@ BEGIN;
 SET search_path TO exchange;
 
 #include "exchange_do_amount_specific.sql"
-#include "exchange_do_withdraw.sql"
 #include "exchange_do_batch_withdraw.sql"
 #include "exchange_do_batch_withdraw_insert.sql"
 #include "exchange_do_age_withdraw.sql"
diff --git a/src/exchangedb/test_exchangedb.c b/src/exchangedb/test_exchangedb.c
index eeaaffad..336c1460 100644
--- a/src/exchangedb/test_exchangedb.c
+++ b/src/exchangedb/test_exchangedb.c
@@ -1410,26 +1410,39 @@ run (void *cls)
 
   {
     bool found;
-    bool nonce_ok;
+    bool nonce_reuse;
     bool balance_ok;
     bool age_ok;
+    bool conflict;
+    bool denom_unknown;
     uint16_t maximum_age;
     uint64_t ruuid;
+    struct TALER_Amount reserve_balance;
 
     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
-            plugin->do_withdraw (plugin->cls,
-                                 NULL,
-                                 &cbc,
-                                 now,
-                                 false,
-                                 &found,
-                                 &balance_ok,
-                                 &nonce_ok,
-                                 &age_ok,
-                                 &maximum_age,
-                                 &ruuid));
+            plugin->do_batch_withdraw (plugin->cls,
+                                       now,
+                                       &reserve_pub,
+                                       &value,
+                                       true,
+                                       &found,
+                                       &balance_ok,
+                                       &reserve_balance,
+                                       &age_ok,
+                                       &maximum_age,
+                                       &ruuid));
+    FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+            plugin->do_batch_withdraw_insert (plugin->cls,
+                                              NULL,
+                                              &cbc,
+                                              now,
+                                              ruuid,
+                                              &denom_unknown,
+                                              &conflict,
+                                              &nonce_reuse));
     GNUNET_assert (found);
-    GNUNET_assert (nonce_ok);
+    GNUNET_assert (! nonce_reuse);
+    GNUNET_assert (! denom_unknown);
     GNUNET_assert (balance_ok);
   }
 
diff --git a/src/include/taler_exchange_service.h 
b/src/include/taler_exchange_service.h
index 30c966d7..75252449 100644
--- a/src/include/taler_exchange_service.h
+++ b/src/include/taler_exchange_service.h
@@ -2449,15 +2449,6 @@ TALER_EXCHANGE_reserves_history_cancel (
   struct TALER_EXCHANGE_ReservesHistoryHandle *rsh);
 
 
-/* ********************* POST /reserves/$RESERVE_PUB/withdraw 
*********************** */
-
-
-/**
- * @brief A /reserves/$RESERVE_PUB/withdraw Handle
- */
-struct TALER_EXCHANGE_WithdrawHandle;
-
-
 /**
  * Information input into the withdraw process per coin.
  */
@@ -2511,119 +2502,6 @@ struct TALER_EXCHANGE_PrivateCoinDetails
 };
 
 
-/**
- * Details about a response for a withdraw request.
- */
-struct TALER_EXCHANGE_WithdrawResponse
-{
-  /**
-   * HTTP response data.
-   */
-  struct TALER_EXCHANGE_HttpResponse hr;
-
-  /**
-   * Details about the response.
-   */
-  union
-  {
-    /**
-     * Details if the status is #MHD_HTTP_OK.
-     */
-    struct TALER_EXCHANGE_PrivateCoinDetails ok;
-
-    /**
-     * Details if the status is #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS.
-     */
-    struct
-    {
-      /**
-       * Requirement row that the merchant should use
-       * to check for its KYC status.
-       */
-      uint64_t requirement_row;
-
-      /**
-       * Hash of the payto-URI of the account to KYC;
-       */
-      struct TALER_PaytoHashP h_payto;
-
-    } unavailable_for_legal_reasons;
-
-    /**
-     * Details if the status is #MHD_HTTP_CONFLICT.
-     */
-    struct
-    {
-      /* TODO: returning full details is not implemented */
-    } conflict;
-
-    /**
-     * Details if the status is #MHD_HTTP_GONE.
-     */
-    struct
-    {
-      /* TODO: returning full details is not implemented */
-    } gone;
-
-  } details;
-};
-
-
-/**
- * Callbacks of this type are used to serve the result of submitting a
- * withdraw request to a exchange.
- *
- * @param cls closure
- * @param wr response details
- */
-typedef void
-(*TALER_EXCHANGE_WithdrawCallback) (
-  void *cls,
-  const struct TALER_EXCHANGE_WithdrawResponse *wr);
-
-
-/**
- * Withdraw a coin from the exchange using a /reserves/$RESERVE_PUB/withdraw
- * request.  This API is typically used by a wallet to withdraw from a
- * reserve.
- *
- * Note that to ensure that no money is lost in case of hardware
- * failures, the caller must have committed (most of) the arguments to
- * disk before calling, and be ready to repeat the request with the
- * same arguments in case of failures.
- *
- * @param curl_ctx The curl context to use
- * @param exchange_url The base-URL of the exchange
- * @param keys The /keys material from the exchange
- * @param reserve_priv private key of the reserve to withdraw from
- * @param wci inputs that determine the planchet
- * @param res_cb the callback to call when the final result for this request 
is available
- * @param res_cb_cls closure for @a res_cb
- * @return NULL
- *         if the inputs are invalid (i.e. denomination key not with this 
exchange).
- *         In this case, the callback is not called.
- */
-struct TALER_EXCHANGE_WithdrawHandle *
-TALER_EXCHANGE_withdraw (
-  struct GNUNET_CURL_Context *curl_ctx,
-  const char *exchange_url,
-  struct TALER_EXCHANGE_Keys *keys,
-  const struct TALER_ReservePrivateKeyP *reserve_priv,
-  const struct TALER_EXCHANGE_WithdrawCoinInput *wci,
-  TALER_EXCHANGE_WithdrawCallback res_cb,
-  void *res_cb_cls);
-
-
-/**
- * Cancel a withdraw status request.  This function cannot be used
- * on a request handle if a response is already served for it.
- *
- * @param wh the withdraw handle
- */
-void
-TALER_EXCHANGE_withdraw_cancel (struct TALER_EXCHANGE_WithdrawHandle *wh);
-
-
 /**
  * @brief A /reserves/$RESERVE_PUB/batch-withdraw Handle
  */
@@ -2883,6 +2761,21 @@ struct TALER_EXCHANGE_BatchWithdraw2Response
       unsigned int blind_sigs_length;
 
     } ok;
+
+    struct
+    {
+      /**
+       * Hash of the payto-URI of the account to KYC;
+       */
+      struct TALER_PaytoHashP h_payto;
+
+      /**
+       * ID identifying the KYC requirement to withdraw.
+       */
+      uint64_t kyc_requirement_id;
+
+    } unavailable_for_legal_reasons;
+
   } details;
 
 };
@@ -3084,6 +2977,7 @@ struct TALER_EXCHANGE_AgeWithdrawResponse
   } details;
 };
 
+
 typedef void
 (*TALER_EXCHANGE_AgeWithdrawCallback)(
   void *cls,
diff --git a/src/include/taler_exchangedb_plugin.h 
b/src/include/taler_exchangedb_plugin.h
index f0f4d6aa..252c27a7 100644
--- a/src/include/taler_exchangedb_plugin.h
+++ b/src/include/taler_exchangedb_plugin.h
@@ -3877,38 +3877,6 @@ struct TALER_EXCHANGEDB_Plugin
                        struct TALER_EXCHANGEDB_CollectableBlindcoin 
*collectable);
 
 
-  /**
-   * Perform withdraw operation, checking for sufficient balance
-   * and possibly persisting the withdrawal details.
-   *
-   * @param cls the `struct PostgresClosure` with the plugin-specific state
-   * @param nonce client-contributed input for CS denominations that must be 
checked for idempotency, or NULL for non-CS withdrawals
-   * @param collectable corresponding collectable coin (blind signature)
-   * @param now current time (rounded)
-   * @param do_age_check set to true if age requirements must be checked.
-   * @param[out] found set to true if the reserve was found
-   * @param[out] balance_ok set to true if the balance was sufficient
-   * @param[out] nonce_ok set to false if the nonce was reused
-   * @param[out] age_ok set to true if no age requirements were defined on the 
reserve or @e do_age_check was false
-   * @param[out] allowed_maximum_age when @e age_ok is false, set to the 
allowed maximum age for withdrawal from the reserve.  The client MUST then use 
the age-withdraw endpoint
-   * @param[out] ruuid set to the reserve's UUID (reserves table row)
-   * @return query execution status
-   */
-  enum GNUNET_DB_QueryStatus
-  (*do_withdraw)(
-    void *cls,
-    const struct TALER_CsNonce *nonce,
-    const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable,
-    struct GNUNET_TIME_Timestamp now,
-    bool do_age_check,
-    bool *found,
-    bool *balance_ok,
-    bool *nonce_ok,
-    bool *age_ok,
-    uint16_t *allowed_maximum_age,
-    uint64_t *ruuid);
-
-
   /**
    * FIXME: merge do_batch_withdraw and do_batch_withdraw_insert into one API,
    * which takes as input (among others)
@@ -3930,6 +3898,7 @@ struct TALER_EXCHANGEDB_Plugin
    * @param do_age_check if set, the batch-withdrawal can only succeed when 
the reserve has no age restriction (birthday) set.
    * @param[out] found set to true if the reserve was found
    * @param[out] balance_ok set to true if the balance was sufficient
+   * @param[out] reserve_balance set to original balance of the reserve
    * @param[out] age_ok set to true if no age requirements were defined on the 
reserve or @e do_age_check was false
    * @param[out] allowed_maximum_age when @e age_ok is false, set to the 
allowed maximum age for withdrawal from the reserve.  The client MUST then use 
the age-withdraw endpoint
    * @param[out] ruuid set to the reserve's UUID (reserves table row)
@@ -3944,6 +3913,7 @@ struct TALER_EXCHANGEDB_Plugin
     bool do_age_check,
     bool *found,
     bool *balance_ok,
+    struct TALER_Amount *reserve_balance,
     bool *age_ok,
     uint16_t *allowed_maximum_age,
     uint64_t *ruuid);
@@ -4001,6 +3971,7 @@ struct TALER_EXCHANGEDB_Plugin
    * @param commitment corresponding commitment for the age-withdraw
    * @param[out] found set to true if the reserve was found
    * @param[out] balance_ok set to true if the balance was sufficient
+   * @param[out] reserve_balance set to original balance of the reserve
    * @param[out] age_ok set to true if age requirements were met
    * @param[out] allowed_maximum_age if @e age_ok is FALSE, this is set to the 
allowed maximum age
    * @param[out] reserve_birthday if @e age_ok is FALSE, this is set to the 
reserve's birthday
@@ -4013,6 +3984,7 @@ struct TALER_EXCHANGEDB_Plugin
     struct GNUNET_TIME_Timestamp now,
     bool *found,
     bool *balance_ok,
+    struct TALER_Amount *reserve_balance,
     bool *age_ok,
     uint16_t *allowed_maximum_age,
     uint32_t *reserve_birthday,
@@ -4923,6 +4895,7 @@ struct TALER_EXCHANGEDB_Plugin
    * @param now when did we the client initiate the action
    * @param open_fee annual fee to be charged for the open operation by the 
exchange
    * @param[out] no_funds set to true if reserve balance is insufficient
+   * @param[out] reserve_balance set to original balance of the reserve
    * @param[out] open_cost set to the actual cost
    * @param[out] final_expiration when will the reserve expire now
    * @return transaction status code
@@ -4938,6 +4911,7 @@ struct TALER_EXCHANGEDB_Plugin
                      struct GNUNET_TIME_Timestamp now,
                      const struct TALER_Amount *open_fee,
                      bool *no_funds,
+                     struct TALER_Amount *reserve_balance,
                      struct TALER_Amount *open_cost,
                      struct GNUNET_TIME_Timestamp *final_expiration);
 
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
index 12f991d8..230dfba2 100644
--- a/src/lib/Makefile.am
+++ b/src/lib/Makefile.am
@@ -75,9 +75,7 @@ libtalerexchange_la_SOURCES = \
   exchange_api_reserves_history.c \
   exchange_api_reserves_open.c \
   exchange_api_stefan.c \
-  exchange_api_transfers_get.c \
-  exchange_api_withdraw.c \
-  exchange_api_withdraw2.c
+  exchange_api_transfers_get.c 
 libtalerexchange_la_LIBADD = \
   libtalerauditor.la \
   $(top_builddir)/src/json/libtalerjson.la \
diff --git a/src/lib/exchange_api_age_withdraw.c 
b/src/lib/exchange_api_age_withdraw.c
index 4092c5c2..ea9c0371 100644
--- a/src/lib/exchange_api_age_withdraw.c
+++ b/src/lib/exchange_api_age_withdraw.c
@@ -396,27 +396,6 @@ handle_reserve_age_withdraw_blinded_finished (
     GNUNET_assert (NULL == awbh->callback);
     TALER_EXCHANGE_age_withdraw_blinded_cancel (awbh);
     return;
-  case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
-    /* only validate reply is well-formed */
-    {
-      uint64_t ptu;
-      struct GNUNET_JSON_Specification spec[] = {
-        GNUNET_JSON_spec_uint64 ("legitimization_uuid",
-                                 &ptu),
-        GNUNET_JSON_spec_end ()
-      };
-
-      if (GNUNET_OK !=
-          GNUNET_JSON_parse (j_response,
-                             spec,
-                             NULL, NULL))
-      {
-        GNUNET_break_op (0);
-        awbr.hr.http_status = 0;
-        awbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
-        break;
-      }
-    }
   case MHD_HTTP_BAD_REQUEST:
     /* This should never happen, either us or the exchange is buggy
        (or API version conflict); just pass JSON reply to the application */
@@ -452,6 +431,27 @@ handle_reserve_age_withdraw_blinded_finished (
     awbr.hr.ec = TALER_JSON_get_error_code (j_response);
     awbr.hr.hint = TALER_JSON_get_error_hint (j_response);
     break;
+  case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+    /* only validate reply is well-formed */
+    {
+      uint64_t ptu;
+      struct GNUNET_JSON_Specification spec[] = {
+        GNUNET_JSON_spec_uint64 ("requirement_row",
+                                 &ptu),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (j_response,
+                             spec,
+                             NULL, NULL))
+      {
+        GNUNET_break_op (0);
+        awbr.hr.http_status = 0;
+        awbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+        break;
+      }
+    }
   case MHD_HTTP_INTERNAL_SERVER_ERROR:
     /* Server had an internal issue; we should retry, but this API
        leaves this to the application */
diff --git a/src/lib/exchange_api_batch_withdraw2.c 
b/src/lib/exchange_api_batch_withdraw2.c
index 12c6aeff..b6f77319 100644
--- a/src/lib/exchange_api_batch_withdraw2.c
+++ b/src/lib/exchange_api_batch_withdraw2.c
@@ -193,28 +193,6 @@ handle_reserve_batch_withdraw_finished (void *cls,
     GNUNET_assert (NULL == wh->cb);
     TALER_EXCHANGE_batch_withdraw2_cancel (wh);
     return;
-  case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
-    /* only validate reply is well-formed */
-    {
-      uint64_t ptu;
-      struct GNUNET_JSON_Specification spec[] = {
-        GNUNET_JSON_spec_uint64 ("legitimization_uuid",
-                                 &ptu),
-        GNUNET_JSON_spec_end ()
-      };
-
-      if (GNUNET_OK !=
-          GNUNET_JSON_parse (j,
-                             spec,
-                             NULL, NULL))
-      {
-        GNUNET_break_op (0);
-        bwr.hr.http_status = 0;
-        bwr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
-        break;
-      }
-    }
-    break;
   case MHD_HTTP_BAD_REQUEST:
     /* This should never happen, either us or the exchange is buggy
        (or API version conflict); just pass JSON reply to the application */
@@ -249,6 +227,29 @@ handle_reserve_batch_withdraw_finished (void *cls,
     bwr.hr.ec = TALER_JSON_get_error_code (j);
     bwr.hr.hint = TALER_JSON_get_error_hint (j);
     break;
+  case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+    {
+      struct GNUNET_JSON_Specification spec[] = {
+        GNUNET_JSON_spec_fixed_auto (
+          "h_payto",
+          &bwr.details.unavailable_for_legal_reasons.h_payto),
+        GNUNET_JSON_spec_uint64 ("requirement_row",
+                                 &bwr.details.unavailable_for_legal_reasons.
+                                 kyc_requirement_id),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (j,
+                             spec,
+                             NULL, NULL))
+      {
+        GNUNET_break_op (0);
+        bwr.hr.http_status = 0;
+        bwr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+        break;
+      }
+    }
   case MHD_HTTP_INTERNAL_SERVER_ERROR:
     /* Server had an internal issue; we should retry, but this API
        leaves this to the application */
diff --git a/src/lib/exchange_api_withdraw.c b/src/lib/exchange_api_withdraw.c
deleted file mode 100644
index 87218989..00000000
--- a/src/lib/exchange_api_withdraw.c
+++ /dev/null
@@ -1,364 +0,0 @@
-/*
-  This file is part of TALER
-  Copyright (C) 2014-2022 Taler Systems SA
-
-  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.
-
-  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
-  TALER; see the file COPYING.  If not, see
-  <http://www.gnu.org/licenses/>
-*/
-/**
- * @file lib/exchange_api_withdraw.c
- * @brief Implementation of /reserves/$RESERVE_PUB/withdraw requests with 
blinding/unblinding
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <jansson.h>
-#include <microhttpd.h> /* just for HTTP status codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_exchange_service.h"
-#include "taler_json_lib.h"
-#include "exchange_api_handle.h"
-#include "taler_signatures.h"
-#include "exchange_api_curl_defaults.h"
-
-
-/**
- * @brief A Withdraw Handle
- */
-struct TALER_EXCHANGE_WithdrawHandle
-{
-
-  /**
-   * The curl context to use
-   */
-  struct GNUNET_CURL_Context *curl_ctx;
-
-  /**
-   * The base-URL to the exchange
-   */
-  const char *exchange_url;
-
-  /**
-   * The /keys material from the exchange
-   */
-  struct TALER_EXCHANGE_Keys *keys;
-
-  /**
-   * Handle for the actual (internal) withdraw operation.
-   */
-  struct TALER_EXCHANGE_Withdraw2Handle *wh2;
-
-  /**
-   * Function to call with the result.
-   */
-  TALER_EXCHANGE_WithdrawCallback cb;
-
-  /**
-   * Closure for @a cb.
-   */
-  void *cb_cls;
-
-  /**
-   * Reserve private key.
-   */
-  const struct TALER_ReservePrivateKeyP *reserve_priv;
-
-  /**
-   * Seed of the planchet.
-   */
-  struct TALER_PlanchetMasterSecretP ps;
-
-  /**
-   *  blinding secret
-   */
-  union TALER_DenominationBlindingKeyP bks;
-
-  /**
-   * Private key of the coin we are withdrawing.
-   */
-  struct TALER_CoinSpendPrivateKeyP priv;
-
-  /**
-   * Details of the planchet.
-   */
-  struct TALER_PlanchetDetail pd;
-
-  /**
-   * Values of the @cipher selected
-   */
-  struct TALER_ExchangeWithdrawValues alg_values;
-
-  /**
-   * Hash of the age commitment for this coin, if applicable. Maybe NULL
-   */
-  const struct TALER_AgeCommitmentHash *ach;
-
-  /**
-   * Denomination key we are withdrawing.
-   */
-  struct TALER_EXCHANGE_DenomPublicKey pk;
-
-  /**
-   * Hash of the public key of the coin we are signing.
-   */
-  struct TALER_CoinPubHashP c_hash;
-
-  /**
-   * Handler for the CS R request (only used for TALER_DENOMINATION_CS 
denominations)
-   */
-  struct TALER_EXCHANGE_CsRWithdrawHandle *csrh;
-
-};
-
-
-/**
- * Function called when we're done processing the
- * HTTP /reserves/$RESERVE_PUB/withdraw request.
- *
- * @param cls the `struct TALER_EXCHANGE_WithdrawHandle`
- * @param w2r response data
- */
-static void
-handle_reserve_withdraw_finished (
-  void *cls,
-  const struct TALER_EXCHANGE_Withdraw2Response *w2r)
-{
-  struct TALER_EXCHANGE_WithdrawHandle *wh = cls;
-  struct TALER_EXCHANGE_WithdrawResponse wr = {
-    .hr = w2r->hr
-  };
-
-  wh->wh2 = NULL;
-  switch (w2r->hr.http_status)
-  {
-  case MHD_HTTP_OK:
-    {
-      struct TALER_FreshCoin fc;
-
-      if (GNUNET_OK !=
-          TALER_planchet_to_coin (&wh->pk.key,
-                                  &w2r->details.ok.blind_sig,
-                                  &wh->bks,
-                                  &wh->priv,
-                                  wh->ach,
-                                  &wh->c_hash,
-                                  &wh->alg_values,
-                                  &fc))
-      {
-        wr.hr.http_status = 0;
-        wr.hr.ec = TALER_EC_EXCHANGE_WITHDRAW_UNBLIND_FAILURE;
-        break;
-      }
-      wr.details.ok.coin_priv = wh->priv;
-      wr.details.ok.bks = wh->bks;
-      wr.details.ok.sig = fc.sig;
-      wr.details.ok.exchange_vals = wh->alg_values;
-      break;
-    }
-  case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
-    {
-      struct GNUNET_JSON_Specification spec[] = {
-        GNUNET_JSON_spec_fixed_auto (
-          "h_payto",
-          &wr.details.unavailable_for_legal_reasons.h_payto),
-        GNUNET_JSON_spec_uint64 (
-          "requirement_row",
-          &wr.details.unavailable_for_legal_reasons.requirement_row),
-        GNUNET_JSON_spec_end ()
-      };
-
-      if (GNUNET_OK !=
-          GNUNET_JSON_parse (w2r->hr.reply,
-                             spec,
-                             NULL, NULL))
-      {
-        GNUNET_break_op (0);
-        wr.hr.http_status = 0;
-        wr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
-        break;
-      }
-    }
-    break;
-  default:
-    break;
-  }
-  wh->cb (wh->cb_cls,
-          &wr);
-  if (MHD_HTTP_OK == w2r->hr.http_status)
-    TALER_denom_sig_free (&wr.details.ok.sig);
-  TALER_EXCHANGE_withdraw_cancel (wh);
-}
-
-
-/**
- * Function called when stage 1 of CS withdraw is finished (request r_pub's)
- *
- * @param cls the `struct TALER_EXCHANGE_WithdrawHandle`
- * @param csrr replies from the /csr-withdraw request
- */
-static void
-withdraw_cs_stage_two_callback (
-  void *cls,
-  const struct TALER_EXCHANGE_CsRWithdrawResponse *csrr)
-{
-  struct TALER_EXCHANGE_WithdrawHandle *wh = cls;
-  struct TALER_EXCHANGE_WithdrawResponse wr = {
-    .hr = csrr->hr
-  };
-
-  wh->csrh = NULL;
-  GNUNET_assert (TALER_DENOMINATION_CS == wh->pk.key.cipher);
-  switch (csrr->hr.http_status)
-  {
-  case MHD_HTTP_OK:
-    wh->alg_values = csrr->details.ok.alg_values;
-    TALER_planchet_setup_coin_priv (&wh->ps,
-                                    &wh->alg_values,
-                                    &wh->priv);
-    TALER_planchet_blinding_secret_create (&wh->ps,
-                                           &wh->alg_values,
-                                           &wh->bks);
-    /* This initializes the 2nd half of the
-       wh->pd.blinded_planchet! */
-    if (GNUNET_OK !=
-        TALER_planchet_prepare (&wh->pk.key,
-                                &wh->alg_values,
-                                &wh->bks,
-                                &wh->priv,
-                                wh->ach,
-                                &wh->c_hash,
-                                &wh->pd))
-    {
-      GNUNET_break (0);
-      break;
-    }
-    wh->wh2 = TALER_EXCHANGE_withdraw2 (wh->curl_ctx,
-                                        wh->exchange_url,
-                                        wh->keys,
-                                        &wh->pd,
-                                        wh->reserve_priv,
-                                        &handle_reserve_withdraw_finished,
-                                        wh);
-    return;
-  default:
-    break;
-  }
-  wh->cb (wh->cb_cls,
-          &wr);
-  TALER_EXCHANGE_withdraw_cancel (wh);
-}
-
-
-struct TALER_EXCHANGE_WithdrawHandle *
-TALER_EXCHANGE_withdraw (
-  struct GNUNET_CURL_Context *curl_ctx,
-  const char *exchange_url,
-  struct TALER_EXCHANGE_Keys *keys,
-  const struct TALER_ReservePrivateKeyP *reserve_priv,
-  const struct TALER_EXCHANGE_WithdrawCoinInput *wci,
-  TALER_EXCHANGE_WithdrawCallback res_cb,
-  void *res_cb_cls)
-{
-  struct TALER_EXCHANGE_WithdrawHandle *wh;
-
-  wh = GNUNET_new (struct TALER_EXCHANGE_WithdrawHandle);
-  wh->keys = TALER_EXCHANGE_keys_incref (keys);
-  wh->exchange_url = exchange_url;
-  wh->curl_ctx = curl_ctx;
-  wh->cb = res_cb;
-  wh->cb_cls = res_cb_cls;
-  wh->reserve_priv = reserve_priv;
-  wh->ps = *wci->ps;
-  wh->ach = wci->ach;
-  wh->pk = *wci->pk;
-  TALER_denom_pub_deep_copy (&wh->pk.key,
-                             &wci->pk->key);
-
-  switch (wci->pk->key.cipher)
-  {
-  case TALER_DENOMINATION_RSA:
-    {
-      wh->alg_values.cipher = TALER_DENOMINATION_RSA;
-      TALER_planchet_setup_coin_priv (&wh->ps,
-                                      &wh->alg_values,
-                                      &wh->priv);
-      TALER_planchet_blinding_secret_create (&wh->ps,
-                                             &wh->alg_values,
-                                             &wh->bks);
-      if (GNUNET_OK !=
-          TALER_planchet_prepare (&wh->pk.key,
-                                  &wh->alg_values,
-                                  &wh->bks,
-                                  &wh->priv,
-                                  wh->ach,
-                                  &wh->c_hash,
-                                  &wh->pd))
-      {
-        GNUNET_break (0);
-        GNUNET_free (wh);
-        return NULL;
-      }
-      wh->wh2 = TALER_EXCHANGE_withdraw2 (curl_ctx,
-                                          exchange_url,
-                                          keys,
-                                          &wh->pd,
-                                          wh->reserve_priv,
-                                          &handle_reserve_withdraw_finished,
-                                          wh);
-      break;
-    }
-  case TALER_DENOMINATION_CS:
-    {
-      TALER_cs_withdraw_nonce_derive (
-        &wh->ps,
-        &wh->pd.blinded_planchet.details.cs_blinded_planchet.nonce);
-      /* Note that we only initialize the first half
-         of the blinded_planchet here; the other part
-         will be done after the /csr-withdraw request! */
-      wh->pd.blinded_planchet.cipher = TALER_DENOMINATION_CS;
-      wh->csrh = TALER_EXCHANGE_csr_withdraw (
-        curl_ctx,
-        exchange_url,
-        &wh->pk,
-        &wh->pd.blinded_planchet.details.cs_blinded_planchet.nonce,
-        &withdraw_cs_stage_two_callback,
-        wh);
-      break;
-    }
-  default:
-    GNUNET_break (0);
-    GNUNET_free (wh);
-    return NULL;
-  }
-  return wh;
-}
-
-
-void
-TALER_EXCHANGE_withdraw_cancel (struct TALER_EXCHANGE_WithdrawHandle *wh)
-{
-  TALER_blinded_planchet_free (&wh->pd.blinded_planchet);
-  if (NULL != wh->csrh)
-  {
-    TALER_EXCHANGE_csr_withdraw_cancel (wh->csrh);
-    wh->csrh = NULL;
-  }
-  if (NULL != wh->wh2)
-  {
-    TALER_EXCHANGE_withdraw2_cancel (wh->wh2);
-    wh->wh2 = NULL;
-  }
-  TALER_EXCHANGE_keys_decref (wh->keys);
-  TALER_denom_pub_free (&wh->pk.key);
-  GNUNET_free (wh);
-}
diff --git a/src/lib/exchange_api_withdraw2.c b/src/lib/exchange_api_withdraw2.c
deleted file mode 100644
index 53a5934d..00000000
--- a/src/lib/exchange_api_withdraw2.c
+++ /dev/null
@@ -1,389 +0,0 @@
-/*
-  This file is part of TALER
-  Copyright (C) 2014-2023 Taler Systems SA
-
-  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.
-
-  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
-  TALER; see the file COPYING.  If not, see
-  <http://www.gnu.org/licenses/>
-*/
-/**
- * @file lib/exchange_api_withdraw2.c
- * @brief Implementation of /reserves/$RESERVE_PUB/withdraw requests without 
blinding/unblinding
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <jansson.h>
-#include <microhttpd.h> /* just for HTTP status codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_exchange_service.h"
-#include "taler_json_lib.h"
-#include "exchange_api_handle.h"
-#include "taler_signatures.h"
-#include "exchange_api_curl_defaults.h"
-
-
-/**
- * @brief A Withdraw Handle
- */
-struct TALER_EXCHANGE_Withdraw2Handle
-{
-
-  /**
-   * The /keys material from the exchange
-   */
-  struct TALER_EXCHANGE_Keys *keys;
-
-  /**
-   * The url for this request.
-   */
-  char *url;
-
-  /**
-   * Handle for the request.
-   */
-  struct GNUNET_CURL_Job *job;
-
-  /**
-   * Function to call with the result.
-   */
-  TALER_EXCHANGE_Withdraw2Callback cb;
-
-  /**
-   * Closure for @a cb.
-   */
-  void *cb_cls;
-
-  /**
-   * Context for #TEH_curl_easy_post(). Keeps the data that must
-   * persist for Curl to make the upload.
-   */
-  struct TALER_CURL_PostContext post_ctx;
-
-  /**
-   * Total amount requested (value plus withdraw fee).
-   */
-  struct TALER_Amount requested_amount;
-
-  /**
-   * Public key of the reserve we are withdrawing from.
-   */
-  struct TALER_ReservePublicKeyP reserve_pub;
-
-};
-
-
-/**
- * We got a 200 OK response for the /reserves/$RESERVE_PUB/withdraw operation.
- * Extract the coin's signature and return it to the caller.  The signature we
- * get from the exchange is for the blinded value.  Thus, we first must
- * unblind it and then should verify its validity against our coin's hash.
- *
- * If everything checks out, we return the unblinded signature
- * to the application via the callback.
- *
- * @param wh operation handle
- * @param json reply from the exchange
- * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
- */
-static enum GNUNET_GenericReturnValue
-reserve_withdraw_ok (struct TALER_EXCHANGE_Withdraw2Handle *wh,
-                     const json_t *json)
-{
-  struct TALER_EXCHANGE_Withdraw2Response w2r = {
-    .hr.reply = json,
-    .hr.http_status = MHD_HTTP_OK
-  };
-  struct GNUNET_JSON_Specification spec[] = {
-    TALER_JSON_spec_blinded_denom_sig ("ev_sig",
-                                       &w2r.details.ok.blind_sig),
-    GNUNET_JSON_spec_end ()
-  };
-
-  if (GNUNET_OK !=
-      GNUNET_JSON_parse (json,
-                         spec,
-                         NULL, NULL))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-
-  /* signature is valid, return it to the application */
-  wh->cb (wh->cb_cls,
-          &w2r);
-  /* make sure callback isn't called again after return */
-  wh->cb = NULL;
-  GNUNET_JSON_parse_free (spec);
-  return GNUNET_OK;
-}
-
-
-/**
- * Function called when we're done processing the
- * HTTP /reserves/$RESERVE_PUB/withdraw request.
- *
- * @param cls the `struct TALER_EXCHANGE_WithdrawHandle`
- * @param response_code HTTP response code, 0 on error
- * @param response parsed JSON result, NULL on error
- */
-static void
-handle_reserve_withdraw_finished (void *cls,
-                                  long response_code,
-                                  const void *response)
-{
-  struct TALER_EXCHANGE_Withdraw2Handle *wh = cls;
-  const json_t *j = response;
-  struct TALER_EXCHANGE_Withdraw2Response w2r = {
-    .hr.reply = j,
-    .hr.http_status = (unsigned int) response_code
-  };
-
-  wh->job = NULL;
-  switch (response_code)
-  {
-  case 0:
-    w2r.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
-    break;
-  case MHD_HTTP_OK:
-    if (GNUNET_OK !=
-        reserve_withdraw_ok (wh,
-                             j))
-    {
-      GNUNET_break_op (0);
-      w2r.hr.http_status = 0;
-      w2r.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
-      break;
-    }
-    GNUNET_assert (NULL == wh->cb);
-    TALER_EXCHANGE_withdraw2_cancel (wh);
-    return;
-  case MHD_HTTP_BAD_REQUEST:
-    /* This should never happen, either us or the exchange is buggy
-       (or API version conflict); just pass JSON reply to the application */
-    w2r.hr.ec = TALER_JSON_get_error_code (j);
-    w2r.hr.hint = TALER_JSON_get_error_hint (j);
-    break;
-  case MHD_HTTP_FORBIDDEN:
-    GNUNET_break_op (0);
-    /* Nothing really to verify, exchange says one of the signatures is
-       invalid; as we checked them, this should never happen, we
-       should pass the JSON reply to the application */
-    w2r.hr.ec = TALER_JSON_get_error_code (j);
-    w2r.hr.hint = TALER_JSON_get_error_hint (j);
-    break;
-  case MHD_HTTP_NOT_FOUND:
-    /* Nothing really to verify, the exchange basically just says
-       that it doesn't know this reserve.  Can happen if we
-       query before the wire transfer went through.
-       We should simply pass the JSON reply to the application. */
-    w2r.hr.ec = TALER_JSON_get_error_code (j);
-    w2r.hr.hint = TALER_JSON_get_error_hint (j);
-    break;
-  case MHD_HTTP_CONFLICT:
-    w2r.hr.ec = TALER_JSON_get_error_code (j);
-    w2r.hr.hint = TALER_JSON_get_error_hint (j);
-    break;
-  case MHD_HTTP_GONE:
-    /* could happen if denomination was revoked */
-    /* Note: one might want to check /keys for revocation
-       signature here, alas tricky in case our /keys
-       is outdated => left to clients */
-    w2r.hr.ec = TALER_JSON_get_error_code (j);
-    w2r.hr.hint = TALER_JSON_get_error_hint (j);
-    break;
-  case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
-    /* only validate reply is well-formed */
-    {
-      uint64_t ptu;
-      struct GNUNET_JSON_Specification spec[] = {
-        GNUNET_JSON_spec_uint64 ("requirement_row",
-                                 &ptu),
-        GNUNET_JSON_spec_end ()
-      };
-
-      if (GNUNET_OK !=
-          GNUNET_JSON_parse (j,
-                             spec,
-                             NULL, NULL))
-      {
-        GNUNET_break_op (0);
-        w2r.hr.http_status = 0;
-        w2r.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
-        break;
-      }
-    }
-    break;
-  case MHD_HTTP_INTERNAL_SERVER_ERROR:
-    /* Server had an internal issue; we should retry, but this API
-       leaves this to the application */
-    w2r.hr.ec = TALER_JSON_get_error_code (j);
-    w2r.hr.hint = TALER_JSON_get_error_hint (j);
-    break;
-  default:
-    /* unexpected response code */
-    GNUNET_break_op (0);
-    w2r.hr.ec = TALER_JSON_get_error_code (j);
-    w2r.hr.hint = TALER_JSON_get_error_hint (j);
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u/%d for exchange withdraw\n",
-                (unsigned int) response_code,
-                (int) w2r.hr.ec);
-    break;
-  }
-  if (NULL != wh->cb)
-  {
-    wh->cb (wh->cb_cls,
-            &w2r);
-    wh->cb = NULL;
-  }
-  TALER_EXCHANGE_withdraw2_cancel (wh);
-}
-
-
-struct TALER_EXCHANGE_Withdraw2Handle *
-TALER_EXCHANGE_withdraw2 (
-  struct GNUNET_CURL_Context *curl_ctx,
-  const char *exchange_url,
-  struct TALER_EXCHANGE_Keys *keys,
-  const struct TALER_PlanchetDetail *pd,
-  const struct TALER_ReservePrivateKeyP *reserve_priv,
-  TALER_EXCHANGE_Withdraw2Callback res_cb,
-  void *res_cb_cls)
-{
-  struct TALER_EXCHANGE_Withdraw2Handle *wh;
-  const struct TALER_EXCHANGE_DenomPublicKey *dk;
-  struct TALER_ReserveSignatureP reserve_sig;
-  char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
-  struct TALER_BlindedCoinHashP bch;
-
-  GNUNET_assert (NULL != keys);
-  dk = TALER_EXCHANGE_get_denomination_key_by_hash (keys,
-                                                    &pd->denom_pub_hash);
-  if (NULL == dk)
-  {
-    GNUNET_break (0);
-    return NULL;
-  }
-  wh = GNUNET_new (struct TALER_EXCHANGE_Withdraw2Handle);
-  wh->keys = TALER_EXCHANGE_keys_incref (keys);
-  wh->cb = res_cb;
-  wh->cb_cls = res_cb_cls;
-  /* Compute how much we expected to charge to the reserve */
-  if (0 >
-      TALER_amount_add (&wh->requested_amount,
-                        &dk->value,
-                        &dk->fees.withdraw))
-  {
-    /* Overflow here? Very strange, our CPU must be fried... */
-    GNUNET_break (0);
-    GNUNET_free (wh);
-    return NULL;
-  }
-
-  GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
-                                      &wh->reserve_pub.eddsa_pub);
-
-  {
-    char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
-    char *end;
-
-    end = GNUNET_STRINGS_data_to_string (
-      &wh->reserve_pub,
-      sizeof (struct TALER_ReservePublicKeyP),
-      pub_str,
-      sizeof (pub_str));
-    *end = '\0';
-    GNUNET_snprintf (arg_str,
-                     sizeof (arg_str),
-                     "reserves/%s/withdraw",
-                     pub_str);
-  }
-
-  if (GNUNET_OK !=
-      TALER_coin_ev_hash (&pd->blinded_planchet,
-                          &pd->denom_pub_hash,
-                          &bch))
-  {
-    GNUNET_break (0);
-    GNUNET_free (wh);
-    return NULL;
-  }
-
-  TALER_wallet_withdraw_sign (&pd->denom_pub_hash,
-                              &wh->requested_amount,
-                              &bch,
-                              reserve_priv,
-                              &reserve_sig);
-  {
-    json_t *withdraw_obj = GNUNET_JSON_PACK (
-      GNUNET_JSON_pack_data_auto ("denom_pub_hash",
-                                  &pd->denom_pub_hash),
-      TALER_JSON_pack_blinded_planchet ("coin_ev",
-                                        &pd->blinded_planchet),
-      GNUNET_JSON_pack_data_auto ("reserve_sig",
-                                  &reserve_sig));
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Attempting to withdraw from reserve %s\n",
-                TALER_B2S (&wh->reserve_pub));
-    wh->url = TALER_url_join (exchange_url,
-                              arg_str,
-                              NULL);
-    if (NULL == wh->url)
-    {
-      json_decref (withdraw_obj);
-      GNUNET_free (wh);
-      return NULL;
-    }
-    {
-      CURL *eh;
-
-      eh = TALER_EXCHANGE_curl_easy_get_ (wh->url);
-      if ( (NULL == eh) ||
-           (GNUNET_OK !=
-            TALER_curl_easy_post (&wh->post_ctx,
-                                  eh,
-                                  withdraw_obj)) )
-      {
-        GNUNET_break (0);
-        if (NULL != eh)
-          curl_easy_cleanup (eh);
-        json_decref (withdraw_obj);
-        GNUNET_free (wh->url);
-        GNUNET_free (wh);
-        return NULL;
-      }
-      json_decref (withdraw_obj);
-      wh->job = GNUNET_CURL_job_add2 (curl_ctx,
-                                      eh,
-                                      wh->post_ctx.headers,
-                                      &handle_reserve_withdraw_finished,
-                                      wh);
-    }
-  }
-  return wh;
-}
-
-
-void
-TALER_EXCHANGE_withdraw2_cancel (struct TALER_EXCHANGE_Withdraw2Handle *wh)
-{
-  if (NULL != wh->job)
-  {
-    GNUNET_CURL_job_cancel (wh->job);
-    wh->job = NULL;
-  }
-  GNUNET_free (wh->url);
-  TALER_curl_easy_post_finished (&wh->post_ctx);
-  TALER_EXCHANGE_keys_decref (wh->keys);
-  GNUNET_free (wh);
-}
diff --git a/src/testing/testing_api_cmd_withdraw.c 
b/src/testing/testing_api_cmd_withdraw.c
index c45e29ff..8a88f60f 100644
--- a/src/testing/testing_api_cmd_withdraw.c
+++ b/src/testing/testing_api_cmd_withdraw.c
@@ -158,7 +158,7 @@ struct WithdrawState
   /**
    * Withdraw handle (while operation is running).
    */
-  struct TALER_EXCHANGE_WithdrawHandle *wsh;
+  struct TALER_EXCHANGE_BatchWithdrawHandle *wsh;
 
   /**
    * Task scheduled to try later.
@@ -242,7 +242,7 @@ do_retry (void *cls)
  */
 static void
 reserve_withdraw_cb (void *cls,
-                     const struct TALER_EXCHANGE_WithdrawResponse *wr)
+                     const struct TALER_EXCHANGE_BatchWithdrawResponse *wr)
 {
   struct WithdrawState *ws = cls;
   struct TALER_TESTING_Interpreter *is = ws->is;
@@ -292,11 +292,12 @@ reserve_withdraw_cb (void *cls,
   switch (wr->hr.http_status)
   {
   case MHD_HTTP_OK:
+    GNUNET_assert (1 == wr->details.ok.num_coins);
     TALER_denom_sig_deep_copy (&ws->sig,
-                               &wr->details.ok.sig);
-    ws->coin_priv = wr->details.ok.coin_priv;
-    ws->bks = wr->details.ok.bks;
-    ws->exchange_vals = wr->details.ok.exchange_vals;
+                               &wr->details.ok.coins[0].sig);
+    ws->coin_priv = wr->details.ok.coins[0].coin_priv;
+    ws->bks = wr->details.ok.coins[0].bks;
+    ws->exchange_vals = wr->details.ok.coins[0].exchange_vals;
     if (0 != ws->total_backoff.rel_value_us)
     {
       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -442,11 +443,13 @@ withdraw_run (void *cls,
       .ps = &ws->ps,
       .ach = 0 < ws->age ? &ws->h_age_commitment : NULL
     };
-    ws->wsh = TALER_EXCHANGE_withdraw (
+
+    ws->wsh = TALER_EXCHANGE_batch_withdraw (
       TALER_TESTING_interpreter_get_context (is),
       TALER_TESTING_get_exchange_url (is),
       TALER_TESTING_get_keys (is),
       rp,
+      1,
       &wci,
       &reserve_withdraw_cb,
       ws);
@@ -477,7 +480,7 @@ withdraw_cleanup (void *cls,
   {
     TALER_TESTING_command_incomplete (ws->is,
                                       cmd->label);
-    TALER_EXCHANGE_withdraw_cancel (ws->wsh);
+    TALER_EXCHANGE_batch_withdraw_cancel (ws->wsh);
     ws->wsh = NULL;
   }
   if (NULL != ws->retry_task)

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